Reputation: 979
I am building an application behind a Spring WebMVC UI with hibernate HPA. Each of the individual pieces of the puzzle have passed their unit testing, but I'm having trouble gluing the application together.
It looks like the Hibernate transaction strategy is preventing Hibernate from participating in the transaction.
Research so far: Hibernate has the jta transaction strategy configured by default. The assumption is that the
The @Transactional annotation on the service layer is being executed (I can see it in the logs). The JDBC datasource is being retrieved from the transaction manager. Then at the point that the transaction manager tries to attach the JDBC datasource to the transaction, it suddenly can't find the transaction. I have traced into the code, and the transaction is simply not there when Bitronix goes hunting for it.
The stack for this problem is:
Here is the code for the failing method:
@Service
public class ConfigPropertiesServiceImpl {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private ConfigPropertiesSDO configPropertiesSDO;
@Autowired
private ConfigPropertiesDAO configPropertiesDAO;
@Transactional
public PropertyValue fetchPropertyValue (String key)
{
return configPropertiesSDO.fetchProperty(key);
}
}
Log showing that transaction is started:
DEBUG DispatcherServlet - DispatcherServlet with name 'dispatcher' processing GET request for [/myapp-war/ajax/cfgFind]
DEBUG RequestMappingHandlerMapping - Looking up handler method for path /ajax/cfgFind
DEBUG RequestMappingHandlerMapping - Returning handler method [public org.springframework.web.servlet.ModelAndView com.myApp.ui.security.controller.ConfigurationLayoutController.getCfgFind()]
DEBUG DefaultListableBeanFactory - Returning cached instance of singleton bean 'configurationLayoutController'
DEBUG DispatcherServlet - Last-Modified value for [/myapp-war/ajax/cfgFind] is: -1
DEBUG JtaTransactionManager - Creating new transaction with name [com.myApp.svc.config.ConfigPropertiesServiceImpl.fetchPropertyValue]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
DEBUG BitronixTransactionManager - beginning a new transaction
DEBUG BitronixTransactionManager - dumping 0 transaction context(s)
DEBUG BitronixTransaction - creating new transaction with GTRID [3139322E3136382E302E35300000000005692D6D00000002]
DEBUG BitronixTransactionManager - creating new thread context
DEBUG BitronixTransactionManager - changing current thread context to a ThreadContext with transaction null, default timeout 60s
DEBUG ThreadContext - assigning <a Bitronix Transaction with GTRID [3139322E3136382E302E35300000000005692D6D00000002], status=NO_TRANSACTION, 0 resource(s) enlisted (started null)> to <a ThreadContext with transaction null, default timeout 60s>
DEBUG BitronixTransaction - changing transaction status to ACTIVE
DEBUG TransactionLogAppender - between 14848 and 14909, writing a Bitronix TransactionLogRecord with status=ACTIVE, recordLength=53, headerLength=28, time=90778990, sequenceNumber=4, crc32=-880986447, gtrid=3139322E3136382E302E35300000000005692D6D00000002, uniqueNames=
DEBUG TransactionLogAppender - disk journal appender now at position 14909
DEBUG BitronixTransaction - transaction status is changing from NO_TRANSACTION to ACTIVE - executing 0 listener(s)
DEBUG TaskScheduler - scheduling transaction timeout task on a Bitronix Transaction with GTRID [3139322E3136382E302E35300000000005692D6D00000002], status=ACTIVE, 0 resource(s) enlisted (started Thu Jan 01 19:12:58 CST 1970) for Thu Jan 01 19:13:58 CST 1970
DEBUG TaskScheduler - removing task by a Bitronix Transaction with GTRID [3139322E3136382E302E35300000000005692D6D00000002], status=ACTIVE, 0 resource(s) enlisted (started Thu Jan 01 19:12:58 CST 1970)
DEBUG TaskScheduler - scheduled a TransactionTimeoutTask on a Bitronix Transaction with GTRID [3139322E3136382E302E35300000000005692D6D00000002], status=ACTIVE, 0 resource(s) enlisted (started Thu Jan 01 19:12:58 CST 1970) scheduled for Thu Jan 01 19:13:58 CST 1970, total task(s) queued: 3
DEBUG BitronixTransactionManager - begun new transaction at Thu Jan 01 19:12:58 CST 1970
Log showing that the statement was unable to join the transaction:
DEBUG EntityManagerFactoryUtils - Opening JPA EntityManager
TRACE SessionFactoryImpl$SessionBuilderImpl - Opening Hibernate Session. tenant=null, owner=org.hibernate.jpa.internal.EntityManagerImpl@7b6bfb1d
TRACE SessionImpl - Opened session at timestamp: 14400807842
DEBUG TransactionCoordinatorImpl - Skipping JTA sync registration due to auto join checking
TRACE TransactionCoordinatorImpl - registered JTA platform says we cannot currently register synchronization; skipping
DEBUG AbstractEntityManagerImpl - Looking for a JTA transaction to join
DEBUG AbstractEntityManagerImpl - Unable to join JTA transaction
DEBUG TransactionCoordinatorImpl - Skipping JTA sync registration due to auto join checking
Here is my Spring Context initialization:
public class AppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) throws ServletException {
Logger logger = LoggerFactory.getLogger(this.getClass());
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(DatabaseConfig.class);
rootContext.refresh();
// Manage the lifecycle of the root appcontext
container.addListener(new ContextLoaderListener(rootContext));
container.setInitParameter("defaultHtmlEscape", "true");
AnnotationConfigWebApplicationContext mvcContext = new AnnotationConfigWebApplicationContext();
mvcContext.register(ViewConfig.class);
mvcContext.setServletContext(container);
ServletRegistration.Dynamic servlet = container.addServlet(
"dispatcher", new DispatcherServlet(mvcContext));
servlet.setLoadOnStartup(1);
Set<String> mappingConflicts = servlet.addMapping("/");
if (!mappingConflicts.isEmpty()) {
for (String s : mappingConflicts) {
logger.error("Mapping conflict: " + s);
}
throw new IllegalStateException(
"'appServlet' cannot be mapped to '/' under Tomcat versions <= 7.0.14");
}
}
}
... and the Database Configuration class where transactioning is configured ...
@EnableTransactionManagement
@ComponentScan({ "com.myApp.svc.*","com.myApp.core.*","com.myApp.ui.*" })
@Configuration
public class DatabaseConfig {
@Bean(destroyMethod="shutdown")
public BitronixTransactionManager bitronixTransactionManager() {
btmConfig();
return new BitronixTransactionManager();
}
@Bean
public JtaTransactionManager transactionManager() {
JtaTransactionManager mgr = new JtaTransactionManager();
mgr.setTransactionManager(bitronixTransactionManager ());
mgr.setUserTransaction(bitronixTransactionManager ());
mgr.afterPropertiesSet();
return mgr;
}
@Bean
public TransactionTemplate transactionTemplate ()
{
return new TransactionTemplate (transactionManager());
}
@Bean(initMethod = "init", destroyMethod = "close")
public DataSource dataSource() {
PoolingDataSource dataSourceInstance = new PoolingDataSource();
dataSourceInstance.set ... ();
dataSourceInstance.init();
return dataSourceInstance;
}
@Bean
public LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean()
throws IOException {
// The bean isn't created yet, but we need some of its functions
// This instance will be released as soon as the method goes out of
// scope
BitronixTransactionManager tm = bitronixTransactionManager();
PrivateDatastoreServiceImpl pds = new PrivateDatastoreServiceImpl();
pds.setPrivateDatastorePropertiesFile(privateDatastorePropertiesFile());
pds.init();
LocalContainerEntityManagerFactoryBean emf = null;
PrivateDatastoreConfigurationModel model = pds
.retrieveRawJdbcParameters();
Properties props = new Properties();
props.setProperty(HIBERNATE_DIALECT,
pds.getHibernateStrategy(model.getDbType()));
props.setProperty(HIBERNATE_DDL_AUTO, "false");
props.setProperty(HIBERNATE_SHOW_SQL, "false");
props.setProperty(HIBERNATE_FORMAT_SQL, "false");
props.setProperty("hibernate.current_session_context_class",
"jta");
props.setProperty("hibernate.transaction.jta.platform",
org.hibernate.engine.transaction.jta.platform.internal.BitronixJtaPlatform.class.getName());
props.setProperty("hibernate.jndi.class",
bitronix.tm.jndi.BitronixInitialContextFactory.class.getName());
props.setProperty("jta.UserTransaction",
"BTMUserTransaction");
emf = new LocalContainerEntityManagerFactoryBean();
emf.setJtaDataSource(securityDataSource());
emf.setJpaVendorAdapter(jpaVendorAdapter());
emf.setPersistenceUnitName("myAppPersistence");
emf.setPackagesToScan("com.myApp.*");
emf.setJpaProperties(props);
return emf;
}
}
Edits: added details of software stack
Upvotes: 0
Views: 1409
Reputation: 979
There were multiple interacting problems that all resulted in a hibernate declining to join the transaction.
The hibernate properties weren't quite right. As written above, hibernate wasn't even trying to join the transaction. The final hibernate configurations are:
Properties props = new Properties();
props.setProperty(HIBERNATE_DIALECT, pds.getHibernateStrategy(model.getDbType()));
props.setProperty(HIBERNATE_DDL_AUTO, "false");
props.setProperty(HIBERNATE_SHOW_SQL, "false");
props.setProperty(HIBERNATE_FORMAT_SQL, "false");
props.setProperty("hibernate.transaction.jta.platform", org.hibernate.engine.transaction.jta.platform.internal.BitronixJtaPlatform.class.getName());
props.setProperty("hibernate.jndi.class", bitronix.tm.jndi.BitronixInitialContextFactory.class.getName());
props.setProperty("jta.UserTransaction", "BTMUserTransaction");
props.setProperty("jdbc.exclusive-connection.mode", "Transactional");
// JTA stuff
props.setProperty("hibernate.connection.release_mode","after_statement");
props.setProperty("hibernate.current_session_context_class","jta");
props.setProperty("hibernate.transaction.manager_lookup_class","org.hibernate.transaction.BTMTransactionManagerLookup");
The bitronix Transaction Manager has to be a singleton. Insteead of building a new transaction manager, the bean should have been defined as :
@Bean(destroyMethod="shutdown")
public BitronixTransactionManager bitronixTransactionManager() {
btmConfig();
return TransactionManagerServices.getTransactionManager();
}
Having two transaction managers floating around confused the issue.
Upvotes: 0