HHH-11186 - Add examples for all Hibernate annotations
Document @Subselect and @Synchronize annotations
This commit is contained in:
parent
f164223226
commit
c2150ffc3b
|
@ -1222,7 +1222,7 @@ See the <<chapters/query/native/Native.adoc#sql-custom-crud-example, Custom CRUD
|
||||||
|
|
||||||
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Subselect.html[`@Subselect`] annotation is used to specify an immutable and read-only entity using a custom SQL `SELECT` statement.
|
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Subselect.html[`@Subselect`] annotation is used to specify an immutable and read-only entity using a custom SQL `SELECT` statement.
|
||||||
|
|
||||||
//TODO: Add example
|
See the <<chapters/domain/entity.adoc#entity-sql-query-mapping, Mapping the entity to a SQL query>> section for more info.
|
||||||
|
|
||||||
[[annotations-hibernate-synchronize]]
|
[[annotations-hibernate-synchronize]]
|
||||||
==== `@Synchronize`
|
==== `@Synchronize`
|
||||||
|
@ -1233,14 +1233,14 @@ With this information in place, Hibernate will properly trigger an entity flush
|
||||||
|
|
||||||
Therefore, the `@Synchronize` annotation prevents the derived entity from returning stale data when executing entity queries against the `@Subselect` entity.
|
Therefore, the `@Synchronize` annotation prevents the derived entity from returning stale data when executing entity queries against the `@Subselect` entity.
|
||||||
|
|
||||||
//TODO: Add example
|
See the <<chapters/domain/entity.adoc#entity-sql-query-mapping, Mapping the entity to a SQL query>> section for more info.
|
||||||
|
|
||||||
[[annotations-hibernate-table]]
|
[[annotations-hibernate-table]]
|
||||||
==== `@Table`
|
==== `@Table`
|
||||||
|
|
||||||
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Table.html[`@Table`] annotation is used to specify additional information to a JPA <<annotations-hibernate-table>> annotation, like custom `INSERT`, `UPDATE` or `DELETE` statements or a specific https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/FetchMode.html[`FetchMode`].
|
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Table.html[`@Table`] annotation is used to specify additional information to a JPA <<annotations-hibernate-table>> annotation, like custom `INSERT`, `UPDATE` or `DELETE` statements or a specific https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/FetchMode.html[`FetchMode`].
|
||||||
|
|
||||||
//TODO: Add example
|
See the <<chapters/query/native/Native.adoc#sql-custom-crud-secondary-table-example, `@SecondaryTable` mapping>> section for more info about Hibernate-specific `@Table` mapping.
|
||||||
|
|
||||||
[[annotations-hibernate-tables]]
|
[[annotations-hibernate-tables]]
|
||||||
==== `@Tables`
|
==== `@Tables`
|
||||||
|
@ -1248,8 +1248,7 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern
|
||||||
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Tables.html[`@Tables`] annotation is used to group multiple <<annotations-hibernate-table>> annotations.
|
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Tables.html[`@Tables`] annotation is used to group multiple <<annotations-hibernate-table>> annotations.
|
||||||
|
|
||||||
[[annotations-hibernate-target]]
|
[[annotations-hibernate-target]]
|
||||||
==== `@Target`
|
==== `@Target`Se
|
||||||
|
|
||||||
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Target.html[`@Target`] annotation is used to specify an explicit target implementation when the current annotated association is using an interface type.
|
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Target.html[`@Target`] annotation is used to specify an explicit target implementation when the current annotated association is using an interface type.
|
||||||
|
|
||||||
See the <<chapters/domain/basic_types.adoc#mapping-Target,`@Target` mapping>> section for more info.
|
See the <<chapters/domain/basic_types.adoc#mapping-Target,`@Target` mapping>> section for more info.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
[[entity]]
|
[[entity]]
|
||||||
=== Entity types
|
=== Entity types
|
||||||
:sourcedir: ../../../../../test/java/org/hibernate/userguide/locking
|
:sourcedir-locking: ../../../../../test/java/org/hibernate/userguide/locking
|
||||||
|
:sourcedir-mapping: ../../../../../test/java/org/hibernate/userguide/mapping/basic
|
||||||
:extrasdir: extras
|
:extrasdir: extras
|
||||||
|
|
||||||
.Usage of the word _entity_
|
.Usage of the word _entity_
|
||||||
|
@ -335,7 +336,7 @@ There are 4 available OptimisticLockTypes:
|
||||||
====
|
====
|
||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
include::{sourcedir}/OptimisticLockTypeAllTest.java[tag=locking-optimistic-lock-type-all-example,indent=0]
|
include::{sourcedir-locking}/OptimisticLockTypeAllTest.java[tag=locking-optimistic-lock-type-all-example,indent=0]
|
||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
|
@ -346,7 +347,7 @@ When you need to modify the `Person` entity above:
|
||||||
====
|
====
|
||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
include::{sourcedir}/OptimisticLockTypeAllTest.java[tag=locking-optimistic-lock-type-all-update-example,indent=0]
|
include::{sourcedir-locking}/OptimisticLockTypeAllTest.java[tag=locking-optimistic-lock-type-all-update-example,indent=0]
|
||||||
----
|
----
|
||||||
|
|
||||||
[source,SQL]
|
[source,SQL]
|
||||||
|
@ -376,7 +377,7 @@ since the entity was loaded in the currently running Persistence Context.
|
||||||
====
|
====
|
||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
include::{sourcedir}/OptimisticLockTypeDirtyTest.java[tag=locking-optimistic-lock-type-dirty-example,indent=0]
|
include::{sourcedir-locking}/OptimisticLockTypeDirtyTest.java[tag=locking-optimistic-lock-type-dirty-example,indent=0]
|
||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
|
@ -387,7 +388,7 @@ When you need to modify the `Person` entity above:
|
||||||
====
|
====
|
||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
include::{sourcedir}/OptimisticLockTypeDirtyTest.java[tag=locking-optimistic-lock-type-dirty-update-example,indent=0]
|
include::{sourcedir-locking}/OptimisticLockTypeDirtyTest.java[tag=locking-optimistic-lock-type-dirty-update-example,indent=0]
|
||||||
----
|
----
|
||||||
|
|
||||||
[source,SQL]
|
[source,SQL]
|
||||||
|
@ -408,3 +409,49 @@ When using `OptimisticLockType.DIRTY`, you should also use `@DynamicUpdate` beca
|
||||||
and also the `@SelectBeforeUpdate` annotation so that detached entities are properly handled by the
|
and also the `@SelectBeforeUpdate` annotation so that detached entities are properly handled by the
|
||||||
https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Session.html#update-java.lang.Object-[`Session#update(entity)`] operation.
|
https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Session.html#update-java.lang.Object-[`Session#update(entity)`] operation.
|
||||||
====
|
====
|
||||||
|
|
||||||
|
[[entity-sql-query-mapping]]
|
||||||
|
==== Mapping the entity to a SQL query
|
||||||
|
|
||||||
|
You can map an entity to a SQL query using the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Subselect.html[`@Subselect`] annotation.
|
||||||
|
|
||||||
|
[[mapping-Subselect-example]]
|
||||||
|
.`@Subselect` entity mapping
|
||||||
|
====
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
include::{sourcedir-mapping}/SubselectTest.java[tag=mapping-Subselect-example,indent=0]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
In the example above, the `Account` entity does not retain any balance since every account operation is registered as an `AccountTransaction`.
|
||||||
|
To find the `Account` balance, we need to query the `AccountSummary` which shares the same identifier with the `Account` entity.
|
||||||
|
|
||||||
|
However, the `AccountSummary` is not mapped to a physical table, but to an SQL query.
|
||||||
|
|
||||||
|
So, if we have the following `AccountTransaction` record, the `AccountSummary` balance will mach the proper amount of money in this `Account`.
|
||||||
|
|
||||||
|
[[mapping-Subselect-entity-find-example]]
|
||||||
|
.Finding a `@Subselect` entity
|
||||||
|
====
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
include::{sourcedir-mapping}/SubselectTest.java[tag=mapping-Subselect-entity-find-example,indent=0]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
If we add a new `AccountTransaction` entity and refresh the `AccountSummary` entity, the balance is updated accordingly:
|
||||||
|
|
||||||
|
[[mapping-Subselect-refresh-find-example]]
|
||||||
|
.Refreshing a `@Subselect` entity
|
||||||
|
====
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
include::{sourcedir-mapping}/SubselectTest.java[tag=mapping-Subselect-entity-refresh-example,indent=0]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
The goala of the `@Synchronize` annotation in the `AccountSummary` entity mapping is to instruct Hibernate which database tables are needed by the
|
||||||
|
underlying `@Subselect` SQL query. This way, when executing a HQL or JPQL which selects from the `AccountSummary` entity,
|
||||||
|
Hibernate will trigger a Persistence Context flush if there are pending `Account`, `Client` or `AccountTransaction` entity state transitions.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,285 @@
|
||||||
|
/*
|
||||||
|
* 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.mapping.basic;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.Subselect;
|
||||||
|
import org.hibernate.annotations.Synchronize;
|
||||||
|
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vlad Mihalcea
|
||||||
|
*/
|
||||||
|
public class SubselectTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?>[] getAnnotatedClasses() {
|
||||||
|
return new Class<?>[] {
|
||||||
|
Client.class,
|
||||||
|
Account.class,
|
||||||
|
AccountTransaction.class,
|
||||||
|
AccountSummary.class
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLifecycle() {
|
||||||
|
//tag::mapping-Subselect-entity-find-example[]
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
Client client = new Client();
|
||||||
|
client.setId( 1L );
|
||||||
|
client.setFirstName( "John" );
|
||||||
|
client.setLastName( "Doe" );
|
||||||
|
entityManager.persist( client );
|
||||||
|
|
||||||
|
Account account = new Account();
|
||||||
|
account.setId( 1L );
|
||||||
|
account.setClient( client );
|
||||||
|
account.setDescription( "Checking account" );
|
||||||
|
entityManager.persist( account );
|
||||||
|
|
||||||
|
AccountTransaction transaction = new AccountTransaction();
|
||||||
|
transaction.setAccount( account );
|
||||||
|
transaction.setDescription( "Salary" );
|
||||||
|
transaction.setCents( 100 * 7000 );
|
||||||
|
entityManager.persist( transaction );
|
||||||
|
|
||||||
|
AccountSummary summary = entityManager.createQuery(
|
||||||
|
"select s " +
|
||||||
|
"from AccountSummary s " +
|
||||||
|
"where s.id = :id", AccountSummary.class)
|
||||||
|
.setParameter( "id", account.getId() )
|
||||||
|
.getSingleResult();
|
||||||
|
|
||||||
|
assertEquals( "John Doe", summary.getClientName() );
|
||||||
|
assertEquals( 100 * 7000, summary.getBalance() );
|
||||||
|
} );
|
||||||
|
//end::mapping-Subselect-entity-find-example[]
|
||||||
|
|
||||||
|
//tag::mapping-Subselect-entity-refresh-example[]
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
AccountSummary summary = entityManager.find( AccountSummary.class, 1L );
|
||||||
|
assertEquals( "John Doe", summary.getClientName() );
|
||||||
|
assertEquals( 100 * 7000, summary.getBalance() );
|
||||||
|
|
||||||
|
AccountTransaction transaction = new AccountTransaction();
|
||||||
|
transaction.setAccount( entityManager.getReference( Account.class, 1L ) );
|
||||||
|
transaction.setDescription( "Shopping" );
|
||||||
|
transaction.setCents( -100 * 2200 );
|
||||||
|
entityManager.persist( transaction );
|
||||||
|
entityManager.flush();
|
||||||
|
|
||||||
|
entityManager.refresh( summary );
|
||||||
|
assertEquals( 100 * 4800, summary.getBalance() );
|
||||||
|
} );
|
||||||
|
|
||||||
|
//end::mapping-Subselect-entity-refresh-example[]
|
||||||
|
}
|
||||||
|
|
||||||
|
//tag::mapping-Subselect-example[]
|
||||||
|
@Entity(name = "Client")
|
||||||
|
@Table(name = "client")
|
||||||
|
public static class Client {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "first_name")
|
||||||
|
private String firstName;
|
||||||
|
|
||||||
|
@Column(name = "last_name")
|
||||||
|
private String lastName;
|
||||||
|
|
||||||
|
//Getters and setters omitted for brevity
|
||||||
|
|
||||||
|
//end::mapping-Subselect-example[]
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastName() {
|
||||||
|
return lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
//tag::mapping-Subselect-example[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Account")
|
||||||
|
@Table(name = "account")
|
||||||
|
public static class Account {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
private Client client;
|
||||||
|
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
//Getters and setters omitted for brevity
|
||||||
|
|
||||||
|
//end::mapping-Subselect-example[]
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Client getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClient(Client client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
//tag::mapping-Subselect-example[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "AccountTransaction")
|
||||||
|
@Table(name = "account_transaction")
|
||||||
|
public static class AccountTransaction {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
private Account account;
|
||||||
|
|
||||||
|
private Integer cents;
|
||||||
|
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
//Getters and setters omitted for brevity
|
||||||
|
|
||||||
|
//end::mapping-Subselect-example[]
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Account getAccount() {
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccount(Account account) {
|
||||||
|
this.account = account;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCents() {
|
||||||
|
return cents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCents(Integer cents) {
|
||||||
|
this.cents = cents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
//tag::mapping-Subselect-example[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "AccountSummary")
|
||||||
|
@Subselect(
|
||||||
|
"select " +
|
||||||
|
" a.id as id, " +
|
||||||
|
" c.first_name||' '||c.last_name as clientName, " +
|
||||||
|
" sum(at.cents) as balance " +
|
||||||
|
"from account a " +
|
||||||
|
"join client c on c.id = a.client_id " +
|
||||||
|
"join account_transaction at on a.id = at.account_id " +
|
||||||
|
"group by a.id, c.first_name||' '||c.last_name"
|
||||||
|
)
|
||||||
|
@Synchronize( {"client", "account", "account_transaction"} )
|
||||||
|
public static class AccountSummary {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String clientName;
|
||||||
|
|
||||||
|
private int balance;
|
||||||
|
|
||||||
|
//Getters and setters omitted for brevity
|
||||||
|
|
||||||
|
//end::mapping-Subselect-example[]
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientName() {
|
||||||
|
return clientName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientName(String clientName) {
|
||||||
|
this.clientName = clientName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBalance() {
|
||||||
|
return balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBalance(int balance) {
|
||||||
|
this.balance = balance;
|
||||||
|
}
|
||||||
|
//tag::mapping-Subselect-example[]
|
||||||
|
}
|
||||||
|
//end::mapping-Subselect-example[]
|
||||||
|
}
|
Loading…
Reference in New Issue