Compatibility with Hibernate 4.3

Due to key SPI interfaces such as ConnectionProvider and JtaPlatform changing their package location in Hibernate 4.3, we have to resort to reflection in a few places. Most importantly, the ConfigurableJtaPlatform used by the setJtaTransactionManager method has now been redesigned as a reflective proxy.

Issue: SPR-10839
This commit is contained in:
Juergen Hoeller 2013-09-03 17:58:18 +02:00
parent 0b37cec287
commit a9e727cd88
5 changed files with 164 additions and 32 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2013 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,60 +16,137 @@
package org.springframework.orm.hibernate4; package org.springframework.orm.hibernate4;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager; import javax.transaction.TransactionManager;
import javax.transaction.TransactionSynchronizationRegistry;
import javax.transaction.UserTransaction; import javax.transaction.UserTransaction;
import org.hibernate.service.jta.platform.internal.AbstractJtaPlatform; import org.hibernate.TransactionException;
import org.hibernate.service.Service;
import org.springframework.transaction.jta.UserTransactionAdapter; import org.springframework.transaction.jta.UserTransactionAdapter;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* Implementation of Hibernate 4's {@link org.hibernate.service.jta.platform.spi.JtaPlatform} * Implementation of Hibernate 4's JtaPlatform SPI (which has a different package
* SPI, exposing passed-in {@link TransactionManager} and {@link UserTransaction} references. * location in Hibernate 4.0-4.2 vs 4.3), exposing passed-in {@link TransactionManager},
* {@link UserTransaction} and {@link TransactionSynchronizationRegistry} references.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 3.1.2 * @since 3.1.2
*/ */
@SuppressWarnings("serial") @SuppressWarnings({"serial", "unchecked"})
class ConfigurableJtaPlatform extends AbstractJtaPlatform { class ConfigurableJtaPlatform implements InvocationHandler {
static final Class<? extends Service> jtaPlatformClass;
static {
Class<?> jpClass;
try {
// Try Hibernate 4.0-4.2 JtaPlatform variant
jpClass = SpringSessionContext.class.getClassLoader().loadClass(
"org.hibernate.service.jta.platform.spi.JtaPlatform");
}
catch (ClassNotFoundException ex) {
try {
// Try Hibernate 4.3 JtaPlatform variant
jpClass = SpringSessionContext.class.getClassLoader().loadClass(
"org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform");
}
catch (ClassNotFoundException ex2) {
throw new IllegalStateException("Neither Hibernate 4.0-4.2 nor 4.3 variant of JtaPlatform found");
}
}
jtaPlatformClass = (Class<? extends Service>) jpClass;
}
static String getJtaPlatformBasePackage() {
String className = jtaPlatformClass.getName();
return className.substring(0, className.length() - "spi.JtaPlatform".length());
}
private final TransactionManager transactionManager; private final TransactionManager transactionManager;
private final UserTransaction userTransaction; private final UserTransaction userTransaction;
private final TransactionSynchronizationRegistry transactionSynchronizationRegistry;
/** /**
* Create a new ConfigurableJtaPlatform instance with the given * Create a new ConfigurableJtaPlatform instance with the given
* JTA TransactionManager and optionally a given UserTransaction. * JTA TransactionManager and optionally a given UserTransaction.
* @param tm the JTA TransactionManager reference (required) * @param tm the JTA TransactionManager reference (required)
* @param ut the JTA UserTransaction reference (optional) * @param ut the JTA UserTransaction reference (optional)
* @param tsr the JTA 1.1 TransactionSynchronizationRegistry (optional)
*/ */
public ConfigurableJtaPlatform(TransactionManager tm, UserTransaction ut) { public ConfigurableJtaPlatform(TransactionManager tm, UserTransaction ut, TransactionSynchronizationRegistry tsr) {
Assert.notNull(tm, "TransactionManager reference must not be null"); Assert.notNull(tm, "TransactionManager reference must not be null");
this.transactionManager = tm; this.transactionManager = tm;
this.userTransaction = (ut != null ? ut : new UserTransactionAdapter(tm)); this.userTransaction = (ut != null ? ut : new UserTransactionAdapter(tm));
this.transactionSynchronizationRegistry = tsr;
} }
@Override public TransactionManager retrieveTransactionManager() {
protected TransactionManager locateTransactionManager() {
return this.transactionManager; return this.transactionManager;
} }
@Override public UserTransaction retrieveUserTransaction() {
protected UserTransaction locateUserTransaction() {
return this.userTransaction; return this.userTransaction;
} }
@Override public Object getTransactionIdentifier(Transaction transaction) {
protected boolean canCacheTransactionManager() { return transaction;
return true;
} }
public boolean canRegisterSynchronization() {
try {
return (this.transactionManager.getStatus() == Status.STATUS_ACTIVE);
}
catch (SystemException ex) {
throw new TransactionException("Could not determine JTA transaction status", ex);
}
}
public void registerSynchronization(Synchronization synchronization) {
if (this.transactionSynchronizationRegistry != null) {
this.transactionSynchronizationRegistry.registerInterposedSynchronization(synchronization);
}
else {
try {
this.transactionManager.getTransaction().registerSynchronization(synchronization);
}
catch (Exception ex) {
throw new TransactionException("Could not access JTA Transaction to register synchronization", ex);
}
}
}
public int getCurrentStatus() throws SystemException {
return this.transactionManager.getStatus();
}
@Override @Override
protected boolean canCacheUserTransaction() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return true; Method targetMethod = getClass().getMethod(method.getName(), method.getParameterTypes());
return targetMethod.invoke(this, args);
}
/**
* Obtain a proxy that implements the current Hibernate version's JtaPlatform interface
* in the right package location, delegating all invocations to the same-named methods
* on this ConfigurableJtaPlatform class itself.
*/
public Object getJtaPlatformProxy() {
return Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] {jtaPlatformClass}, this);
} }
} }

