HHH-6897 serialization of the EntityManager should be possible

This commit is contained in:
Scott Marlow 2011-12-16 15:34:56 -05:00
parent d02864bac6
commit e18d087592
8 changed files with 307 additions and 95 deletions

View File

@ -1547,4 +1547,9 @@ public interface CoreMessageLogger extends BasicLogger {
@Message(value = "update timestamps cache misses: %s", id = 435)
void timestampCacheMisses(long updateTimestampsCachePutCount);
@LogMessage(level = WARN)
@Message(value = "Entity manager factory name (%s) is already registered. If entity manager will be clustered "+
"or passivated, specify a unique value for property '%s'", id = 436)
void entityManagerFactoryAlreadyRegistered(String emfName, String propertyName);
}

View File

@ -63,7 +63,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jboss.logging.Logger;
import org.hibernate.AssertionFailure;
import org.hibernate.CacheMode;
@ -114,6 +113,8 @@ import org.hibernate.proxy.HibernateProxy;
import org.hibernate.service.jta.platform.spi.JtaPlatform;
import org.hibernate.transform.BasicTransformerAdapter;
import org.hibernate.type.Type;
import org.jboss.logging.Logger;
/**
* @author <a href="mailto:gavin@hibernate.org">Gavin King</a>
@ -138,7 +139,7 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage
entityManagerSpecificProperties.add( QueryHints.SPEC_HINT_TIMEOUT );
}
private EntityManagerFactoryImpl entityManagerFactory;
private transient EntityManagerFactoryImpl entityManagerFactory;
protected transient TransactionImpl tx = new TransactionImpl( this );
protected PersistenceContextType persistenceContextType;
private PersistenceUnitTransactionType transactionType;
@ -1235,10 +1236,12 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
entityManagerFactory.serialize(oos);
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
entityManagerFactory = (EntityManagerFactoryImpl)EntityManagerFactoryImpl.deserialize(ois);
tx = new TransactionImpl( this );
}

View File

@ -307,6 +307,11 @@ public class AvailableSettings {
*/
public static final String PACKAGE_NAMES = "hibernate.ejb.packages";
/**
* EntityManagerFactory name
*/
public static final String ENTITY_MANAGER_FACTORY_NAME = "hibernate.ejb.entitymanager_factory_name";
/**
* List of classes names
* Internal use only
@ -318,4 +323,5 @@ public class AvailableSettings {
public static final String JACC_PREFIX = "hibernate.jacc";
public static final String JACC_ENABLED = "hibernate.jacc.enabled";
public static final String PERSISTENCE_UNIT_NAME = "hibernate.ejb.persistenceUnitName";
}

View File

@ -23,19 +23,6 @@
*/
package org.hibernate.ejb;
import javax.naming.BinaryRefAddr;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityNotFoundException;
import javax.persistence.MappedSuperclass;
import javax.persistence.PersistenceException;
import javax.persistence.spi.PersistenceUnitInfo;
import javax.persistence.spi.PersistenceUnitTransactionType;
import javax.sql.DataSource;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@ -58,12 +45,21 @@ import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import javax.naming.BinaryRefAddr;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityNotFoundException;
import javax.persistence.MappedSuperclass;
import javax.persistence.PersistenceException;
import javax.persistence.spi.PersistenceUnitInfo;
import javax.persistence.spi.PersistenceUnitTransactionType;
import javax.sql.DataSource;
import org.dom4j.Element;
import org.jboss.logging.Logger;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.hibernate.HibernateException;
import org.hibernate.Interceptor;
import org.hibernate.MappingException;
@ -109,6 +105,9 @@ import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import org.hibernate.service.internal.StandardServiceRegistryImpl;
import org.hibernate.service.jdbc.connections.internal.DatasourceConnectionProviderImpl;
import org.jboss.logging.Logger;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
/**
* Allow a fine tuned configuration of an EJB 3.0 EntityManagerFactory
@ -902,12 +901,13 @@ public class Ejb3Configuration implements Serializable, Referenceable {
try {
final ServiceRegistry serviceRegistry = buildLifecycleControledServiceRegistry( builder );
return new EntityManagerFactoryImpl(
return new EntityManagerFactoryImpl(
transactionType,
discardOnClose,
getSessionInterceptorClass( cfg.getProperties() ),
cfg,
serviceRegistry
serviceRegistry,
persistenceUnitName
);
}
catch (HibernateException e) {

View File

@ -21,25 +21,31 @@
*/
package org.hibernate.ejb;
import javax.persistence.Cache;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContextType;
import javax.persistence.PersistenceUnitUtil;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.spi.LoadState;
import javax.persistence.spi.PersistenceUnitTransactionType;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import javax.persistence.Cache;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContextType;
import javax.persistence.PersistenceUnitUtil;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.spi.LoadState;
import javax.persistence.spi.PersistenceUnitTransactionType;
import org.hibernate.Hibernate;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.ejb.criteria.CriteriaBuilderImpl;
import org.hibernate.ejb.internal.EntityManagerFactoryRegistry;
import org.hibernate.ejb.metamodel.MetamodelImpl;
import org.hibernate.ejb.util.PersistenceUtilHelper;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -63,6 +69,7 @@ public class EntityManagerFactoryImpl implements HibernateEntityManagerFactory {
private final Metamodel metamodel;
private final HibernatePersistenceUnitUtil util;
private final Map<String,Object> properties;
private final String entityManagerFactoryName;
private final PersistenceUtilHelper.MetadataCache cache = new PersistenceUtilHelper.MetadataCache();
@ -72,7 +79,8 @@ public class EntityManagerFactoryImpl implements HibernateEntityManagerFactory {
boolean discardOnClose,
Class sessionInterceptorClass,
Configuration cfg,
ServiceRegistry serviceRegistry) {
ServiceRegistry serviceRegistry,
String persistenceUnitName) {
this.sessionFactory = cfg.buildSessionFactory( serviceRegistry );
this.transactionType = transactionType;
this.discardOnClose = discardOnClose;
@ -92,6 +100,12 @@ public class EntityManagerFactoryImpl implements HibernateEntityManagerFactory {
addAll( props, ( (SessionFactoryImplementor) sessionFactory ).getProperties() );
addAll( props, cfg.getProperties() );
this.properties = Collections.unmodifiableMap( props );
String entityManagerFactoryName = (String)this.properties.get(AvailableSettings.ENTITY_MANAGER_FACTORY_NAME);
if (entityManagerFactoryName == null) {
entityManagerFactoryName = persistenceUnitName;
}
this.entityManagerFactoryName = entityManagerFactoryName;
EntityManagerFactoryRegistry.INSTANCE.addEntityManagerFactory(entityManagerFactoryName, this);
}
private static void addAll(HashMap<String, Object> propertyMap, Properties properties) {
@ -124,6 +138,7 @@ public class EntityManagerFactoryImpl implements HibernateEntityManagerFactory {
public void close() {
sessionFactory.close();
EntityManagerFactoryRegistry.INSTANCE.removeEntityManagerFactory(entityManagerFactoryName, this);
}
public Map<String, Object> getProperties() {
@ -153,6 +168,10 @@ public class EntityManagerFactoryImpl implements HibernateEntityManagerFactory {
return sessionFactory;
}
public String getEntityManagerFactoryName() {
return entityManagerFactoryName;
}
private static class JPACache implements Cache {
private SessionFactory sessionFactory;
@ -180,6 +199,39 @@ public class EntityManagerFactoryImpl implements HibernateEntityManagerFactory {
}
}
/**
* Custom serialization hook used during EntityManager serialization.
*
* @param oos The stream to which to write the factory
* @throws IOException Indicates problems writing out the serial data stream
*/
void serialize(ObjectOutputStream oos) throws IOException {
if (entityManagerFactoryName == null) {
throw new InvalidObjectException( "could not serialize entity manager factory with null entityManagerFactoryName" );
}
oos.writeUTF( entityManagerFactoryName );
}
/**
* Custom deserialization hook used during EntityManager deserialization.
*
* @param ois The stream from which to "read" the factory
* @return The deserialized factory
* @throws IOException indicates problems reading back serial data stream
* @throws ClassNotFoundException indicates problems reading back serial data stream
*/
static EntityManagerFactory deserialize(ObjectInputStream ois) throws IOException, ClassNotFoundException {
final String entityManagerFactoryName = ois.readUTF();
Object result = EntityManagerFactoryRegistry.INSTANCE.getNamedEntityManagerFactory(entityManagerFactoryName);
if ( result == null ) {
throw new InvalidObjectException( "could not resolve entity manager factory during entity manager deserialization [name=" + entityManagerFactoryName + "]" );
}
return (EntityManagerFactory)result;
}
private static class HibernatePersistenceUnitUtil implements PersistenceUnitUtil, Serializable {
private final HibernateEntityManagerFactory emf;
private transient PersistenceUtilHelper.MetadataCache cache;

View File

@ -0,0 +1,146 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.ejb.internal;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.persistence.EntityManagerFactory;
import org.hibernate.HibernateException;
import org.hibernate.ejb.AvailableSettings;
import org.hibernate.internal.CoreMessageLogger;
import org.jboss.logging.Logger;
/**
* An internal registry of all {@link org.hibernate.ejb.EntityManagerFactoryImpl} instances for the same
* classloader as this class.
*
* This registry is used for serialization/deserialization of entity managers.
*
* @author Scott Marlow
*/
public class EntityManagerFactoryRegistry {
private static final CoreMessageLogger LOG = Logger.getMessageLogger(
CoreMessageLogger.class,
EntityManagerFactoryRegistry.class.getName()
);
public static final EntityManagerFactoryRegistry INSTANCE = new EntityManagerFactoryRegistry();
private final ConcurrentHashMap<String, Set<EntityManagerFactory>> entityManagerFactoryMap = new ConcurrentHashMap<String, Set<EntityManagerFactory>>();
public EntityManagerFactoryRegistry() {
LOG.debugf( "Initializing EntityManagerFactoryRegistry : %s", this );
}
/**
* register the specified entity manager factory
*
* @param name to register the passed entity manager factory
* @param entityManagerFactory
*/
public void addEntityManagerFactory(String name, EntityManagerFactory entityManagerFactory) {
LOG.debugf( "Registering EntityManagerFactory: %s ", name );
if (name == null) { // allow unit tests that don't specify the pu name to succeed
LOG.tracef( "not registering EntityManagerFactory because name is null");
return;
}
Set<EntityManagerFactory> entityManagerFactorySet = new HashSet<EntityManagerFactory>();
entityManagerFactorySet.add(entityManagerFactory);
Set<EntityManagerFactory> previous = entityManagerFactoryMap.putIfAbsent( name, entityManagerFactorySet);
// if multiple entries are found, give warning and add EMF to existing set
// later, if EntityManagerFactoryImpl.deserialize is called for an EM returned from any EMF in the set
// an exception will be thrown (failing the deserialization attempt).
if (previous != null) {
LOG.entityManagerFactoryAlreadyRegistered(name, AvailableSettings.ENTITY_MANAGER_FACTORY_NAME);
boolean done = false;
while( !done) {
synchronized (previous) {
if (entityManagerFactoryMap.get(name) == previous) { // compare and add EMF if same
previous.add(entityManagerFactory);
done = true;
}
else { // else it was removed or a new set added
previous = entityManagerFactoryMap.get(name); // get the set added by another thread
if (null == previous) { // or add it here if not
entityManagerFactoryMap.putIfAbsent( name, new HashSet<EntityManagerFactory>());
previous = entityManagerFactoryMap.get(name);// use the current set
}
}
}
}
}
}
/**
* remove the specified entity manager factory from the EntityManagerFactoryRegistry
* @param name
* @param entityManagerFactory
* @throws HibernateException if the specified entity manager factory could not be found in the registry
*/
public void removeEntityManagerFactory(String name, EntityManagerFactory entityManagerFactory) throws HibernateException {
LOG.debugf( "Remove: name=%s", name );
if (name == null) { // allow unit tests that don't specify the pu name to succeed
LOG.tracef( "not removing EntityManagerFactory from registry because name is null");
return;
}
Set<EntityManagerFactory> entityManagerFactorySet = entityManagerFactoryMap.get(name);
if (entityManagerFactorySet == null) {
throw new HibernateException( "registry does not contain entity manager factory: " + name);
}
synchronized (entityManagerFactorySet) {
boolean removed = entityManagerFactorySet.remove(entityManagerFactory);
if (entityManagerFactorySet.size() == 0) {
entityManagerFactoryMap.remove( name );
}
}
}
/**
* Lookup the specified entity manager factory by name
* @param name
* @return
* @throws HibernateException if entity manager factory is not found or if more than one
* entity manager factory was registered with name.
*/
public EntityManagerFactory getNamedEntityManagerFactory(String name) throws HibernateException {
LOG.debugf( "Lookup: name=%s", name );
Set<EntityManagerFactory> entityManagerFactorySet = entityManagerFactoryMap.get(name);
if (entityManagerFactorySet == null) {
throw new HibernateException( "registry does not contain entity manager factory: " + name);
}
if (entityManagerFactorySet.size() > 1) {
throw new HibernateException( "registry contains more than one (" + entityManagerFactorySet.size()+ ") entity manager factories: " + name);
}
return entityManagerFactorySet.iterator().next();
}
}

View File

@ -23,15 +23,15 @@
*/
package org.hibernate.ejb.test.lock;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import org.hibernate.ejb.test.BaseEntityManagerFunctionalTestCase;
import org.jboss.logging.Logger;
import org.junit.Test;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertTrue;
/**
@ -50,60 +50,65 @@ public class UpgradeLockTest extends BaseEntityManagerFunctionalTestCase {
*/
@Test
public void testUpgradeReadLockToOptimisticForceIncrement() throws Exception {
Lock lock = new Lock(); //
lock.setName( "name" );
EntityManager em = getOrCreateEntityManager();
final EntityManager em2 = createIsolatedEntityManager();
em.getTransaction().begin(); // create the test entity first
em.persist( lock );
em.getTransaction().commit();
em.getTransaction().begin(); // start tx1
lock = em.getReference( Lock.class, lock.getId() );
final Integer id = lock.getId();
em.lock( lock, LockModeType.READ ); // start with READ lock in tx1
// upgrade to OPTIMISTIC_FORCE_INCREMENT in tx1
em.lock( lock, LockModeType.OPTIMISTIC_FORCE_INCREMENT);
lock.setName( "surname" ); // don't end tx1 yet
final CountDownLatch latch = new CountDownLatch(1);
Thread t = new Thread( new Runnable() {
public void run() {
try {
em2.getTransaction().begin(); // start tx2
Lock lock2 = em2.getReference( Lock.class, id );
lock2.setName("renamed"); // change entity
}
finally {
em2.getTransaction().commit();
latch.countDown(); // signal that tx2 is committed
em2.close();
}
}
} );
t.setDaemon( true );
t.setName("testUpgradeReadLockToOptimisticForceIncrement tx2");
t.start();
log.info("testUpgradeReadLockToOptimisticForceIncrement: wait on BG thread");
boolean latchSet = latch.await( 10, TimeUnit.SECONDS );
assertTrue( "background test thread finished (lock timeout is broken)", latchSet );
// tx2 is complete, try to commit tx1
try {
Lock lock = new Lock(); //
lock.setName( "name" );
em.getTransaction().begin(); // create the test entity first
em.persist( lock );
em.getTransaction().commit();
}
catch (Throwable expectedToFail) {
while(expectedToFail != null &&
!(expectedToFail instanceof javax.persistence.OptimisticLockException)) {
expectedToFail = expectedToFail.getCause();
em.getTransaction().begin(); // start tx1
lock = em.getReference( Lock.class, lock.getId() );
final Integer id = lock.getId();
em.lock( lock, LockModeType.READ ); // start with READ lock in tx1
// upgrade to OPTIMISTIC_FORCE_INCREMENT in tx1
em.lock( lock, LockModeType.OPTIMISTIC_FORCE_INCREMENT);
lock.setName( "surname" ); // don't end tx1 yet
final CountDownLatch latch = new CountDownLatch(1);
Thread t = new Thread( new Runnable() {
public void run() {
try {
em2.getTransaction().begin(); // start tx2
Lock lock2 = em2.getReference( Lock.class, id );
lock2.setName("renamed"); // change entity
}
finally {
em2.getTransaction().commit();
latch.countDown(); // signal that tx2 is committed
em2.close();
}
}
} );
t.setDaemon( true );
t.setName("testUpgradeReadLockToOptimisticForceIncrement tx2");
t.start();
log.info("testUpgradeReadLockToOptimisticForceIncrement: wait on BG thread");
boolean latchSet = latch.await( 10, TimeUnit.SECONDS );
assertTrue( "background test thread finished (lock timeout is broken)", latchSet );
// tx2 is complete, try to commit tx1
try {
em.getTransaction().commit();
}
catch (Throwable expectedToFail) {
while(expectedToFail != null &&
!(expectedToFail instanceof javax.persistence.OptimisticLockException)) {
expectedToFail = expectedToFail.getCause();
}
assertTrue("upgrade to OPTIMISTIC_FORCE_INCREMENT is expected to fail at end of transaction1 since tranaction2 already updated the entity",
expectedToFail instanceof javax.persistence.OptimisticLockException);
}
assertTrue("upgrade to OPTIMISTIC_FORCE_INCREMENT is expected to fail at end of transaction1 since tranaction2 already updated the entity",
expectedToFail instanceof javax.persistence.OptimisticLockException);
}
em.close();
finally {
em.close();
}
}

View File

@ -23,18 +23,12 @@
*/
package org.hibernate.ejb.test;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.jboss.logging.Logger;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
@ -46,12 +40,11 @@ import org.hibernate.internal.SessionFactoryImpl;
import org.hibernate.service.BootstrapServiceRegistryBuilder;
import org.hibernate.service.ServiceRegistryBuilder;
import org.hibernate.service.internal.StandardServiceRegistryImpl;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.jboss.logging.Logger;
import org.junit.After;
import org.junit.Before;
import org.hibernate.testing.junit4.BaseUnitTestCase;
/**
* A base class for all ejb tests.
*
@ -116,7 +109,9 @@ public abstract class BaseEntityManagerFunctionalTestCase extends BaseUnitTestCa
}
ejb3Configuration
.getHibernateConfiguration()
.setProperty( org.hibernate.cfg.AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true" );
.setProperty( org.hibernate.cfg.AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true")
.setProperty( AvailableSettings.ENTITY_MANAGER_FACTORY_NAME, "EMF_BaseEntityManagerFunctionalTestCase");
ejb3Configuration
.getHibernateConfiguration()
.setProperty( Environment.DIALECT, getDialect().getClass().getName() );