一尘不染

使用同一应用程序代码管理具有多个数据源和实体管理器的事务

spring-boot

我正在构建一个具有多个数据源,实体管理器,事务管理器和数据库的Spring Boot应用程序。每个客户都是一个客户,并共享相同的DAO,服务。

数据源之间的畅通无阻。但是我交易有问题

这是我的配置:

package org.foo.config;

@Configuration
@EnableJpaRepositories(basePackages = "org.foo")
@EnableTransactionManagement
public class DataSourceConfiguration
{

@Value("#{'${load.datasources}'.split(',')}")
private List<String> toLoadDatasources;

@Value("${default.datasource}")
private String defaultDatasource;

@Bean
@ConfigurationProperties("spring.jpa")
public JpaProperties jpaProperties()
{
    return new JpaProperties();
}

@Bean
@Primary
public DataSource dataSource()
{
    if(toLoadDatasources.isEmpty())
    {
        throw new IllegalArgumentException("At least one datasource to load must be provided. Please check datasources configuration");
    }
    if(defaultDatasource == null || defaultDatasource.isEmpty())
    {
        throw new IllegalArgumentException("No default datasource provided. Please check datasources configuration");
    }
    if(!toLoadDatasources.contains(defaultDatasource))
    {
        throw new IllegalArgumentException("Default datasource must appear in the list of datasources to load. Please check datasources configuration");
    }

    final Map<Object, Object> map = new HashMap<Object, Object>();

    if(toLoadDatasources.contains(Customer.CUST1.name()))
    {
        map.put("datasourceCust1", dataSourceCust1());
    }
    if(toLoadDatasources.contains(Customer.CUST2.name()))
    {
        map.put("datasourceCust2", dataSourceCust2());
    }
    if(toLoadDatasources.contains(Customer.CUST3.name()))
    {
        map.put("datasourceCust3", dataSourceCust3());
    }
    if(toLoadDatasources.contains(Customer.CUST4.name()))
    {
        map.put("datasourceCust4", dataSourceCust4());
    }

    DataSourceRouter router = new DataSourceRouter();
    router.setTargetDataSources(map);

    if(Customer.CUST1.name().equalsIgnoreCase(defaultDatasource))
    {
        router.setDefaultTargetDataSource(dataSourceCust1());
    }
    else if(Customer.CUST2.name().equalsIgnoreCase(defaultDatasource))
    {
        router.setDefaultTargetDataSource(dataSourceCust2());
    }
    else if(Customer.CUST3.name().equalsIgnoreCase(defaultDatasource))
    {
        router.setDefaultTargetDataSource(dataSourceCust3());
    }
    else if(Customer.CUST4.name().equalsIgnoreCase(defaultDatasource))
    {
        router.setDefaultTargetDataSource(dataSourceCust4());
    }
    else
    {
        throw new IllegalArgumentException("At least one default datasource must be provided.");
    }

    return router;
}

@Bean
@Primary
public LocalContainerEntityManagerFactoryBean emfb(DataSource ds, EntityManagerFactoryBuilder builder, final JpaProperties jpaProperties)
{
    return builder.dataSource(ds)
            .packages("org.foo")
            .build();
}

@Bean
@Primary
public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder, final JpaProperties jpaProperties)
{
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(emfb(dataSource(), builder, jpaProperties).getObject());
    return transactionManager;
}

@Bean(name="dataSourceCust1")
@Conditional(LoadCust1DatasourceCondition.class)
@ConfigurationProperties(prefix = "spring.cust1.datasource")
public DataSource dataSourceCust1()
{
    return DataSourceBuilder.create().build();
}

@PersistenceContext(unitName = "entityManagerCust1")
@Bean(name="entityManagerCust1")
@Conditional(LoadCust1DatasourceCondition.class)
public LocalContainerEntityManagerFactoryBean emfbCust1(DataSource ds, EntityManagerFactoryBuilder builder, final JpaProperties jpaProperties)
{
    return builder.dataSource(ds)
            .packages("org.foo")
            .persistenceUnit("entityManagerCust1")
            .build();
}

