From 87ce2670e72273ac9925e3f2cf2d1c1b5072d39f Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Tue, 4 Jul 2017 12:05:12 +0300 Subject: [PATCH] HHH-11290 - Migrate all documentation snippets that derive the source code from extras instead of actual Unit Tests Fixed in the Entity chapter --- .../userguide/chapters/domain/entity.adoc | 88 +++++-- .../domain/extras/entity/Identifier.java | 2 - .../domain/extras/entity/SimpleEntity.java | 4 - .../extras/entity/SimpleEntityWithTable.java | 5 - .../domain/extras/entity/listing1.java | 7 - .../domain/extras/entity/listing2.java | 8 - .../domain/extras/entity/listing3.java | 12 - .../domain/extras/entity/listing4.java | 13 - .../domain/extras/entity/listing5.java | 12 - .../domain/extras/entity/listing6.java | 24 -- .../domain/extras/entity/listing7.java | 15 -- .../domain/extras/entity/listing8.java | 19 -- .../domain/extras/entity/listing9.java | 36 --- .../NaiveEqualsHashCodeEntityTest.java | 201 +++++++++++++++ .../NaturalIdEqualsHashCodeEntityTest.java | 180 +++++++++++++ .../identifier/SimpleEntityTableTest.java | 82 ++++++ .../mapping/identifier/SimpleEntityTest.java | 236 ++++++++++++++++++ 17 files changed, 761 insertions(+), 183 deletions(-) delete mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/Identifier.java delete mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/SimpleEntity.java delete mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/SimpleEntityWithTable.java delete mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing1.java delete mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing2.java delete mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing3.java delete mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing4.java delete mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing5.java delete mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing6.java delete mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing7.java delete mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing8.java delete mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing9.java create mode 100644 documentation/src/test/java/org/hibernate/userguide/mapping/identifier/NaiveEqualsHashCodeEntityTest.java create mode 100644 documentation/src/test/java/org/hibernate/userguide/mapping/identifier/NaturalIdEqualsHashCodeEntityTest.java create mode 100644 documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimpleEntityTableTest.java create mode 100644 documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimpleEntityTest.java diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc index 5afd1cea62..121c4b85ab 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc @@ -1,7 +1,7 @@ [[entity]] === Entity types :sourcedir-locking: ../../../../../test/java/org/hibernate/userguide/locking -:sourcedir-mapping: ../../../../../test/java/org/hibernate/userguide/mapping/basic +:sourcedir-mapping: ../../../../../test/java/org/hibernate/userguide/mapping/ :sourcedir-proxy: ../../../../../test/java/org/hibernate/userguide/proxy :sourcedir-persister: ../../../../../test/java/org/hibernate/userguide/persister :extrasdir: extras @@ -99,11 +99,12 @@ We recommend that you declare consistently-named identifier attributes on persis The placement of the `@Id` annotation marks the <>. -.Identifier +[[entity-pojo-identifier-mapping-example]] +.Identifier mapping ==== [source,java] ---- -include::{extrasdir}/entity/Identifier.java[] +include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-identifier-mapping-example, indent=0] ---- ==== @@ -116,11 +117,12 @@ The main piece in mapping the entity is the `javax.persistence.Entity` annotatio The `@Entity` annotation defines just one attribute `name` which is used to give a specific entity name for use in JPQL queries. By default, the entity name represents the unqualified name of the entity class itself. -.Simple `@Entity` +[[entity-pojo-mapping-example]] +.Simple `@Entity` mapping ==== [source,java] ---- -include::{extrasdir}/entity/SimpleEntity.java[] +include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-mapping-example, indent=0] ---- ==== @@ -129,11 +131,12 @@ The identifier uniquely identifies each row in that table. By default, the name of the table is assumed to be the same as the name of the entity. To explicitly give the name of the table or to specify other information about the table, we would use the `javax.persistence.Table` annotation. +[[entity-pojo-table-mapping-example]] .Simple `@Entity` with `@Table` ==== [source,java] ---- -include::{extrasdir}/entity/SimpleEntityWithTable.java[] +include::{sourcedir-mapping}/identifier/SimpleEntityTableTest.java[tag=entity-pojo-table-mapping-example, indent=0] ---- ==== @@ -162,88 +165,109 @@ Hibernate, however, works hard to make sure that does not happen within a given In fact, Hibernate guarantees equivalence of persistent identity (database row) and Java identity inside a particular session scope. So if we ask a Hibernate `Session` to load that specific Person multiple times we will actually get back the same __instance__: +[[entity-pojo-identity-scope-example]] .Scope of identity ==== [source,java] ---- -include::{extrasdir}/entity/listing1.java[] +include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-identity-scope-example, indent=0] ---- ==== -Consider another example using a persistent `java.util.Set`: +Consider we have a `Library` parent entity which contains a `java.util.Set` of `Book` entities: +[[entity-pojo-set-mapping-example]] +Library entity mapping +==== +[source,java] +---- +include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-set-mapping-example, indent=0] +---- +==== + +[[entity-pojo-set-identity-scope-example]] .Set usage with Session-scoped identity ==== [source,java] ---- -include::{extrasdir}/entity/listing3.java[] +include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-set-identity-scope-example, indent=0] ---- ==== However, the semantic changes when we mix instances loaded from different Sessions: +[[entity-pojo-multi-session-identity-scope-example]] .Mixed Sessions ==== [source,java] ---- -include::{extrasdir}/entity/listing2.java[] +include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-multi-session-identity-scope-example, indent=0] ---- [source,java] ---- -include::{extrasdir}/entity/listing4.java[] +include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-multi-session-set-identity-scope-example, indent=0] ---- ==== -Specifically the outcome in this last example will depend on whether the `Person` class implemented equals/hashCode, and, if so, how. +Specifically the outcome in this last example will depend on whether the `Book` class +implemented equals/hashCode, and, if so, how. + +If the `Book` class did not override the default equals/hashCode, +then the two `Book` object reference are not going to be equal since their references are different. Consider yet another case: +[[entity-pojo-transient-set-identity-scope-example]] .Sets with transient entities ==== [source,java] ---- -include::{extrasdir}/entity/listing5.java[] +include::{sourcedir-mapping}/identifier/SimpleEntityTest.java[tag=entity-pojo-transient-set-identity-scope-example, indent=0] ---- ==== -In cases where you will be dealing with entities outside of a Session (whether they be transient or detached), especially in cases where you will be using them in Java collections, +In cases where you will be dealing with entities outside of a Session (whether they be transient or detached), +especially in cases where you will be using them in Java collections, you should consider implementing equals/hashCode. A common initial approach is to use the entity's identifier attribute as the basis for equals/hashCode calculations: +[[entity-pojo-naive-equals-hashcode-example]] .Naive equals/hashCode implementation ==== [source,java] ---- -include::{extrasdir}/entity/listing6.java[] +include::{sourcedir-mapping}/identifier/NaiveEqualsHashCodeEntityTest.java[tag=entity-pojo-naive-equals-hashcode-example, indent=0] ---- ==== -It turns out that this still breaks when adding transient instance of `Person` to a set as we saw in the last example: +It turns out that this still breaks when adding transient instance of `Book` to a set as we saw in the last example: -.Still trouble +[[entity-pojo-naive-equals-hashcode-example]] +.Auto-generated identifiers with Sets and naive equals/hashCode ==== [source,java] ---- -include::{extrasdir}/entity/listing7.java[] +include::{sourcedir-mapping}/identifier/NaiveEqualsHashCodeEntityTest.java[tag=entity-pojo-naive-equals-hashcode-persist-example, indent=0] ---- ==== The issue here is a conflict between the use of generated identifier, the contract of `Set` and the equals/hashCode implementations. `Set` says that the equals/hashCode value for an object should not change while the object is part of the `Set`. -But that is exactly what happened here because the equals/hasCode are based on the (generated) id, which was not set until the `session.getTransaction().commit()` call. +But that is exactly what happened here because the equals/hasCode are based on the (generated) id, which was not set until the JPA transaction is committed. Note that this is just a concern when using generated identifiers. If you are using assigned identifiers this will not be a problem, assuming the identifier value is assigned prior to adding to the `Set`. Another option is to force the identifier to be generated and set prior to adding to the `Set`: -.Forcing identifier generation +[[entity-pojo-naive-equals-hashcode-persist-force-flush-example]] +.Forcing the flush before adding to the Set ==== [source,java] ---- -include::{extrasdir}/entity/listing8.java[] +include::{sourcedir-mapping}/identifier/NaiveEqualsHashCodeEntityTest.java[tag=entity-pojo-naive-equals-hashcode-persist-force-flush-example, indent=0] ---- ==== @@ -251,11 +275,23 @@ But this is often not feasible. The final approach is to use a "better" equals/hashCode implementation, making use of a natural-id or business-key. -.Better equals/hashCode with natural-id +[[entity-pojo-natural-id-equals-hashcode-example]] +.Natural Id equals/hashCode ==== [source,java] ---- -include::{extrasdir}/entity/listing9.java[] +include::{sourcedir-mapping}/identifier/NaturalIdEqualsHashCodeEntityTest.java[tag=entity-pojo-natural-id-equals-hashcode-example, indent=0] +---- +==== + +This time, when adding a `Book` to the `Library` `Set`, you can retrieve the `Book` even after it's being persisted: + +[[entity-pojo-natural-id-equals-hashcode-persist-example]] +.Natural Id equals/hashCode persist example +==== +[source,java] +---- +include::{sourcedir-mapping}/identifier/NaturalIdEqualsHashCodeEntityTest.java[tag=entity-pojo-natural-id-equals-hashcode-persist-example, indent=0] ---- ==== @@ -283,7 +319,7 @@ You can map an entity to a SQL query using the https://docs.jboss.org/hibernate/ ==== [source,java] ---- -include::{sourcedir-mapping}/SubselectTest.java[tag=mapping-Subselect-example,indent=0] +include::{sourcedir-mapping}/basic/SubselectTest.java[tag=mapping-Subselect-example,indent=0] ---- ==== @@ -299,7 +335,7 @@ So, if we have the following `AccountTransaction` record, the `AccountSummary` b ==== [source,java] ---- -include::{sourcedir-mapping}/SubselectTest.java[tag=mapping-Subselect-entity-find-example,indent=0] +include::{sourcedir-mapping}/basic/SubselectTest.java[tag=mapping-Subselect-entity-find-example,indent=0] ---- ==== @@ -310,7 +346,7 @@ If we add a new `AccountTransaction` entity and refresh the `AccountSummary` ent ==== [source,java] ---- -include::{sourcedir-mapping}/SubselectTest.java[tag=mapping-Subselect-entity-refresh-example,indent=0] +include::{sourcedir-mapping}/basic/SubselectTest.java[tag=mapping-Subselect-entity-refresh-example,indent=0] ---- ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/Identifier.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/Identifier.java deleted file mode 100644 index c55468aefd..0000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/Identifier.java +++ /dev/null @@ -1,2 +0,0 @@ -@Id -private Integer id; \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/SimpleEntity.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/SimpleEntity.java deleted file mode 100644 index 295c27a305..0000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/SimpleEntity.java +++ /dev/null @@ -1,4 +0,0 @@ -@Entity -public class Simple { - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/SimpleEntityWithTable.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/SimpleEntityWithTable.java deleted file mode 100644 index 386d780ec8..0000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/SimpleEntityWithTable.java +++ /dev/null @@ -1,5 +0,0 @@ -@Entity -@Table( catalog = "CRM", schema = "purchasing", name = "t_simple" ) -public class Simple { - ... -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing1.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing1.java deleted file mode 100644 index 366dd9bb97..0000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing1.java +++ /dev/null @@ -1,7 +0,0 @@ -Session session=...; - -Person p1 = session.get( Person.class,1 ); -Person p2 = session.get( Person.class,1 ); - -// this evaluates to true -assert p1==p2; \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing2.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing2.java deleted file mode 100644 index 650401d369..0000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing2.java +++ /dev/null @@ -1,8 +0,0 @@ -Session session1=...; -Session session2=...; - -Person p1 = session1.get( Person.class,1 ); -Person p2 = session2.get( Person.class,1 ); - -// this evaluates to false -assert p1==p2; \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing3.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing3.java deleted file mode 100644 index cc7a01b398..0000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing3.java +++ /dev/null @@ -1,12 +0,0 @@ -Session session=...; - -Club club = session.get( Club.class,1 ); - -Person p1 = session.get( Person.class,1 ); -Person p2 = session.get( Person.class,1 ); - -club.getMembers().add( p1 ); -club.getMembers().add( p2 ); - -// this evaluates to true -assert club.getMembers.size()==1; \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing4.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing4.java deleted file mode 100644 index a46b8a6c43..0000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing4.java +++ /dev/null @@ -1,13 +0,0 @@ -Session session1=...; -Session session2=...; - -Club club = session1.get( Club.class,1 ); - -Person p1 = session1.get( Person.class,1 ); -Person p2 = session2.get( Person.class,1 ); - -club.getMembers().add( p1 ); -club.getMembers().add( p2 ); - -// this evaluates to ... well it depends -assert club.getMembers.size()==1; \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing5.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing5.java deleted file mode 100644 index d22d415af0..0000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing5.java +++ /dev/null @@ -1,12 +0,0 @@ -Session session=...; - -Club club = session.get( Club.class,1 ); - -Person p1 = new Person(...); -Person p2 = new Person(...); - -club.getMembers().add( p1 ); -club.getMembers().add( p2 ); - -// this evaluates to ... again, it depends -assert club.getMembers.size()==1; \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing6.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing6.java deleted file mode 100644 index 6ef093c2f8..0000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing6.java +++ /dev/null @@ -1,24 +0,0 @@ -@Entity -public class Person { - - @Id - @GeneratedValue - private Integer id; - - @Override - public int hashCode() { - return Objects.hash( id ); - } - - @Override - public boolean equals(Object o) { - if ( this == o ) { - return true; - } - if ( !( o instanceof Person ) ) { - return false; - } - Person person = (Person) o; - return Objects.equals( id, person.id ); - } -} \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing7.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing7.java deleted file mode 100644 index 0cf83a328f..0000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing7.java +++ /dev/null @@ -1,15 +0,0 @@ -Session session=...; -session.getTransaction().begin(); - -Club club = session.get( Club.class,1 ); - -Person p1 = new Person(...); -Person p2 = new Person(...); - -club.getMembers().add( p1 ); -club.getMembers().add( p2 ); - -session.getTransaction().commit(); - -// will actually resolve to false! -assert club.getMembers().contains( p1 ); \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing8.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing8.java deleted file mode 100644 index 5d8eb567ba..0000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing8.java +++ /dev/null @@ -1,19 +0,0 @@ -Session session=...; -session.getTransaction().begin(); - -Club club = session.get( Club.class,1 ); - -Person p1 = new Person(...); -Person p2 = new Person(...); - -session.save( p1 ); -session.save( p2 ); -session.flush(); - -club.getMembers().add( p1 ); -club.getMembers().add( p2 ); - -session.getTransaction().commit(); - -// will actually resolve to false! -assert club.getMembers().contains( p1 ); \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing9.java b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing9.java deleted file mode 100644 index a0a296efb3..0000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/entity/listing9.java +++ /dev/null @@ -1,36 +0,0 @@ -@Entity -public class Person { - - @Id - @GeneratedValue - private Integer id; - - @NaturalId - private String ssn; - - protected Person() { - // Constructor for ORM - } - - public Person( String ssn ) { - // Constructor for app - this.ssn = ssn; - } - - @Override - public int hashCode() { - return Objects.hash( ssn ); - } - - @Override - public boolean equals(Object o) { - if ( this == o ) { - return true; - } - if ( !( o instanceof Person ) ) { - return false; - } - Person person = (Person) o; - return Objects.equals( ssn, person.ssn ); - } -} \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/NaiveEqualsHashCodeEntityTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/NaiveEqualsHashCodeEntityTest.java new file mode 100644 index 0000000000..9b201eb31a --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/NaiveEqualsHashCodeEntityTest.java @@ -0,0 +1,201 @@ +/* + * 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 . + */ +package org.hibernate.userguide.mapping.identifier; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class NaiveEqualsHashCodeEntityTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Library.class, + Book.class + }; + } + + @Before + public void init() { + doInJPA( this::entityManagerFactory, entityManager -> { + Library library = new Library(); + library.setId( 1L ); + library.setName( "Amazon" ); + + entityManager.persist( library ); + } ); + } + + @Test + public void testPersist() { + + //tag::entity-pojo-naive-equals-hashcode-persist-example[] + Book book1 = new Book(); + book1.setTitle( "High-Performance Java Persistence" ); + + Book book2 = new Book(); + book2.setTitle( "Java Persistence with Hibernate" ); + + Library library = doInJPA( this::entityManagerFactory, entityManager -> { + Library _library = entityManager.find( Library.class, 1L ); + + _library.getBooks().add( book1 ); + _library.getBooks().add( book2 ); + + return _library; + } ); + + assertFalse( library.getBooks().contains( book1 ) ); + assertFalse( library.getBooks().contains( book2 ) ); + //end::entity-pojo-naive-equals-hashcode-persist-example[] + } + + @Test + public void testPersistForceFlush() { + + //tag::entity-pojo-naive-equals-hashcode-persist-force-flush-example[] + Book book1 = new Book(); + book1.setTitle( "High-Performance Java Persistence" ); + + Book book2 = new Book(); + book2.setTitle( "Java Persistence with Hibernate" ); + + Library library = doInJPA( this::entityManagerFactory, entityManager -> { + Library _library = entityManager.find( Library.class, 1L ); + + entityManager.persist( book1 ); + entityManager.persist( book2 ); + entityManager.flush(); + + _library.getBooks().add( book1 ); + _library.getBooks().add( book2 ); + + return _library; + } ); + + assertTrue( library.getBooks().contains( book1 ) ); + assertTrue( library.getBooks().contains( book2 ) ); + //end::entity-pojo-naive-equals-hashcode-persist-force-flush-example[] + } + + //tag::entity-pojo-naive-equals-hashcode-example[] + @Entity(name = "Library") + public static class Library { + + @Id + private Long id; + + private String name; + + @OneToMany(cascade = CascadeType.ALL) + @JoinColumn(name = "book_id") + private Set books = new HashSet<>(); + + //Getters and setters are omitted for brevity + //end::entity-pojo-naive-equals-hashcode-example[] + + 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 Set getBooks() { + return books; + } + //tag::entity-pojo-naive-equals-hashcode-example[] + } + + @Entity(name = "Book") + public static class Book { + + @Id + @GeneratedValue + private Long id; + + private String title; + + private String author; + + //Getters and setters are omitted for brevity + //end::entity-pojo-naive-equals-hashcode-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + //tag::entity-pojo-naive-equals-hashcode-example[] + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Book book = (Book) o; + return Objects.equals( id, book.id ); + } + + @Override + public int hashCode() { + return Objects.hash( id ); + } + } + //end::entity-pojo-naive-equals-hashcode-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/NaturalIdEqualsHashCodeEntityTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/NaturalIdEqualsHashCodeEntityTest.java new file mode 100644 index 0000000000..a5644b6f58 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/NaturalIdEqualsHashCodeEntityTest.java @@ -0,0 +1,180 @@ +/* + * 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 . + */ +package org.hibernate.userguide.mapping.identifier; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.NaturalId; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class NaturalIdEqualsHashCodeEntityTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Library.class, + Book.class + }; + } + + @Before + public void init() { + doInJPA( this::entityManagerFactory, entityManager -> { + Library library = new Library(); + library.setId( 1L ); + library.setName( "Amazon" ); + + entityManager.persist( library ); + } ); + } + + @Test + public void testPersist() { + + //tag::entity-pojo-natural-id-equals-hashcode-persist-example[] + Book book1 = new Book(); + book1.setTitle( "High-Performance Java Persistence" ); + book1.setIsbn( "978-9730228236" ); + + Library library = doInJPA( this::entityManagerFactory, entityManager -> { + Library _library = entityManager.find( Library.class, 1L ); + + _library.getBooks().add( book1 ); + + return _library; + } ); + + assertTrue( library.getBooks().contains( book1 ) ); + //end::entity-pojo-natural-id-equals-hashcode-persist-example[] + } + + //tag::entity-pojo-natural-id-equals-hashcode-example[] + @Entity(name = "Library") + public static class Library { + + @Id + private Long id; + + private String name; + + @OneToMany(cascade = CascadeType.ALL) + @JoinColumn(name = "book_id") + private Set books = new HashSet<>(); + + //Getters and setters are omitted for brevity + //end::entity-pojo-natural-id-equals-hashcode-example[] + + 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 Set getBooks() { + return books; + } + //tag::entity-pojo-natural-id-equals-hashcode-example[] + } + + @Entity(name = "Book") + public static class Book { + + @Id + @GeneratedValue + private Long id; + + private String title; + + private String author; + + @NaturalId + private String isbn; + + //Getters and setters are omitted for brevity + //end::entity-pojo-natural-id-equals-hashcode-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getIsbn() { + return isbn; + } + + public void setIsbn(String isbn) { + this.isbn = isbn; + } + + //tag::entity-pojo-natural-id-equals-hashcode-example[] + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Book book = (Book) o; + return Objects.equals( isbn, book.isbn ); + } + + @Override + public int hashCode() { + return Objects.hash( isbn ); + } + } + //end::entity-pojo-natural-id-equals-hashcode-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimpleEntityTableTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimpleEntityTableTest.java new file mode 100644 index 0000000000..b9b9156d9f --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimpleEntityTableTest.java @@ -0,0 +1,82 @@ +/* + * 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 . + */ +package org.hibernate.userguide.mapping.identifier; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class SimpleEntityTableTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Book.class + }; + } + + @Test + public void test() { + + } + + //tag::entity-pojo-table-mapping-example[] + @Entity(name = "Book") + @Table( + catalog = "public", + schema = "store", + name = "book" + ) + public static class Book { + + @Id + private Long id; + + private String title; + + private String author; + + //Getters and setters are omitted for brevity + //end::entity-pojo-table-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + //tag::entity-pojo-table-mapping-example[] + } + //end::entity-pojo-table-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimpleEntityTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimpleEntityTest.java new file mode 100644 index 0000000000..2d6b5c874a --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimpleEntityTest.java @@ -0,0 +1,236 @@ +/* + * 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 . + */ +package org.hibernate.userguide.mapping.identifier; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +public class SimpleEntityTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Library.class, + Book.class + }; + } + + @Before + public void init() { + doInJPA( this::entityManagerFactory, entityManager -> { + Library library = new Library(); + library.setId( 1L ); + library.setName( "Amazon" ); + + entityManager.persist( library ); + + Book book = new Book(); + book.setId( 1L ); + book.setTitle( "High-Performance Java Persistence" ); + book.setAuthor( "Vlad Mihalcea" ); + + entityManager.persist( book ); + } ); + } + + + @Test + public void testIdentityScope() { + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::entity-pojo-identity-scope-example[] + Book book1 = entityManager.find( Book.class, 1L ); + Book book2 = entityManager.find( Book.class, 1L ); + + assertTrue( book1 == book2 ); + //end::entity-pojo-identity-scope-example[] + } ); + + } + + @Test + public void testSetIdentityScope() { + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::entity-pojo-set-identity-scope-example[] + Library library = entityManager.find( Library.class, 1L ); + + Book book1 = entityManager.find( Book.class, 1L ); + Book book2 = entityManager.find( Book.class, 1L ); + + library.getBooks().add( book1 ); + library.getBooks().add( book2 ); + + assertEquals( 1, library.getBooks().size() ); + //end::entity-pojo-set-identity-scope-example[] + } ); + } + + @Test + public void testMultiSessionIdentityScope() { + + //tag::entity-pojo-multi-session-identity-scope-example[] + Book book1 = doInJPA( this::entityManagerFactory, entityManager -> { + return entityManager.find( Book.class, 1L ); + } ); + + Book book2 = doInJPA( this::entityManagerFactory, entityManager -> { + return entityManager.find( Book.class, 1L ); + } ); + + assertFalse( book1 == book2 ); + //end::entity-pojo-multi-session-identity-scope-example[] + } + + @Test + public void testMultiSessionSetIdentityScope() { + + Book book1 = doInJPA( this::entityManagerFactory, entityManager -> { + return entityManager.find( Book.class, 1L ); + } ); + + Book book2 = doInJPA( this::entityManagerFactory, entityManager -> { + return entityManager.find( Book.class, 1L ); + } ); + //tag::entity-pojo-multi-session-set-identity-scope-example[] + + doInJPA( this::entityManagerFactory, entityManager -> { + Set books = new HashSet<>(); + + books.add( book1 ); + books.add( book2 ); + + assertEquals( 2, books.size() ); + } ); + //end::entity-pojo-multi-session-set-identity-scope-example[] + } + + @Test + public void testTransientSetIdentityScope() { + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::entity-pojo-transient-set-identity-scope-example[] + Library library = entityManager.find( Library.class, 1L ); + + Book book1 = new Book(); + book1.setId( 100L ); + book1.setTitle( "High-Performance Java Persistence" ); + + Book book2 = new Book(); + book2.setId( 101L ); + book2.setTitle( "Java Persistence with Hibernate" ); + + library.getBooks().add( book1 ); + library.getBooks().add( book2 ); + + assertEquals( 2, library.getBooks().size() ); + //end::entity-pojo-transient-set-identity-scope-example[] + } ); + } + + //tag::entity-pojo-set-mapping-example[] + @Entity(name = "Library") + public static class Library { + + @Id + private Long id; + + private String name; + + @OneToMany(cascade = CascadeType.ALL) + @JoinColumn(name = "book_id") + private Set books = new HashSet<>(); + + //Getters and setters are omitted for brevity + //end::entity-pojo-set-mapping-example[] + + 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 Set getBooks() { + return books; + } + //tag::entity-pojo-set-mapping-example[] + } + //end::entity-pojo-set-mapping-example[] + + //tag::entity-pojo-mapping-example[] + @Entity(name = "Book") + public static class Book { + + //tag::entity-pojo-identifier-mapping-example[] + @Id + private Long id; + //end::entity-pojo-identifier-mapping-example[] + + private String title; + + private String author; + + //Getters and setters are omitted for brevity + //end::entity-pojo-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + //tag::entity-pojo-mapping-example[] + } + //end::entity-pojo-mapping-example[] +}