Skip to content

Commit

Permalink
HHH-11186 - Add examples for all Hibernate annotations
Browse files Browse the repository at this point in the history
Document @MapKeyClass annotation
  • Loading branch information
vladmihalcea committed Jun 1, 2017
1 parent a4db601 commit 1fb9d1f
Show file tree
Hide file tree
Showing 8 changed files with 329 additions and 12 deletions.
Expand Up @@ -296,7 +296,7 @@ See the <<chapters/domain/collections.adoc#collections-map-unidirectional-exampl

The http://docs.oracle.com/javaee/7/api/javax/persistence/MapKeyClass.html[`@MapKeyClass`] annotation is used to specify the type of the map key of a `java.util.Map` associations.

//TODO: Add example
See the <<chapters/domain/collections.adoc#collections-map-key-class, `@MapKeyClass` mapping>> section for more info.

[[annotations-jpa-mapkeycolumn]]
==== `@MapKeyColumn`
Expand Down
Expand Up @@ -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]
----
Expand All @@ -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

Expand Down
Expand Up @@ -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
@@ -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]
@@ -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
@@ -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]
@@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<PhoneNumber, Integer> 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<PhoneNumber, Integer> 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[]
}
Expand Up @@ -110,7 +110,7 @@ public static class PersonDummy {
@ElementCollection
@CollectionTable(
name = "call_register",
joinColumns = @JoinColumn(name = "phone_id")
joinColumns = @JoinColumn(name = "person_id")
)
@MapKeyColumn( name = "call_timestamp_epoch" )
@Column(name = "phone_number")
Expand All @@ -136,7 +136,7 @@ public static class Person {
@ElementCollection
@CollectionTable(
name = "call_register",
joinColumns = @JoinColumn(name = "phone_id")
joinColumns = @JoinColumn(name = "person_id")
)
@MapKeyType(
@Type(
Expand Down

0 comments on commit 1fb9d1f

Please sign in to comment.