Spring + Mybatis项目实现数据库读写分离
主要思路:通过实现AbstractRoutingDataSource类来动态管理数据源,利用面向切面思维,每一次进入service方法前,选择数据源。
1、首先pom.xml中添加aspect依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
2、实现AbstractRoutingDataSource类 作为数据源
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /**
* 实现AbstractRoutingDataSource类 作为数据源
* @author 木瓜牛奶泡咖啡
*
*/
public class DynamicDataSource extends AbstractRoutingDataSource { @Override
protected Object determineCurrentLookupKey() {
System.out.println("DynamicDataSourceHolder.getDataSouce()====="+DynamicDataSourceHolder.getDataSouce());
return DynamicDataSourceHolder.getDataSouce();
} }
3、用ThreadLcoal管理当前数据源
/**
* 用ThreadLcoal管理当前数据源
* @author 木瓜牛奶泡咖啡
*
*/
public class DynamicDataSourceHolder {
public static final ThreadLocal<String> holder = new ThreadLocal<String>(); public static void putDataSource(String name) {
holder.set(name);
} public static String getDataSouce() {
return holder.get();
} public static void clearDataSource() {
holder.remove();
}
}
4、用注解的形式实现AOP管理数据源
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* 用注解的形式实现AOP管理数据源
* @author 木瓜牛奶泡咖啡
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
String value();
}
5、创建切面类,将注解放在service实现类的方法前,自动设置当前数据源为注解中数据源。
import java.lang.reflect.Method; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 切面类
* 将注解放在service实现类的方法前,自动设置当前数据源为注解中数据源。
* @author 木瓜牛奶泡咖啡
*
*/ /**
* 切换数据源(不同方法调用不同数据源)
*/
@Aspect
@Order(1)
@Component
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DataSourceAspect { @Pointcut("execution(* com.navi.shell.shop.service.*.*(..))")
public void aspect() {
} /**
* 配置前置处理,使用在方法aspect()上注册的切入点,绑定数据源信息
*/
@Before("aspect()")
public void before(JoinPoint point)
{
Object target = point.getTarget();
String method = point.getSignature().getName();
System.out.println("method============" +method);
Class<?> classz = target.getClass();
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
.getMethod().getParameterTypes();
try {
Method m = classz.getMethod(method, parameterTypes);
System.out.println(m.getName());
if (m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource data = m.getAnnotation(DataSource.class);
System.out.println("value==========="+data.value());
DynamicDataSourceHolder.putDataSource(data.value());
} } catch (Exception e) {
e.printStackTrace();
}
} /**
* 配置后置处理,清空数据源信息
* @param point
*/
@After("aspect()")
public void after(JoinPoint point) {
DynamicDataSourceHolder.clearDataSource();
} }
6、配置数据源applicationContext-project.xml
<?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: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-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- 引入配置文件 -->
<context:property-placeholder location="classpath:db.properties" ignore-unresolvable="true"/> <!-- 数据源配置 -->
<bean id="dataSource_wr" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
<property name="connectionProperties" value="${db.driver}"></property> <!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1"/>
<property name="minIdle" value="1"/>
<property name="maxActive" value="20"/> <!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000"/> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000"/> <property name="validationQuery" value="SELECT 'x'"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="false"/>
</bean>
<bean id="dataSource_r" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${db1.url}"/>
<property name="username" value="${db1.username}"/>
<property name="password" value="${db1.password}"/>
<property name="connectionProperties" value="${db.driver}"></property> <!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1"/>
<property name="minIdle" value="1"/>
<property name="maxActive" value="20"/> <!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000"/> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000"/> <property name="validationQuery" value="SELECT 'x'"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="false"/>
</bean> <bean id="dataSource" class="com.ifeng.auto.we_provider.common.db.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- write -->
<entry key="write" value-ref="dataSource_wr"/>
<!-- read -->
<entry key="read" value-ref="dataSource_r"/>
</map> </property>
<property name="defaultTargetDataSource" ref="dataSource_wr"/>
</bean> <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 自动扫描mapping.xml文件-->
<!--
<property name="mapperLocations" value="classpath:com/ifeng/auto/we_provider/mapping/*.xml" />
-->
<property name="mapperLocations" value="classpath:mapping/*.xml"/>
</bean>
      <!-- 配置SQLSession模板 -->
	      <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
		        <constructor-arg index="0" ref="sqlSessionFactory" />
	      </bean>
      <!-- 设定transactionManager -->
	      <bean id="transactionManager"
		      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		        <property name="dataSource" ref="dataSource" />
	      </bean>
      <!-- 使用annotation定义事务 -->
	      <tx:annotation-driven transaction-manager="transactionManager" />
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.ifeng.auto.we_provider.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean> </beans>
8、在service实现类中添加注解
   @DataSource("write")
    public void savetag(UserTag userTag) {
        userTagMapper.addUserTag(userTag);
    }  
   @DataSource("read")
    public UserTag getUserTagByUUID(String uuid) {
        return userTagMapper.getUserTagByUUID(uuid);
    }  
