Update custom CRUD in the User Guide Native chapter
This commit is contained in:
parent
a5cbe326d6
commit
8eae88d6a4
|
@ -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>>.
|
|
@ -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.
|
||||
====
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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[]
|
||||
|
||||
}
|
|
@ -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[]
|
||||
|
||||
}
|
|
@ -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[]
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue