我正在构建一个具有多个数据源,实体管理器,事务管理器和数据库的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用于检查是否加载。
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动态更改此值。
transactionManager
@Transactional
或全球交易经理,但如果所有客户同时使用相同的服务和DAO,则不会出现交易问题。
在使用发票服务的Web服务层中确定客户。
感谢您的回复。
我会尝试创建一个自定义PlatformTransactionManager,将其调用委派给当前客户的正确交易管理器。为此,它必须能够从某个地方(例如,从ThreadLocal变量中)获取当前客户。像这样:
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:
DataSourceConfiguration
@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以使其成为默认交易管理器。