• Spring 数据源配置三:多数据源


    在上一节中,我们讲述了多数据的情况:

    1. 数据源不同(数据库厂商不同, 业务范围不同, 业务数据不同)

    2. SQL mapper 文件不同,

    3. mybatis + 数据方言不同

    即最为简单的多数据, 将多个数据源叠加在一起,不同service---》dao--->sessionFactory;

    如果上述的所有条件都相同,仅仅是数据的的多个拷贝情况,想做主备(读写分离),那我们当然可以用2套复制的代码和配置,但是显然不是最佳的实现方式。

    这里,我们就讲解一下多数据源的读写分离,怎么实现。

    首先,我们从读写分离的入口来看,  读read (对应JAVA/ getXXX,  queryXXX),   写write(对应updateXXX,  insertXXX),  如果能在这些方法执行时,自动切换到只读数据库/只写数据,那么就可以实现读写分离, 那我们自然可以想到spring 的AOP, 并以annotation的方式实现。

    具体实现步骤如下:

    1.   实现自定义的数据源注解: @DataSource("read")  / @DataSource("write")

     1 package com.robin.it.ds;
     2 
     3 import java.lang.annotation.Retention;
     4 import java.lang.annotation.Target;
     5 import java.lang.annotation.RetentionPolicy;
     6 import java.lang.annotation.ElementType;
     7 /**  
     8  * RUNTIME  
     9  * 编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。  
    10  * @author Robin.yi  
    11  *  
    12  */  
    13 
    14 @Retention(RetentionPolicy.RUNTIME)  
    15 @Target(ElementType.METHOD)  
    16 public @interface DataSource {
    17        String value();  
    18 }

    2.  实现一个数据库选择器ChooseDataSource

    package com.robin.it.ds;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    public class ChooseDataSource extends AbstractRoutingDataSource {
    
        @Override
        protected Object determineCurrentLookupKey() {
            return HandleDataSource.getDataSource();
        }
    
    }
    
    ============
    package com.robin.it.ds;
    
    public class HandleDataSource {
        
         public static final ThreadLocal<String> holder = new ThreadLocal<String>();  
         
            public static void putDataSource(String datasource) {  
                holder.set(datasource);  
            }  
              
            public static String getDataSource() {  
                return holder.get();  
            }      
    }

    3.   实现AOP 的的数据拦截,切换功能

    package com.robin.it.ds;
    
    import java.lang.reflect.Method;  
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.reflect.MethodSignature;
    //@Aspect  
    //@Component  
    public class DataSourceAspect {  
        //@Pointcut("execution(* com.apc.cms.service.*.*(..))")    
        public void pointCut(){};    
          
      //  @Before(value = "pointCut()")  
         public void before(JoinPoint point)  
            {  
                Object target = point.getTarget();  
                System.out.println(target.toString());  
                String method = point.getSignature().getName();  
                System.out.println(method);  
                Class<?>[] classz = target.getClass().getInterfaces();  
                Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())  
                        .getMethod().getParameterTypes();  
                try {  
                    Method m = classz[0].getMethod(method, parameterTypes);  
                    System.out.println(m.getName()); 
                    if (m != null && m.isAnnotationPresent(DataSource.class)) {  
                        DataSource data = m.getAnnotation(DataSource.class); 
                        System.out.println("==============================={}"+data.value()); 
                        HandleDataSource.putDataSource(data.value());  
                    }else{
                        System.out.println("000000000000000000000");
                    }  
                      
                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
            }  
    }  

     注意:  黄色高亮部分,利用反射读取 Interface的 的定义(包括annotation 的申明);动态读取参数,进行数据源切换;

    4.  定义接口服务

    package com.robin.it.permission.service;
    
    import com.robin.it.ds.DataSource;
    import com.robin.it.permission.module.User;
    
    /**
     * Created by Robin.yi  on 2015-4-11.
     */
    public interface IUserService {
        
         @DataSource("read")
        public User getUser(Integer userId);
    
         @DataSource("read")  
        public User getUser(String account, String password);
         
         
         @DataSource("write")
        public void insertUser(User user);
    }

     主意:上面是决定走读库/写库的语句:   @DataSource("write")   /  @DataSource("read")

      接口的实现无特别说明,此次省略。

    5.  整个Spring 的applicationContent.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:tx="http://www.springframework.org/schema/tx"
              xmlns:aop="http://www.springframework.org/schema/aop"
              xmlns:util="http://www.springframework.org/schema/util"
              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/tx
                                    http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
                                    http://www.springframework.org/schema/aop
                                    http://www.springframework.org/schema/aop/spring-aop-4.1.xsd 
                                    http://www.springframework.org/schema/util
                                    http://www.springframework.org/schema/util/spring-util-4.1.xsd ">
    
        <description>
           This file is used to define the authority info.
        </description>
    
         <!-- 用于扫描 Spring 的各类annotation bean 变量:
            @autowired  ; @Resource
          -->
        <context:annotation-config/>
    
          <!-- 用于扫描类中组建:
             如: @Controller, @Service, @Component
         -->
         <context:component-scan base-package="com.robin.it.permission.*">
             <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
         </context:component-scan>
         
         
         <tx:annotation-driven />  
         
         
         
         
         
         
         
         
    
        <bean  id="configProperties"  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="ignoreResourceNotFound" value="false" />
            <property name="locations">
                <list>
                    <!-- 这里支持多种寻址方式:classpath和file -->
                     <value>classpath:/database.properties</value>
                     <value>classpath:/config.properties</value>  
                    <!-- 推荐使用file的方式引入,这样可以将配置和代码分离 -->
                   <!-- <value>file:/opt/demo/config/demo-message.properties</value>-->
                </list>
            </property>
        </bean>
        
        <util:properties  id="props" location="classpath:/config.properties"/>
        
        <!-- MYSQL 配置   -->
        
        <bean id="readDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName"><value>${mysql.jdbc.driverClassName}</value></property>
            <property name="url"><value>${mysql.jdbc.url}</value></property>
            <property name="username"><value>${mysql.jdbc.username}</value></property>
            <property name="password"><value>${mysql.jdbc.password}</value></property>
        </bean>
        
        <bean id="writeDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName"><value>${mysql2.jdbc.driverClassName}</value></property>
            <property name="url"><value>${mysql2.jdbc.url}</value></property>
            <property name="username"><value>${mysql2.jdbc.username}</value></property>
            <property name="password"><value>${mysql2.jdbc.password}</value></property>
        </bean>
    
        <bean id="dataSource" class="com.robin.it.ds.ChooseDataSource">  
            <property name="targetDataSources">    
                  <map key-type="java.lang.String">    
                      <!-- write -->  
                     <entry key="write" value-ref="writeDataSource"/>    
                     <!-- read -->  
                     <entry key="read" value-ref="readDataSource"/>    
                  </map>    
                    
            </property>    
            <property name="defaultTargetDataSource" ref="writeDataSource"/>    
        </bean>  
       
         <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
           <property name="dataSource" ref="dataSource" />  
         </bean>  
       
       
        <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <!-- 指定sqlMapConfig总配置文件,订制的environment在spring容器中不在生效-->
             <property  name="configLocation"  value="classpath:mybatis-config-mysql.xml"/>
            <!--指定实体类映射文件,可以指定同时指定某一包以及子包下面的所有配置文件,mapperLocations和configLocation有一个即可,
             当需要为实体类指定别名时,可指定configLocation属性,再在mybatis总配置文件中采用mapper引入实体类映射文件 -->
             <property  name="mapperLocations">
                   <list>
                    <value>classpath*:/mysqlmapper/*Mapper.xml</value>
                </list>
             </property>
        </bean>
    
        <bean  class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.robin.it.permission.dao" />
            <!-- optional unless there are multiple session factories defined -->
            <property name="sqlSessionFactoryBeanName" value="sessionFactory" />
        </bean>
         
         
         <aop:aspectj-autoproxy proxy-target-class="true"/> 
          
          <bean id="dataSourceAspect" class="com.robin.it.ds.DataSourceAspect" />  
              
           <aop:config>  
                <aop:aspect id="c" ref="dataSourceAspect">  
                    <aop:pointcut id="tx" expression="execution(* com.robin.it.permission.service.*.*(..))"/>  
                    <aop:before pointcut-ref="tx" method="before"/>  
                </aop:aspect>  
            </aop:config> 
    </beans>

    重点关注标为黄色的部分: 如何配置多数据源;  当知悉对应service  [com.robin.it.permission.service.*.*(..)] , 的相关方法之前(aop:before),  执行DataSourceAspect切面方法;

    数据库配置文件database.properties 参考,上一章节。

    到此,已完成多数据源的,读写分离配置。

  • 相关阅读:
    no route to host
    修改文件失败,提示E509: 无法创建备份文件 (请加 ! 强制执行)
    OAuth2.0的四种授权模式
    Django--JWT认证
    Django--数据库ORM操作
    DRF中限流、过滤、排序、分页
    Django框架中序列化与反序列化与字段验证
    21.Django三种风格的模型继承
    20.第三方微博登录Django+Vue
    博客园图片点击放大功能
  • 原文地址:https://www.cnblogs.com/dragonflyyi/p/4716477.html
Copyright © 2020-2023  润新知