Add documentation for CompositeUserType
This commit is contained in:
parent
c18e611ed6
commit
a961ba45ac
|
@ -1568,7 +1568,7 @@ There are also corresponding, specialized forms of `@Type` for specific model pa
|
|||
* For other collection mappings, `@Type` describes the elements
|
||||
* For discriminated association mappings (`@Any` and `@ManyToAny`), `@Type` describes the discriminator value
|
||||
|
||||
`@Type` allows for more complex mapping concerns; but, <<basic-jpa-convert,AttributeConverter> and
|
||||
`@Type` allows for more complex mapping concerns; but, <<basic-jpa-convert,AttributeConverter>> and
|
||||
<<basic-mapping-composition>> should generally be preferred as simpler solutions
|
||||
|
||||
[[basic-nationalized]]
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
:coreProjectDir: {rootProjectDir}/hibernate-core
|
||||
:coreTestSrcDir: {rootProjectDir}/hibernate-core/src/test/java
|
||||
:instantiatorTestDir: {coreTestSrcDir}/org/hibernate/orm/test/mapping/embeddable/strategy/instantiator
|
||||
:usertypeTestDir: {coreTestSrcDir}/org/hibernate/orm/test/mapping/embeddable/strategy/usertype
|
||||
:extrasdir: extras
|
||||
|
||||
Historically Hibernate called these components.
|
||||
|
@ -325,6 +326,78 @@ include::{instantiatorTestDir}/registered/Person.java[tags=embeddable-instantiat
|
|||
|
||||
|
||||
|
||||
[[embeddable-mapping-custom]]
|
||||
==== Custom type mapping
|
||||
|
||||
Another approach is to supply the implementation of the `org.hibernate.usertype.CompositeUserType` contract using `@CompositeType`,
|
||||
which is an extension to the `org.hibernate.metamodel.spi.EmbeddableInstantiator` contract.
|
||||
|
||||
There are also corresponding, specialized forms of `@CompositeType` for specific model parts:
|
||||
|
||||
* When mapping a Map, `@CompositeType` describes the Map value while `@MapKeyCompositeType` describes the Map key
|
||||
* For collection mappings, `@CompositeType` describes the elements
|
||||
|
||||
For example, consider the following custom type:
|
||||
|
||||
[[embeddable-usertype-domain-ex]]
|
||||
.`CompositeUserType` - Domain type
|
||||
====
|
||||
[source, JAVA, indent=0]
|
||||
----
|
||||
include::{usertypeTestDir}/embedded/Name.java[tags=embeddable-usertype-domain]
|
||||
----
|
||||
====
|
||||
|
||||
Here, `Name` only allows use of the constructor accepting its state. Because this class does not follow Java Bean
|
||||
conventions, a custom user type for instantiation and state access is needed.
|
||||
|
||||
[[embeddable-usertype-impl-ex]]
|
||||
.`CompositeUserType` - Implementation
|
||||
====
|
||||
[source, JAVA, indent=0]
|
||||
----
|
||||
include::{usertypeTestDir}/embedded/NameCompositeUserType.java[tags=embeddable-usertype-impl]
|
||||
----
|
||||
====
|
||||
|
||||
A composite user type needs an embeddable mapper class, which represents the embeddable mapping structure of the type
|
||||
i.e. the way the type would look like if you had the option to write a custom `@Embeddable` class.
|
||||
|
||||
In addition to the instantiation logic, a composite user type also has to provide a way to decompose the returned type
|
||||
into the individual components/properties of the embeddable mapper class through `getPropertyValue`.
|
||||
The property index, just like in the `instantiate` method, is based on the alphabetical order of the attribute names
|
||||
of the embeddable mapper class.
|
||||
|
||||
The composite user type also needs to provide methods to handle the mutability, equals, hashCode and the cache
|
||||
serialization and deserialization of the returned type.
|
||||
|
||||
There are a few ways to specify the composite user type. The `@org.hibernate.annotations.CompositeType`
|
||||
annotation can be used on the embedded and element collection attributes:
|
||||
|
||||
[[embeddable-usertype-property-ex]]
|
||||
.`@CompositeType` on attribute
|
||||
====
|
||||
[source, JAVA, indent=0]
|
||||
----
|
||||
include::{usertypeTestDir}/embedded/Person.java[tags=embeddable-usertype-property]
|
||||
----
|
||||
====
|
||||
|
||||
Or `@org.hibernate.annotations.CompositeTypeRegistration` may be used, which is useful
|
||||
when the application developer wants to apply the composite user type for all domain type uses.
|
||||
|
||||
[[embeddable-usertype-registration-ex]]
|
||||
.`@CompositeTypeRegistration`
|
||||
====
|
||||
[source, JAVA, indent=0]
|
||||
----
|
||||
include::{usertypeTestDir}/registered/Person.java[tags=embeddable-usertype-registration]
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
|
||||
[[embeddable-multiple-namingstrategy]]
|
||||
==== Embeddables and ImplicitNamingStrategy
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package org.hibernate.orm.test.mapping.embeddable.strategy.usertype.embedded;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DomainModel( annotatedClasses = { Person.class, Name.class } )
|
||||
@SessionFactory
|
||||
public class CompositeUserTypeTests {
|
||||
|
||||
@Test
|
||||
public void basicTest(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final Person mick = new Person( 1, new Name( "Mick", "Jagger" ) );
|
||||
session.persist( mick );
|
||||
|
||||
final Person john = new Person( 2, new Name( "John", "Doe" ) );
|
||||
john.addAlias( new Name( "Jon", "Doe" ) );
|
||||
session.persist( john );
|
||||
} );
|
||||
scope.inTransaction( (session) -> {
|
||||
final Person mick = session.createQuery( "from Person where id = 1", Person.class ).uniqueResult();
|
||||
assertThat( mick.getName().firstName() ).isEqualTo( "Mick" );
|
||||
} );
|
||||
scope.inTransaction( (session) -> {
|
||||
final Person john = session.createQuery( "from Person p join fetch p.aliases where p.id = 2", Person.class ).uniqueResult();
|
||||
assertThat( john.getName().firstName() ).isEqualTo( "John" );
|
||||
assertThat( john.getAliases() ).hasSize( 1 );
|
||||
final Name alias = john.getAliases().iterator().next();
|
||||
assertThat( alias.firstName() ).isEqualTo( "Jon" );
|
||||
} );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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.orm.test.mapping.embeddable.strategy.usertype.embedded;
|
||||
|
||||
//tag::embeddable-usertype-domain[]
|
||||
public class Name {
|
||||
private final String first;
|
||||
private final String last;
|
||||
|
||||
public Name(String first, String last) {
|
||||
this.first = first;
|
||||
this.last = last;
|
||||
}
|
||||
|
||||
public String firstName() {
|
||||
return first;
|
||||
}
|
||||
|
||||
public String lastName() {
|
||||
return last;
|
||||
}
|
||||
}
|
||||
//end::embeddable-usertype-domain[]
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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.orm.test.mapping.embeddable.strategy.usertype.embedded;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.spi.ValueAccess;
|
||||
import org.hibernate.usertype.CompositeUserType;
|
||||
|
||||
//tag::embeddable-usertype-impl[]
|
||||
public class NameCompositeUserType implements CompositeUserType<Name> {
|
||||
|
||||
public static class NameMapper {
|
||||
String firstName;
|
||||
String lastName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> embeddable() {
|
||||
return NameMapper.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Name> returnedClass() {
|
||||
return Name.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Name instantiate(ValueAccess valueAccess, SessionFactoryImplementor sessionFactory) {
|
||||
// alphabetical
|
||||
final String first = valueAccess.getValue( 0, String.class );
|
||||
final String last = valueAccess.getValue( 1, String.class );
|
||||
return new Name( first, last );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPropertyValue(Name component, int property) throws HibernateException {
|
||||
// alphabetical
|
||||
switch ( property ) {
|
||||
case 0:
|
||||
return component.firstName();
|
||||
case 1:
|
||||
return component.lastName();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Name x, Name y) {
|
||||
return x == y || x != null && Objects.equals( x.firstName(), y.firstName() )
|
||||
&& Objects.equals( x.lastName(), y.lastName() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode(Name x) {
|
||||
return Objects.hash( x.firstName(), x.lastName() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Name deepCopy(Name value) {
|
||||
return value; // immutable
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMutable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Serializable disassemble(Name value) {
|
||||
return new String[] { value.firstName(), value.lastName() };
|
||||
}
|
||||
|
||||
@Override
|
||||
public Name assemble(Serializable cached, Object owner) {
|
||||
final String[] parts = (String[]) cached;
|
||||
return new Name( parts[0], parts[1] );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Name replace(Name detached, Name managed, Object owner) {
|
||||
return detached;
|
||||
}
|
||||
|
||||
}
|
||||
//end::embeddable-usertype-impl[]
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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.orm.test.mapping.embeddable.strategy.usertype.embedded;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.annotations.CompositeType;
|
||||
|
||||
import jakarta.persistence.AttributeOverride;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Embedded;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
|
||||
@Table(name = "people")
|
||||
//tag::embeddable-usertype-property[]
|
||||
@Entity
|
||||
public class Person {
|
||||
@Id
|
||||
public Integer id;
|
||||
|
||||
@Embedded
|
||||
@AttributeOverride(name = "firstName", column = @Column(name = "first_name"))
|
||||
@AttributeOverride(name = "lastName", column = @Column(name = "last_name"))
|
||||
@CompositeType( NameCompositeUserType.class )
|
||||
public Name name;
|
||||
|
||||
@ElementCollection
|
||||
@AttributeOverride(name = "firstName", column = @Column(name = "first_name"))
|
||||
@AttributeOverride(name = "lastName", column = @Column(name = "last_name"))
|
||||
@CompositeType( NameCompositeUserType.class )
|
||||
public Set<Name> aliases;
|
||||
|
||||
//end::embeddable-usertype-property[]
|
||||
|
||||
private Person() {
|
||||
// for Hibernate use
|
||||
}
|
||||
|
||||
public Person(Integer id, Name name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Name getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(Name name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Set<Name> getAliases() {
|
||||
return aliases;
|
||||
}
|
||||
|
||||
public void setAliases(Set<Name> aliases) {
|
||||
this.aliases = aliases;
|
||||
}
|
||||
|
||||
public void addAlias(Name alias) {
|
||||
if ( aliases == null ) {
|
||||
aliases = new HashSet<>();
|
||||
}
|
||||
aliases.add( alias );
|
||||
}
|
||||
|
||||
//tag::embeddable-usertype-property[]
|
||||
}
|
||||
//end::embeddable-usertype-property[]
|
|
@ -0,0 +1,38 @@
|
|||
package org.hibernate.orm.test.mapping.embeddable.strategy.usertype.registered;
|
||||
|
||||
import org.hibernate.orm.test.mapping.embeddable.strategy.usertype.embedded.Name;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DomainModel( annotatedClasses = { Person.class, Name.class } )
|
||||
@SessionFactory
|
||||
public class CompositeUserTypeTests {
|
||||
|
||||
@Test
|
||||
public void basicTest(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final Person mick = new Person( 1, new Name( "Mick", "Jagger" ) );
|
||||
session.persist( mick );
|
||||
|
||||
final Person john = new Person( 2, new Name( "John", "Doe" ) );
|
||||
john.addAlias( new Name( "Jon", "Doe" ) );
|
||||
session.persist( john );
|
||||
} );
|
||||
scope.inTransaction( (session) -> {
|
||||
final Person mick = session.createQuery( "from Person where id = 1", Person.class ).uniqueResult();
|
||||
assertThat( mick.getName().firstName() ).isEqualTo( "Mick" );
|
||||
} );
|
||||
scope.inTransaction( (session) -> {
|
||||
final Person john = session.createQuery( "from Person p join fetch p.aliases where p.id = 2", Person.class ).uniqueResult();
|
||||
assertThat( john.getName().firstName() ).isEqualTo( "John" );
|
||||
assertThat( john.getAliases() ).hasSize( 1 );
|
||||
final Name alias = john.getAliases().iterator().next();
|
||||
assertThat( alias.firstName() ).isEqualTo( "Jon" );
|
||||
} );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.orm.test.mapping.embeddable.strategy.usertype.registered;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.annotations.CompositeType;
|
||||
import org.hibernate.annotations.CompositeTypeRegistration;
|
||||
import org.hibernate.orm.test.mapping.embeddable.strategy.usertype.embedded.Name;
|
||||
import org.hibernate.orm.test.mapping.embeddable.strategy.usertype.embedded.NameCompositeUserType;
|
||||
|
||||
import jakarta.persistence.AttributeOverride;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Embedded;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
|
||||
@Table(name = "people")
|
||||
//tag::embeddable-usertype-registration[]
|
||||
@Entity
|
||||
@CompositeTypeRegistration( embeddableClass = Name.class, userType = NameCompositeUserType.class )
|
||||
public class Person {
|
||||
@Id
|
||||
public Integer id;
|
||||
|
||||
@Embedded
|
||||
@AttributeOverride(name = "firstName", column = @Column(name = "first_name"))
|
||||
@AttributeOverride(name = "lastName", column = @Column(name = "last_name"))
|
||||
public Name name;
|
||||
|
||||
@ElementCollection
|
||||
@AttributeOverride(name = "firstName", column = @Column(name = "first_name"))
|
||||
@AttributeOverride(name = "lastName", column = @Column(name = "last_name"))
|
||||
public Set<Name> aliases;
|
||||
|
||||
//end::embeddable-usertype-registration[]
|
||||
|
||||
private Person() {
|
||||
// for Hibernate use
|
||||
}
|
||||
|
||||
public Person(Integer id, Name name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Name getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(Name name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Set<Name> getAliases() {
|
||||
return aliases;
|
||||
}
|
||||
|
||||
public void setAliases(Set<Name> aliases) {
|
||||
this.aliases = aliases;
|
||||
}
|
||||
|
||||
public void addAlias(Name alias) {
|
||||
if ( aliases == null ) {
|
||||
aliases = new HashSet<>();
|
||||
}
|
||||
aliases.add( alias );
|
||||
}
|
||||
|
||||
//tag::embeddable-usertype-registration[]
|
||||
}
|
||||
//end::embeddable-usertype-registration[]
|
Loading…
Reference in New Issue