【发布时间】:2018-07-11 18:12:55
【问题描述】:
我正在构建一个具有多个数据源、实体管理器、事务管理器和数据库的 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;
}
在服务级别中,如果@Transactional 的transactionManager 属性未使用底层客户transactionManager bean 设置,则EntityManager 的persist 方法不会持久化到数据库。我希望根据使用的 Datasource/EntityManager 动态更改此值。
或全局事务管理器,但如果所有客户同时使用相同的服务和 DAO,则不会出现事务问题。
客户在使用 Invoice Service 的 Webservice 层确定。
感谢您的回复。
【问题讨论】:
-
您什么时候开始了解您想要合作的客户(CUST1、CUST2 ...)?整个请求都一样吗?
-
我已经更新了描述。客户是从 Web 服务层传递过来的。如果是 CUST1,他使用 entityManagerCust1,但他不能持久化到数据库,因为我们必须将 @Transactional 注解的 transactionManager 属性设置为 transactionManagerCust1。我不能这样做,因为它适用于 CUST1,但不适用于其他人
标签: hibernate jpa spring-boot spring-transactions