Update custom CRUD in the User Guide Native chapter

This commit is contained in:
Vlad Mihalcea 2016-02-23 13:14:39 +02:00
parent a5cbe326d6
commit 8eae88d6a4
7 changed files with 741 additions and 163 deletions

View File

@ -371,5 +371,3 @@ You can also define an entity loader that loads a collection by join fetching:
WHERE ID=?
</sql-query>
----
The annotation equivalent `<loader>` is the `@Loader` annotation as seen in <<example-custom-crdu-via-annotations>>.

View File

@ -782,82 +782,48 @@ Hibernate will iterate the results and take the first result that is a result se
For SQL Server, if you can enable `SET NOCOUNT ON` in your procedure it will probably be more efficient, but this is not a requirement.
====
[[sql-cud]]
=== Custom SQL for create, update and delete
[[sql-crud]]
=== Custom SQL for create, update, and delete
Hibernate can use custom SQL for create, update, and delete operations.
The SQL can be overridden at the statement level or individual column level.
This section describes statement overrides.
For columns, see <<chapters/domain/basic_types.adoc#mapping-column-read-and-write,Column transformers: read and write expressions>>.
The following example shows how to define custom SQL operations using annotations.
[[example-custom-crdu-via-annotations]]
.Custom CRUD via annotations
The following example shows how to define custom SQL operations using annotations.
`@SQLInsert`, `@SQLUpdate` and `@SQLDelete` override the INSERT, UPDATE, DELETE statements of a given entity.
For the SELECT clause, a `@Loader` must be defined along with a `@NamedNativeQuery` used for loading the underlying table record.
For collections, Hibernate allows defining a custom `@SQLDeleteAll` which is used for removing all child records associated to a given parent entity.
To filter collections, the `@Where` annotation allows customizing the underlying SQL WHERE clause.
[[sql-custom-crud-example]]
.Custom CRUD
====
[source, JAVA, indent=0]
----
@Entity
@Table(name = "CHAOS")
@SQLInsert( sql = "INSERT INTO CHAOS(size, name, nickname, id) VALUES(?,upper(?),?,?)")
@SQLUpdate( sql = "UPDATE CHAOS SET size = ?, name = upper(?), nickname = ? WHERE id = ?")
@SQLDelete( sql = "DELETE CHAOS WHERE id = ?")
@SQLDeleteAll( sql = "DELETE CHAOS")
@Loader(namedQuery = "chaos")
@NamedNativeQuery(name = "chaos", query="select id, size, name, lower( nickname ) as nickname from CHAOS where id= ?", resultClass = Chaos.class)
public class Chaos {
@Id
private Long id;
private Long size;
private String name;
private String nickname;
include::{sourcedir}/CustomSQLTest.java[tags=sql-custom-crud-example]
----
====
`@SQLInsert`, `@SQLUpdate`, `@SQLDelete`, `@SQLDeleteAll` respectively override the INSERT, UPDATE, DELETE, and DELETE all statement.
The same can be achieved using Hibernate mapping files and the `<sql-insert>`, `<sql-update>` and `<sql-delete>` nodes.
In the example above, the entity is mapped so that entries are soft-deleted (the records are not removed from the database, but instead, a flag marks the row validity).
The `Person` entity benefits from custom INSERT, UPDATE, and DELETE statements which update the `valid` column accordingly.
The custom `@Loader` is used to retrieve only `Person` rows that are valid.
.Custom CRUD XML
====
[source,xml]
----
<class name = "Person">
<id name = "id">
<generator class = "increment"/>
</id>
<property name = "name" not-null = "true"/>
<sql-insert>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert>
<sql-update>UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?</sql-update>
<sql-delete>DELETE FROM PERSON WHERE ID=?</sql-delete>
</class>
----
====
The same is done for the `phones` collection. The `@SQLDeleteAll` and the `SQLInsert` queries are used whenever the collection is modified.
[NOTE]
====
If you expect to call a store procedure, be sure to set the `callable` attribute to `true`, in annotations as well as in xml.
You also call a store procedure using the custom CRUD statements; the only requirement is to set the `callable` attribute to `true`.
====
To check that the execution happens correctly, Hibernate allows you to define one of those three strategies:
* none: no check is performed: the store procedure is expected to fail upon issues
* count: use of rowcount to check that the update is successful
* param: like COUNT but using an output parameter rather that the standard mechanism
* none: no check is performed; the store procedure is expected to fail upon constraint violations
* count: use of row-count returned by the `executeUpdate()` method call to check that the update was successful
* param: like count but using a `CallableStatement` output parameter.
To define the result check style, use the `check` parameter which is again available in annotations as well as in xml.
You can use the exact same set of annotations respectively xml nodes to override the collection related statements, as you can see in the following example.
.Overriding SQL statements for collections using annotations
====
[source, JAVA, indent=0]
----
@OneToMany
@JoinColumn(name = "chaos_fk")
@SQLInsert( sql = "UPDATE CASIMIR_PARTICULE SET chaos_fk = ? where id = ?")
@SQLDelete( sql = "UPDATE CASIMIR_PARTICULE SET chaos_fk = null where id = ?")
private Set<CasimirParticle> particles = new HashSet<CasimirParticle>();
----
====
To define the result check style, use the `check` parameter.
[TIP]
====
@ -867,133 +833,48 @@ You can see the expected order by enabling debug logging, so Hibernate can print
To see the expected sequence, remember to not include your custom SQL through annotations or mapping files as that will override the Hibernate generated static sql.
====
Overriding SQL statements for secondary tables is also possible using `@org.hibernate.annotations.Table` and either (or all) attributes `sqlInsert`, `sqlUpdate`, `sqlDelete`:
Overriding SQL statements for secondary tables is also possible using `@org.hibernate.annotations.Table` and the `sqlInsert`, `sqlUpdate`, `sqlDelete` attributes.
[[sql-custom-crud-secondary-table-example]]
.Overriding SQL statements for secondary tables
====
[source, JAVA, indent=0]
----
@Entity
@SecondaryTables({
@SecondaryTable(name = "`Cat nbr1`"),
@SecondaryTable(name = "Cat2"})
@org.hibernate.annotations.Tables( {
@Table(
appliesTo = "Cat",
comment = "My cat table"
),
@Table(
appliesTo = "Cat2",
foreignKey = @ForeignKey(name = "FK_CAT2_CAT"), fetch = FetchMode.SELECT,
sqlInsert = @SQLInsert(
sql = "insert into Cat2(storyPart2, id) values(upper(?), ?)"
)
)
} )
public class Cat implements Serializable {
include::{sourcedir}/CustomSQLSecondaryTableTest.java[tags=sql-custom-crud-secondary-table-example]
----
====
The previous example also shows that you can give a comment to a given table (primary or secondary): This comment will be used for DDL generation.
[TIP]
====
The SQL is directly executed in your database, so you can use any dialect you like.
This will, however, reduce the portability of your mapping if you use database specific SQL.
====
Last but not least, stored procedures are in most cases required to return the number of rows inserted, updated and deleted.
Hibernate always registers the first statement parameter as a numeric output parameter for the CUD operations:
You can also use stored procedures for customizing the CRUD statements.
.Stored procedures and their return value
Assuming the following stored procedure:
[[sql-sp-soft-delete-example]]
.Oracle stored procedure to soft-delete a given entity
====
[source]
[source, JAVA, indent=0]
----
CREATE OR REPLACE FUNCTION updatePerson (uid IN NUMBER, uname IN VARCHAR2)
RETURN NUMBER IS
BEGIN
update PERSON
set
NAME = uname,
where
ID = uid;
return SQL%ROWCOUNT;
END updatePerson;
include::{sourcedir}/OracleCustomSQLWithStoredProcedureTest.java[tags=sql-sp-soft-delete-example]
----
====
[[sql-load]]
=== Custom SQL for loading
The entity can use this stored procedure to soft-delete the entity in question:
You can also declare your own SQL (or HQL) queries for entity loading.
As with inserts, updates, and deletes, this can be done at the individual column level as described in
For columns, see <<chapters/domain/basic_types.adoc#mapping-column-read-and-write,Column transformers: read and write expressions>> or at the statement level.
Here is an example of a statement level override:
[source,xml]
[[sql-sp-custom-crud-example]]
.Customizing the entity delete statement to use the Oracle stored procedure= instead
====
[source, JAVA, indent=0]
----
<sql-query name = "person">
<return alias = "pers" class = "Person" lock-mod e= "upgrade"/>
SELECT NAME AS {pers.name}, ID AS {pers.id}
FROM PERSON
WHERE ID=?
FOR UPDATE
</sql-query>
include::{sourcedir}/OracleCustomSQLWithStoredProcedureTest.java[tags=sql-sp-custom-crud-example]
----
====
This is just a named query declaration, as discussed earlier. You can reference this named query in a class mapping:
[source,xml]
----
<class name = "Person">
<id name = "id">
<generator class = "increment"/>
</id>
<property name = "name" not-null = "true"/>
<loader query-ref = "person"/>
</class>
----
This even works with stored procedures.
You can even define a query for collection loading:
[source,xml]
----
<set name = "employments" inverse = "true">
<key/>
<one-to-many class = "Employment"/>
<loader query-ref = "employments"/>
</set>
----
[source,xml]
----
<sql-query name = "employments">
<load-collection alias = "emp" role = "Person.employments"/>
SELECT {emp.*}
FROM EMPLOYMENT emp
WHERE EMPLOYER = :id
ORDER BY STARTDATE ASC, EMPLOYEE ASC
</sql-query>
----
You can also define an entity loader that loads a collection by join fetching:
[source,xml]
----
<sql-query name = "person">
<return alias = "pers" class = "Person"/>
<return-join alias = "emp" property = "pers.employments"/>
SELECT NAME AS {pers.*}, {emp.*}
FROM PERSON pers
LEFT OUTER JOIN EMPLOYMENT emp
ON pers.ID = emp.PERSON_ID
WHERE ID=?
</sql-query>
----
The annotation equivalent `<loader>` is the `@Loader` annotation as seen in <<example-custom-crdu-via-annotations>>.
[NOTE]
====
You need to set the `callable` attribute when using a stored procedure instead of an SQL statement.
====

View File

@ -0,0 +1,74 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.userguide.jmx;
import java.util.Map;
import javax.management.NotCompliantMBeanException;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
import org.jboss.logging.Logger;
import org.apache.commons.lang3.exception.ExceptionUtils;
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
import static org.junit.Assert.assertTrue;
/**
* @author Vlad Mihalcea
*/
public class JmxTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( JmxTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class,
};
}
@Override
protected Map buildSettings() {
Map properties = super.buildSettings();
properties.put( AvailableSettings.JMX_ENABLED, Boolean.TRUE.toString());
properties.put( AvailableSettings.JMX_DOMAIN_NAME, "test");
return properties;
}
@Test @TestForIssue( jiraKey = "HHH-7405" )
public void test() {
try {
doInJPA( this::entityManagerFactory, entityManager -> {
Person person = new Person();
person.id = 1L;
entityManager.persist(person);
});
}
catch (Exception e) {
log.error( "HHH-7405", e );
assertTrue(ExceptionUtils.getRootCause(e) instanceof NotCompliantMBeanException);
}
}
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
private String firstName;
private String lastName;
}
}

