apache / incubator-seata

:fire: Seata is an easy-to-use, high-performance, open source distributed transaction solution.
https://seata.apache.org/
Apache License 2.0
25.38k stars 8.79k forks source link

Multiple datasource transaction does not work for JPA (Oracle 11g) #1553

Closed robinyeeh closed 5 years ago

robinyeeh commented 5 years ago

I tried to use seata for multiple data source transaction but not work. Could anyone can help check if the way i m using is correct? Thanks a lot.

DataSourceProxyConfig has two data sources primaryDataSource and primaryDataSource

@Configuration public class DataSourceProxyConfig {

@Bean("primaryDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource dataSourceMaster() {
    return DruidDataSourceBuilder.create().build();
}

@Bean("secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource dataSourceStorage() {
    return DruidDataSourceBuilder.create().build();
}

@Bean(name = "primaryDataSourceProxy")
public DataSourceProxy masterDataSourceProxy(@Qualifier("primaryDataSource") DataSource dataSource) {
    return new DataSourceProxy(dataSource);
}

@Bean(name = "secondarySourceProxy")
public DataSourceProxy storageDataSourceProxy(@Qualifier("secondaryDataSource") DataSource dataSource) {
    return new DataSourceProxy(dataSource);
}

@Bean("dynamicDataSource")
public DataSource dynamicDataSource(@Qualifier("primaryDataSource") DataSource dataSourceOrder,
                                    @Qualifier("secondaryDataSource") DataSource dataSourceStorage) {

    DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();

    Map<Object, Object> dataSourceMap = new HashMap<>(3);
    dataSourceMap.put(DataSourceKey.PRIMARY.name(), dataSourceOrder);
    dataSourceMap.put(DataSourceKey.SECONDARY.name(), dataSourceStorage);

    dynamicRoutingDataSource.setDefaultTargetDataSource(dataSourceOrder);
    dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);

    DynamicDataSourceContextHolder.getDataSourceKeys().addAll(dataSourceMap.keySet());

    return dynamicRoutingDataSource;
}

}

PrimaryConfig class is for primary repository and models config

@Configuration @EnableTransactionManagement @EnableJpaRepositories( entityManagerFactoryRef = "primaryEntityManagerFactory", transactionManagerRef = "primaryTransactionManager", basePackages = "com.robinye.mbp.repository.primary" ) public class PrimaryConfig { @Resource @Qualifier("primaryDataSourceProxy") private DataSource primaryDataSource;

@Resource
private Properties jpaProperties;

@Bean(name = "primaryEntityManager")
@Primary
public EntityManager getEntityManager(EntityManagerFactoryBuilder builder) {
    EntityManagerFactory emf = getEntityManagerFactory(builder).getObject();
    return emf.createEntityManager();
}

/**
 *  * EntitymanagerFactory, EntityManager, TransactionManager
 *  */
@Primary
@Bean(name = "primaryEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean getEntityManagerFactory(EntityManagerFactoryBuilder builder) {
    LocalContainerEntityManagerFactoryBean entityManagerFactory = builder
            .dataSource(primaryDataSource)
            .packages("com.robinye.mbp.model.primary")
            .persistenceUnit("primaryPersistenceUnit")
            .build();
    entityManagerFactory.setJpaProperties(jpaProperties);
    return entityManagerFactory;
}

@Primary
@Bean(name = "primaryTransactionManager")
public PlatformTransactionManager getTransactionManager(EntityManagerFactoryBuilder builder) {
    return new JpaTransactionManager(getEntityManagerFactory(builder).getObject());
}

}

SecondaryConfig class is for secondary repository and model config. @Configuration @EnableTransactionManagement @EnableJpaRepositories( entityManagerFactoryRef = "secondaryEntityManagerFactory", transactionManagerRef = "secondaryTransactionManager", basePackages = "com.robinye.mbp.repository.secondary" ) public class SecondaryConfig { @Resource @Qualifier("secondaryDataSourceProxy") private DataSource secondaryDataSource;

@Resource
private Properties jpaProperties;

@Bean(name = "secondaryEntityManager")
public EntityManager getEntityManager(EntityManagerFactoryBuilder builder) {
    EntityManagerFactory emf = getEntityManagerFactory(builder).getObject();
    return emf.createEntityManager();
}

/**
 *  * EntitymanagerFactory, EntityManager, TransactionManager
 *  */
@Bean(name = "secondaryEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean getEntityManagerFactory(EntityManagerFactoryBuilder builder) {
    LocalContainerEntityManagerFactoryBean entityManagerFactory = builder
            .dataSource(secondaryDataSource)
            .packages("com.robinye.mbp.model.secondary")
            .persistenceUnit("secondaryPersistenceUnit")
            .build();
    entityManagerFactory.setJpaProperties(jpaProperties);
    return entityManagerFactory;
}

@Bean(name = "secondaryTransactionManager")
public PlatformTransactionManager getTransactionManager(EntityManagerFactoryBuilder builder) {
    return new JpaTransactionManager(getEntityManagerFactory(builder).getObject());
}

}

Method in service to insert 2 records to databases respectively. I should rollback first record but worked unexpectedly.

