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