HHH-11186 - Add examples for all Hibernate annotations

Document @Tuplizer annotation
This commit is contained in:
Vlad Mihalcea 2017-06-22 12:20:45 +03:00
parent 027ca97090
commit 385f6d056c
11 changed files with 466 additions and 2 deletions

View File

@ -1298,7 +1298,7 @@ For entities, the tupelizer must implement the https://docs.jboss.org/hibernate/
For embeddables, the tupelizer must implement the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/tuple/component/ComponentTuplizer.html[`ComponentTuplizer`] interface.
//TODO: Add example
See the <<chapters/domain/entity.adoc#entity-tuplizer, `@Tuplizer` mapping>> section for more info.
[[annotations-hibernate-tuplizers]]
==== `@Tuplizers`

View File

@ -2,6 +2,7 @@
=== Entity types
:sourcedir-locking: ../../../../../test/java/org/hibernate/userguide/locking
:sourcedir-mapping: ../../../../../test/java/org/hibernate/userguide/mapping/basic
:sourcedir-proxy: ../../../../../test/java/org/hibernate/userguide/proxy
:extrasdir: extras
.Usage of the word _entity_
@ -312,7 +313,76 @@ include::{sourcedir-mapping}/SubselectTest.java[tag=mapping-Subselect-entity-ref
----
====
The goala of the `@Synchronize` annotation in the `AccountSummary` entity mapping is to instruct Hibernate which database tables are needed by the
The goal 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.
[[entity-tuplizer]]
==== Dynamic entity proxies using the @Tuplizer annotation
It is possible to map your entities as dynamic proxies using
the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Tuplizer.html[`@Tuplizer`] annotation.
In the following entity mapping, both the embeddable and the entity are mapped as interfaces, not Pojos.
[[entity-tuplizer-entity-mapping]]
.Dynamic entity proxy mapping
====
[source,java]
----
include::{sourcedir-proxy}/tuplizer/Cuisine.java[tag=entity-tuplizer-entity-mapping,indent=0]
----
[source,java]
----
include::{sourcedir-proxy}/tuplizer/Country.java[tag=entity-tuplizer-entity-mapping,indent=0]
----
====
The `@Tuplizer` instructs Hibernate to use the `DynamicEntityTuplizer` and `DynamicEmbeddableTuplizer` to handle
the associated entity and embeddable object types.
Both the `Cuising` entity and the `Country` embeddable types are going to be instantiated as Java dynamic proxies,
as you can see in the following `DynamicInstantiator` example:
[[entity-tuplizer-instantiator]]
.Instantiating entities and embeddables as dynamic proxies
====
[source,java]
----
include::{sourcedir-proxy}/tuplizer/DynamicEntityTuplizer.java[tag=entity-tuplizer-instantiator,indent=0]
----
[source,java]
----
include::{sourcedir-proxy}/tuplizer/DynamicEmbeddableTuplizer.java[tag=entity-tuplizer-instantiator,indent=0]
----
[source,java]
----
include::{sourcedir-proxy}/tuplizer/DynamicInstantiator.java[tag=entity-tuplizer-instantiator,indent=0]
----
[source,java]
----
include::{sourcedir-proxy}/tuplizer/ProxyHelper.java[tag=entity-tuplizer-instantiator,indent=0]
----
[source,java]
----
include::{sourcedir-proxy}/tuplizer/DataProxyHandler.java[tag=entity-tuplizer-instantiator,indent=0]
----
====
With the `DynamicInstantiator` in place, we can work with the dynamic proxy entities just like with Pojo entities.
[[entity-tuplizer-dynamic-proxy-example]]
.Persisting entities and embeddables as dynamic proxies
====
[source,java]
----
include::{sourcedir-proxy}/tuplizer/TuplizerTest.java[tag=entity-tuplizer-dynamic-proxy-example,indent=0]
----
====

View File

@ -0,0 +1,26 @@
/*
* 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>.
*/
//$Id$
package org.hibernate.userguide.proxy.tuplizer;
import javax.persistence.Column;
import javax.persistence.Embeddable;
/**
* @author Emmanuel Bernard
*/
//tag::entity-tuplizer-entity-mapping[]
@Embeddable
public interface Country {
@Column(name = "CountryName")
String getName();
void setName(String name);
}
//end::entity-tuplizer-entity-mapping[]

View File

@ -0,0 +1,36 @@
/*
* 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>.
*/
//$Id$
package org.hibernate.userguide.proxy.tuplizer;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import org.hibernate.annotations.Tuplizer;
/**
* @author Emmanuel Bernard
*/
//tag::entity-tuplizer-entity-mapping[]
@Entity
@Tuplizer(impl = DynamicEntityTuplizer.class)
public interface Cuisine {
@Id
@GeneratedValue
Long getId();
void setId(Long id);
String getName();
void setName(String name);
@Tuplizer(impl = DynamicEmbeddableTuplizer.class)
Country getCountry();
void setCountry(Country country);
}
//end::entity-tuplizer-entity-mapping[]

View File

@ -0,0 +1,61 @@
/*
* 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>.
*/
//$Id$
package org.hibernate.userguide.proxy.tuplizer;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* A simple {@link InvocationHandler} to act as the handler for our generated
* {@link java.lang.reflect.Proxy}-based entity instances.
* <p/>
* This is a trivial impl which simply keeps the property values into
* a Map.
*
* @author <a href="mailto:steve@hibernate.org">Steve Ebersole </a>
*/
//tag::entity-tuplizer-instantiator[]
public final class DataProxyHandler implements InvocationHandler {
private String entityName;
private Map<String, Object> data = new HashMap<>();
public DataProxyHandler(String entityName, Serializable id) {
this.entityName = entityName;
data.put( "Id", id );
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ( methodName.startsWith( "set" ) ) {
String propertyName = methodName.substring( 3 );
data.put( propertyName, args[0] );
}
else if ( methodName.startsWith( "get" ) ) {
String propertyName = methodName.substring( 3 );
return data.get( propertyName );
}
else if ( "toString".equals( methodName ) ) {
return entityName + "#" + data.get( "Id" );
}
else if ( "hashCode".equals( methodName ) ) {
return this.hashCode();
}
return null;
}
public String getEntityName() {
return entityName;
}
}
//end::entity-tuplizer-instantiator[]

View File

@ -0,0 +1,32 @@
/*
* 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>.
*/
//$Id$
package org.hibernate.userguide.proxy.tuplizer;
import org.hibernate.mapping.Component;
import org.hibernate.tuple.Instantiator;
import org.hibernate.tuple.component.PojoComponentTuplizer;
/**
* @author Emmanuel Bernard
*/
//tag::entity-tuplizer-instantiator[]
public class DynamicEmbeddableTuplizer
extends PojoComponentTuplizer {
public DynamicEmbeddableTuplizer(Component embeddable) {
super( embeddable );
}
protected Instantiator buildInstantiator(Component embeddable) {
return new DynamicInstantiator(
embeddable.getComponentClassName()
);
}
}
//end::entity-tuplizer-instantiator[]

View File

@ -0,0 +1,49 @@
/*
* 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.proxy.tuplizer;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.property.access.spi.Setter;
import org.hibernate.proxy.ProxyFactory;
import org.hibernate.tuple.Instantiator;
import org.hibernate.tuple.entity.EntityMetamodel;
import org.hibernate.tuple.entity.PojoEntityTuplizer;
/**
* @author Emmanuel Bernard
*/
//tag::entity-tuplizer-instantiator[]
public class DynamicEntityTuplizer extends PojoEntityTuplizer {
public DynamicEntityTuplizer(
EntityMetamodel entityMetamodel,
PersistentClass mappedEntity) {
super( entityMetamodel, mappedEntity );
}
@Override
protected Instantiator buildInstantiator(
EntityMetamodel entityMetamodel,
PersistentClass persistentClass) {
return new DynamicInstantiator(
persistentClass.getClassName()
);
}
@Override
protected ProxyFactory buildProxyFactory(
PersistentClass persistentClass,
Getter idGetter,
Setter idSetter) {
return super.buildProxyFactory(
persistentClass, idGetter,
idSetter
);
}
}
//end::entity-tuplizer-instantiator[]

View File

@ -0,0 +1,54 @@
/*
* 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>.
*/
//$Id$
package org.hibernate.userguide.proxy.tuplizer;
import java.io.Serializable;
import org.hibernate.HibernateException;
import org.hibernate.tuple.Instantiator;
/**
* @author Emmanuel Bernard
*/
//tag::entity-tuplizer-instantiator[]
public class DynamicInstantiator
implements Instantiator {
private final Class targetClass;
public DynamicInstantiator(String targetClassName) {
try {
this.targetClass = Class.forName( targetClassName );
}
catch (ClassNotFoundException e) {
throw new HibernateException( e );
}
}
public Object instantiate(Serializable id) {
return ProxyHelper.newProxy( targetClass, id );
}
public Object instantiate() {
return instantiate( null );
}
public boolean isInstance(Object object) {
try {
return targetClass.isInstance( object );
}
catch( Throwable t ) {
throw new HibernateException(
"could not get handle to entity as interface : " + t
);
}
}
}
//end::entity-tuplizer-instantiator[]

View File

@ -0,0 +1,31 @@
/*
* 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>.
*/
//$Id$
package org.hibernate.userguide.proxy.tuplizer;
import org.hibernate.EmptyInterceptor;
/**
* @author Emmanuel Bernard
*/
public class EntityNameInterceptor extends EmptyInterceptor {
/**
* The callback from Hibernate to determine the entity name given
* a presumed entity instance.
*
* @param object The presumed entity instance.
* @return The entity name (pointing to the proper entity mapping).
*/
public String getEntityName(Object object) {
String entityName = ProxyHelper.extractEntityName( object );
if ( entityName == null ) {
entityName = super.getEntityName( object );
}
return entityName;
}
}

View File

@ -0,0 +1,48 @@
/*
* 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>.
*/
//$Id$
package org.hibernate.userguide.proxy.tuplizer;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @author Emmanuel Bernard
*/
//tag::entity-tuplizer-instantiator[]
public class ProxyHelper {
public static <T> T newProxy(Class<T> targetClass, Serializable id) {
return ( T ) Proxy.newProxyInstance(
targetClass.getClassLoader(),
new Class[] {
targetClass
},
new DataProxyHandler(
targetClass.getName(),
id
)
);
}
public static String extractEntityName(Object object) {
if ( Proxy.isProxyClass( object.getClass() ) ) {
InvocationHandler handler = Proxy.getInvocationHandler(
object
);
if ( DataProxyHandler.class.isAssignableFrom( handler.getClass() ) ) {
DataProxyHandler myHandler = (DataProxyHandler) handler;
return myHandler.getEntityName();
}
}
return null;
}
}
//end::entity-tuplizer-instantiator[]

View File

@ -0,0 +1,57 @@
/*
* 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.proxy.tuplizer;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernateSessionBuilder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* @author Emmanuel Bernard
*/
public class TuplizerTest extends BaseCoreFunctionalTestCase {
@Test
public void testEntityTuplizer() throws Exception {
//tag::entity-tuplizer-dynamic-proxy-example[]
Cuisine _cuisine = doInHibernateSessionBuilder(
() -> sessionFactory()
.withOptions()
.interceptor( new EntityNameInterceptor() ),
session -> {
Cuisine cuisine = ProxyHelper.newProxy( Cuisine.class, null );
cuisine.setName( "Française" );
Country country = ProxyHelper.newProxy( Country.class, null );
country.setName( "France" );
cuisine.setCountry( country );
session.persist( cuisine );
return cuisine;
} );
doInHibernateSessionBuilder(
() -> sessionFactory()
.withOptions()
.interceptor( new EntityNameInterceptor() ),
session -> {
Cuisine cuisine = session.get( Cuisine.class, _cuisine.getId() );
assertEquals( "Française", cuisine.getName() );
assertEquals( "France", cuisine.getCountry().getName() );
} );
//end::entity-tuplizer-dynamic-proxy-example[]
}
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { Cuisine.class };
}
}