View File

@ -0,0 +1,165 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.userguide.sql;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedNativeQueries;
import javax.persistence.NamedNativeQuery;
import org.hibernate.Session;
import org.hibernate.annotations.Loader;
import org.hibernate.annotations.ResultCheckStyle;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLDeleteAll;
import org.hibernate.annotations.SQLInsert;
import org.hibernate.annotations.SQLUpdate;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.TestForIssue;
import org.junit.Before;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
/**
* This test is for replicating the HHH-10557 issue.
*
* @author Vlad Mihalcea
*/
public class CollectionLoaderTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( CollectionLoaderTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class
};
}
@Before
public void init() {
doInJPA( this::entityManagerFactory, entityManager -> {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try(Statement statement = connection.createStatement(); ) {
statement.executeUpdate( "ALTER TABLE person ADD COLUMN valid boolean" );
statement.executeUpdate( "ALTER TABLE Person_phones ADD COLUMN valid boolean" );
}
} );
});
}
@Test @TestForIssue( jiraKey = "HHH-10557")
public void test_HHH10557() {
Person _person = doInJPA( this::entityManagerFactory, entityManager -> {
Person person = new Person();
person.setName( "John Doe" );
entityManager.persist( person );
person.getPhones().add( "123-456-7890" );
person.getPhones().add( "123-456-0987" );
return person;
} );
try {
doInJPA( this::entityManagerFactory, entityManager -> {
Long postId = _person.getId();
Person person = entityManager.find( Person.class, postId );
assertEquals( 2, person.getPhones().size() );
person.getPhones().remove( 0 );
person.setName( "Mr. John Doe" );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Long postId = _person.getId();
Person person = entityManager.find( Person.class, postId );
assertEquals( 1, person.getPhones().size() );
} );
}
catch (Exception e) {
log.error( "Throws NullPointerException because the bag is not initialized by the @Loader" );
}
}
//tag::sql-custom-crud-example[]
@Entity(name = "Person")
@SQLInsert(
sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) ",
check = ResultCheckStyle.COUNT
)
@SQLUpdate(
sql = "UPDATE person SET name = ? where id = ? ")
@SQLDelete(
sql = "UPDATE person SET valid = false WHERE id = ? ")
@Loader(namedQuery = "find_valid_person")
@NamedNativeQueries({
@NamedNativeQuery(
name = "find_valid_person",
query = "SELECT id, name " +
"FROM person " +
"WHERE id = ? and valid = true",
resultClass = Person.class
),
@NamedNativeQuery(
name = "find_valid_phones",
query = "SELECT person_id, phones " +
"FROM Person_phones " +
"WHERE person_id = ? and valid = true "
)
})
public static class Person {
@Id
@GeneratedValue
private Long id;
private String name;
@ElementCollection
@SQLInsert(
sql = "INSERT INTO person_phones (person_id, phones, valid) VALUES (?, ?, true) ")
@SQLDeleteAll(
sql = "UPDATE person_phones SET valid = false WHERE person_id = ?")
@Loader(namedQuery = "find_valid_phones")
private List<String> phones = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getPhones() {
return phones;
}
}
//end::sql-custom-crud-example[]
}

