欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring + Mybatis 项目实现动态切换数据源实例详解

程序员文章站 2024-02-27 18:56:21
项目背景:项目开发中数据库使用了读写分离,所有查询语句走从库,除此之外走主库。 最简单的办法其实就是建两个包,把之前数据源那一套配置copy一份,指向另外的包,但是这样扩...

项目背景:项目开发中数据库使用了读写分离,所有查询语句走从库,除此之外走主库。

最简单的办法其实就是建两个包,把之前数据源那一套配置copy一份,指向另外的包,但是这样扩展很有限,所有采用下面的办法。

参考了两篇文章如下:

这两篇文章都对原理进行了分析,下面只写自己的实现过程其他不再叙述。

实现思路是:

第一步,实现动态切换数据源:配置两个datasource,配置两个sqlsessionfactory指向两个不同的datasource,两个sqlsessionfactory都用一个sqlsessiontemplate,同时重写mybatis提供的sqlsessiontemplate类,最后配置mybatis自动扫描。

第二步,利用aop切面,拦截dao层所有方法,因为dao层方法命名的特点,比如所有查询sql都是select开头,或者get开头等等,拦截这些方法,并把当前数据源切换至从库。

spring中配置如下:

主库数据源配置:

 <bean id="masterdatasource" class="com.mchange.v2.c3p0.combopooleddatasource" destroy-method="close">2 <property name="driverclass" value="${master_mysql_jdbc_driver}" />
 <property name="jdbcurl" value="${master_mysql_jdbc_url}" />
 <property name="user" value="${master_mysql_jdbc_user}" />
 <property name="password" value="${master_mysql_jdbc_password}" />
 </bean>

从库数据源配置:

 <bean id="masterdatasource" class="com.mchange.v2.c3p0.combopooleddatasource" destroy-method="close">
 <property name="driverclass" value="${slave_mysql_jdbc_driver}" />
 <property name="jdbcurl" value="${slave_mysql_jdbc_url}" />
 <property name="user" value="${slave_mysql_jdbc_user}" />
 <property name="password" value="${slave_mysql_jdbc_password}" />
 </bean>

主库sqlsessionfactory配置:

 <bean id="mastersqlsessionfactory" class="org.mybatis.spring.sqlsessionfactorybean">
 <property name="datasource" ref="masterdatasource" />
 <property name="mapperlocations" value="classpath:com/sincetimes/slg/dao/*.xml"/>
 </bean>

从库sqlsessionfactory配置:

 <bean id="slavesqlsessionfactory" class="org.mybatis.spring.sqlsessionfactorybean">
 <property name="datasource" ref="slavedatasource" />
 <property name="mapperlocations" value="classpath:com/sincetimes/slg/dao/*.xml"/>
 </bean>

两个sqlsessionfactory使用同一个sqlsessiontemplate配置:

 <bean id="masterandslavesqlsessiontemplate" class="com.sincetimes.slg.framework.core.dynamicsqlsessiontemplate">
 <constructor-arg index="0" ref="mastersqlsessionfactory" />
 <property name="targetsqlsessionfactorys">
 <map> 
 <entry value-ref="mastersqlsessionfactory" key="master"/> 
 <entry value-ref="slavesqlsessionfactory" key="slave"/> 
 </map> 
 </property>
 </bean>

配置mybatis自动扫描dao

 <bean class="org.mybatis.spring.mapper.mapperscannerconfigurer">
 <property name="basepackage" value="com.sincetimes.slg.dao" />
 <property name="sqlsessiontemplatebeanname" value="masterandslavesqlsessiontemplate" />
 </bean>

自己重写了sqlsessiontemplate代码如下:

package com.sincetimes.slg.framework.core;
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;
import com.sincetimes.slg.framework.util.sqlsessioncontentholder;
/**
 * 
 * todo 重写sqlsessiontemplate
 * @author ccg
 * @version 1.0
 * created 2017年4月21日 下午3:15:15
 */
public class dynamicsqlsessiontemplate 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 map<object, sqlsessionfactory> gettargetsqlsessionfactorys(){
 return targetsqlsessionfactorys;
 }
 public void setdefaulttargetsqlsessionfactory(sqlsessionfactory defaulttargetsqlsessionfactory) {
 this.defaulttargetsqlsessionfactory = defaulttargetsqlsessionfactory;
 }
 public dynamicsqlsessiontemplate(sqlsessionfactory sqlsessionfactory) {
 this(sqlsessionfactory, sqlsessionfactory.getconfiguration().getdefaultexecutortype());
 }
 public dynamicsqlsessiontemplate(sqlsessionfactory sqlsessionfactory, executortype executortype) {
 this(sqlsessionfactory, executortype, new mybatisexceptiontranslator(sqlsessionfactory.getconfiguration()
 .getenvironment().getdatasource(), true));
 }
 public dynamicsqlsessiontemplate(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(sqlsessioncontentholder.getcontexttype());
 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(
  dynamicsqlsessiontemplate.this.getsqlsessionfactory(),
  dynamicsqlsessiontemplate.this.executortype, 
  dynamicsqlsessiontemplate.this.exceptiontranslator);
 try {
 object result = method.invoke(sqlsession, args);
 if (!issqlsessiontransactional(sqlsession, dynamicsqlsessiontemplate.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 (dynamicsqlsessiontemplate.this.exceptiontranslator != null && unwrapped instanceof persistenceexception) {
  throwable translated = dynamicsqlsessiontemplate.this.exceptiontranslator
  .translateexceptionifpossible((persistenceexception) unwrapped);
  if (translated != null) {
  unwrapped = translated;
  }
 }
 throw unwrapped;
 } finally {
 closesqlsession(sqlsession, dynamicsqlsessiontemplate.this.getsqlsessionfactory());
 }
 }
 }
}

sqlsessioncontentholder类代码如下:

package com.sincetimes.slg.framework.util;
public abstract class sqlsessioncontentholder {
 public final static string session_factory_master = "master";
 public final static string session_factory_slave = "slave";
 private static final threadlocal<string> contextholder = new threadlocal<string>(); 
 public static void setcontexttype(string contexttype) { 
 contextholder.set(contexttype); 
 } 
 public static string getcontexttype() { 
 return contextholder.get(); 
 } 
 public static void clearcontexttype() { 
 contextholder.remove(); 
 } 
}

最后就是写切面去对dao所有方法进行处理了,代码很简单如下:

package com.sincetimes.slg.framework.core;
import org.aspectj.lang.joinpoint;
import org.aspectj.lang.annotation.aspect;
import org.aspectj.lang.annotation.before;
import org.aspectj.lang.annotation.pointcut;
import com.sincetimes.slg.framework.util.sqlsessioncontentholder;
@aspect
public class dynamicdatasourceaspect {
 @pointcut("execution( * com.sincetimes.slg.dao.*.*(..))")
 public void pointcut(){
 }
 @before("pointcut()")
 public void before(joinpoint jp){
 string methodname = jp.getsignature().getname(); 
 //dao方法查询走从库
 if(methodname.startswith("query") || methodname.startswith("get") || methodname.startswith("count") || methodname.startswith("list")){
 sqlsessioncontentholder.setcontexttype(sqlsessioncontentholder.session_factory_slave);
 }else{
 sqlsessioncontentholder.setcontexttype(sqlsessioncontentholder.session_factory_master);
 }
 }
}

以上所述是小编给大家介绍的spring + mybatis 项目实现动态切换数据源实例详解,希望对大家有所帮助