springboot jta-atomikos动态数据源

功能点:通过配置控制数据源注册个数,实现事先不知道数据源个数和别名,在不修改任何有关数据库相关代码条件下,仅仅在使用时按照规则添加配置来注册多数据源,并实现分布式事务.可以将其作为基础jar提供公共服务.

弊端:一,通过继承并重写SqlSessionTemplate,在切换数据源时通过切换SqlSessionFactory实现动态切换,但是在实例化SqlSessionFactory时,会将通过注解编写的sql装配到默认的SqlSessionFactory,导致其他数据源无法找到sql,从而无法执行,而通过xml配置的sql可以正常访问
       问题示例:假设有两个数据源分别是产品prod(默认数据源)和订单order,用@Insert这类注解sql写法,会将资源解析到默认的SqlSessionFactory中,从而导致其他数据源order无法使用order下的sql
   
   二,同一个方法中调用不同数据源,由于Aop用法限制,只能从类外部调用,而无法直接在类中另写方法加注解,来实现数据源切换

核心代码:重写SqlSessionTemplate


import static java.lang.reflect.Proxy.newProxyInstance;
import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable;
import static org.mybatis.spring.SqlSessionUtils.closeSqlSession;
import static org.mybatis.spring.SqlSessionUtils.getSqlSession;
import static org.mybatis.spring.SqlSessionUtils.isSqlSessionTransactional;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.exceptions.PersistenceException;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.MyBatisExceptionTranslator;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.util.Assert;

/**
 * 功能说明: <br>
 * 系统版本: v1.0<br>
 * 开发人员: @author liansh<br>
 * 开发时间: 2019年7月2日<br>
 */
public class CustomSqlSessionTemplate extends SqlSessionTemplate {

	private final SqlSessionFactory sqlSessionFactory;
	private final ExecutorType executorType;
	private final SqlSession sqlSessionProxy;
	private final PersistenceExceptionTranslator exceptionTranslator;
	private Map<Object, SqlSessionFactory> targetSqlSessionFactorys;
	private SqlSessionFactory defaultTargetSqlSessionFactory;

	public void setTargetSqlSessionFactorys(Map<Object, SqlSessionFactory> targetSqlSessionFactorys) {
		this.targetSqlSessionFactorys = targetSqlSessionFactorys;
	}

	public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {
		this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;
	}

