当前位置: 首页 > 知识库问答 >
问题:

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

管炳
2023-03-14

我正在构建一个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;
}

在服务级别,如果@TransactionaltransactionManager属性未与底层客户transactionManager bean一起设置,则EntityManager的持久化方法不会持久化到数据库。我希望根据使用的数据源/EntityManager动态更改此值。

或全局事务管理器,但如果所有客户在同一时间使用相同的服务和DAO,则没有事务问题。

客户是在使用发票服务的Webservice层确定的。

谢谢你的回复。

共有2个答案

时同
2023-03-14

这是一个多租户设置的示例,一个应用程序,多个数据库,每个客户端一个DB。在Spring boot上看到我的答案-多数据库访问(MYSQL)

我在使用Spring Boot、JPA、Hibernate和Postgres的多租户应用程序的博客文章中也介绍了这一点

基本上为多租户支持配置持久层包括:

  • Hibernate、JPA和数据源属性。类似于:

application.yml

...
multitenancy:
  dvdrental:
    dataSources:
      -
        tenantId: TENANT_01
        url: jdbc:postgresql://172.16.69.133:5432/db_dvdrental
        username: user_dvdrental
        password: changeit
        driverClassName: org.postgresql.Driver
      -
        tenantId: TENANT_02
        url: jdbc:postgresql://172.16.69.133:5532/db_dvdrental
        username: user_dvdrental
        password: changeit
        driverClassName: org.postgresql.Driver
...

我使用了一个属性文件来存储租户数据,但可以将租户信息存储在一种主数据库中。

多租户JPA配置。Java语言

 ...
 @Configuration
 @EnableConfigurationProperties({ MultiTenantDvdRentalProperties.class, JpaProperties.class })
 @ImportResource(locations = { "classpath:applicationContent.xml" })
 @EnableTransactionManagement
 public class MultiTenantJpaConfiguration {

   @Autowired
   private JpaProperties jpaProperties;

   @Autowired
   private MultiTenantDvdRentalProperties multiTenantDvdRentalProperties;
 ...
 }

多租户房地产。Java语言

...
@Configuration
@ConfigurationProperties(prefix = "multitenancy.dvdrental")
public class MultiTenantDvdRentalProperties {

  private List<DataSourceProperties> dataSourcesProps;
  // Getters and Setters

  public static class DataSourceProperties extends org.springframework.boot.autoconfigure.jdbc.DataSourceProperties {

    private String tenantId;
    // Getters and Setters
  }
}
  • 数据源bean

多租户JPA配置。Java语言

 ...
 public class MultiTenantJpaConfiguration {
 ...
   @Bean(name = "dataSourcesDvdRental" )
   public Map<String, DataSource> dataSourcesDvdRental() {
       ...
   }
 ...
 }
  • 实体管理器工厂bean

多租户JPA配置。Java语言

 ...
 public class MultiTenantJpaConfiguration {
 ...
   @Bean
   public MultiTenantConnectionProvider multiTenantConnectionProvider() {
       ...
   }

   @Bean
   public CurrentTenantIdentifierResolver currentTenantIdentifierResolver() {
       ...
   }

   @Bean
   public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(MultiTenantConnectionProvider multiTenantConnectionProvider,
     CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
       ...  
   }
 ...
 }
  • 事务管理器bean

多租户JPA配置。Java语言

 ...
 public class MultiTenantJpaConfiguration {
 ...
   @Bean
   public EntityManagerFactory entityManagerFactory(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
       ...
   }

   @Bean
   public PlatformTransactionManager txManager(EntityManagerFactory entityManagerFactory) {
       ...
   }
 ...
 }
  • Spring Data JPA和事务支持配置

应用Content.xml

...
<jpa:repositories base-package="com.asimio.dvdrental.dao" transaction-manager-ref="txManager" />
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />
...

阿克托多。Java语言

public interface ActorDao extends JpaRepository<Actor, Integer> {
}

根据您的需要,可以这样做:

...
@Autowired
private ActorDao actorDao;
...

DvdRentalTenantContext.setTenantId("TENANT_01");
this.actorDao.findOne(...);
...

// Or
DvdRentalTenantContext.setTenantId("TENANT_02");
this.actorDao.save(...);
...

设置tenantId可以在将要执行JPA操作等的servlet过滤器/Spring MVC拦截器/线程中完成。

曹乐意
2023-03-14

我会尝试创建一个定制的平台TransactionManager,它会将其调用委托给当前客户的正确事务管理器。要使其工作,它必须能够从某处获取当前客户,例如从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变量来存储当前客户:

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;
}
}

在调用我们服务的create方法的webservice方法开始时,我将当前客户存储在CustomerHolder中,并在同一方法结束时删除当前客户以避免内存泄漏。

然后不要使用Transactional的属性,并将此自定义事务管理器命名为transactionManager,使其成为默认事务管理器。

 类似资料:
  • 问题内容: 我在Junit应用程序上下文文件中定义了三个(JDBC)。其中两个需要进行事务管理;使用这两个数据源时,我不必链接任何方法(它们完全彼此独立)。 当我使用单个事务管理器作为时,我没有问题,即使已被使用但未在相应方法中进行管理。尽管如此,在还需要管理来自各种DAO类的方法(仅使用这些方法)后,我添加了第二个事务- 。上下文文件包含以下内容: 由于要定义多个事务管理器,因此我用他们自己的值

  • 我正在使用Spring的事务支持和JPA(Hibernate)来持久化我的实体。一切正常,但我在处理一个请求中的部分更新时陷入困境: 对于每个用户(HTTP)请求,我必须将一个日志条目写入数据库表,即使“主”业务实体的更新失败(例如,由于验证错误)。因此,我的第一个/主要事务get被回滚,但第二个(写日志)应该提交。这似乎可以使用正确的传播级别来写入日志条目: 然而,我的问题是,我在第二个事务中注

  • 在我的实际应用程序中,我有一个业务层,它根据一些业务规则使用JPA来持久化数据,问题是camel JPA事务没有与业务层事务共享。我需要业务类中的EntityManager与Camel事务范围集成,我该怎么做? 下面是一个简单的例子,但这反映了实际设计中的问题。 项目实例 服务级别 骆驼路线 骆驼背景。xml

  • 我正在开发一个应用程序,其中我必须根据从客户端传递的客户id连接到不同的数据库。所有数据库的模式都是相同的。它是一种多租户应用程序。由于我不知道会有多少客户,我无法使用xml配置静态创建数据源,因此我必须手动创建数据源。 我们使用Spring JdbcTemplate连接到数据库,连接参数来自另一个保存应用程序配置的数据库。我能够正确连接到数据库,但方法调用不会在事务中发生。下面的代码片段只对一个

  • 我有一个用例,我想我需要两个实体管理器,它们访问相同的持久性单元。所以本质上,我希望在同一个数据库上有两个持久性上下文。这是否可以通过PersistenceContext注释实现? 我想写以下内容,但不知道如何告诉JPA注入两个不同的manager实例。 我想我可以切换到应用程序管理的事务,然后我可以使用工厂创建另一个事务。但是我不想自己管理事务,如果不是绝对必要的话。

  • 事务处理(transaction processing) 可以用来维护数据的完整性,保证SQL的操作要么完全执行,要么完全不执行,如果发生错误就进行撤销。 保证数据的完整性。 保证数据不受外影响。 事务处理的几道术语 事务(transaction) 一组SQL语句 退回(rollback)撤销执行SQL语句的过程 提交(commit) 将为执行的SQL语句写入数据库表 保留点(savepoint)