HHH-11186 - Add examples for all Hibernate annotations

Document @Subselect and @Synchronize annotations
This commit is contained in:
Vlad Mihalcea 2017-05-17 17:44:10 +03:00
parent f164223226
commit c2150ffc3b
3 changed files with 341 additions and 10 deletions

View File

@ -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.
//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]]
==== `@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.
//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]]
==== `@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`].
//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]]
==== `@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.
[[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.
See the <<chapters/domain/basic_types.adoc#mapping-Target,`@Target` mapping>> section for more info.

View File

@ -1,6 +1,7 @@
[[entity]]
=== 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
.Usage of the word _entity_
@ -335,7 +336,7 @@ There are 4 available OptimisticLockTypes:
====
[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]
----
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]
@ -376,7 +377,7 @@ since the entity was loaded in the currently running Persistence Context.
====
[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]
----
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]
@ -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
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.

View File

@ -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[]
}