	public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
		this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
	}

	public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
		this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
	}

	public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
		super(sqlSessionFactory, executorType, exceptionTranslator);
		this.sqlSessionFactory = sqlSessionFactory;
		this.executorType = executorType;
		this.exceptionTranslator = exceptionTranslator;
		this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor());
		this.defaultTargetSqlSessionFactory = sqlSessionFactory;
	}

	@Override
	public SqlSessionFactory getSqlSessionFactory() {
		SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(DynamicDataSource.getDataSource());
		if (targetSqlSessionFactory != null) {
			return targetSqlSessionFactory;
		} else if (defaultTargetSqlSessionFactory != null) {
			return defaultTargetSqlSessionFactory;
		} else {
			Assert.notNull(targetSqlSessionFactorys, "Property 'targetSqlSessionFactorys' or 'defaultTargetSqlSessionFactory' are required");
			Assert.notNull(defaultTargetSqlSessionFactory, "Property 'defaultTargetSqlSessionFactory' or 'targetSqlSessionFactorys' are required");
		}
		return this.sqlSessionFactory;
	}

	@Override
	public Configuration getConfiguration() {
		return this.getSqlSessionFactory().getConfiguration();
	}

	public ExecutorType getExecutorType() {
		return this.executorType;
	}

	public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
		return this.exceptionTranslator;
	}

	/**
	 * {@inheritDoc}
	 */
	public <T> T selectOne(String statement) {
		return this.sqlSessionProxy.<T> selectOne(statement);
	}

	/**
	 * {@inheritDoc}
	 */
	public <T> T selectOne(String statement, Object parameter) {
		return this.sqlSessionProxy.<T> selectOne(statement, parameter);
	}

	/**
	 * {@inheritDoc}
	 */
	public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
		return this.sqlSessionProxy.<K, V> selectMap(statement, mapKey);
	}

	/**
	 * {@inheritDoc}
	 */
	public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
		return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey);
	}

	/**
	 * {@inheritDoc}
	 */
	public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
		return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey, rowBounds);
	}

	/**
	 * {@inheritDoc}
	 */
	public <E> List<E> selectList(String statement) {
		return this.sqlSessionProxy.<E> selectList(statement);
	}

	/**
	 * {@inheritDoc}
	 */
	public <E> List<E> selectList(String statement, Object parameter) {
		return this.sqlSessionProxy.<E> selectList(statement, parameter);
	}

	/**
	 * {@inheritDoc}
	 */
	public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
		return this.sqlSessionProxy.<E> selectList(statement, parameter, rowBounds);
	}

	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("rawtypes")
	public void select(String statement, ResultHandler handler) {
		this.sqlSessionProxy.select(statement, handler);
	}

	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("rawtypes")
	public void select(String statement, Object parameter, ResultHandler handler) {
		this.sqlSessionProxy.select(statement, parameter, handler);
	}

	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("rawtypes")
	public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
		this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
	}

	/**
	 * {@inheritDoc}
	 */
	public int insert(String statement) {
		return this.sqlSessionProxy.insert(statement);
	}

	/**
	 * {@inheritDoc}
	 */
	public int insert(String statement, Object parameter) {
		return this.sqlSessionProxy.insert(statement, parameter);
	}

	/**
	 * {@inheritDoc}
	 */
	public int update(String statement) {
		return this.sqlSessionProxy.update(statement);
	}

	/**
	 * {@inheritDoc}
	 */
	public int update(String statement, Object parameter) {
		return this.sqlSessionProxy.update(statement, parameter);
	}

	/**
	 * {@inheritDoc}
	 */
	public int delete(String statement) {
		return this.sqlSessionProxy.delete(statement);
	}

	/**
	 * {@inheritDoc}
	 */
	public int delete(String statement, Object parameter) {
		return this.sqlSessionProxy.delete(statement, parameter);
	}

	/**
	 * {@inheritDoc}
	 */
	public <T> T getMapper(Class<T> type) {
		return getConfiguration().getMapper(type, this);
	}

	/**
	 * {@inheritDoc}
	 */
	public void commit() {
		throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
	}

	/**
	 * {@inheritDoc}
	 */
	public void commit(boolean force) {
		throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
	}

	/**
	 * {@inheritDoc}
	 */
	public void rollback() {
		throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
	}

	/**
	 * {@inheritDoc}
	 */
	public void rollback(boolean force) {
		throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
	}

	/**
	 * {@inheritDoc}
	 */
	public void close() {
		throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
	}

	/**
	 * {@inheritDoc}
	 */
	public void clearCache() {
		this.sqlSessionProxy.clearCache();
	}

	/**
	 * {@inheritDoc}
	 */
	public Connection getConnection() {
		return this.sqlSessionProxy.getConnection();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @since 1.0.2
	 */
	public List<BatchResult> flushStatements() {
		return this.sqlSessionProxy.flushStatements();
	}

	/**
	 * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also unwraps exceptions thrown by
	 * {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to the {@code PersistenceExceptionTranslator}.
	 */
	private class SqlSessionInterceptor implements InvocationHandler {
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			final SqlSession sqlSession = getSqlSession(CustomSqlSessionTemplate.this.getSqlSessionFactory(), CustomSqlSessionTemplate.this.executorType,
					CustomSqlSessionTemplate.this.exceptionTranslator);
			try {
				Object result = method.invoke(sqlSession, args);
				if (!isSqlSessionTransactional(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory())) {
					// force commit even on non-dirty sessions because some
					// databases require
					// a commit/rollback before calling close()
					sqlSession.commit(true);
				}
				return result;
			} catch (Throwable t) {
				Throwable unwrapped = unwrapThrowable(t);
				if (CustomSqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
					Throwable translated = CustomSqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
					if (translated != null) {
						unwrapped = translated;
					}
				}
				throw unwrapped;
			} finally {
				closeSqlSession(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory());
			}
		}
	}

}

动态数据源切换类:


