首页 > 解决方案 > 使用面临 IllegalStateException 的多个数据源时的 Spring 批处理问题:无法打开 JPA EntityManager 进行事务处理:已经值

问题描述

我正在尝试在春季批处理中使用多个数据源,其中第一步连接到一个数据库(sybase),第二步连接到不同的数据源(posgres),但是当我尝试运行相同的数据时,我遇到了以下错误:

org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder@713a4f8d] for key [org.springframework.jdbc.datasource.DriverManagerDataSource@25a51f53] bound to thread [http-nio-9093-exec-1]
    at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:448)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.startTransaction(AbstractPlatformTransactionManager.java:400)
..
..
Caused by: java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder@713a4f8d] for key [org.springframework.jdbc.datasource.DriverManagerDataSource@25a51f53] bound to thread [http-nio-9093-exec-1]
    at org.springframework.transaction.support.TransactionSynchronizationManager.bindResource(TransactionSynchronizationManager.java:193)
    at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:423)

@Configuration
@EnableJpaRepositories(
        basePackages = "com.**.dao.posgres",
        entityManagerFactoryRef = "positionEntityManager",
        transactionManagerRef = "positionTransactionManager"
)
@EnableTransactionManagement
@EnableAutoConfiguration
public class PosgresdbConfig {

    @Autowired
    private Environment env;

    @Bean
    @Primary
    @Qualifier("positionEntityManager")
    public LocalContainerEntityManagerFactoryBean positionEntityManager() {
        LocalContainerEntityManagerFactoryBean em
                = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(positionDataSource());
        em.setPackagesToScan(
                new String[] { "com.**.dao.posgres" });

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        HashMap<String, Object> properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto",
                env.getProperty("hibernate.hbm2ddl.auto"));
        properties.put("hibernate.dialect",
                env.getProperty("hibernate.dialect"));
        em.setJpaPropertyMap(properties);

        return em;
    }

    @Bean
    @Primary
    @Qualifier("positionDataSource")
    public DataSource positionDataSource() {

        DriverManagerDataSource dataSource
                = new DriverManagerDataSource();
        dataSource.setDriverClassName(
                env.getProperty("datasource.posgres.driver-class-name"));
        dataSource.setUrl(env.getProperty("datasource.posgres.url"));
        dataSource.setUsername(env.getProperty("datasource.posgres.username"));
        dataSource.setPassword(env.getProperty("datasource.posgres.password"));

        return dataSource;
    }

    @Bean
    @Primary
    @Qualifier("positionTransactionManager")
    public PlatformTransactionManager positionTransactionManager() {

        JpaTransactionManager transactionManager
                = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(
                positionEntityManager().getObject());
        return transactionManager;
    }

}



@Configuration
@EnableJpaRepositories(
        basePackages = "com.**.dao.Sybase",
        entityManagerFactoryRef = "SybaseEntityManager",
        transactionManagerRef = "SybaseTransactionManager"
)
public class SybasedbConfig {

    @Autowired
    private Environment env;

    @Bean
    public LocalContainerEntityManagerFactoryBean SybaseEntityManager() {
        LocalContainerEntityManagerFactoryBean em
                = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(sybaseDataSource());
        em.setPackagesToScan(
                new String[] { "com.**.Sybase" });

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        HashMap<String, Object> properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto",
                env.getProperty("hibernate.hbm2ddl.auto"));
        properties.put("hibernate.dialect",
                env.getProperty("hibernate.dialect"));
        em.setJpaPropertyMap(properties);
        
        return em;
    }

    @Bean
    public DataSource sybaseDataSource() {

        DriverManagerDataSource dataSource
                = new DriverManagerDataSource();
        dataSource.setDriverClassName(
                env.getProperty("datasource.posgres.driver-class-name"));
        dataSource.setUrl(env.getProperty("spring.datasource.url"));
        dataSource.setUsername(env.getProperty("spring.datasource.username"));
        dataSource.setPassword(env.getProperty("spring.datasource.password"));

        return dataSource;
    }

    @Bean
    public PlatformTransactionManager SybaseTransactionManager() {

        JpaTransactionManager transactionManager
                = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(
                SybaseEntityManager().getObject());
        return transactionManager;
    }

}

批量配置

    @Bean
    public Step loadPositionData(final ItemReader<PositionDataResult> positionDataReader,
                                 final ItemProcessor<Object, PositionDataResult> positionRecordProcessor,
                                 final ItemWriter<Object> positionFileWriter) {
        return stepBuilderFactory.get("position-load-account")
                .<LoadPositionForRiskSP, LoadPositionForRiskSP>chunk(2000)
                .reader(positionDataReader)
                .processor(positionRecordProcessor)
                .writer(positionFileWriter)
                .listener(new NoDataFoundListener())
                .transactionManager(positionTransactionManager)
                .build();

    }

在第一步阅读器中使用第一个数据源可以正常工作:

    @Transactional(value="SybaseTransactionManager", propagation= Propagation.NOT_SUPPORTED, isolation= Isolation.READ_UNCOMMITTED,readOnly = true)
    public PositionDataResult loadDataFromSP() {}

现在在第二步中使用辅助数据源会出错:

    @Transactional(transactionManager="positionTransactionManager", readOnly = true, propagation = Propagation.REQUIRED, noRollbackFor = Exception.class)
    public void write(List<? extends Object> list) throws Exception {}

标签: javaspringhibernatespring-batchmultiple-databases

解决方案


该问题与在此处使ItemWriter#write方法具有事务性有关:

@Transactional(transactionManager="positionTransactionManager", readOnly = true, propagation = Propagation.REQUIRED, noRollbackFor = Exception.class)
    public void write(List<? extends Object> list) throws Exception {}

项目编写器将在 Spring Batch 驱动的事务中调用,因此不需要此注释,应将其删除。对读者来说也是如此。事务管理器和步骤的事务属性应使用StepBuilder.

如果该步骤涉及多个事务资源,JtaTransactionManager则应使用 a 来协调这些资源之间的事务。


推荐阅读