失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > JAVA中使用代码创建多数据源 并实现动态切换(二)-集成分布式事务

JAVA中使用代码创建多数据源 并实现动态切换(二)-集成分布式事务

时间:2020-09-26 20:10:57

相关推荐

JAVA中使用代码创建多数据源 并实现动态切换(二)-集成分布式事务

独角兽企业重金招聘Python工程师标准>>>

8月16日11:45:39更新:这里贴出我自己写的一个demo,放在CSDN上了。/download/llzhaoyun/9912658

前面写了一篇关于使用Spring +druid + Mybatis配置多数据源,并在代码中动态选择使用不同的数据源的博文,当时写该文时,由于比较忙,只简单做了记录,后来又做了一些扩展,添加了分布式事务等,今天就另起一文,详细记录下目前生产线上正在使用的稳定版本。

业务场景:

有一个系统A作为数据中转服务器。若干个系统数据都需要经过系统A做处理、转发,而数据交互有多种方式,如WebService、REST API、中间表等,A也会操作自己的数据库。本文主要围绕操作中间表的方式来讲。

A需要操作的自己的数据库是确定的,但由于接入的系统可能随业务发展而增加或改变,所以不能将中间表的连接信息写死,需要使用配置文件维护,并在服务器启动的时候动态加载,然后由不同的业务去决定使用哪一个数据源。(该段文本描述的业务问题已在上篇文章/simpleton/blog/868608中解决)

由于系统A会读中间表,然后将读取的数据预处理,然后存入A系统的数据库或其他中间库,如果使用DataSourceTransactionManager作为事务管理器来控制事务,会出现多数据库事务不一致的问题,那么,解决这类问题有两种方案:编程式事务和分布式事务。如果使用编程式事务的话,需要在每个地方显示地管理实务(开启、提交、回滚),耦合较高。对于复杂的业务来说,不好处理,故不适合系统A的业务场景。后来看了分布式事务框架atomikos,觉得应该能解决我目前的问题,经测试,效果很赞,下面就让我们一起来看看它是什么集成到系统的。(原理的话,大家自己百度一下)

好了,说了这么多,下面来一步一步解决我们问题。

