Spring + Mybatis 项目实现动态切换数据源实例详解
项目背景:项目开发中数据库使用了读写分离,所有查询语句走从库,除此之外走主库。
最简单的办法其实就是建两个包,把之前数据源那一套配置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 项目实现动态切换数据源实例详解,希望对大家有所帮助
下一篇: linux正确重启MySQL的方法
推荐阅读
-
关于Spring3 + Mybatis3整合时多数据源动态切换的问题
-
Spring + Mybatis 项目实现动态切换数据源实例详解
-
spring实现动态切换、添加数据源及源码分析
-
详解Spring Boot + Mybatis 实现动态数据源
-
基于spring的aop实现多数据源动态切换 博客分类: javaspring 动态切换springaopAbstractRoutingDataSource
-
Java注解实现动态数据源切换的实例代码
-
详解Spring Boot + Mybatis 实现动态数据源
-
Spring动态数据源实现读写分离详解
-
Spring整合多数据源实现动态切换的实例讲解
-
Spring+Mybatis动态切换数据源的方法