View File

@ -0,0 +1,161 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.userguide.sql;
import java.sql.Statement;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedNativeQueries;
import javax.persistence.NamedNativeQuery;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.SecondaryTable;
import javax.persistence.Table;
import org.hibernate.Session;
import org.hibernate.annotations.Loader;
import org.hibernate.annotations.ResultCheckStyle;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLInsert;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Before;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNull;
/**
* @author Vlad Mihalcea
*/
public class CustomSQLSecondaryTableTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( CustomSQLSecondaryTableTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class
};
}
@Before
public void init() {
doInJPA( this::entityManagerFactory, entityManager -> {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try(Statement statement = connection.createStatement(); ) {
statement.executeUpdate( "ALTER TABLE person ADD COLUMN valid boolean" );
statement.executeUpdate( "ALTER TABLE person_details ADD COLUMN valid boolean" );
}
} );
});
}
@Test
public void test_sql_custom_crud() {
Person _person = doInJPA( this::entityManagerFactory, entityManager -> {
Person person = new Person();
person.setName( "John Doe" );
entityManager.persist( person );
person.setImage( new byte[] {1, 2, 3} );
return person;
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Long postId = _person.getId();
Person person = entityManager.find( Person.class, postId );
assertArrayEquals(new byte[] {1, 2, 3}, person.getImage());
entityManager.remove( person );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Long postId = _person.getId();
Person person = entityManager.find( Person.class, postId );
assertNull(person);
} );
}
//tag::sql-custom-crud-secondary-table-example[]
@Entity(name = "Person")
@Table(name = "person")
@SQLInsert(
sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) "
)
@SQLDelete(
sql = "UPDATE person SET valid = false WHERE id = ? "
)
@SecondaryTable(name = "person_details",
pkJoinColumns = @PrimaryKeyJoinColumn(name = "person_id"))
@org.hibernate.annotations.Table(
appliesTo = "person_details",
sqlInsert = @SQLInsert(
sql = "INSERT INTO person_details (image, person_id, valid) VALUES (?, ?, true) ",
check = ResultCheckStyle.COUNT
),
sqlDelete = @SQLDelete(
sql = "UPDATE person_details SET valid = false WHERE person_id = ? "
)
)
@Loader(namedQuery = "find_valid_person")
@NamedNativeQueries({
@NamedNativeQuery(
name = "find_valid_person",
query = "select " +
" p.id, " +
" p.name, " +
" pd.image " +
"from person p " +
"left outer join person_details pd on p.id = pd.person_id " +
"where p.id = ? and p.valid = true and pd.valid = true",
resultClass = Person.class
)
})
public static class Person {
@Id
@GeneratedValue
private Long id;
private String name;
@Column(name = "image", table = "person_details")
private byte[] image;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public byte[] getImage() {
return image;
}
public void setImage(byte[] image) {
this.image = image;
}
}
//end::sql-custom-crud-secondary-table-example[]
}