@Bean(name="transactionManagerCust1")
@Conditional(LoadCust1DatasourceCondition.class)
public PlatformTransactionManager transactionManagerCust1(EntityManagerFactoryBuilder builder, final JpaProperties jpaProperties)
{
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(emfbCust1(dataSourceCust1(), builder, jpaProperties).getObject());
    return transactionManager;
}

@Bean(name="dataSourceCust2")
@Conditional(LoadCust2DatasourceCondition.class)
@ConfigurationProperties(prefix = "spring.cust2.datasource")
public DataSource dataSourceCust2()
{
    return DataSourceBuilder.create().build();
}

@PersistenceContext(unitName = "entityManagerCust2")
@Bean(name="entityManagerCust2")
@Conditional(LoadCust2DatasourceCondition.class)
public LocalContainerEntityManagerFactoryBean emfbCust2(@Qualifier("dataSourceCust2") DataSource ds, EntityManagerFactoryBuilder builder, final JpaProperties jpaProperties)
{
    return builder.dataSource(ds)
            .packages("org.foo")
            .persistenceUnit("entityManagerCust2")
            .build();
}

@Bean(name="transactionManagerCust2")
@Conditional(LoadCust2DatasourceCondition.class)
public PlatformTransactionManager transactionManagerCust2(EntityManagerFactoryBuilder builder, final JpaProperties jpaProperties)
{
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(emfbCust2(dataSourceCust2(), builder, jpaProperties).getObject());
    return transactionManager;
}

@Bean(name="dataSourceCust3")
@Conditional(LoadCust3DatasourceCondition.class)
@ConfigurationProperties(prefix = "spring.cust3.datasource")
public DataSource dataSourceCust3()
{
    return DataSourceBuilder.create().build();
}

@PersistenceContext(unitName = "entityManagerCust3")
@Bean(name="entityManagerCust3")
@Conditional(LoadCust3DatasourceCondition.class)
public LocalContainerEntityManagerFactoryBean emfbCust3(@Qualifier("dataSourceCust3") DataSource ds, EntityManagerFactoryBuilder builder, final JpaProperties jpaProperties)
{
    return builder.dataSource(ds)
            .packages("org.foo")
            .persistenceUnit("entityManagerCust3")
            .build();
}

@Bean(name="transactionManagerCust3")
@Conditional(LoadCust3DatasourceCondition.class)
public PlatformTransactionManager transactionManagerCust3(EntityManagerFactoryBuilder builder, final JpaProperties jpaProperties)
{
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(emfbCust3(dataSourceCust3(), builder, jpaProperties).getObject());
    return transactionManager;
}

@Bean(name="dataSourceCust4")
@Conditional(LoadCust4DatasourceCondition.class)
@ConfigurationProperties(prefix = "spring.cust4.datasource")
public DataSource dataSourceCust4()
{
    return DataSourceBuilder.create().build();
}

@PersistenceContext(unitName = "entityManagerCust4")
@Bean(name="entityManagerCust4")
@Conditional(LoadCust4DatasourceCondition.class)
public LocalContainerEntityManagerFactoryBean emfbCust4(@Qualifier("dataSourceCust4") DataSource ds, EntityManagerFactoryBuilder builder, final JpaProperties jpaProperties)
{
    return builder.dataSource(ds)
            .packages("org.foo")
            .persistenceUnit("entityManagerCust4")
            .build();
}

@Bean(name="transactionManagerCust4")
@Conditional(LoadCust4DatasourceCondition.class)
public PlatformTransactionManager transactionManagerCust4(EntityManagerFactoryBuilder builder, final JpaProperties jpaProperties)
{
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(emfbCust4(dataSourceCust4(), builder, jpaProperties).getObject());
    return transactionManager;
}

}

