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.
|
||||
|
||||
//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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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