前言
基于mybatis的AbstractRoutingDataSource和Interceptor用拦截器的方式实现读写分离,根据MappedStatement的boundsql,查询sql的select、insert、update、delete,根据起判断使用读写连接串。
开发环境
SpringMVC4、mybatis3
项目结构
读写分离实现
1、pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version> 4.10 </version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version> 4.3 . 6 .RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version> 4.3 . 6 .RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version> 4.3 . 6 .RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version> 4.3 . 6 .RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version> 4.3 . 6 .RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version> 4.3 . 6 .RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version> 4.3 . 6 .RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version> 1.6 . 2 </version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-tools</artifactId>
<version> 2.0 </version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version> 3.4 . 2 </version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version> 1.3 . 0 </version>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>sqljdbc4</artifactId>
<version> 4.0 </version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version> 1.4 </version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version> 3.1 . 0 </version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version> 1.7 . 25 </version>
</dependency>
</dependencies>
|
2、jdbc.properties
1
2
3
4
5
6
|
sqlserver.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
sqlserver.url=jdbc:sqlserver: //127.0.0.1:1433;databaseName=test
sqlserver.read.username=sa
sqlserver.read.password= 000000
sqlserver.writer.username=sa
sqlserver.writer.password= 000000
|
3、springmvc-serlvet.xml,主要配置都在这里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
<?xml version= "1.0" encoding= "UTF-8" ?>
<beans xmlns= "http://www.springframework.org/schema/beans"
xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc= "http://www.springframework.org/schema/mvc"
xmlns:context= "http://www.springframework.org/schema/context"
xmlns:aop= "http://www.springframework.org/schema/aop"
xsi:schemaLocation="http: //www.springframework.org/schema/beans
http: //www.springframework.org/schema/beans/spring-beans.xsd
http: //www.springframework.org/schema/context
http: //www.springframework.org/schema/context/spring-context.xsd
http: //www.springframework.org/schema/mvc
http: //www.springframework.org/schema/mvc/spring-mvc.xsd
http: //www.springframework.org/schema/aop
http: //www.springframework.org/schema/aop/spring-aop.xsd
">
<!--从配置文件加载数据库信息-->
<bean class = "org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" >
<property name= "locations" value= "classpath:config/jdbc.properties" />
<property name= "fileEncoding" value= "UTF-8" />
</bean>
<!--配置数据源,这里使用Spring默认-->
<bean id= "abstractDataSource" class = "org.springframework.jdbc.datasource.DriverManagerDataSource" >
<property name= "driverClassName" value= "${sqlserver.driver}" />
<property name= "url" value= "${sqlserver.url}" />
</bean>
<!--读-->
<bean id= "shawnTimeDataSourceRead" parent= "abstractDataSource" >
<property name= "username" value= "${sqlserver.read.username}" />
<property name= "password" value= "${sqlserver.read.password}" />
</bean>
<!--写-->
<bean id= "shawnTimeDataSourceWiter" parent= "abstractDataSource" >
<property name= "username" value= "${sqlserver.writer.username}" />
<property name= "password" value= "${sqlserver.writer.password}" />
</bean>
<bean id= "shawnTimeDataSource" class = "com.autohome.rwdb.DynamicDataSource" >
<property name= "readDataSource" ref= "shawnTimeDataSourceRead" />
<property name= "writeDataSource" ref= "shawnTimeDataSourceRead" />
</bean>
<bean id= "shawnTimeTransactionManager" class = "com.autohome.rwdb.DynamicDataSourceTransactionManager" >
<property name= "dataSource" ref= "shawnTimeDataSource" />
</bean>
<!--配置sqlSessionFactory-->
<bean id= "sqlSessionFactory" class = "org.mybatis.spring.SqlSessionFactoryBean" >
<property name= "configLocation" value= "classpath:springmvc-mybatis.xml" />
<property name= "dataSource" ref= "shawnTimeDataSource" />
<property name= "plugins" >
<array>
<bean class = "com.autohome.rwdb.DynamicPlugin" />
</array>
</property>
</bean>
<!--扫描Mapper-->
<bean class = "org.mybatis.spring.mapper.MapperScannerConfigurer" >
<property name= "basePackage" value= "com.autohome.dao" />
</bean>
<!--启用最新的注解器、映射器-->
<mvc:annotation-driven/>
<context:component-scan base- package = "com.autohome.*" />
<!--jsp视图解析器-->
<bean class = "org.springframework.web.servlet.view.InternalResourceViewResolver" >
<property name= "prefix" value= "/WEB-INF/views/" />
<property name= "suffix" value= ".jsp" />
</bean>
</beans>
|
4、DynamicDataSource。实现AbstractRoutingDataSource
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
package com.autohome.rwdb;
import java.util.HashMap;
import java.util.Map;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
private Object writeDataSource; //写数据源
private Object readDataSource; //读数据源
@Override
public void afterPropertiesSet() {
if ( this .writeDataSource == null ) {
throw new IllegalArgumentException( "Property 'writeDataSource' is required" );
}
setDefaultTargetDataSource(writeDataSource);
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
targetDataSources.put(DynamicDataSourceGlobal.WRITE.name(), writeDataSource);
if (readDataSource != null ) {
targetDataSources.put(DynamicDataSourceGlobal.READ.name(), readDataSource);
}
setTargetDataSources(targetDataSources);
super .afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
DynamicDataSourceGlobal dynamicDataSourceGlobal = DynamicDataSourceHolder.getDataSource();
if (dynamicDataSourceGlobal == null
|| dynamicDataSourceGlobal == DynamicDataSourceGlobal.WRITE) {
return DynamicDataSourceGlobal.WRITE.name();
}
return DynamicDataSourceGlobal.READ.name();
}
public void setWriteDataSource(Object writeDataSource) {
this .writeDataSource = writeDataSource;
}
public Object getWriteDataSource() {
return writeDataSource;
}
public Object getReadDataSource() {
return readDataSource;
}
public void setReadDataSource(Object readDataSource) {
this .readDataSource = readDataSource;
}
}
|
5、DynamicDataSourceGlobal
1
2
3
4
|
package com.autohome.rwdb;
public enum DynamicDataSourceGlobal {
READ, WRITE;
}
|
6、DynamicDataSourceHolder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package com.autohome.rwdb;
public final class DynamicDataSourceHolder {
private static final ThreadLocal<DynamicDataSourceGlobal> holder = new ThreadLocal<DynamicDataSourceGlobal>();
private DynamicDataSourceHolder() {
//
}
public static void putDataSource(DynamicDataSourceGlobal dataSource){
holder.set(dataSource);
}
public static DynamicDataSourceGlobal getDataSource(){
return holder.get();
}
public static void clearDataSource() {
holder.remove();
}
}
|
7、DynamicDataSourceTransactionManager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package com.autohome.rwdb;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
public class DynamicDataSourceTransactionManager extends DataSourceTransactionManager {
/**
* 只读事务到读库,读写事务到写库
* @param transaction
* @param definition
*/
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
//设置数据源
boolean readOnly = definition.isReadOnly();
if (readOnly) {
DynamicDataSourceHolder.putDataSource(DynamicDataSourceGlobal.READ);
} else {
DynamicDataSourceHolder.putDataSource(DynamicDataSourceGlobal.WRITE);
}
super .doBegin(transaction, definition);
}
/**
* 清理本地线程的数据源
* @param transaction
*/
@Override
protected void doCleanupAfterCompletion(Object transaction) {
super .doCleanupAfterCompletion(transaction);
DynamicDataSourceHolder.clearDataSource();
}
}
|
8、DynamicPlugin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
package com.autohome.rwdb;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.support.TransactionSynchronizationManager;
@Intercepts ({
@Signature (type = Executor. class , method = "update" , args = {
MappedStatement. class , Object. class }),
@Signature (type = Executor. class , method = "query" , args = {
MappedStatement. class , Object. class , RowBounds. class ,
ResultHandler. class }) })
public class DynamicPlugin implements Interceptor {
protected static final Logger logger = LoggerFactory.getLogger(DynamicPlugin. class );
private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*" ;
private static final Map<String, DynamicDataSourceGlobal> cacheMap = new ConcurrentHashMap<String, DynamicDataSourceGlobal>();
@Override
public Object intercept(Invocation invocation) throws Throwable {
boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
if (!synchronizationActive) {
Object[] objects = invocation.getArgs();
MappedStatement ms = (MappedStatement) objects[ 0 ];
DynamicDataSourceGlobal dynamicDataSourceGlobal = null ;
if ((dynamicDataSourceGlobal = cacheMap.get(ms.getId())) == null ) {
//读方法
if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
//!selectKey 为自增id查询主键(SELECT LAST_INSERT_ID() )方法,使用主库
if (ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
dynamicDataSourceGlobal = DynamicDataSourceGlobal.WRITE;
} else {
BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[ 1 ]);
//获取MappedStatement 的sql语句,select update delete insert
String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll( "[\\t\\n\\r]" , " " );
if (sql.matches(REGEX)) {
dynamicDataSourceGlobal = DynamicDataSourceGlobal.WRITE;
} else {
dynamicDataSourceGlobal = DynamicDataSourceGlobal.READ;
}
}
} else {
dynamicDataSourceGlobal = DynamicDataSourceGlobal.WRITE;
}
System.out.println( "设置方法[" +ms.getId()+ "] use [" + dynamicDataSourceGlobal.name()+ "] Strategy, SqlCommandType [" +ms.getSqlCommandType().name()+ "].." );
cacheMap.put(ms.getId(), dynamicDataSourceGlobal);
}
DynamicDataSourceHolder.putDataSource(dynamicDataSourceGlobal);
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this );
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
}
}
|
测试分离是否实现
运行UserController.index方法,然后从控制台看打印结果
以上所述是小编给大家介绍的SpringMVC4+MyBatis+SQL Server2014实现读写分离,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对服务器之家网站的支持!
原文链接:http://www.cnblogs.com/sword-successful/archive/2017/04/24/6756886.html