项目背景:项目开发中数据库使用了读写分离,所有查询语句走从库,除此之外走主库。
最简单的办法其实就是建两个包,把之前数据源那一套配置copy一份,指向另外的包,但是这样扩展很有限,所有采用下面的办法。
参考了两篇文章如下:
这两篇文章都对原理进行了分析,下面只写自己的实现过程其他不再叙述。
实现思路是:
第一步,实现动态切换数据源:配置两个DataSource,配置两个SqlSessionFactory指向两个不同的DataSource,两个SqlSessionFactory都用一个SqlSessionTemplate,同时重写Mybatis提供的SqlSessionTemplate类,最后配置Mybatis自动扫描。
第二步,利用aop切面,拦截dao层所有方法,因为dao层方法命名的特点,比如所有查询sql都是select开头,或者get开头等等,拦截这些方法,并把当前数据源切换至从库。
spring中配置如下:
主库数据源配置:
从库数据源配置:
主库SqlSessionFactory配置:
从库SqlSessionFactory配置:
两个SqlSessionFactory使用同一个SqlSessionTemplate配置:
配置Mybatis自动扫描dao
自己重写了SqlSessionTemplate代码如下:
1 package com.sincetimes.slg.framework.core; 2 3 import static java.lang.reflect.Proxy.newProxyInstance; 4 import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable; 5 import static org.mybatis.spring.SqlSessionUtils.closeSqlSession; 6 import static org.mybatis.spring.SqlSessionUtils.getSqlSession; 7 import static org.mybatis.spring.SqlSessionUtils.isSqlSessionTransactional; 8 9 import java.lang.reflect.InvocationHandler; 10 import java.lang.reflect.Method; 11 import java.sql.Connection; 12 import java.util.List; 13 import java.util.Map; 14 15 import org.apache.ibatis.exceptions.PersistenceException; 16 import org.apache.ibatis.executor.BatchResult; 17 import org.apache.ibatis.session.Configuration; 18 import org.apache.ibatis.session.ExecutorType; 19 import org.apache.ibatis.session.ResultHandler; 20 import org.apache.ibatis.session.RowBounds; 21 import org.apache.ibatis.session.SqlSession; 22 import org.apache.ibatis.session.SqlSessionFactory; 23 import org.mybatis.spring.MyBatisExceptionTranslator; 24 import org.mybatis.spring.SqlSessionTemplate; 25 import org.springframework.dao.support.PersistenceExceptionTranslator; 26 import org.springframework.util.Assert; 27 28 import com.sincetimes.slg.framework.util.SqlSessionContentHolder; 29 30 31 /** 32 * 33 * TODO 重写SqlSessionTemplate 34 * @author ccg 35 * @version 1.0 36 * Created 2017年4月21日 下午3:15:15 37 */ 38 public class DynamicSqlSessionTemplate extends SqlSessionTemplate { 39 40 private final SqlSessionFactory sqlSessionFactory; 41 private final ExecutorType executorType; 42 private final SqlSession sqlSessionProxy; 43 private final PersistenceExceptionTranslator exceptionTranslator; 44 45 private Map
SqlSessionContentHolder类代码如下:
1 package com.sincetimes.slg.framework.util; 2 3 public abstract class SqlSessionContentHolder { 4 5 public final static String SESSION_FACTORY_MASTER = "master"; 6 public final static String SESSION_FACTORY_SLAVE = "slave"; 7 8 private static final ThreadLocal contextHolder = new ThreadLocal (); 9 10 public static void setContextType(String contextType) { 11 contextHolder.set(contextType); 12 } 13 14 public static String getContextType() { 15 return contextHolder.get(); 16 } 17 18 public static void clearContextType() { 19 contextHolder.remove(); 20 } 21 }
最后就是写切面去对dao所有方法进行处理了,代码很简单如下:
1 package com.sincetimes.slg.framework.core; 2 3 import org.aspectj.lang.JoinPoint; 4 import org.aspectj.lang.annotation.Aspect; 5 import org.aspectj.lang.annotation.Before; 6 import org.aspectj.lang.annotation.Pointcut; 7 8 import com.sincetimes.slg.framework.util.SqlSessionContentHolder; 9 10 @Aspect11 public class DynamicDataSourceAspect {12 13 @Pointcut("execution( * com.sincetimes.slg.dao.*.*(..))")14 public void pointCut(){15 16 }17 @Before("pointCut()")18 public void before(JoinPoint jp){19 String methodName = jp.getSignature().getName(); 20 //dao方法查询走从库21 if(methodName.startsWith("query") || methodName.startsWith("get") || methodName.startsWith("count") || methodName.startsWith("list")){22 SqlSessionContentHolder.setContextType(SqlSessionContentHolder.SESSION_FACTORY_SLAVE);23 }else{24 SqlSessionContentHolder.setContextType(SqlSessionContentHolder.SESSION_FACTORY_MASTER);25 }26 }27 28 }