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的处理,加入后就好了。

参考博客:http://blog.csdn.net/byp502955177/article/details/68927230

        http://www.jianshu.com/p/2222257f96d3

     http://www.cnblogs.com/zrbfree/p/6484940.html

时间: 2024-08-06 07:54:34

Spring + Mybatis项目实现数据库读写分离的相关文章

Spring+Mybatis实现主从数据库读写分离

Spring+Mybatis实现主从数据库读写分离 采用配置+注解的方式. 自定义@DataSource注解 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME

161920、使用Spring AOP实现MySQL数据库读写分离案例分析

一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库的主从配置,最简单的是一台Master和一台Slave(大型网站系统的话,当然会很复杂,这里只是分析了最简单的情况).通过主从配置主从数据库保持了相同的数据,我们在进行读操作的时候访问从数据库Slave,在进行写操作的时候访问主数据库Master.这样的话就减轻了一台服务器的压力. 在进行读写分离案

使用Spring AOP实现MySQL数据库读写分离案例分析

一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库的主从配置,最简单的是一台Master和一台Slave(大型网站系统的话,当然会很复杂,这里只是分析了最简单的情况).通过主从配置主从数据库保持了相同的数据,我们在进行读操作的时候访问从数据库Slave,在进行写操作的时候访问主数据库Master.这样的话就减轻了一台服务器的压力. 在进行读写分离案

使用Spring AOP切面解决数据库读写分离

http://blog.jobbole.com/103496/ 为了减轻数据库的压力,一般会使用数据库主从(master/slave)的方式,但是这种方式会给应用程序带来一定的麻烦,比如说,应用程序如何做到把数据写到master库,而读取数据的时候,从slave库读取.如果应用程序判断失误,把数据写入到slave库,会给系统造成致命的打击. 解决读写分离的方案很多,常用的有SQL解析.动态设置数据源.SQL解析主要是通过分析sql语句是insert/select/update/delete中的哪

spring+mybatis利用interceptor(plugin)兑现数据库读写分离

使用spring的动态路由实现数据库负载均衡 系统中存在的多台服务器是“地位相当”的,不过,同一时间他们都处于活动(Active)状态,处于负载均衡等因素考虑,数据访问请求需要在这几台数据库服务器之间进行合理分配, 这个时候,通过统一的一个DataSource来屏蔽这种请求分配的需求,从而屏蔽数据访问类与具体DataSource的耦合: 系统中存在的多台数据库服务器现在地位可能相当也可能不相当,但数据访问类在系统启动时间无法明确到底应该使用哪一个数据源进行数据访问,而必须在系统运行期间通过某种条

Spring+MyBatis实现数据库读写分离方案

方案1通过MyBatis配置文件创建读写分离两个DataSource,每个SqlSessionFactoryBean对象的mapperLocations属性制定两个读写数据源的配置文件.将所有读的操作配置在读文件中,所有写的操作配置在写文件中. 优点:实现简单缺点:维护麻烦,需要对原有的xml文件进行重新修改,不支持多读,不易扩展实现方式 <bean id="abstractDataSource" abstract="true" class="com

MyBatis多数据源配置(读写分离)

MyBatis多数据源配置(读写分离) 首先说明,本文的配置使用的最直接的方式,实际用起来可能会很麻烦. 实际应用中可能存在多种结合的情况,你可以理解本文的含义,不要死板的使用. 多数据源的可能情况 1.主从 通常是MySql一主多从的情况,本文的例子就是主从的情况,但是只有两个数据源,所以采用直接配置不会太麻烦,但是不利于后续扩展,主要是作为一个例子来说明,实际操作请慎重考虑. 针对这种情况,一个更好的解决方法可以参考(本人没有实际尝试过): http://blog.csdn.net/lixi

利用amoeba(变形虫)实现mysql数据库读写分离

关于mysql的读写分离架构有很多,百度的话几乎都是用mysql_proxy实现的.由于proxy是基于lua脚本语言实现的,所以网上不少网友表示proxy效率不高,也不稳定,不建议在生产环境使用:amoeba是阿里开发的一款数据库读写分离的项目(读写分离只是它的一个小功能),由于是基于java编写的,所以运行环境需要安装jdk: 前期准备工作:1.两个数据库,一主一从,主从同步:master: 172.22.10.237:3306 :主库负责写入操作:slave: 10.4.66.58:330

新春好跑步,以及数据库“读写分离”的点滴考虑

新春的好日子: 小风吹来: 花一样的味道: 满满的幸福滋味. 迈开步子在宽敞的马路上跑步,步伐轻盈,多么美好的事情. 跑步总是枯燥的,只有奔跑奔跑: 跑步是孤独的,每个人的都有自己的节奏: 跑步的时候总爱瞎想,昨天和一些同学聊到了数据库的"读写分离". 在我有限的认识中,我一直认为数据库"读写分离",是为了提升数据库的瓶颈,因为数据库写总是相对比较少,而读取可能总是比较多,甚至高几个数量级. 比如一个电子商务网站,把某一个产品上架,可能一个月才需要写一次,但是每天都