import lombok.extern.slf4j.Slf4j;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 功能说明: 通过重写determineCurrentLookupKey得到切换之后的数据源名称<br>
 * 系统版本: v1.0<br>
 * 开发人员: @author liansh<br>
 * 开发时间: 2019年6月25日<br>
 */
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
	private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

	@Override
	protected Object determineCurrentLookupKey() {
		log.debug("数据源为:{}", DynamicDataSource.getDataSource());
		return DynamicDataSource.getDataSource();
	}

	public static void setDataSource(String dataSource) {
		contextHolder.set(dataSource);
	}

	public static String getDataSource() {
		return contextHolder.get();
	}

	public static void clearDataSource() {
		contextHolder.remove();
	}
}

动态数据源注册

package com.lsh.jdbc.jta.dataSource;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.lsh.jdbc.jta.aspect.DS;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;

/**
 * 功能说明: <br>
 * 系统版本: v1.0<br>
 * 开发人员: @author liansh<br>
 * 开发时间: 2019年7月2日<br>
 */
@Configuration
public class JtaDataSourceConfig {
	@Value("${jdbc.mapper-locations:classpath*:**/*Mapper.xml}")
	private String mappingPath;
	/** 需要注册的数据库别名列表 */
	@Value("${jdbc.target-sources:default,order}")
	private String targetSources = "";
	@Autowired
	private Environment env;

	@Bean
	public DynamicDataSource initDef() throws SQLException {
		DynamicDataSource source = new DynamicDataSource();
		Map<Object, Object> target = getTargetSource();
		source.setDefaultTargetDataSource(target.get(DS.defaultStr));
		source.setTargetDataSources(target);
		return source;
	}

	@Bean
	public CustomSqlSessionTemplate initSqlSessionTemplate() throws Exception {
		Map<Object, SqlSessionFactory> targetSqlSessionFactory = getSqlSessionFactory();
		CustomSqlSessionTemplate sql = new CustomSqlSessionTemplate(targetSqlSessionFactory.get(DS.defaultStr));
		sql.setTargetSqlSessionFactorys(targetSqlSessionFactory);
		sql.setDefaultTargetSqlSessionFactory(targetSqlSessionFactory.get(DS.defaultStr));
		return sql;
	}

	private AtomikosDataSourceBean setAtomikosProperty() throws SQLException {
		// 遍历数据源名称,读取相关配置,配置格式为spring.datasource.xxx.username
		AtomikosDataSourceBean atomBean = new AtomikosDataSourceBean();
		atomBean.setPoolSize(10);
		atomBean.setMinPoolSize(10);
		atomBean.setMaxPoolSize(30);
		atomBean.setBorrowConnectionTimeout(60);
		atomBean.setReapTimeout(60);
		atomBean.setMaxIdleTime(60);// 最大空闲时间
		atomBean.setMaintenanceInterval(60);
		atomBean.setLoginTimeout(60);
		atomBean.setTestQuery("select 1");
		return atomBean;
	}

	private Map<Object, Object> getTargetSource() throws SQLException {
		Map<Object, Object> target = new HashMap<Object, Object>();
		for (String key : targetSources.split(",")) {
			AtomikosDataSourceBean atomBean = setAtomikosProperty();
			String pkg = DS.defaultStr.equals(key) ? "" : (key + ".");
			MysqlXADataSource xa = new MysqlXADataSource();
			xa.setUrl(env.getProperty("spring.datasource." + pkg + "url"));
			xa.setUser(env.getProperty("spring.datasource." + pkg + "username"));
			xa.setPassword(env.getProperty("spring.datasource." + pkg + "password"));
			xa.setPinGlobalTxToPhysicalConnection(true);
			atomBean.setXaDataSource(xa);
			atomBean.setUniqueResourceName(key);
			target.put(key, atomBean);
		}
		return target;
	}

	private Map<Object, SqlSessionFactory> getSqlSessionFactory() throws Exception {
		Map<Object, Object> targetSource = getTargetSource();
		Map<Object, SqlSessionFactory> target = new HashMap<Object, SqlSessionFactory>();
		for (String key : targetSources.split(",")) {
			SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
			bean.setDataSource((DataSource) targetSource.get(key));
			if (StringUtils.isNotBlank(mappingPath)) {
				Resource[] loca = new PathMatchingResourcePatternResolver().getResources(mappingPath.replace("classpath*:**", "classpath:" + key));
				bean.setMapperLocations(loca);
			}
			target.put(key, bean.getObject());
		}
		return target;
	}
}

源码地址

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值