一、配置2套数据源(defaultDataSource:管理A系统自己数据库连接的数据源。atomikosDynamicDataSource:管理数据源动态初始化、选择、销毁的数据源)。

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="/schema/beans"xmlns:xsi="/2001/XMLSchema-instance" xmlns:tx="/schema/tx"xsi:schemaLocation="/schema/beans /schema/beans/spring-beans-4.2.xsd/schema/tx /schema/tx/spring-tx-4.2.xsd"><!-- 默认的dataSource --><bean id="defaultDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" destroy-method="close"><!-- uniqueResourceName很重要,不能重复 --><property name="uniqueResourceName" value="defaultDataSource"/><property name="xaDataSourceClassName" value="com.alibaba.druid.pool.xa.DruidXADataSource" /><property name="borrowConnectionTimeout" value="${pro.borrowConnectionTimeout}" /><property name="minPoolSize" value="${pro.minPoolSize}" /><property name="maxPoolSize" value="${pro.maxPoolSize}" /><property name="maxLifetime" value="0" /> <!-- xaProperties实质上是设置属性xaDataSourceClassName的对象的值,即com.alibaba.druid.pool.xa.DruidXADataSource的参数值 --><property name="xaProperties"><props><prop key="driverClassName">${pro.driver}</prop> <prop key="url">${pro.url}</prop> <prop key="username">${pro.username}</prop> <prop key="password">${pro.password}</prop> <prop key="initialSize">${pro.initialSize}</prop> <prop key="minIdle">${pro.minIdle}</prop> <prop key="maxActive">${pro.maxActive}</prop> <prop key="maxWait">${pro.maxWait}</prop> <prop key="timeBetweenEvictionRunsMillis">${pro.timeBetweenEvictionRunsMillis}</prop> <prop key="minEvictableIdleTimeMillis">${pro.minEvictableIdleTimeMillis}</prop> <prop key="validationQuery">SELECT 1</prop> <prop key="testWhileIdle">true</prop> <prop key="testOnBorrow">false</prop> <prop key="testOnReturn">false</prop> <prop key="poolPreparedStatements">true</prop> <prop key="maxPoolPreparedStatementPerConnectionSize">${pro.maxPoolPreparedStatementPerConnectionSize}</prop> <prop key="filters">stat</prop> </props> </property></bean><!-- 定义主业务使用的sqlSessionFactory --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="configLocation"><value>classpath:config/sqlMapConfig.xml</value></property><property name="dataSource" ref="defaultDataSource" /><property name="typeAliasesPackage" value="com.eya.model.domain" /><!-- 扫描defaultDataSource对应的数据源的SQL配置文件,这部分数据库操作就不需要动态切换数据源,直接使用默认的数据源操作数据库即可 --><property name="mapperLocations" value="classpath:com/eya/dao/**/*.xml" /></bean><!-- 扫描mybatis的接口所在的文件 --><bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.eya.dao,com.eya.pubmapper" /><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/></bean><!-- 定义中间表使用的数据源、sqlSessionFactory等 --><bean id="atomikosDynamicDataSource" class="com.eya.pubservice.datasource.AtomikosDynamicDataSource" ><!-- <property name="defaultTargetDataSource" ref="defaultDataSource"></property> --><property name="targetDataSources"><map><!-- 这里还可以加多个数据源,由于不确定数据源的地址信息,所以这里不配置,在代码中去动态初始化 --></map></property></bean><!-- 定义数据交互业务使用的sqlSessionFactory --><bean id="sqlSessionFactory4MiddleTable" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="atomikosDynamicDataSource" /><!-- 扫描中间表的SQL配置文件 --><property name="mapperLocations" value="classpath:com/eya/middletable/**/*.xml" /></bean><!-- 扫描mybatis的接口所在的文件,注意这里扫描的mybatis的xml文件和主业务的xml文件不同 --><bean id="mapperScannerConfigurer4MiddleTable" class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.eya.middletable" /><!-- 这里要使用sqlSessionFactoryBeanName属性,否则会报错 --><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory4MiddleTable"/></bean><!-- jta分布式事务 --><!-- Atomikos 事务管理器配置 --><bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"init-method="init" destroy-method="close"><!-- <property name="startupTransactionService" value="false" /> --><!-- close()时是否强制终止事务 --><property name="forceShutdown" value="false" /></bean><!-- Atomikos UserTransaction配置 --><bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp"></bean><!-- JTA事务管理器 --><bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"><property name="transactionManager"><ref bean="atomikosTransactionManager" /></property><property name="userTransaction"><ref bean="atomikosUserTransaction" /></property></bean><!-- 开启注解事务,使用@Transactional来标记需要事务控制的方法或类 --><tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" /></beans>

说明一下,这是spring的配置文件,主要配置mybatis的信息,其中涉及到了数据源、mybatis的SQL文件和接口类、声明事务管理。这里面有几个需要重点指出的地方

1.文件中的表达式,如${pro.url},是引入properties的内容。

2.DataSource的定义:和原来定义DataSource不同,原来定义时,是使用class为com.alibaba.druid.pool.xa.DruidXADataSource(也可以是c3p0)来定义bean,然后配置数据源的信息,现在是定义class为com.atomikos.jdbc.AtomikosDataSourceBean的bean,然后使用xaDataSourceClassName指明具体使用的数据源类,使用xaProperties来配置数据源的属性。

3.defaultDataSource:defaultDataSource为系统A自己的数据库信息,这个是确定的,所以直接在配置文件中定义好,然后根据defaultDataSource来配置sqlSessionFactory,并使用sqlSessionFactory来配置mapperScannerConfigurer,这部分是完成系统A自己的数据源配置以及myBatis配置(SQL文件扫描和接口类)。注意:定义org.mybatis.spring.mapper.MapperScannerConfigurer的bean时,需要使用sqlSessionFactoryBeanName属性来指定使用哪一个sqlSessionFactory,不然会抛出异常。

