JpaTransactionManager transactions not used by Hib

2019-03-19 09:14发布

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 Sessions from SessionFactorys 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;
        }    
    }

0条回答
登录 后发表回答