我正在构建一个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的持久化方法不会持久化到数据库。我希望根据使用的数据源/EntityManager动态更改此值。
或全局事务管理器,但如果所有客户在同一时间使用相同的服务和DAO,则没有事务问题。
客户是在使用发票服务的Webservice层确定的。
谢谢你的回复。
这是一个多租户设置的示例,一个应用程序,多个数据库,每个客户端一个DB。在Spring boot上看到我的答案-多数据库访问(MYSQL)
我在使用Spring Boot、JPA、Hibernate和Postgres的多租户应用程序的博客文章中也介绍了这一点
基本上为多租户支持配置持久层包括:
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
}
}
多租户JPA配置。Java语言
...
public class MultiTenantJpaConfiguration {
...
@Bean(name = "dataSourcesDvdRental" )
public Map<String, DataSource> dataSourcesDvdRental() {
...
}
...
}
多租户JPA配置。Java语言
...
public class MultiTenantJpaConfiguration {
...
@Bean
public MultiTenantConnectionProvider multiTenantConnectionProvider() {
...
}
@Bean
public CurrentTenantIdentifierResolver currentTenantIdentifierResolver() {
...
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(MultiTenantConnectionProvider multiTenantConnectionProvider,
CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
...
}
...
}
多租户JPA配置。Java语言
...
public class MultiTenantJpaConfiguration {
...
@Bean
public EntityManagerFactory entityManagerFactory(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
...
}
@Bean
public PlatformTransactionManager txManager(EntityManagerFactory entityManagerFactory) {
...
}
...
}
应用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拦截器/线程中完成。
我会尝试创建一个定制的平台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)