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

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

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

参考了两篇文章如下:

http://blog.csdn.net/zl3450341/article/details/20150687

http://www.blogjava.net/hoojo/archive/2013/10/22/405488.html

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

实现思路是:

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

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

spring中配置如下:

主库数据源配置:

1 <bean id="masterDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
2     <property name="driverClass" value="${master_mysql_jdbc_driver}" />
3     <property name="jdbcUrl" value="${master_mysql_jdbc_url}" />
4     <property name="user" value="${master_mysql_jdbc_user}" />
5     <property name="password" value="${master_mysql_jdbc_password}" />
6 </bean>

从库数据源配置:

1 <bean id="masterDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
2     <property name="driverClass" value="${slave_mysql_jdbc_driver}" />
3     <property name="jdbcUrl" value="${slave_mysql_jdbc_url}" />
4     <property name="user" value="${slave_mysql_jdbc_user}" />
5     <property name="password" value="${slave_mysql_jdbc_password}" />
6 </bean>

主库SqlSessionFactory配置:

1 <bean id="masterSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
2     <property name="dataSource" ref="masterDataSource" />
3     <property name="mapperLocations"  value="classpath:com/sincetimes/slg/dao/*.xml"/>
4 </bean>

从库SqlSessionFactory配置:

1 <bean id="slaveSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
2     <property name="dataSource" ref="slaveDataSource" />
3     <property name="mapperLocations"  value="classpath:com/sincetimes/slg/dao/*.xml"/>
4 </bean>

两个SqlSessionFactory使用同一个SqlSessionTemplate配置:

1 <bean id="MasterAndSlaveSqlSessionTemplate" class="com.sincetimes.slg.framework.core.DynamicSqlSessionTemplate">
2     <constructor-arg index="0" ref="masterSqlSessionFactory" />
3     <property name="targetSqlSessionFactorys">
4         <map>
5             <entry value-ref="masterSqlSessionFactory" key="master"/>
6             <entry value-ref="slaveSqlSessionFactory" key="slave"/>
7         </map>
8     </property>
9 </bean>

配置Mybatis自动扫描dao

1 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
2     <property name="basePackage" value="com.sincetimes.slg.dao" />
3     <property name="sqlSessionTemplateBeanName" value="MasterAndSlaveSqlSessionTemplate" />
4 </bean>

自己重写了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<Object, SqlSessionFactory> targetSqlSessionFactorys;
 46     private SqlSessionFactory defaultTargetSqlSessionFactory;
 47
 48     public void setTargetSqlSessionFactorys(Map<Object, SqlSessionFactory> targetSqlSessionFactorys) {
 49         this.targetSqlSessionFactorys = targetSqlSessionFactorys;
 50     }
 51
 52     public Map<Object, SqlSessionFactory> getTargetSqlSessionFactorys(){
 53         return targetSqlSessionFactorys;
 54     }
 55
 56     public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {
 57         this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;
 58     }
 59
 60     public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
 61         this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
 62     }
 63
 64     public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
 65         this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration()
 66                 .getEnvironment().getDataSource(), true));
 67     }
 68
 69     public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
 70             PersistenceExceptionTranslator exceptionTranslator) {
 71
 72         super(sqlSessionFactory, executorType, exceptionTranslator);
 73
 74         this.sqlSessionFactory = sqlSessionFactory;
 75         this.executorType = executorType;
 76         this.exceptionTranslator = exceptionTranslator;
 77
 78         this.sqlSessionProxy = (SqlSession) newProxyInstance(
 79                 SqlSessionFactory.class.getClassLoader(),
 80                 new Class[] { SqlSession.class },
 81                 new SqlSessionInterceptor());
 82
 83         this.defaultTargetSqlSessionFactory = sqlSessionFactory;
 84     }
 85
 86     @Override
 87     public SqlSessionFactory getSqlSessionFactory() {
 88
 89         SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(SqlSessionContentHolder.getContextType());
 90         if (targetSqlSessionFactory != null) {
 91             return targetSqlSessionFactory;
 92         } else if (defaultTargetSqlSessionFactory != null) {
 93             return defaultTargetSqlSessionFactory;
 94         } else {
 95             Assert.notNull(targetSqlSessionFactorys, "Property ‘targetSqlSessionFactorys‘ or ‘defaultTargetSqlSessionFactory‘ are required");
 96             Assert.notNull(defaultTargetSqlSessionFactory, "Property ‘defaultTargetSqlSessionFactory‘ or ‘targetSqlSessionFactorys‘ are required");
 97         }
 98         return this.sqlSessionFactory;
 99     }