View File

@ -0,0 +1,158 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.userguide.sql;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedNativeQueries;
import javax.persistence.NamedNativeQuery;
import org.hibernate.Session;
import org.hibernate.annotations.Loader;
import org.hibernate.annotations.ResultCheckStyle;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLDeleteAll;
import org.hibernate.annotations.SQLInsert;
import org.hibernate.annotations.SQLUpdate;
import org.hibernate.annotations.Where;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Before;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* @author Vlad Mihalcea
*/
public class CustomSQLTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( CustomSQLTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class
};
}
@Before
public void init() {
doInJPA( this::entityManagerFactory, entityManager -> {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try(Statement statement = connection.createStatement(); ) {
statement.executeUpdate( "ALTER TABLE person ADD COLUMN valid boolean" );
statement.executeUpdate( "ALTER TABLE Person_phones ADD COLUMN valid boolean" );
}
} );
});
}
@Test
public void test_sql_custom_crud() {
Person _person = doInJPA( this::entityManagerFactory, entityManager -> {
Person person = new Person();
person.setName( "John Doe" );
entityManager.persist( person );
person.getPhones().add( "123-456-7890" );
person.getPhones().add( "123-456-0987" );
return person;
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Long postId = _person.getId();
Person person = entityManager.find( Person.class, postId );
assertEquals( 2, person.getPhones().size() );
person.getPhones().remove( 0 );
person.setName( "Mr. John Doe" );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Long postId = _person.getId();
Person person = entityManager.find( Person.class, postId );
assertEquals( 1, person.getPhones().size() );
entityManager.remove( person );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Long postId = _person.getId();
Person person = entityManager.find( Person.class, postId );
assertNull(person);
} );
}
//tag::sql-custom-crud-example[]
@Entity(name = "Person")
@SQLInsert(
sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) ",
check = ResultCheckStyle.COUNT
)
@SQLUpdate(
sql = "UPDATE person SET name = ? where id = ? ")
@SQLDelete(
sql = "UPDATE person SET valid = false WHERE id = ? ")
@Loader(namedQuery = "find_valid_person")
@NamedNativeQueries({
@NamedNativeQuery(
name = "find_valid_person",
query = "SELECT id, name " +
"FROM person " +
"WHERE id = ? and valid = true",
resultClass = Person.class
)
})
public static class Person {
@Id
@GeneratedValue
private Long id;
private String name;
@ElementCollection
@SQLInsert(
sql = "INSERT INTO person_phones (person_id, phones, valid) VALUES (?, ?, true) ")
@SQLDeleteAll(
sql = "UPDATE person_phones SET valid = false WHERE person_id = ?")
@Where( clause = "valid = true" )
private List<String> phones = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getPhones() {
return phones;
}
}
//end::sql-custom-crud-example[]
}