自此数据库读写分离实现。
在配置过程中遇到的问题:
1、起初配置DataSourceAspect类的时候将其配置在applicationContext-project.xml,如下所示:
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="dataSourceAspect" class="com.ifeng.auto.we_provider.common.proxy.DataSourceAspect"/>
<aop:config>
<aop:aspect id="c" ref="dataSourceAspect" order="-9999">
<aop:pointcut id="tx" expression="execution(* com.ifeng.auto.we_provider.service..*.*(..))"/>
<aop:before pointcut-ref="tx" method="before"/>
</aop:aspect>
</aop:config>
这样配置的结果导致在正常调用被注释过的service时,无法进入切面类,网上说是因为有事务的原因导致,说数据库事务的优先级总是高于AOP导致,所有我在aop配置文件中加入了“order=-9999”,这样aop的优先级总最高了吧,但是依然没有起作用,纠结了半天,通过注解的方式写在切面类中,竟然可以了,这两种方式应该是一样的,但实际却不同,不知道是什么原因。
2、之前在切面类中,少加了 public void after(JoinPoint point),导致在查询一次注解为read的service后,在去请求未被注解的service(没有注解默认走write),却走了read,原因就是少加了after的处理,加入后就好了。
3、记得要在applicationContext-project.xml中添加<context:component-scan base-package="com.navi.common.db" />
参考博客:http://blog.csdn.net/byp502955177/article/details/68927230
http://www.jianshu.com/p/2222257f96d3
http://www.cnblogs.com/zrbfree/p/6484940.html
Spring + Mybatis项目实现数据库读写分离的更多相关文章
- Spring+mybatis 实现aop数据库读写分离,多数据库源配置
		在数据库层面大都采用读写分离技术,就是一个Master数据库,多个Slave数据库.Master库负责数据更新和实时数据查询,Slave库当然负责非实时数据查询.因为在实际的应用中,数据库都是读多写少 ... 
- 170301、使用Spring AOP实现MySQL数据库读写分离案例分析
		使用Spring AOP实现MySQL数据库读写分离案例分析 原创 2016-12-29 徐刘根 Java后端技术 一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案 ... 
- spring+mybatis+mysql5.7实现读写分离,主从复制
		申明:请尽量与我本博文所有的软件版本保持一致,避免不必要的错误. 所用软件版本列表:MySQL 5.7spring5mybaties3.4.6 首先搭建一个完整的spring5+springMVC5+ ... 
- 161220、使用Spring AOP实现MySQL数据库读写分离案例分析
		一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库 ... 
- 使用Spring AOP实现MySQL数据库读写分离案例分析
		一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库 ... 
- 使用Spring AOP切面解决数据库读写分离
		http://blog.jobbole.com/103496/ 为了减轻数据库的压力,一般会使用数据库主从(master/slave)的方式,但是这种方式会给应用程序带来一定的麻烦,比如说,应用程序如 ... 
- MyBatis SpringBoot2.0 数据库读写分离
		1.自定义DataSource import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * @ ... 
- Spring aop应用之实现数据库读写分离
		Spring加Mybatis实现MySQL数据库主从读写分离 ,实现的原理是配置了多套数据源,相应的sqlsessionfactory,transactionmanager和事务代理各配置了一套,如果 ... 
- spring+mybatis利用interceptor(plugin)兑现数据库读写分离
		使用spring的动态路由实现数据库负载均衡 系统中存在的多台服务器是"地位相当"的,不过,同一时间他们都处于活动(Active)状态,处于负载均衡等因素考虑,数据访问请求需要在这 ... 
随机推荐
- netty基础
			1,ServerBootstrap [Bootstrap] 
- Python示例
			http request:put # 定义函数:refresh segement # curl -X PUT -s --user "****:*****" -H 'Content- ... 
- CAS Client集群环境的Session问题及解决方案 不能退出登录
			casclient源代码下载链接:https://github.com/apereo/java-cas-client cas官网链接:https://www.apereo.org/projects/c ... 
- js 字符串加密解密
			Welcome to jzzy.com 
- Gym 101617J	Treasure Map(bfs暴力)
			http://codeforces.com/gym/101617/attachments 题意:给出一个图,每个顶点代表一个金矿,每个金矿有g和d两个值,g代表金矿初始的金子量,d是该金矿每天的金子量 ... 
- 理解 Redis(4) - 关于 string 的更多命令(SETEX, SETNX, INCR, DECR, MSET...)
			上一节介绍了关于字符串值的一些基本命令, 这一节将介绍一些进阶命令: 清理终端: 127.0.0.1:6379> clear 设置一个键值对, 同时设置过期时间为10秒: 127.0.0.1:6 ... 
- 由设置body线性背景色引发的问题-----当声明文档类型时,对body设置线性背景色,页面背景色无法整体线性过渡
			问题:当声明文档类型时,对body设置线性背景色,页面背景色无法整体线性过渡 不声明文档类型时,对body设置线性背景色 <HTML> <head> <meta char ... 
- The content of element type "web-app" must match "(icon?,display-name?,description?,distributable?,context-param*,filter*,filter-mapping*,listener*,servlet*,servlet-  mapping*,session-config?,mime-map
			修改了一下web.xml,加入了一个<filter>,然后就报这样的错??? The content of element type "web-app" must ma ... 
- DAY2 初识python
			一.编程语言介绍 1.1 机器语言:直接用计算机能理解的二进制指令编写程序,直接控制硬件 1.2 汇编语言:用英文标签取代二进制指令取编写程序,本质也是在直接控制硬件 1.3 高级语言:用人能理解的表 ... 
- 一些angular/js/ts的坑和吐槽
			------20190318 ------------- 回头看,很多槽点已经随着升级改掉了 绑定string字面值到子组件@Input <app-overlay-static [name] ... 