100
101     @Override
102     public Configuration getConfiguration() {
103         return this.getSqlSessionFactory().getConfiguration();
104     }
105
106     public ExecutorType getExecutorType() {
107         return this.executorType;
108     }
109
110     public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
111         return this.exceptionTranslator;
112     }
113
114     /**
115      * {@inheritDoc}
116      */
117     public <T> T selectOne(String statement) {
118         return this.sqlSessionProxy.<T> selectOne(statement);
119     }
120
121     /**
122      * {@inheritDoc}
123      */
124     public <T> T selectOne(String statement, Object parameter) {
125         return this.sqlSessionProxy.<T> selectOne(statement, parameter);
126     }
127
128     /**
129      * {@inheritDoc}
130      */
131     public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
132         return this.sqlSessionProxy.<K, V> selectMap(statement, mapKey);
133     }
134
135     /**
136      * {@inheritDoc}
137      */
138     public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
139         return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey);
140     }
141
142     /**
143      * {@inheritDoc}
144      */
145     public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
146         return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey, rowBounds);
147     }
148
149     /**
150      * {@inheritDoc}
151      */
152     public <E> List<E> selectList(String statement) {
153         return this.sqlSessionProxy.<E> selectList(statement);
154     }
155
156     /**
157      * {@inheritDoc}
158      */
159     public <E> List<E> selectList(String statement, Object parameter) {
160         return this.sqlSessionProxy.<E> selectList(statement, parameter);
161     }
162
163     /**
164      * {@inheritDoc}
165      */
166     public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
167         return this.sqlSessionProxy.<E> selectList(statement, parameter, rowBounds);
168     }
169
170     /**
171      * {@inheritDoc}
172      */
173     public void select(String statement, ResultHandler handler) {
174         this.sqlSessionProxy.select(statement, handler);
175     }
176
177     /**
178      * {@inheritDoc}
179      */
180     public void select(String statement, Object parameter, ResultHandler handler) {
181         this.sqlSessionProxy.select(statement, parameter, handler);
182     }
183
184     /**
185      * {@inheritDoc}
186      */
187     public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
188         this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
189     }
190
191     /**
192      * {@inheritDoc}
193      */
194     public int insert(String statement) {
195         return this.sqlSessionProxy.insert(statement);
196     }
197
198     /**
199      * {@inheritDoc}
200      */
201     public int insert(String statement, Object parameter) {
202         return this.sqlSessionProxy.insert(statement, parameter);
203     }
204
205     /**
206      * {@inheritDoc}
207      */
208     public int update(String statement) {
209         return this.sqlSessionProxy.update(statement);
210     }
211
212     /**
213      * {@inheritDoc}
214      */
215     public int update(String statement, Object parameter) {
216         return this.sqlSessionProxy.update(statement, parameter);
217     }
218
219     /**
220      * {@inheritDoc}
221      */
222     public int delete(String statement) {
223         return this.sqlSessionProxy.delete(statement);
224     }
225
226     /**
227      * {@inheritDoc}
228      */
229     public int delete(String statement, Object parameter) {
230         return this.sqlSessionProxy.delete(statement, parameter);
231     }
232
233     /**
234      * {@inheritDoc}
235      */
236     public <T> T getMapper(Class<T> type) {
237         return getConfiguration().getMapper(type, this);
238     }
239
240     /**
241      * {@inheritDoc}
242      */
243     public void commit() {
244         throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
245     }
246
247     /**
248      * {@inheritDoc}
249      */
250     public void commit(boolean force) {
251         throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
252     }
253
254     /**
255      * {@inheritDoc}
256      */
257     public void rollback() {
258         throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
259     }
260
261     /**
262      * {@inheritDoc}
263      */
264     public void rollback(boolean force) {
265         throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
266     }
267
268     /**
269      * {@inheritDoc}
270      */
271     public void close() {
272         throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
273     }
274
275     /**
276      * {@inheritDoc}
277      */
278     public void clearCache() {
279         this.sqlSessionProxy.clearCache();
280     }
281
282     /**
283      * {@inheritDoc}
284      */
285     public Connection getConnection() {
286         return this.sqlSessionProxy.getConnection();
287     }
288
289     /**
290      * {@inheritDoc}
291      * @since 1.0.2
292      */
293     public List<BatchResult> flushStatements() {
294         return this.sqlSessionProxy.flushStatements();
295     }
296
297     /**
298      * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring‘s Transaction Manager It also
299      * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to
300      * the {@code PersistenceExceptionTranslator}.
301      */
302     private class SqlSessionInterceptor implements InvocationHandler {
303         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
304             final SqlSession sqlSession = getSqlSession(
305                     DynamicSqlSessionTemplate.this.getSqlSessionFactory(),
306                     DynamicSqlSessionTemplate.this.executorType,
307                     DynamicSqlSessionTemplate.this.exceptionTranslator);
308             try {
309                 Object result = method.invoke(sqlSession, args);
310                 if (!isSqlSessionTransactional(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory())) {
311                     // force commit even on non-dirty sessions because some databases require
312                     // a commit/rollback before calling close()
313                     sqlSession.commit(true);
314                 }
315                 return result;
316             } catch (Throwable t) {
317                 Throwable unwrapped = unwrapThrowable(t);
318                 if (DynamicSqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
319                     Throwable translated = DynamicSqlSessionTemplate.this.exceptionTranslator
320                         .translateExceptionIfPossible((PersistenceException) unwrapped);
321                     if (translated != null) {
322                         unwrapped = translated;
323                     }
324                 }
325                 throw unwrapped;
326             } finally {
327                 closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory());
328             }
329         }
330     }
331
332 }

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<String> contextHolder = new ThreadLocal<String>();
 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 @Aspect
