
Spring多数据源与事务
多数据源H2
实际业务中经常会遇到需要访问多个数据库的情况,有些情况是表和数据库之间的关系是固定的,而有些情况同一张表会存在于多个数据库中。
CASE 1H3
针对第一种情况,通过Spring的配置就能完成,例如
bash
@Configuration@EnableTransactionManagement@EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory",basePackages = { "com.foobar.foo.repo" })public class FooDbConfig {@Primary@Bean(name = "dataSource")@ConfigurationProperties(prefix = "spring.datasource")public DataSource dataSource() {return DataSourceBuilder.create().build();}@Primary@Bean(name = "entityManagerFactory")public LocalContainerEntityManagerFactoryBeanentityManagerFactory(EntityManagerFactoryBuilder builder,@Qualifier("dataSource") DataSource dataSource) {return builder.dataSource(dataSource).packages("com.foobar.foo.domain").persistenceUnit("foo").build();}@Primary@Bean(name = "transactionManager")public PlatformTransactionManager transactionManager(@Qualifier("entityManagerFactory") EntityManagerFactoryentityManagerFactory) {return new JpaTransactionManager(entityManagerFactory);}}
bash
@Configuration@EnableTransactionManagement@EnableJpaRepositories(entityManagerFactoryRef = "barEntityManagerFactory",transactionManagerRef = "barTransactionManager",basePackages = { "com.foobar.bar.repo" })public class BarDbConfig {@Bean(name = "barDataSource")@ConfigurationProperties(prefix = "bar.datasource")public DataSource dataSource() {return DataSourceBuilder.create().build();}@Bean(name = "barEntityManagerFactory")public LocalContainerEntityManagerFactoryBeanbarEntityManagerFactory(EntityManagerFactoryBuilder builder,@Qualifier("barDataSource") DataSource dataSource) {returnbuilder.dataSource(dataSource).packages("com.foobar.bar.domain").persistenceUnit("bar").build();}@Bean(name = "barTransactionManager")public PlatformTransactionManager barTransactionManager(@Qualifier("barEntityManagerFactory") EntityManagerFactorybarEntityManagerFactory) {return new JpaTransactionManager(barEntityManagerFactory);}}
CASE 2H3
第二种情况,则通过框架提供的AbstractRoutingDataSource来解决。通常的方案是根据自己的业务需求把要访问的数据库的标识存在TheadLocal中,然后在determineCurrentLookupKey去获取这个值。
bash
public class ContextRoutingDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DatabaseContextHolder.getContext();}}public class DatabaseContextHolder {private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();public static void setContext(String contextName) {Assert.notNull(contextName, "Context cannot be null");contextHolder.set(contextName);}public static String getContext() {return contextHolder.get();}public static void clearContext() {contextHolder.remove();}}
事务的处理H2
多个数据源之间的访问,有些情况需要保证在一个事务中进行,这就涉及到分布式事务。Atomikos 是一个维护的较好的内嵌的事务管理器,能够直接集成到Spring应用中。
下面是在 Spring Boot 中配置Datasource的示例,主要在于需要用AtomikosDataSourceBean来包装各个数据库的XA驱动
bash
private DataSource createXaDataSource(DataSourceProperties dataSourceProperties) throws Exception {String className = dataSourceProperties.getXa().getDataSourceClassName();if (!StringUtils.hasLength(className)) {className = DatabaseDriver.fromJdbcUrl(dataSourceProperties.determineUrl()).getXaDataSourceClassName();}Assert.state(StringUtils.hasLength(className),"No XA DataSource class name specified");XADataSource dataSource = createXaDataSourceInstance(className);bindXaProperties(dataSource, dataSourceProperties);return wrapDataSource(dataSourceProperties, dataSource);}private XADataSource createXaDataSourceInstance(String className) {try {Class<?> dataSourceClass = ClassUtils.forName(className, this.classLoader);Object instance = BeanUtils.instantiate(dataSourceClass);Assert.isInstanceOf(XADataSource.class, instance);return (XADataSource) instance;} catch (Exception ex) {throw new IllegalStateException("Unable to create XADataSource instance from '" + className + "'");}}private void bindXaProperties(XADataSource target, DataSourceProperties dataSourceProperties) {MutablePropertyValues values = new MutablePropertyValues();values.add("user", dataSourceProperties.determineUsername());values.add("password", dataSourceProperties.determinePassword());values.add("url", dataSourceProperties.determineUrl());values.addPropertyValues(dataSourceProperties.getXa().getProperties());new RelaxedDataBinder(target).withAlias("user", "username").bind(values);}private DataSource wrapDataSource(DataSourceProperties dataSourceProperties, XADataSource dataSource) throws Exception {DataSource wrappedDataSource = this.wrapper.wrapDataSource(dataSource);if (wrappedDataSource instanceof AtomikosDataSourceBean) {((AtomikosDataSourceBean) wrappedDataSource).setUniqueResourceName(dataSourceProperties.getName());((AtomikosDataSourceBean) wrappedDataSource).setMaxPoolSize(env.getProperty("spring.jta.atomikos.datasource.max-pool-size", Integer.class));((AtomikosDataSourceBean) wrappedDataSource).setMinPoolSize(env.getProperty("spring.jta.atomikos.datasource.min-pool-size", Integer.class));((AtomikosDataSourceBean) wrappedDataSource).setTestQuery(env.getProperty("spring.jta.atomikos.datasource.test-query"));}return wrappedDataSource;}
Spring框架中一个事务中用到的EntityManger、Connection等都会绑定到TheadLocal中进行复用,这就导致一个问题。为了让两个数据库的操作在一些事务中进行,我们需要用@Transactional注解标示事务,但是在一个事务中由于绑定了ThreadLocal,切换数据源的设置又会无效。
解决的办法是手动清除掉绑定到ThreadLocal的一些对象,实际只要清除EntityManagerFactory即可
bash
TransactionSynchronizationManager.unbindResource(emf);
事务结束时会自动unbind 所有注册的TransactionSynchronization中引用的 EntityManagerFactory。TransactionSynchronization对象是获取EntityManagerFactory后注册的。
由于我们手动清除了一次EntityManagerFactory,再次生成EntityManagerFactory对象时会再registerSynchronization一次,导致synchronizations中两个TransactionSynchronization中引用了同一个TransactionSynchronization,事务结束时会对这个EntityManagerFactory对象unbind两次,导致错误。
所以需要在unbindResource之后把之前的TransactionSynchronization对象清除掉
bash
TransactionSynchronizationManager.clearSynchronization();TransactionSynchronizationManager.initSynchronization();
评论
新的评论
上一篇
Spring Security ACL
Spring Security ACL 默认提供了 AclEntryAfterInvocationProvider ,使用 Acl.isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode…
下一篇
前端环境变量
Node 系统环境变量 Windows set NODE_ENV=production Linux NODE_ENV=production 可以在node环境中通过 process.env 获取,为了平台兼容性,可以使用 cross-env 来设置环境变量 cross-e…
