最近项目要求从远程数据库获取用户的登录信息,跟本地数据库是两个不同的mysql数据库,中间遇到几次有点意思的问题,现在记录下来留作翻阅。
1.多数据源切换一直失败,切换数据源重写的determineTargetDataSource方法一直不执行,重写了SqlSessionTemplate类的内部类getSqlSessionFactory方法才生效
2.一个类的方法里面的内部方法进行数据源切换不生效 ,因为内部方法是当前类this对象进行调用,必须通过spring的方式进行调用,可以通过spring的getbean方法获取当前对象调用方法的内部方法。
3.aop切换数据源一定要在spring获取事物之前进行操作,否则在spring内部是无法进行切换的
1.多数据源切换一直失败,切换数据源重写的determineTargetDataSource方法一直不执行,重写了SqlSessionTemplate类的内部类getSqlSessionFactory方法才生效
2.一个类的方法里面的内部方法进行数据源切换不生效 ,因为内部方法是当前类this对象进行调用,必须通过spring的方式进行调用,可以通过spring的getbean方法获取当前对象调用方法的内部方法。
3.aop切换数据源一定要在spring获取事物之前进行操作,否则在spring内部是无法进行切换的
源码如下
1.xml
<!-- 数据源 -->
<!--see https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_DruidDataSource%E5%8F%82%E8%80%83%E9%85%8D%E7%BD%AE-->
<bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${connection.url}"/>
<property name="username" value="${connection.username}"/>
<property name="password" value="${connection.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${druid.initialSize}"/>
<property name="minIdle" value="${druid.minIdle}"/>
<property name="maxActive" value="${druid.maxActive}"/>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="${druid.maxWait}"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />
<property name="validationQuery" value="${druid.validationQuery}" />
<property name="testWhileIdle" value="${druid.testWhileIdle}" />
<property name="testOnBorrow" value="${druid.testOnBorrow}" />
<property name="testOnReturn" value="${druid.testOnReturn}" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 如果用Oracle,则把poolPreparedStatements配置为true,mysql可以配置为false。-->
<property name="poolPreparedStatements" value="${druid.poolPreparedStatements}" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="${druid.maxPoolPreparedStatementPerConnectionSize}" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="${druid.filters}" />
</bean>
<!-- 配置目标数据源 -->
<bean id="targetDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${target.connection.url}"/>
<property name="username" value="${target.connection.username}"/>
<property name="password" value="${target.connection.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${target.druid.initialSize}"/>
<property name="minIdle" value="${target.druid.minIdle}"/>
<property name="maxActive" value="${target.druid.maxActive}"/>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="${target.druid.maxWait}"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${target.druid.timeBetweenEvictionRunsMillis}" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${target.druid.minEvictableIdleTimeMillis}" />
<property name="validationQuery" value="${target.druid.validationQuery}" />
<property name="testWhileIdle" value="${target.druid.testWhileIdle}" />
<property name="testOnBorrow" value="${target.druid.testOnBorrow}" />
<property name="testOnReturn" value="${target.druid.testOnReturn}" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 如果用Oracle,则把poolPreparedStatements配置为true,mysql可以配置为false。-->
<property name="poolPreparedStatements" value="${target.druid.poolPreparedStatements}" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="${target.druid.maxPoolPreparedStatementPerConnectionSize}" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="${target.druid.filters}" />
</bean>
<!-- mybatis -->
<bean id="masterSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="masterDataSource" />
<!-- 全局配置文件位置 -->
<property name="configLocation" value="classpath:sqlMap/configuration.xml"></property>
<!-- mapper文件位置 -->
<property name="mapperLocations" value="classpath:sqlMap/*Mapper.xml"></property>
</bean>
<bean id="targetSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="targetDataSource" />
<!-- 全局配置文件位置 -->
<property name="configLocation" value="classpath:sqlMap/configuration.xml"></property>
<!-- mapper文件位置 -->
<property name="mapperLocations" value="classpath:sqlMap/*Mapper.xml"></property>
</bean>
<!-- 使用sqlSession的代理类,在代码中无需关闭sqlSession,由该代理类管理 -->
<bean id="sqlSession" class="com.linktone.fumubang.spring.CustomSqlSessionTemplate">
<constructor-arg ref="masterSqlSessionFactory" />
<property name="targetSqlSessionFactorys">
<map>
<entry value-ref="masterSqlSessionFactory" key="masterDataSource"/>
<entry value-ref="targetSqlSessionFactory" key="targetDataSource"/>
</map>
</property>
</bean>
<!-- 添加数据源切换aop -->
<bean id="dbSourceAspect" class="com.linktone.fumubang.spring.DbSourceAspect"/>
<aop:config>
<aop:aspect ref="dbSourceAspect" order="0">
<aop:pointcut id="dbScopePointcut" expression="execution(* com.linktone.fumubang.userstat.service..*.*(..))" />
<aop:before pointcut-ref="dbScopePointcut" method="before"/>
<aop:after pointcut-ref="dbScopePointcut" method="after"/>
</aop:aspect>
</aop:config>
<!--事务管理器配置-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="masterDataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config expose-proxy="true" proxy-target-class="true">
<!-- 只对业务逻辑层实施事务 -->
<aop:pointcut id="txPointcut" expression="execution(* com.linktone.fumubang..service..*+.*(..))"/>
<aop:advisor id="txAdvisor" advice-ref="txAdvice" pointcut-ref="txPointcut" order="2"/>
</aop:config>
2.java
public class DbContextHolder {
public static final String MASTER_DATA_SOURCE = "masterDataSource";
public static final String TARGET_DATA_SOURCE = "targetDataSource";
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();// 本地线程变量,保存当前线程对应的数据库信息
public static void setDbType(String dbType) {
contextHolder.set(dbType);
}
public static String getDbType() {
return (String) contextHolder.get();
}
public static void clearDbType() {
contextHolder.remove();
}
}
import java.lang.reflect.Method;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import com.linktone.fumubang.annotation.StatisticalDbSource;
public class DbSourceAspect{
private Logger logger = Logger.getLogger(DbSourceAspect.class);
/**
* 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
*
* @param point
* @throws Exception
*/
public void before(JoinPoint point) throws Exception {
Class<?> target = point.getTarget().getClass();
MethodSignature signature = (MethodSignature) point.getSignature();
// 默认使用目标类型的注解,如果没有则使用其实现接口的注解
for (Class<?> clazz : target.getInterfaces()) {
resolveDataSource(clazz, signature.getMethod());
}
resolveDataSource(target, signature.getMethod());
}
/**
* 提取目标对象方法注解和类型注解中的数据源标识
*
* @param clazz
* @param method
*/
private void resolveDataSource(Class<?> clazz, Method method) {
try {
Class<?>[] types = method.getParameterTypes();
// 默认使用类型注解
if (clazz.isAnnotationPresent(StatisticalDbSource.class)) {
DbContextHolder.setDbType(DbContextHolder.TARGET_DATA_SOURCE);
}
// 方法注解可以覆盖类型注解
Method m = clazz.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(StatisticalDbSource.class)) {
DbContextHolder.setDbType(DbContextHolder.TARGET_DATA_SOURCE);
}
} catch (Exception e) {
logger.error("数据源aop切换:"+e.getMessage());
}
}
public void after(JoinPoint point) throws Exception {
DbContextHolder.setDbType(DbContextHolder.MASTER_DATA_SOURCE);
}
}
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType();
}
}
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;
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() {
if(DbContextHolder.getDbType() == null){
DbContextHolder.setDbType(DbContextHolder.MASTER_DATA_SOURCE);
}
SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(DbContextHolder.getDbType());
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}
*/
public void select(String statement, ResultHandler handler) {
this.sqlSessionProxy.select(statement, handler);
}
/**
* {@inheritDoc}
*/
public void select(String statement, Object parameter, ResultHandler handler) {
this.sqlSessionProxy.select(statement, parameter, handler);
}
/**
* {@inheritDoc}
*/
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 java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface StatisticalDbSource {
String value() default "";
}
@StatisticalDbSource
public List<AppUserRetention> queryAllRegAppUser(String day){
return retentionDao.queryAllRegAppUser(day);
}
调用方法的内部方法一定要用getbean的方式获取当前spring对象,否则aop失效
SpringUtils.getBean(AppUserRetentionService.class).queryAllRegAppUser(null)