11 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 }
时间: 2024-11-06 12:16:13

Spring + Mybatis 项目实现动态切换数据源的相关文章

Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源 方法(转)

一.开篇 这里整合分别采用了Hibernate和MyBatis两大持久层框架,Hibernate主要完成增删改功能和一些单一的对象查询功能,MyBatis主要负责查询功能.所以在出来数据库方言的时候基本上没有什么问题,但唯一可能出现问题的就是在hibernate做添加操作生成主键策略的时候.因为我们都知道hibernate的数据库本地方言会针对不同的数据库采用不同的主键生成策略. 所以针对这一问题不得不采用自定义的主键生成策略,自己写一个主键生成器的表来维护主键生成方式或以及使用其他的方式来生成

基于spring+mybatis+atomikos+jta实现分布式事务(2)-动态切换数据源

本文介绍基于spring+mybatis+atomikos+jta实现分布式事务,由程序动态切换数据源,通过atomikos可实现分布式事务一致性. 版本:spring-3.2.9.RELEASE.mybatis-3.4.4.atomikos-4.0.5.jdk1.8 1,maven配置文件pom.xml如下: <!-- test --> <dependency> <groupId>junit</groupId> <artifactId>juni

hibernate动态切换数据源

起因: 公司的当前产品,主要是两个项目集成的,一个是java项目,还有一个是php项目,两个项目用的是不同的数据源,但都是mysql数据库,因为java这边的开发工作已经基本完成了,而php那边任务还很多,人手也比较紧,产品上线日期紧促,所以领导希望java这边能够帮助php那边写接口,所以需要切换数据源 思路: 动态切换数据源确切的来说是在同一类型数据库的情况下的.意思就是说 , 在系统中的使用的数据库分布在多台数据库服务器或者在同台服务器上的多个数据库. 在运行时期间根据某种标识符来动态的选

AOP获取方法注解实现动态切换数据源

(其中@Order(1)作用: Spring中的事务是通过aop来实现的,当我们自己写aop拦截的时候,会遇到跟spring的事务aop执行的先后顺序问题,比如说动态切换数据源的问题,如果事务在前,数据源切换在后,会导致数据源切换失效,所以就用到了Order(排序)这个关键字.) @Order(1) @Aspect @Repository public class DataSourceAspect { @Pointcut("execution(* com.xxx.service.impl.*.*

Spring MVC动态切换数据源(多数据库类型)

最近由于项目需求,需要将Sql Server 和 Mysql 两种数据库整合到一个项目,项目的用到的框架是SSM. 因此尝试了利用AOP切面来切每次执行的Servcie方法,根据Service所在的包名来实现数据源自动切换. 1.项目架构如下: 2.在com.jiefupay.database包中建立四个类: 其中 DataSourceContextHolder.java类源码如下: package com.jiefupay.datebase; public class DataSourceCo

mybatis动态切换数据源

(#)背景:由于业务的需求,导致需要随时切换15个数据源,此时不能low逼的去写十几个mapper,所以想到了实现一个数据源的动态切换 首先要想重写多数据源,那么你应该理解数据源的一个概念是什么,DataSourceTransactionManager这个类就是spring中对于数据源的封装,其中DataSource做为 他的一个成员.接下来我们要介绍一下我们切换动态数据源需要使用的类,AbstractRoutingDataSource,先来看看这个类的源码 首先看看这几个变量,targetDa

springboot+mybatis实现动态切换数据源

前几天有个需求,需要使用不同的数据源,例如某业务要用A数据源,另一个业务要用B数据源.我上网收集了一些资料整合了一下,虽然最后这个需求不了了之了,但是多数据源动态切换还是蛮好用的,所以记录一下,或许以后有用呢?或者自己感兴趣又想玩呢! 1.加个依赖 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifa

spring动态切换数据源(一)

介绍下spring数据源连接的源码类:| 1 spring动态切换连接池需要类AbstractRoutingDataSource的源码 2 /* 3 * Copyright 2002-2017 the original author or authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in comp

Spring代码中动态切换数据源

在Spring-Mybatis中,有这样一个类AbstractRoutingDataSource,根据名字可以猜到,这是一个框架提供的用于动态选择数据源的类.这个类有两个重要的参数,分别叫 defaultTargetDataSource和targetDataSources.一般的工程都是一个数据源,所以不太接触到这个类. [html] <bean id="myoneDataSource" class="org.apache.commons.dbcp2.BasicData