4.atomikosDynamicDataSource:这是一个自己实现的数据源类(代码我稍后贴出来并讲解代码中的内容),属性targetDataSources是一个map集合,用于存储不同的数据源信息。以上配置文件中,没有配置map的值,因为不能像defaultDataSource一样确定数据库连接地址等信息,所以在服务器启动的时候,用代码去初始化不同的数据源信息:根据配置的多个数据库信息,初始化对应个数的数据源,然后存放到targetDataSources中。targetDataSources被初始化后,在系统运行过程中,根据不同的业务,从targetDataSources中选择不同的数据源来操作数据库。

5.分布式事务配置:在配置文件中定义了atomikosTransactionManager、atomikosUserTransaction两个bean,然后在txManager中注入这2个bean,并在最后使用<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />开启注解事务。使用过程中,出现了事务提交超时的问题(com.atomikos.icatch.RollbackException: Prepare: NO vote),解决方案是添加一个atomikos配置文件,配置事务的相关属性,具体内容参考我的博文:/simpleton/blog/915683

说明:大部分情况下,按照上述配置,在配置atomikosDynamicDataSource的时候,同时配置targetDataSources的值,并在代码中动态确定使用哪套数据源(继承AbstractRoutingDataSource并实现determineCurrentLookupKey即可)就可以了,这也是网上目前大部分资料已经完成的内容。

二、创建AbstractDynamicDataSource、AtomikosDynamicDataSource,管理动态创建的数据源,然后创建一个标记使用的数据源的上下文对象:DBContextHolder,用来标记当前业务需要使用哪一个数据源。