@Override @GlobalTransactional public void addUser(UserVO userVO) { CityVO cityVO = userVO.getCity();

    UserEntity userEntity = new UserEntity();
    userEntity.setId(userVO.getId());
    userEntity.setName(userVO.getName());
    userEntity.setCityId(cityVO.getId());

    log.info("=============ORDER=================");
    DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.PRIMARY);
    log.info("Current XID: {}", RootContext.getXID());
    userRepository.save(userEntity);

    int i = 1/0;

    CityEntity cityEntity = new CityEntity();
    cityEntity.setId(cityVO.getId());
    cityEntity.setName(cityVO.getName());
    cityRepository.save(cityEntity);

    log.info("=============ORDER=================");
    DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.PRIMARY);

// int i = 1/0; // addSinleUser(userVO); // int i = 1/0; // addSingleCity(cityVO); }

fescar-robot commented 5 years ago

Hi @robinyeeh, we detect non-English characters in the issue. This comment is an auto translation from @fescar-robot to help other users to understand this issue. We encourage you to describe your issue in English which is more friendly to other users.

Multiple datasource transaction does not work for JPA

I tried to use seata for multiple data source transaction but not work. Could anyone can help check if the way i m using is correct? Thanks a lot.

DataSourceProxyConfig has two data sources primaryDataSource and primaryDataSource

@Configuration public class DataSourceProxyConfig {

@Bean("primaryDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource dataSourceMaster() {
    return DruidDataSourceBuilder.create().build();
}

@Bean("secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource dataSourceStorage() {
    return DruidDataSourceBuilder.create().build();
}

@Bean(name = "primaryDataSourceProxy")
public DataSourceProxy masterDataSourceProxy(@Qualifier("primaryDataSource") DataSource dataSource) {
    return new DataSourceProxy(dataSource);
}

@Bean(name = "secondarySourceProxy")
public DataSourceProxy storageDataSourceProxy(@Qualifier("secondaryDataSource") DataSource dataSource) {
    return new DataSourceProxy(dataSource);
}

@Bean("dynamicDataSource")
public DataSource dynamicDataSource(@Qualifier("primaryDataSource") DataSource dataSourceOrder,
                                    @Qualifier("secondaryDataSource") DataSource dataSourceStorage) {

    DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();

    Map<Object, Object> dataSourceMap = new HashMap<>(3);
    dataSourceMap.put(DataSourceKey.PRIMARY.name(), dataSourceOrder);
    dataSourceMap.put(DataSourceKey.SECONDARY.name(), dataSourceStorage);

    dynamicRoutingDataSource.setDefaultTargetDataSource(dataSourceOrder);
    dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);

    DynamicDataSourceContextHolder.getDataSourceKeys().addAll(dataSourceMap.keySet());

    return dynamicRoutingDataSource;
}

}

PrimaryConfig class is for primary repository and models config

@Configuration @EnableTransactionManagement @EnableJpaRepositories( entityManagerFactoryRef = "primaryEntityManagerFactory", transactionManagerRef = "primaryTransactionManager", basePackages = "com.robinye.mbp.repository.primary" ) public class PrimaryConfig { @Resource @Qualifier("primaryDataSourceProxy") private DataSource primaryDataSource;

@Resource
private Properties jpaProperties;

@Bean(name = "primaryEntityManager")
@Primary
public EntityManager getEntityManager(EntityManagerFactoryBuilder builder) {
    EntityManagerFactory emf = getEntityManagerFactory(builder).getObject();
    return emf.createEntityManager();
}

/**

SecondaryConfig class is for secondary repository and model config. @Configuration @EnableTransactionManagement @EnableJpaRepositories( entityManagerFactoryRef = "secondaryEntityManagerFactory", transactionManagerRef = "secondaryTransactionManager", basePackages = "com.robinye.mbp.repository.secondary" ) public class SecondaryConfig { @Resource @Qualifier("secondaryDataSourceProxy") private DataSource secondaryDataSource;

@Resource
private Properties jpaProperties;

@Bean(name = "secondaryEntityManager")
public EntityManager getEntityManager(EntityManagerFactoryBuilder builder) {
    EntityManagerFactory emf = getEntityManagerFactory(builder).getObject();
    return emf.createEntityManager();
}

/**

Method in service to insert 2 records to databases respectively. I should rollback first record but worked unexpectedly.

@Override @GlobalTransactional public void addUser(UserVO userVO) { CityVO cityVO = userVO.getCity();

    UserEntity userEntity = new UserEntity();
    userEntity.setId(userVO.getId());
    userEntity.setName(userVO.getName());
    userEntity.setCityId(cityVO.getId());

    log.info("=============ORDER=================");
    DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.PRIMARY);

Log.info("current XID: {}", RootContext.getXID()); userRepository.save(userEntity);

    int i = 1/0;

    CityEntity cityEntity = new CityEntity();
    cityEntity.setId(cityVO.getId());
    cityEntity.setName(cityVO.getName());
    cityRepository.save(cityEntity);

    log.info("=============ORDER=================");
    DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.PRIMARY);

// int i = 1/0; // addSinleUser(userVO); // int i = 1/0; // addSingleCity(cityVO); }

robinyeeh commented 5 years ago

Have done the changes for Chinese characters.

robinyeeh commented 5 years ago

There's something wrong with my code, i will close this ticket.

HrshPtl commented 5 years ago

i have same issue