View File

@ -33,7 +33,6 @@ import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment; import org.hibernate.cfg.Environment;
import org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory; import org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory;
import org.hibernate.service.jta.platform.internal.WebSphereExtendedJtaPlatform;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
@ -140,7 +139,7 @@ public class LocalSessionFactoryBuilder extends Configuration {
* instructing Hibernate to interact with externally managed transactions. * instructing Hibernate to interact with externally managed transactions.
* <p>A passed-in Spring {@link JtaTransactionManager} needs to contain a JTA * <p>A passed-in Spring {@link JtaTransactionManager} needs to contain a JTA
* {@link TransactionManager} reference to be usable here, except for the WebSphere * {@link TransactionManager} reference to be usable here, except for the WebSphere
* case where we'll automatically set {@link WebSphereExtendedJtaPlatform} accordingly. * case where we'll automatically set {@code WebSphereExtendedJtaPlatform} accordingly.
* <p>Note: If this is set, the Hibernate settings should not contain a JTA platform * <p>Note: If this is set, the Hibernate settings should not contain a JTA platform
* setting to avoid meaningless double configuration. * setting to avoid meaningless double configuration.
*/ */
@ -149,7 +148,8 @@ public class LocalSessionFactoryBuilder extends Configuration {
if (jtaTransactionManager instanceof JtaTransactionManager) { if (jtaTransactionManager instanceof JtaTransactionManager) {
boolean webspherePresent = ClassUtils.isPresent("com.ibm.wsspi.uow.UOWManager", getClass().getClassLoader()); boolean webspherePresent = ClassUtils.isPresent("com.ibm.wsspi.uow.UOWManager", getClass().getClassLoader());
if (webspherePresent) { if (webspherePresent) {
getProperties().put(AvailableSettings.JTA_PLATFORM, new WebSphereExtendedJtaPlatform()); getProperties().put(AvailableSettings.JTA_PLATFORM,
ConfigurableJtaPlatform.getJtaPlatformBasePackage() + "internal.WebSphereExtendedJtaPlatform");
} }
else { else {
JtaTransactionManager jtaTm = (JtaTransactionManager) jtaTransactionManager; JtaTransactionManager jtaTm = (JtaTransactionManager) jtaTransactionManager;
@ -158,12 +158,13 @@ public class LocalSessionFactoryBuilder extends Configuration {
"Can only apply JtaTransactionManager which has a TransactionManager reference set"); "Can only apply JtaTransactionManager which has a TransactionManager reference set");
} }
getProperties().put(AvailableSettings.JTA_PLATFORM, getProperties().put(AvailableSettings.JTA_PLATFORM,
new ConfigurableJtaPlatform(jtaTm.getTransactionManager(), jtaTm.getUserTransaction())); new ConfigurableJtaPlatform(jtaTm.getTransactionManager(), jtaTm.getUserTransaction(),
jtaTm.getTransactionSynchronizationRegistry()).getJtaPlatformProxy());
} }
} }
else if (jtaTransactionManager instanceof TransactionManager) { else if (jtaTransactionManager instanceof TransactionManager) {
getProperties().put(AvailableSettings.JTA_PLATFORM, getProperties().put(AvailableSettings.JTA_PLATFORM,
new ConfigurableJtaPlatform((TransactionManager) jtaTransactionManager, null)); new ConfigurableJtaPlatform((TransactionManager) jtaTransactionManager, null, null).getJtaPlatformProxy());
} }
else { else {
throw new IllegalArgumentException( throw new IllegalArgumentException(

View File

@ -16,6 +16,7 @@
package org.springframework.orm.hibernate4; package org.springframework.orm.hibernate4;
import java.lang.reflect.Method;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@ -45,7 +46,7 @@ import org.hibernate.exception.DataException;
import org.hibernate.exception.JDBCConnectionException; import org.hibernate.exception.JDBCConnectionException;
import org.hibernate.exception.LockAcquisitionException; import org.hibernate.exception.LockAcquisitionException;
import org.hibernate.exception.SQLGrammarException; import org.hibernate.exception.SQLGrammarException;
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; import org.hibernate.service.spi.Wrapped;
import org.springframework.dao.CannotAcquireLockException; import org.springframework.dao.CannotAcquireLockException;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
@ -57,6 +58,8 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.dao.PessimisticLockingFailureException; import org.springframework.dao.PessimisticLockingFailureException;
import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/** /**
* Helper class featuring methods for Hibernate Session handling. * Helper class featuring methods for Hibernate Session handling.
@ -83,6 +86,12 @@ public abstract class SessionFactoryUtils {
static final Log logger = LogFactory.getLog(SessionFactoryUtils.class); static final Log logger = LogFactory.getLog(SessionFactoryUtils.class);
/**
* Bridging between the different ConnectionProvider package location in 4.0-4.2 vs 4.3.
*/
private static final Method getConnectionProviderMethod =
ClassUtils.getMethodIfAvailable(SessionFactoryImplementor.class, "getConnectionProvider");
/** /**
* Determine the DataSource of the given SessionFactory. * Determine the DataSource of the given SessionFactory.
@ -91,8 +100,8 @@ public abstract class SessionFactoryUtils {
* @see org.hibernate.engine.spi.SessionFactoryImplementor#getConnectionProvider * @see org.hibernate.engine.spi.SessionFactoryImplementor#getConnectionProvider
*/ */
public static DataSource getDataSource(SessionFactory sessionFactory) { public static DataSource getDataSource(SessionFactory sessionFactory) {
if (sessionFactory instanceof SessionFactoryImplementor) { if (getConnectionProviderMethod != null && sessionFactory instanceof SessionFactoryImplementor) {
ConnectionProvider cp = ((SessionFactoryImplementor) sessionFactory).getConnectionProvider(); Wrapped cp = (Wrapped) ReflectionUtils.invokeMethod(getConnectionProviderMethod, sessionFactory);
return cp.unwrap(DataSource.class); return cp.unwrap(DataSource.class);
} }
return null; return null;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2013 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,16 +16,16 @@
package org.springframework.orm.hibernate4; package org.springframework.orm.hibernate4;
import javax.transaction.TransactionManager; import java.lang.reflect.Method;
import org.hibernate.FlushMode; import org.hibernate.FlushMode;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.context.spi.CurrentSessionContext; import org.hibernate.context.spi.CurrentSessionContext;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.service.jta.platform.spi.JtaPlatform;
import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.ReflectionUtils;
/** /**
* Implementation of Hibernate 3.1's CurrentSessionContext interface * Implementation of Hibernate 3.1's CurrentSessionContext interface
@ -44,7 +44,7 @@ public class SpringSessionContext implements CurrentSessionContext {
private final SessionFactoryImplementor sessionFactory; private final SessionFactoryImplementor sessionFactory;
private final CurrentSessionContext jtaSessionContext; private CurrentSessionContext jtaSessionContext;
/** /**
@ -53,9 +53,17 @@ public class SpringSessionContext implements CurrentSessionContext {
*/ */
public SpringSessionContext(SessionFactoryImplementor sessionFactory) { public SpringSessionContext(SessionFactoryImplementor sessionFactory) {
this.sessionFactory = sessionFactory; this.sessionFactory = sessionFactory;
JtaPlatform jtaPlatform = sessionFactory.getServiceRegistry().getService(JtaPlatform.class); try {
TransactionManager transactionManager = jtaPlatform.retrieveTransactionManager(); Object jtaPlatform = sessionFactory.getServiceRegistry().getService(ConfigurableJtaPlatform.jtaPlatformClass);
this.jtaSessionContext = (transactionManager != null ? new SpringJtaSessionContext(sessionFactory) : null); Method rtmMethod = ConfigurableJtaPlatform.jtaPlatformClass.getMethod("retrieveTransactionManager");
Object transactionManager = ReflectionUtils.invokeMethod(rtmMethod, jtaPlatform);
if (transactionManager != null) {
this.jtaSessionContext = new SpringJtaSessionContext(sessionFactory);
}
}
catch (Exception ex) {
throw new IllegalStateException("Could not introspect Hibernate JtaPlatform for SpringJtaSessionContext", ex);
}
} }
@ -79,7 +87,7 @@ public class SpringSessionContext implements CurrentSessionContext {
// Switch to FlushMode.AUTO, as we have to assume a thread-bound Session // Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
// with FlushMode.MANUAL, which needs to allow flushing within the transaction. // with FlushMode.MANUAL, which needs to allow flushing within the transaction.
FlushMode flushMode = session.getFlushMode(); FlushMode flushMode = session.getFlushMode();
if (FlushMode.isManualFlushMode(flushMode) && if (flushMode.equals(FlushMode.MANUAL) &&
!TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
session.setFlushMode(FlushMode.AUTO); session.setFlushMode(FlushMode.AUTO);
sessionHolder.setPreviousFlushMode(flushMode); sessionHolder.setPreviousFlushMode(flushMode);

View File

@ -24,6 +24,9 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import javax.sql.DataSource; import javax.sql.DataSource;
import javax.transaction.TransactionManager;
import javax.transaction.TransactionSynchronizationRegistry;
import javax.transaction.UserTransaction;
import org.hibernate.FlushMode; import org.hibernate.FlushMode;
import org.hibernate.Interceptor; import org.hibernate.Interceptor;
@ -32,8 +35,10 @@ import org.hibernate.Session;
import org.hibernate.SessionBuilder; import org.hibernate.SessionBuilder;
import org.hibernate.SessionFactory; import org.hibernate.SessionFactory;
import org.hibernate.Transaction; import org.hibernate.Transaction;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.HSQLDialect;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.junit.After; import org.junit.After;
import org.junit.Test; import org.junit.Test;
@ -49,6 +54,7 @@ import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.UnexpectedRollbackException; import org.springframework.transaction.UnexpectedRollbackException;
import org.springframework.transaction.jta.JtaTransactionManager;
import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionSynchronizationManager;
@ -1180,6 +1186,37 @@ public class HibernateTransactionManagerTests {
verify(session).close(); verify(session).close();
} }
@Test
public void testSetJtaTransactionManager() throws Exception {
DataSource ds = mock(DataSource.class);
TransactionManager tm = mock(TransactionManager.class);
UserTransaction ut = mock(UserTransaction.class);
TransactionSynchronizationRegistry tsr = mock(TransactionSynchronizationRegistry.class);
JtaTransactionManager jtm = new JtaTransactionManager();
jtm.setTransactionManager(tm);
jtm.setUserTransaction(ut);
jtm.setTransactionSynchronizationRegistry(tsr);
LocalSessionFactoryBuilder lsfb = new LocalSessionFactoryBuilder(ds);
lsfb.setJtaTransactionManager(jtm);
Object jtaPlatform = lsfb.getProperties().get(AvailableSettings.JTA_PLATFORM);
assertNotNull(jtaPlatform);
assertSame(tm, jtaPlatform.getClass().getMethod("retrieveTransactionManager").invoke(jtaPlatform));
assertSame(ut, jtaPlatform.getClass().getMethod("retrieveUserTransaction").invoke(jtaPlatform));
assertTrue(lsfb.getProperties().get(AvailableSettings.TRANSACTION_STRATEGY) instanceof CMTTransactionFactory);
}
@Test
public void testSetTransactionManager() throws Exception {
DataSource ds = mock(DataSource.class);
TransactionManager tm = mock(TransactionManager.class);
LocalSessionFactoryBuilder lsfb = new LocalSessionFactoryBuilder(ds);
lsfb.setJtaTransactionManager(tm);
Object jtaPlatform = lsfb.getProperties().get(AvailableSettings.JTA_PLATFORM);
assertNotNull(jtaPlatform);
assertSame(tm, jtaPlatform.getClass().getMethod("retrieveTransactionManager").invoke(jtaPlatform));
assertTrue(lsfb.getProperties().get(AvailableSettings.TRANSACTION_STRATEGY) instanceof CMTTransactionFactory);
}
public interface ImplementingSession extends Session, SessionImplementor { public interface ImplementingSession extends Session, SessionImplementor {
} }