package com.eya.pubservice.datasource;import java.util.Map;import javax.sql.DataSource;import mons.collections.MapUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/*** 管理动态数据源的数据源父类* @create ll* @createDate 3月27日 下午2:38:05* @update * @updateDate */public abstract class AbstractDynamicDataSource<T extends DataSource> extendsAbstractRoutingDataSourceimplementsApplicationContextAware {/** 日志 */protected Logger logger = LoggerFactory.getLogger(getClass());/** 默认的数据源KEY *///protected static final String DEFAULT_DATASOURCE_KEY = "defaultDataSource";/** 数据源KEY-VALUE键值对 */private Map<Object, Object> targetDataSources;/** spring容器上下文 */private static ApplicationContext ctx;public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {ctx = applicationContext;}public static ApplicationContext getApplicationContext() {return ctx;}public static Object getBean(String name) {return ctx.getBean(name);}/*** @param targetDataSources the targetDataSources to set*/public void setTargetDataSources(Map<Object, Object> targetDataSources) {this.targetDataSources = targetDataSources;super.setTargetDataSources(targetDataSources);// afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的super.afterPropertiesSet();}/*** @return the targetDataSources*/public Map<Object, Object> getTargetDataSources() {return this.targetDataSources;}/*** 创建数据源* @param driverClassName 数据库驱动名称* @param url 连接地址* @param username 用户名* @param password 密码* @return 数据源{@link T}* @Author : ll. create at 3月27日 下午2:44:34*/public abstract T createDataSource(String driverClassName, String url, String username,String password);/*** 设置系统当前使用的数据源* <p>数据源为空或者为0时,自动切换至默认数据源,即在配置文件中定义的默认数据源* @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey()*/@Overrideprotected Object determineCurrentLookupKey() {logger.debug("【设置系统当前使用的数据源】");Map<String, Object> configMap = DBContextHolder.getDBType();logger.debug("【当前数据源配置为:{}】", configMap);if (MapUtils.isEmpty(configMap)) {// 使用默认数据源//return DEFAULT_DATASOURCE_KEY;throw new IllegalArgumentException("没有指定数据源");}// 判断数据源是否需要初始化this.verifyAndInitDataSource();logger.debug("【切换至数据源:{}】", configMap);return configMap.get(DBContextHolder.DATASOURCE_KEY);}/*** 判断数据源是否需要初始化* @Author : ll. create at 3月27日 下午3:57:43*/private void verifyAndInitDataSource() {Map<String, Object> configMap = DBContextHolder.getDBType();Object obj = this.targetDataSources.get(configMap.get(DBContextHolder.DATASOURCE_KEY));if (obj != null) {return;}logger.info("【初始化数据源】");T datasource = this.createDataSource(configMap.get(DBContextHolder.DATASOURCE_DRIVER).toString(), configMap.get(DBContextHolder.DATASOURCE_URL).toString(),configMap.get(DBContextHolder.DATASOURCE_USERNAME).toString(),configMap.get(DBContextHolder.DATASOURCE_PASSWORD).toString());this.addTargetDataSource(configMap.get(DBContextHolder.DATASOURCE_KEY).toString(),datasource);}/*** 往数据源key-value键值对集合添加新的数据源* @param key 新的数据源键* @param dataSource 新的数据源* @Author : ll. create at 3月27日 下午2:56:49*/public void addTargetDataSource(String key, T dataSource) {this.targetDataSources.put(key, dataSource);super.setTargetDataSources(this.targetDataSources);// afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的super.afterPropertiesSet();}}

package com.eya.pubservice.datasource;import java.sql.SQLException;import java.util.List;import mons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import com.alibaba.druid.filter.Filter;import com.alibaba.druid.pool.DruidDataSource;import com.alibaba.druid.pool.xa.DruidXADataSource;import com.atomikos.jdbc.AtomikosDataSourceBean;import com.eya.pubservice.bean.properties.DBProperties;import com.eya.util.ProIdUtil;/*** * @create ll* @createDate 3月31日 下午2:14:27* @update * @updateDate */public class AtomikosDynamicDataSource extends AbstractDynamicDataSource<AtomikosDataSourceBean> {/** db.properties配置信息的对应文件 */@Autowiredprivate DBProperties dBProperties;private boolean testWhileIdle = true;private boolean testOnBorrow = false;private boolean testOnReturn = false;// 是否打开连接泄露自动检测private boolean removeAbandoned = false;// 连接长时间没有使用,被认为发生泄露时长private long removeAbandonedTimeoutMillis = 300 * 1000;// 发生泄露时是否需要输出 log,建议在开启连接泄露检测时开启,方便排错private boolean logAbandoned = false;// 只要maxPoolPreparedStatementPerConnectionSize>0,poolPreparedStatements就会被自动设定为true,使用oracle时可以设定此值。// private int maxPoolPreparedStatementPerConnectionSize = -1;// 配置监控统计拦截的filtersprivate String filters; // 监控统计:"stat" 防SQL注入:"wall" 组合使用: "stat,wall"private List<Filter> filterList;/** 创建数据源,这里创建的数据源是带连接池信息的* @see com.eya.pubservice.datasource.IDynamicDataSource#createDataSource(java.lang.String, java.lang.String, java.lang.String, java.lang.String)*/@Overridepublic AtomikosDataSourceBean createDataSource(String driverClassName, String url,String username, String password) {DruidXADataSource ds = new DruidXADataSource();ds.setName(ProIdUtil.get16TimeRandom());ds.setUrl(url);ds.setUsername(username);ds.setPassword(password);ds.setDriverClassName(driverClassName);ds.setInitialSize(dBProperties.getInitialSize());ds.setMinIdle(dBProperties.getMinIdle());ds.setMaxActive(dBProperties.getMaxActive());ds.setMaxWait(dBProperties.getMaxWait());// ds.setTimeBetweenConnectErrorMillis(dBProperties.getTimeBetweenConnectErrorMillis());ds.setTimeBetweenEvictionRunsMillis(dBProperties.getTimeBetweenEvictionRunsMillis());ds.setMinEvictableIdleTimeMillis(dBProperties.getMinEvictableIdleTimeMillis());// ds.setValidationQuery(dBProperties.getValidationQuery());ds.setTestWhileIdle(testWhileIdle);ds.setTestOnBorrow(testOnBorrow);ds.setTestOnReturn(testOnReturn);ds.setRemoveAbandoned(removeAbandoned);ds.setRemoveAbandonedTimeoutMillis(removeAbandonedTimeoutMillis);ds.setLogAbandoned(logAbandoned);// 只要maxPoolPreparedStatementPerConnectionSize>0,poolPreparedStatements就会被自动设定为true,参照druid的源码ds.setMaxPoolPreparedStatementPerConnectionSize(dBProperties.getMaxPoolPreparedStatementPerConnectionSize());if (StringUtils.isNotBlank(filters))try {ds.setFilters(filters);} catch (SQLException e) {ds.close();throw new RuntimeException(e);}AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();atomikosDataSourceBean.setXaDataSource(ds);// 设置数据源的唯一名称,不允许重复,按照自己的需要,生成一个不重复的值即可atomikosDataSourceBean.setUniqueResourceName(ProIdUtil.getTimeRandomId(32));atomikosDataSourceBean.setMaxPoolSize(dBProperties.getMaxPoolSize());atomikosDataSourceBean.setMinPoolSize(dBProperties.getMinPoolSize());atomikosDataSourceBean.setBorrowConnectionTimeout(dBProperties.getBorrowConnectionTimeout());// atomikosDataSourceBean.getXaProperties().setProperty("", arg1);// atomikosDataSourceBean.getXaProperties().setProperty("", arg1);// atomikosDataSourceBean.getXaProperties().setProperty("", arg1);// atomikosDataSourceBean.getXaProperties().setProperty("", arg1);atomikosDataSourceBean.setMaxLifetime(0);addFilterList(ds);return atomikosDataSourceBean;}private void addFilterList(DruidDataSource ds) {if (filterList != null) {List<Filter> targetList = ds.getProxyFilters();for (Filter add : filterList) {boolean found = false;for (Filter target : targetList) {if (add.getClass().equals(target.getClass())) {found = true;break;}}if (!found)targetList.add(add);}}}}

package com.eya.pubservice.datasource;import java.util.HashMap;import java.util.Map;/*** 当前正在使用的数据源信息的线程上线文* @create ll* @createDate 3月27日 下午2:37:07* @update * @updateDate */public class DBContextHolder {/** 数据源的KEY */public static final String DATASOURCE_KEY = "DATASOURCE_KEY";/** 数据源的URL */public static final String DATASOURCE_URL = "DATASOURCE_URL";/** 数据源的驱动 */public static final String DATASOURCE_DRIVER = "DATASOURCE_DRIVER";/** 数据源的用户名 */public static final String DATASOURCE_USERNAME = "DATASOURCE_USERNAME";/** 数据源的密码 */public static final String DATASOURCE_PASSWORD = "DATASOURCE_PASSWORD";private static final ThreadLocal<Map<String, Object>> contextHolder = new ThreadLocal<Map<String, Object>>();public static void setDBType(Map<String, Object> dataSourceConfigMap) {contextHolder.set(dataSourceConfigMap);}public static Map<String, Object> getDBType() {Map<String, Object> dataSourceConfigMap = contextHolder.get();if (dataSourceConfigMap == null) {dataSourceConfigMap = new HashMap<String, Object>();}return dataSourceConfigMap;}public static void clearDBType() {contextHolder.remove();}}

说明一下:

1.AbstractDynamicDataSource:该类继承AbstractRoutingDataSource,并重写determineCurrentLookupKey方法,用于确定使用哪个数据源。该类又实现了ApplicationContextAware,在Spring容器初始化的时候,会执行setApplicationContext方法,将Spring容器的上下文对象注入到属性ctx中,然后就可以使用ctx.getBean(name)来获取Spring容器中的对象。

2.AtomikosDynamicDataSource:基于atomikos的数据源类,其实我还有一个DruidDynamicDataSource类,这里没有给出,内容大部分一样,只是createDataSource的实现内容不一样,DruidDynamicDataSource只是简单地创建了一个基于Druid的数据源,而AtomikosDynamicDataSource是创建的一个可加入atomikos分布式事务的数据源。

3.DBProperties是一个基于Spring注入来获取properties内容添加的方式添加的一个和数据库配置文件(db.properties)内容相同的JAVA类(参考/simpleton/blog/489129),用于获取数据库配置文件(db.properties)的配置信息。我这里就不贴出该类的代码,大家可以自己想办法获取配置文件信息或直接写死都可以,毕竟这里不是本文的重点。

三、服务启动,根据配置文件,初始化数据源。MiddleTableService.java

package com.eya.pubservice.datasource;import java.util.HashMap;import java.util.List;import java.util.Map;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.ponent;import com.atomikos.jdbc.AtomikosDataSourceBean;import mon.exception.BusinessException;import com.eya.pubservice.InterfaceConfigService;import com.eya.pubservice.bean.interfaceConfig.InterfaceConfigMtDetail;import com.eya.util.ProCollection;import com.eya.util.ProString;/*** 中间表服务类* @create ll* @createDate 3月28日 上午9:36:45* @update * @updateDate */@Componentpublic class MiddleTableService {/** 日志 */private static final Logger LOGGER = LoggerFactory.getLogger(MiddleTableService.class);/** 接口配置服务类 */@Autowiredprivate InterfaceConfigService interfaceConfigService;/** Atomikos数据源 */@Autowiredprivate AtomikosDynamicDataSource atomikosDynamicDataSource;/*** 随服务器启动,根据机构中间表信息配置,初始化数据源 * @Author : ll. create at 3月28日 上午9:37:19*/public synchronized void init() {LOGGER.info("【根据机构中间表信息配置,初始化数据源 】");// 清空所有数据源atomikosDynamicDataSource.getTargetDataSources().clear();// 这段代码是获取所有的中间表数据库配置信息,读者不用关心,按照自己的方式获取配置即可List<InterfaceConfigMtDetail> mtDetails = interfaceConfigService.getMtDetails();if (ProCollection.isEmpty(mtDetails)) {LOGGER.info("【无中间表配置数据,终止中间表数据源初始化任务】");return;}for (InterfaceConfigMtDetail interfaceConfigMtDetail : mtDetails) {// 判断数据源是否已经被初始化if (atomikosDynamicDataSource.getTargetDataSources().containsKey(interfaceConfigMtDetail.getDataSourceKey())) {// 已经初始化continue;}// 创建数据源AtomikosDataSourceBean dataSource = atomikosDynamicDataSource.createDataSource(interfaceConfigMtDetail.getDriver(), interfaceConfigMtDetail.getUrl(),interfaceConfigMtDetail.getUsername(), interfaceConfigMtDetail.getPassword());// 添加到targetDataSource中,缓存起来atomikosDynamicDataSource.addTargetDataSource(interfaceConfigMtDetail.getDataSourceKey(), dataSource);}}/*** 数据源控制开关,用于指定数据源* @param interfaceCode 接口的唯一吗* @Author : ll. create at 3月28日 下午1:56:28*/public void dataSourceSwitch(String interfaceCode) {InterfaceConfigMtDetail mtDetail = interfaceConfigService.getMtDetailMap().get(interfaceCode);if (mtDetail == null) {throw new BusinessException("根据接口唯一码未获取到中间表配置信息");}if (ProString.isBlank(mtDetail.getDriver()) && ProString.isBlank(mtDetail.getUrl())&& ProString.isBlank(mtDetail.getUsername())) {throw new IllegalArgumentException(String.format("接口【%s】未配置中间表信息,无法切换数据源",interfaceCode));}Map<String, Object> dataSourceConfigMap = new HashMap<String, Object>();dataSourceConfigMap.put(DBContextHolder.DATASOURCE_KEY, mtDetail.getDataSourceKey());dataSourceConfigMap.put(DBContextHolder.DATASOURCE_DRIVER, mtDetail.getDriver());dataSourceConfigMap.put(DBContextHolder.DATASOURCE_URL, mtDetail.getUrl());dataSourceConfigMap.put(DBContextHolder.DATASOURCE_USERNAME, mtDetail.getUsername());dataSourceConfigMap.put(DBContextHolder.DATASOURCE_PASSWORD, mtDetail.getPassword());LOGGER.info("【指定数据源】dataSourceConfigMap = {}", dataSourceConfigMap);DBContextHolder.setDBType(dataSourceConfigMap);}}

说明:

1.该类的init方法在服务器启动的时候执行,根据中间表配置信息,初始化数据源。

2.dataSourceSwitch:数据源开关,根据接口唯一码(一个唯一码对应一类业务,同一业务和不同系统的交互方式可能不同,如使用中间库、webService等)判断是否使用中间库,如果是,则根据信息,指定到对应的数据源。

3.这里引发了一个思考:一个数据源中,为何又要有多个不同的数据源?比如文中的AtomikosDynamicDataSource,拥有一个targetDataSources属性(该属性在父类AbstractRoutingDataSource中),存储多个不同的数据源。虽然这样的设计解决了博主的问题,但是目前搞不明白这样设计的目的。

4.通常意义的数据源是指javax.sql.DataSource类,但是它的子类不仅仅只有数据源信息,还包含了连接池属性,如DruidXADataSource。初始化数据源的时候,指定了连接池的信息,所以后面切换了数据源之后,没有每次都创建连接等操作,直接从连接池取现有的空闲连接。

-3-26 补充:近日帮很多朋友答疑的时候发现,博文中缺少一个更为简单的示例,缺少对工作原理的说明,今天补充一下

先看下面一段示例代码,有2个方法,saveUser2DB1和saveUser2DB2,都是保存用户信息,但是存入的分别是testUserDB1和testUserDB2。

工作原理:切换数据源的关键,就是利用DBContextHolder上下文对象,把数据源的信息放进去,然后在执行SQL之前,会调用AtomikosDynamicDataSource的determineCurrentLookupKey方法,该方法返回的其实就是targetDataSources的key值。在determineCurrentLookupKey方法中,调用了verifyAndInitDataSource方法,判断对应的数据源是否已经初始化,没有,则先初始化,再把初始化得到的DataSource子类(本文中就是AtomikosDynamicDataSource)对象,放入到targetDataSources中,并返回对应的key,数据源切换就完成了,后面执行SQL的时候,就是往对应的数据源中添加了。

@Autowiredprivate UserDao userDao;public void saveUser2DB1(User user){// 切换数据源。使用上下文对象标记当前需要使用的数据源Map<String, Object> dataSourceConfigMap = new HashMap<String, Object>();dataSourceConfigMap.put(DBContextHolder.DATASOURCE_KEY, "testUserDB1");dataSourceConfigMap.put(DBContextHolder.DATASOURCE_DRIVER, "com.mysql.jdbc.Driver");dataSourceConfigMap.put(DBContextHolder.DATASOURCE_URL, "jdbc:mysql://localhost:3306/testUserDB1");dataSourceConfigMap.put(DBContextHolder.DATASOURCE_USERNAME, "root");dataSourceConfigMap.put(DBContextHolder.DATASOURCE_PASSWORD, "root");LOGGER.info("【指定数据源】dataSourceConfigMap = {}", dataSourceConfigMap);DBContextHolder.setDBType(dataSourceConfigMap);userDao.save(user);}public void saveUser2DB2(User user){// 切换数据源。使用上下文对象标记当前需要使用的数据源Map<String, Object> dataSourceConfigMap = new HashMap<String, Object>();dataSourceConfigMap.put(DBContextHolder.DATASOURCE_KEY, "testUserDB2");dataSourceConfigMap.put(DBContextHolder.DATASOURCE_DRIVER, "com.mysql.jdbc.Driver");dataSourceConfigMap.put(DBContextHolder.DATASOURCE_URL, "jdbc:mysql://localhost:3306/testUserDB2");dataSourceConfigMap.put(DBContextHolder.DATASOURCE_USERNAME, "root");dataSourceConfigMap.put(DBContextHolder.DATASOURCE_PASSWORD, "root");LOGGER.info("【指定数据源】dataSourceConfigMap = {}", dataSourceConfigMap);DBContextHolder.setDBType(dataSourceConfigMap);userDao.save(user);}

以上内容,完全由博主自己集合网上资料摸索出来的一套业务解决方案,如有缺陷,敬请大家指出交流。

如果有问题,大家可以留言讨论,或加我QQ/发邮件交流,联系方式在我个人资料中。由于平时工作较忙,如未及时回复大家,敬请见谅

如果觉得《JAVA中使用代码创建多数据源 并实现动态切换(二)-集成分布式事务》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。