数据源的加载取决于配置文件。类似的类LoadCust4DatasourceCondition用于检查是否加载。

我的数据源配置文件是:

 # Datasources
 spring.cust1.datasource.driver-class-name: com.mysql.jdbc.Driver
 spring.cust1.datasource.url: 
 spring.cust1.datasource.username: root
 spring.cust1.datasource.password: pass

 spring.cust2.datasource.driver-class-name: com.mysql.jdbc.Driver
 spring.cust2.datasource.url: 
 spring.cust2.datasource.username: root
 spring.cust2.datasource.password: pass

 spring.cust3.datasource.driver-class-name: com.mysql.jdbc.Driver
 spring.cust3.datasource.url: 
 spring.cust3.datasource.username: root
 spring.cust3.datasource.password: pass

 spring.cust4.datasource.driver-class-name: com.mysql.jdbc.Driver
 spring.cust4.datasource.url: jdbc:
 spring.cust4.datasource.username: root
 spring.cust4.datasource.password: pass


 # JPA/Hibernate
 spring.jpa.hibernate.dialect: org.hibernate.dialect.MySQL5Dialect
 spring.jpa.hibernate.show_sql: true
 spring.jpa.hibernate.hbm2ddl.auto: none
 spring.jpa.entitymanager.packagesToScan: org.foo.domain

 load.datasources: CUST1, CUST2, CUST3, CUST4
 default.datasource: CUST1

我的服务就像:

 @Service
 public class InvoiceServiceImpl implements IInvoiceService {
  @Autowired
  private IInvoiceDao invoiceDao;

  @Override
  @Transactional(propagation = Propagation.REQUIRED, rollbackFor = {TechnicalException.class, BusinessException.class}, transactionManager = "transactionManagerCust1")
  public Invoice create(Invoice invoice, Customer customer) throws AbstractException {

    return invoiceDao.persist(invoice, customer);
}

}

我的道像:

@Repository
public class InvoiceDaoImpl implements IInvoiceDao
{

@Autowired(required = false)
@Qualifier("entityManagerCust1")
private EntityManager entityManagerCust1;

@Autowired(required = false)
@Qualifier("entityManagerCust2")
private EntityManager entityManagerCust2;

@Autowired(required = false)
@Qualifier("entityManagerCust3")
private EntityManager entityManagerCust3;

@Autowired(required = false)
@Qualifier("entityManagerCust4")
private EntityManager entityManagerCust4;

@Override
public Invoice persist(Invoice invoice, Customer customer) throws AbstractException {

    try {
        getEntityManager(customer).persist(invoice);
    } catch(EntityExistsException eee) {
        logger.error(ExceptionConstantes.MSG_INV_ALRDY_EXIST);
        throw new BusinessException(ExceptionConstantes.MSG_INV_ALRDY_EXIST, ExceptionConstantes.CODE_INV_ALRDY_EXIST);
    }catch (Exception e){
        logger.error(String.format(ExceptionConstantes.MSG_CREATE_ERR, invoice.getClass().getSimpleName()), e);
        throw new TechnicalException(String.format(ExceptionConstantes.MSG_CREATE_ERR, invoice.getClass().getSimpleName()));
    }

    return invoice;
}




private EntityManager getEntityManager(Customer customer) throws IllegalArgumentException
{
    switch(customer)
    {
        case CUST1 : if(entityManagerCust1 == null){ throw new IllegalArgumentException("Requested " + customer.name() +"'s datasource is not loaded. Please check datasources configuration"); }
                  return entityManagerCust1;
        case CUST2 : if(entityManagerCust2 == null){ throw new IllegalArgumentException("Requested " + customer.name() + "'s datasource is not loaded. Please check datasources configuration"); }
                  return entityManagerCust2;
        case CUST3 : if(entityManagerCust3 == null){ throw new IllegalArgumentException("Requested " + customer.name() + "'s datasource is not loaded. Please check datasources configuration"); }
                  return entityManagerCust3;
        case CUST4 : if(entityManagerCust4 == null){ throw new IllegalArgumentException("Requested " + customer.name() + "'s datasource is not loaded. Please check datasources configuration"); }
                  return entityManagerCust4;
        default: throw new IllegalArgumentException("Invalid customer " + customer.name());
    }
}


@Conditional(LoadCust1DatasourceCondition.class)
public void setEntityManagerCust1(EntityManager entityManagerCust1) 
{
    this.entityManagerCust1 = entityManagerCust1;
}

@Conditional(LoadCust2DatasourceCondition.class)
public void setEntityManagerCust2(EntityManager entityManagerCust2) 
{
    this.entityManagerCust2 = entityManagerCust2;
}
@Conditional(LoadCust3DatasourceCondition.class)
public void setEntityManagerCust3(EntityManager entityManagerCust3) 
{
    this.entityManagerCust3 = entityManagerCust3;
}
@Conditional(LoadCust4DatasourceCondition.class)
public void setEntityManagerCust4(EntityManager entityManagerCust4) 
{
    this.entityManagerCust4 = entityManagerCust4;
}

在服务级别,如果未使用基础客户transactionManager
bean设置的transactionManager属性,@Transactional则EntityManager的persist方法不会持久到数据库。我想要根据使用的Datasource
/ EntityManager动态更改此值。

或全球交易经理,但如果所有客户同时使用相同的服务和DAO,则不会出现交易问题。

在使用发票服务的Web服务层中确定客户。

感谢您的回复。


阅读 227

收藏
2020-05-30

共1个答案

一尘不染

我会尝试创建一个自定义PlatformTransactionManager,将其调用委派给当前客户的正确交易管理器。为此,它必须能够从某个地方(例如,从ThreadLocal变量中)获取当前客户。像这样:

public class CustomerAwareTransactionManager implements PlatformTransactionManager {

