From 1fb9d1fb1a4f4edae0c58d9f9c55585d26def78e Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Thu, 1 Jun 2017 17:58:57 +0300 Subject: [PATCH] HHH-11186 - Add examples for all Hibernate annotations Document @MapKeyClass annotation --- .../userguide/appendices/Annotations.adoc | 2 +- .../chapters/domain/collections.adoc | 67 ++++++- ...ctions-map-custom-key-type-sql-example.sql | 11 +- ...ollections-map-key-class-fetch-example.sql | 24 +++ ...lections-map-key-class-mapping-example.sql | 18 ++ ...lections-map-key-class-persist-example.sql | 35 ++++ .../collections/MapKeyClassTest.java | 180 ++++++++++++++++++ .../userguide/collections/MapKeyTypeTest.java | 4 +- 8 files changed, 329 insertions(+), 12 deletions(-) create mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-fetch-example.sql create mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-mapping-example.sql create mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-persist-example.sql create mode 100644 documentation/src/test/java/org/hibernate/userguide/collections/MapKeyClassTest.java diff --git a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc index f27faf38a1..3704d25a29 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc @@ -296,7 +296,7 @@ See the <> section for more info. [[annotations-jpa-mapkeycolumn]] ==== `@MapKeyColumn` diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc index b1131b48fd..5d74fee543 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc @@ -611,7 +611,7 @@ while the `@Column` mapping gives the value of the `java.util.Map` in question. Since we want to map all the calls by their associated `java.util.Date`, not by their timestamp since epoch which is a number, the entity mapping looks as follows: [[collections-map-custom-key-type-mapping-example]] -.@MapKeyType mapping example +.`@MapKeyType` mapping example ==== [source,java] ---- @@ -628,6 +628,71 @@ include::{sourcedir}/type/TimestampEpochType.java[tags=collections-map-custom-ke The `TimestampEpochType` allows us to map a Unix timestamp since epoch to a `java.util.Date`. But, without the `@MapKeyType` Hibernate annotation, it would nt be possible to customize the `Map` key type. +[[collections-map-key-class]] +===== Maps having an interface type as the key + +Considering you have the following `PhoneNumber` interface with an implementation given by the `MobilePhone` class type: + +[[collections-map-key-class-type-mapping-example]] +.`PhoneNumber` interface and the `MobilePhone` class type +==== +[source,java] +---- +include::{sourcedir}/MapKeyClassTest.java[tags=collections-map-key-class-type-mapping-example,indent=0] +---- +==== + +If you want to use the `PhoneNumber` interface as a `java.util.Map` key, then you need to supply the +http://docs.oracle.com/javaee/7/api/javax/persistence/MapKeyClass.html[`@MapKeyClass`] annotation as well. + +[[collections-map-key-class-mapping-example]] +.`@MapKeyClass` mapping example +==== +[source,java] +---- +include::{sourcedir}/MapKeyClassTest.java[tags=collections-map-key-class-mapping-example,indent=0] +---- + +[source,sql] +---- +include::{extrasdir}/collections-map-key-class-mapping-example.sql[] +---- +==== + +When inserting a `Person` with a `callRegister` containing 2 `MobilePhone` references, +Hibernate generates the following SQL statements: + +[[collections-map-key-class-persist-example]] +.`@MapKeyClass` persist example +==== +[source,java] +---- +include::{sourcedir}/MapKeyClassTest.java[tags=collections-map-key-class-persist-example,indent=0] +---- + +[source,sql] +---- +include::{extrasdir}/collections-map-key-class-persist-example.sql[] +---- +==== + +When fetching a `Person` and accessing the `callRegister` `Map`, +Hibernate generates the following SQL statements: + +[[collections-map-key-class-fetch-example]] +.`@MapKeyClass` fetch example +==== +[source,java] +---- +include::{sourcedir}/MapKeyClassTest.java[tags=collections-map-key-class-fetch-example,indent=0] +---- + +[source,sql] +---- +include::{extrasdir}/collections-map-key-class-fetch-example.sql[] +---- +==== + [[collections-map-unidirectional]] ===== Unidirectional maps diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-custom-key-type-sql-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-custom-key-type-sql-example.sql index 8af719d586..d8fe7c6424 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-custom-key-type-sql-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-custom-key-type-sql-example.sql @@ -4,18 +4,13 @@ create table person ( ) create table call_register ( - phone_id int8 not null, + person_id int8 not null, phone_number int4, call_timestamp_epoch int8 not null, - primary key (phone_id, call_key) + primary key (person_id, call_key) ) alter table if exists call_register add constraint FKsn58spsregnjyn8xt61qkxsub - foreign key (phone_id) - references person - -alter table if exists call_register - add constraint FKsn58spsregnjyn8xt61qkxsub - foreign key (phone_id) + foreign key (person_id) references person \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-fetch-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-fetch-example.sql new file mode 100644 index 0000000000..7b89d456ed --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-fetch-example.sql @@ -0,0 +1,24 @@ +select + cr.person_id as person_i1_0_0_, + cr.call_register as call_reg2_0_0_, + cr.country_code as country_3_0_, + cr.operator_code as operator4_0_, + cr.subscriber_code as subscrib5_0_ +from + call_register cr +where + cr.person_id = ? + +-- binding parameter [1] as [BIGINT] - [1] + +-- extracted value ([person_i1_0_0_] : [BIGINT]) - [1] +-- extracted value ([call_reg2_0_0_] : [INTEGER]) - [101] +-- extracted value ([country_3_0_] : [VARCHAR]) - [01] +-- extracted value ([operator4_0_] : [VARCHAR]) - [234] +-- extracted value ([subscrib5_0_] : [VARCHAR]) - [567] + +-- extracted value ([person_i1_0_0_] : [BIGINT]) - [1] +-- extracted value ([call_reg2_0_0_] : [INTEGER]) - [102] +-- extracted value ([country_3_0_] : [VARCHAR]) - [01] +-- extracted value ([operator4_0_] : [VARCHAR]) - [234] +-- extracted value ([subscrib5_0_] : [VARCHAR]) - [789] \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-mapping-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-mapping-example.sql new file mode 100644 index 0000000000..5f7d89db6b --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-mapping-example.sql @@ -0,0 +1,18 @@ +create table person ( + id bigint not null, + primary key (id) +) + +create table call_register ( + person_id bigint not null, + call_register integer, + country_code varchar(255) not null, + operator_code varchar(255) not null, + subscriber_code varchar(255) not null, + primary key (person_id, country_code, operator_code, subscriber_code) +) + +alter table call_register + add constraint FKqyj2at6ik010jqckeaw23jtv2 + foreign key (person_id) + references person \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-persist-example.sql new file mode 100644 index 0000000000..ba4d929158 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/collections/collections-map-key-class-persist-example.sql @@ -0,0 +1,35 @@ +insert into person (id) values (?) + +-- binding parameter [1] as [BIGINT] - [1] + +insert into call_register( + person_id, + country_code, + operator_code, + subscriber_code, + call_register +) +values + (?, ?, ?, ?, ?) + +-- binding parameter [1] as [BIGINT] - [1] +-- binding parameter [2] as [VARCHAR] - [01] +-- binding parameter [3] as [VARCHAR] - [234] +-- binding parameter [4] as [VARCHAR] - [789] +-- binding parameter [5] as [INTEGER] - [102] + +insert into call_register( + person_id, + country_code, + operator_code, + subscriber_code, + call_register +) +values + (?, ?, ?, ?, ?) + +-- binding parameter [1] as [BIGINT] - [1] +-- binding parameter [2] as [VARCHAR] - [01] +-- binding parameter [3] as [VARCHAR] - [234] +-- binding parameter [4] as [VARCHAR] - [567] +-- binding parameter [5] as [INTEGER] - [101] \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyClassTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyClassTest.java new file mode 100644 index 0000000000..ad718cb4e5 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyClassTest.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.collections; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.MapKeyClass; +import javax.persistence.MapKeyColumn; +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.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class MapKeyClassTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + }; + } + + @Test + public void testLifecycle() { + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::collections-map-key-class-persist-example[] + Person person = new Person(); + person.setId( 1L ); + person.getCallRegister().put( new MobilePhone( "01", "234", "567" ), 101 ); + person.getCallRegister().put( new MobilePhone( "01", "234", "789" ), 102 ); + + entityManager.persist( person ); + //end::collections-map-key-class-persist-example[] + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::collections-map-key-class-fetch-example[] + Person person = entityManager.find( Person.class, 1L ); + assertEquals( 2, person.getCallRegister().size() ); + + assertEquals( + Integer.valueOf( 101 ), + person.getCallRegister().get( MobilePhone.fromString( "01-234-567" ) ) + ); + + assertEquals( + Integer.valueOf( 102 ), + person.getCallRegister().get( MobilePhone.fromString( "01-234-789" ) ) + ); + //end::collections-map-key-class-fetch-example[] + } ); + } + + //tag::collections-map-key-class-mapping-example[] + @Entity + @Table(name = "person") + public static class Person { + + @Id + private Long id; + + @ElementCollection + @CollectionTable( + name = "call_register", + joinColumns = @JoinColumn(name = "person_id") + ) + @MapKeyColumn( name = "call_timestamp_epoch" ) + @MapKeyClass( MobilePhone.class ) + @Column(name = "call_register") + private Map callRegister = new HashMap<>(); + + //Getters and setters are omitted for brevity + //end::collections-map-key-class-mapping-example[] + + public void setId(Long id) { + this.id = id; + } + + public Map getCallRegister() { + return callRegister; + } + //tag::collections-map-key-class-mapping-example[] + } + //end::collections-map-key-class-mapping-example[] + + //tag::collections-map-key-class-type-mapping-example[] + public interface PhoneNumber { + + String get(); + } + + @Embeddable + public static class MobilePhone + implements PhoneNumber { + + static PhoneNumber fromString(String phoneNumber) { + String[] tokens = phoneNumber.split( "-" ); + if ( tokens.length != 3 ) { + throw new IllegalArgumentException( "invalid phone number: " + phoneNumber ); + } + int i = 0; + return new MobilePhone( + tokens[i++], + tokens[i++], + tokens[i] + ); + } + + private MobilePhone() { + } + + public MobilePhone( + String countryCode, + String operatorCode, + String subscriberCode) { + this.countryCode = countryCode; + this.operatorCode = operatorCode; + this.subscriberCode = subscriberCode; + } + + @Column(name = "country_code") + private String countryCode; + + @Column(name = "operator_code") + private String operatorCode; + + @Column(name = "subscriber_code") + private String subscriberCode; + + @Override + public String get() { + return String.format( + "%s-%s-%s", + countryCode, + operatorCode, + subscriberCode + ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + MobilePhone that = (MobilePhone) o; + return Objects.equals( countryCode, that.countryCode ) && + Objects.equals( operatorCode, that.operatorCode ) && + Objects.equals( subscriberCode, that.subscriberCode ); + } + + @Override + public int hashCode() { + return Objects.hash( countryCode, operatorCode, subscriberCode ); + } + } + //end::collections-map-key-class-type-mapping-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyTypeTest.java b/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyTypeTest.java index 58575a701e..deec47df3b 100644 --- a/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyTypeTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/collections/MapKeyTypeTest.java @@ -110,7 +110,7 @@ public class MapKeyTypeTest extends BaseEntityManagerFunctionalTestCase { @ElementCollection @CollectionTable( name = "call_register", - joinColumns = @JoinColumn(name = "phone_id") + joinColumns = @JoinColumn(name = "person_id") ) @MapKeyColumn( name = "call_timestamp_epoch" ) @Column(name = "phone_number") @@ -136,7 +136,7 @@ public class MapKeyTypeTest extends BaseEntityManagerFunctionalTestCase { @ElementCollection @CollectionTable( name = "call_register", - joinColumns = @JoinColumn(name = "phone_id") + joinColumns = @JoinColumn(name = "person_id") ) @MapKeyType( @Type(