I am working on a project that is using Spring framework (4.3.3.RELEASE) and Hibernate (5.2.3.Final) and I am starting to move to using Spring Data JPA.
I have just migrated the LocalSessionFactoryBean
with HibernateTransactionManager
config to using the JPA config of LocalContainerEntityManagerFactoryBean
with JpaTransactionManager
with HibernateJpaSessionFactoryBean
.
The existing hibernate code that uses Session
s from SessionFactory
s seemed to work fine until I tested some code that does a save of one entity and then runs some update queries in the same transaction and the code was failing on the update sql with:
javax.persistence.TransactionRequiredException: Executing an update/delete query
The transaction manager logs showed that the transaction was active and then rolled back which was strange. I then noticed that the save operation had reached the database.
On debugging I can see that the session object does not seem to have any transaction object, so it seems that the hibernate session is not working with or using the configured JpaTransactionManager
transactions.
When I configure an additional transaction manager (HibernateTransactionManager
) marked as the Primary PlatformTransactionManager
the code then works.
Moving forward, as I migrate code to Spring Data Jpa I will be wanting to use some Hibernate based Dao code and some Spring Data Jpa Repository in the same Transaction. How can I get the session factory to use the JpaTransactionManager
?
UPDATE:
I have now found that the above config means that the session does not get flushed to the database by the transaction manager, so does not work correctly.
I have also found that if I inject the EntityManager into my Daos:
@PersistenceContext()
private EntityManager entityManager;
and use:
entityManager.unwrap( Session.class )
Then the code participates in the transaction correctly. But if I get the SessionFactory
(either injected by spring, or unwrapped from the entityManagerFactory
, or using getSessionFactory()
from the unwrapped Session
) and call getCurrentSession()
it returns a different Session object that is not connected to the transaction.
My configuration:
@Configuration
@EnableJpaRepositories(
basePackages = "com.mycompany.common.services",
transactionManagerRef = "jpaTransactionManager"
)
@EnableTransactionManagement(order = 5)
public class PersistenceConfiguration
{
@Bean
@Qualifier(value = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory( DataSource dataSource )
{
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setPersistenceUnitName( "entityManagerFactory" );
factory.setPackagesToScan( entityPackages() );
factory.setJpaVendorAdapter( getHibernateJpaVendorAdapter() );
factory.setJpaProperties( getJpaProperties() );
factory.setDataSource( dataSource );
factory.afterPropertiesSet();
return factory;
}
@Bean
@Qualifier(value = "jpaTransactionManager")
public PlatformTransactionManager jpaTransactionManager( EntityManagerFactory entityManagerFactory, DataSource dataSource )
{
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory( entityManagerFactory );
txManager.setDataSource( dataSource );
return txManager;
}
@Bean
@Qualifier(value = "sessionFactory")
public FactoryBean<SessionFactory> sessionFactory( EntityManagerFactory entityManagerFactory )
{
HibernateJpaSessionFactoryBean hibernateJpaSessionFactoryBean = new HibernateJpaSessionFactoryBean();
hibernateJpaSessionFactoryBean.setEntityManagerFactory( entityManagerFactory );
return hibernateJpaSessionFactoryBean;
}
// How do I remove this and just use the one transaction manager above?
/*
@Bean
@Qualifier(value = "hibernateTransactionManager")
@Primary
public PlatformTransactionManager hibernateTransactionManager( SessionFactory sessionFactory )
{
HibernateTransactionManager hibernateTransactionManager = new HibernateTransactionManager( sessionFactory );
return hibernateTransactionManager;
}
*/
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation()
{
return new PersistenceExceptionTranslationPostProcessor();
}
protected HibernateJpaVendorAdapter getHibernateJpaVendorAdapter()
{
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl( isGenerateDDL() );
vendorAdapter.setDatabase( Database.MYSQL );
vendorAdapter.setDatabasePlatform( com.mycompany.common.utils.hibernate.MySQL56InnoDBDialect.class.getName() );
return vendorAdapter;
}
protected Properties getJpaProperties()
{
Properties properties = new Properties();
properties.put("hibernate.current_session_context_class", "org.springframework.orm.hibernate5.SpringSessionContext");
properties.put("hibernate.hbm2ddl.auto", "validate");
properties.put("hibernate.transaction.flush_before_completion", "true");
properties.put("hibernate.transaction.auto_close_session", "false");
properties.put("hibernate.use_outer_join", "true");
properties.put("hibernate.cache.region.factory_class", "org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory");
properties.put("hibernate.cache.use_second_level_cache", "true");
properties.put("net.sf.ehcache.configurationResourceName", "META-INF/resources/ehcache-hibernate.xml");
properties.put("hibernate.cache.use_query_cache", "true");
properties.put("hibernate.jdbc.batch_size", "100");
properties.put("hibernate.generate_statistics", "true");
properties.put("hibernate.format_sql", "true");
properties.put("hibernate.use_sql_comments", "true");
properties.put("org.hibernate.SQL", "info");
return properties;
}
protected boolean isGenerateDDL()
{
return false;
}
}