View File

@ -0,0 +1,141 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.userguide.sql;
import java.sql.Statement;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedNativeQueries;
import javax.persistence.NamedNativeQuery;
import org.hibernate.Session;
import org.hibernate.annotations.Loader;
import org.hibernate.annotations.ResultCheckStyle;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLInsert;
import org.hibernate.dialect.Oracle8iDialect;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.RequiresDialect;
import org.junit.Before;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(Oracle8iDialect.class)
public class OracleCustomSQLWithStoredProcedureTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( OracleCustomSQLWithStoredProcedureTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class
};
}
@Before
public void init() {
doInJPA( this::entityManagerFactory, entityManager -> {
Session session = entityManager.unwrap( Session.class );
session.doWork( connection -> {
try(Statement statement = connection.createStatement(); ) {
statement.executeUpdate( "ALTER TABLE person ADD valid NUMBER(1) DEFAULT 0 NOT NULL" );
//tag::sql-sp-soft-delete-example[]
statement.executeUpdate(
"CREATE OR REPLACE PROCEDURE sp_delete_person ( " +
" personId IN NUMBER ) " +
"AS " +
"BEGIN " +
" UPDATE person SET valid = 0 WHERE id = personId; " +
"END;"
);}
//end::sql-sp-soft-delete-example[]
} );
});
}
@Test
public void test_sql_custom_crud() {
Person _person = doInJPA( this::entityManagerFactory, entityManager -> {
Person person = new Person();
person.setName( "John Doe" );
entityManager.persist( person );
return person;
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Long postId = _person.getId();
Person person = entityManager.find( Person.class, postId );
assertNotNull(person);
entityManager.remove( person );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Long postId = _person.getId();
Person person = entityManager.find( Person.class, postId );
assertNull(person);
} );
}
@Entity(name = "Person")
@SQLInsert(
sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, 1) ",
check = ResultCheckStyle.COUNT
)
//tag::sql-sp-custom-crud-example[]
@SQLDelete(
sql = "{ call sp_delete_person( ? ) } ",
callable = true
)
//end::sql-sp-custom-crud-example[]
@Loader(namedQuery = "find_valid_person")
@NamedNativeQueries({
@NamedNativeQuery(
name = "find_valid_person",
query = "SELECT id, name " +
"FROM person " +
"WHERE id = ? and valid = 1",
resultClass = Person.class
)
})
public static class Person {
@Id
@GeneratedValue
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}