zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    [翻译]在Windows版或MacOS版的Microsoft Edge上安装一个谷歌浏览器拓展
    《C#并发编程经典实例》学习笔记—2.6 任务完成时的处理
    《C#并发编程经典实例》学习笔记—2.5 等待任意一个任务完成 Task.WhenAny
    Visual Studio 2019 发布活动
    创建索引CreateIndex
    Windows 10 安装ElasticSearch(2)- MSI安装ElasticSearch和安装Kibana
    jQuery框架二
    jQuery框架
    JavaScript——二
    作业 5/17
  • 原文地址:https://www.cnblogs.com/conswin/p/7222278.html
Copyright © 2011-2022 走看看