    // Tx managers beans and their names
    @Autowired 
    private Map<String, PlatformTransactionManager> txManagerMap;

    private PlatformTransactionManager getCurrentManager() {
        // CustomerHolder gets the customer from a ThreadLocal variable
        // something like SecurityContextHolder
        // It should be set just once for a request and removed at the end
        // of each request (to prevent memory leaks)
        String currentIdentifier = CustomerHolder.getCustomer().get().name;
        for (String managerName : txManagerMap.keySet()) {
            if (managerName.equals("transactionManager" + currentIdentifier)) {
                return txManagerMap.get(managerName);
            }
        }
        throw new IllegalStateException("No tx manager for id " + currentIdentifier);
    }
    @Override
    public commit(TransactionStatus status) {
        this.getCurrentManager().commit(status);
    }
    @Override
    public getTransaction(TransactionDefinition definition) {
        this.getCurrentManager().getTransaction(definition);
    }
    @Override
    public rollback(TransactionStatus status) {
        this.getCurrentManager().commit(status);
    }
}

在中,DataSourceConfiguration我用以下代码片段替换了主事务管理器bean:

 @Bean
@Primary
public PlatformTransactionManager transactionManager()
{
    return new CustomerAwareTransactionManager();
}

而且我在CustomerHolder中创建了一个ThreadLocal变量来存储当前的Customer:

public class CustomerHolder
{
   private static ThreadLocal<Customer> customer= new ThreadLocal<Customer>();

public static ThreadLocal<Customer> getCustomer() {
    return customer;
}

public static void setCustomer(ThreadLocal<Customer> customer) {
    CustomerHolder.customer= customer;
}
}

在webservice方法的开头,即调用我们服务的create方法,我将当前客户存储在CustomerHolder中,而在同一方法的结尾,我删除了当前客户,以避免内存泄漏。

然后,请勿使用的transactionManager属性@Transactional并将其命名为该自定义交易管理器,transactionManager以使其成为默认交易管理器。

2020-05-30