• spring动态切换数据源(一)


    介绍下spring数据源连接的源码类:|

      1 spring动态切换连接池需要类AbstractRoutingDataSource的源码
      2 /*
      3  * Copyright 2002-2017 the original author or authors.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package org.springframework.jdbc.datasource.lookup;
     19 
     20 import java.sql.Connection;
     21 import java.sql.SQLException;
     22 import java.util.HashMap;
     23 import java.util.Map;
     24 import javax.sql.DataSource;
     25 
     26 import org.springframework.beans.factory.InitializingBean;
     27 import org.springframework.jdbc.datasource.AbstractDataSource;
     28 import org.springframework.lang.Nullable;
     29 import org.springframework.util.Assert;
     30 
     31 /**
     32  * Abstract {@link javax.sql.DataSource} implementation that routes {@link #getConnection()}
     33  * calls to one of various target DataSources based on a lookup key. The latter is usually
     34  * (but not necessarily) determined through some thread-bound transaction context.
     35  *
     36  * @author Juergen Hoeller
     37  * @since 2.0.1
     38  * @see #setTargetDataSources
     39  * @see #setDefaultTargetDataSource
     40  * @see #determineCurrentLookupKey()
     41  */
     42 public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
     43 
     44     @Nullable
     45     private Map<Object, Object> targetDataSources;
     46 
     47     @Nullable
     48     private Object defaultTargetDataSource;
     49 
     50     private boolean lenientFallback = true;
     51 
     52     private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
     53 
     54     @Nullable
     55     private Map<Object, DataSource> resolvedDataSources;
     56 
     57     @Nullable
     58     private DataSource resolvedDefaultDataSource;
     59 
     60 
     61     / * *
     62         *指定目标数据源的映射,查找键为键。
     63         *映射的值可以是对应的{@link javax.sql.DataSource}
     64          实例或数据源名称字符串(通过{@link setDataSourceLookup DataSourceLookup}解析)。
     65         * <p>密钥可以是任意类型;该类只实现泛型查找过程。
     66         具体的键表示将由{
     67         @link # resolvespeciedlookupkey (Object)}和{@link #行列式urrentlookupkey()}处理。
     68         * /
     69     public void setTargetDataSources(Map<Object, Object> targetDataSources) {
     70         this.targetDataSources = targetDataSources;
     71     }
     72 
     73     
     74 
     75 
     76      / * *
     77      *指定默认的目标数据源(如果有的话)。
     78      * <p>映射值可以是对应的
     79      {@link javax.sql.DataSource}
     80      实例或数据源名称字符串
     81      (通过{@link #setDataSourceLookup DataSourceLookup}解析)。
     82      * <p>如果key {@link #setTargetDataSources targetDataSources}
     83      没有匹配{@link #决定ecurrentlookupkey()}当前查找键,
     84      则此数据源将被用作目标。
     85      * /
     86 
     87     public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
     88         this.defaultTargetDataSource = defaultTargetDataSource;
     89     }
     90 
     91     / * *
     92       *指定是否对默认数据源应用宽松的回退
     93       *如果无法为当前查找键找到特定的数据源。
     94       * <p>默认为“true”,接受在目标数据源映射中没有对应条目的查找键——在这种情况下,简单地返回到默认数据源。
     95       * <p>将此标志切换为“false”,如果您希望回退仅在查找键为{@code null}时应用。
     96       没有数据源项的查找键将导致IllegalStateException。
     97       * @see # setTargetDataSources
     98       * @see # setDefaultTargetDataSource
     99       * @see # determineCurrentLookupKey ()
    100        * /
    101     public void setLenientFallback(boolean lenientFallback) {
    102         this.lenientFallback = lenientFallback;
    103     }
    104 
    105     / * *
    106         *设置DataSourceLookup实现来解析数据源
    107         {@link #setTargetDataSources targetDataSources}映射中的名称字符串。
    108         * <p>默认是{@link JndiDataSourceLookup},允许JNDI名称
    109          *直接指定的应用服务器数据源。
    110         * /
    111     public void setDataSourceLookup(@Nullable DataSourceLookup dataSourceLookup) {
    112         this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
    113     }
    114 
    115 
    116     @Override
    117     public void afterPropertiesSet() {
    118         if (this.targetDataSources == null) {
    119             throw new IllegalArgumentException("Property 'targetDataSources' is required");
    120         }
    121         this.resolvedDataSources = new HashMap<>(this.targetDataSources.size());
    122         this.targetDataSources.forEach((key, value) -> {
    123             Object lookupKey = resolveSpecifiedLookupKey(key);
    124             DataSource dataSource = resolveSpecifiedDataSource(value);
    125             this.resolvedDataSources.put(lookupKey, dataSource);
    126         });
    127         if (this.defaultTargetDataSource != null) {
    128             this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
    129         }
    130     }
    131 
    132     / * *
    133         *将给定的查找键对象
    134         *(如{@link #setTargetDataSources targetDataSources}映射中指定的)解析为实际的查找键,
    135         *用于与{@link #决定ecurrentlookupkey()当前查找键}匹配。
    136         * <p>默认实现只是简单地返回给定的键值。
    137         * @param lookupKey用户指定的查找键对象
    138         * @根据需要返回查找键以进行匹配
    139         * /
    140     protected Object resolveSpecifiedLookupKey(Object lookupKey) {
    141         return lookupKey;
    142     }
    143 
    144     
    145 / * *
    146 *将指定的数据源对象解析为数据源实例。
    147 * <p>默认实现处理数据源实例和数据源名称(通过{@link #setDataSourceLookup DataSourceLookup}解析)。
    148 {@link #setTargetDataSources targetDataSources}映射中指定的数据源值对象
    149 * @返回已解析的数据源(绝不是{@code null})
    150 * @抛出不支持的值类型的IllegalArgumentException
    151 * /
    152 
    153     protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
    154         if (dataSource instanceof DataSource) {
    155             return (DataSource) dataSource;
    156         }
    157         else if (dataSource instanceof String) {
    158             return this.dataSourceLookup.getDataSource((String) dataSource);
    159         }
    160         else {
    161             throw new IllegalArgumentException(
    162                     "Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
    163         }
    164     }
    165 
    166 
    167     @Override
    168     public Connection getConnection() throws SQLException {
    169         return determineTargetDataSource().getConnection();
    170     }
    171 
    172     @Override
    173     public Connection getConnection(String username, String password) throws SQLException {
    174         return determineTargetDataSource().getConnection(username, password);
    175     }
    176 
    177     @Override
    178     @SuppressWarnings("unchecked")
    179     public <T> T unwrap(Class<T> iface) throws SQLException {
    180         if (iface.isInstance(this)) {
    181             return (T) this;
    182         }
    183         return determineTargetDataSource().unwrap(iface);
    184     }
    185 
    186     @Override
    187     public boolean isWrapperFor(Class<?> iface) throws SQLException {
    188         return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
    189     }
    190 
    191     
    192       /**
    193        *检索当前目标数据源。决定了
    194        * {@link # definecurrentlookupkey()当前查找键},在{@link #setTargetDataSources targetDataSources}映射中执行查找,返回指定的
    195        * {@link #setDefaultTargetDataSource默认目标数据源}如果需要。
    196        * @see # determineCurrentLookupKey ()
    197 
    198 通多debug会发现DataSource dataSource = this.resolvedDataSources.get(lookupKey);非常关键。获取数据源类似key的标识
    199 
    200        */
    201 
    202     protected DataSource determineTargetDataSource() {
    203         Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    204         Object lookupKey = determineCurrentLookupKey();
    205         DataSource dataSource = this.resolvedDataSources.get(lookupKey);
    206         if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
    207             dataSource = this.resolvedDefaultDataSource;//没有数据源设置为默认的数据源
    208         }
    209         if (dataSource == null) {//没切换数据源并且没有设置默认数据源,此处抛出异常
    210             throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
    211         }
    212         return dataSource;//返回数据源对象
    213     }
    214 
    215     /*
    216          * 确定当前查找键。这通常是
    217          *用于检查线程绑定的事务上下文。
    218          * <p>允许任意键。返回的密钥需要方法解析后匹配存储的查找键类型
    219          * {@link # resolvespeciedlookupkey}方法。
    220          */
    221     @Nullable
    222     protected abstract Object determineCurrentLookupKey();//抽像方法,需要重写然后在protected DataSource determineTargetDataSource() 中调用
    223 
    224 }
    View Code

    源码中关键类的介绍:

    上面的方法走完后下辖一步debug就是获取驱动连接:

    数据源切换代码标记图:

    下面是配置文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
        ">
    
        <!--原始默认数据源配置C3P0-->
    
        <!--<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  destroy-method="close">-->
        <!--<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>-->
        <!--<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/quanxian?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT%2B8&amp;useSSL=false&amp;allowPublicKeyRetrieval=true"/>-->
        <!--<property name="user" value="wangbiao"/>-->
        <!--<property name="password" value="w@2014221317b"/>-->
        <!--&lt;!&ndash;默认为0,单位为秒,表示在连接池中未被使用的连接最长存活多久不被移除&ndash;&gt;-->
        <!--<property name="maxIdleTime" value="3600"/>-->
        <!--&lt;!&ndash;默认为3表示连接池中任何时候可以存放的连接最小数量。&ndash;&gt;-->
        <!--<property name="minPoolSize" value="1"/>-->
        <!--&lt;!&ndash; 默认为15,表示连接池中任何时候可以存放的连接最大数量。&ndash;&gt;-->
        <!--<property name="maxPoolSize" value="5"/>-->
        <!--&lt;!&ndash;默认为3,表示初始化连接池时获取的连接个数。该数值在miniPoolSize和maxPoolSize之间。&ndash;&gt;-->
        <!--<property name="initialPoolSize" value="2"/>-->
        <!--&lt;!&ndash;表示当连接池中连接用完时,客户端调用getConnection获取连接等待的时间 如果超时,则抛出SQLException异常。特殊值0表示无限期等待&ndash;&gt;-->
        <!--<property name="checkoutTimeout" value="4800000"/>-->
        <!--</bean>-->
    
        <!--数据源0-->
        <bean id="dataSource0" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://localhost:3306/quanxian?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT%2B8&amp;useSSL=false&amp;allowPublicKeyRetrieval=true"/>
            <property name="username" value="wangbiao"/>
            <property name="password" value="w@2014221317b"/>
        </bean>
    
    <!--数据源1-->
        <bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource"
              init-method="init" destroy-method="close">
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://localhost:3306/qrtz_timer?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT%2B8&amp;useSSL=false&amp;allowPublicKeyRetrieval=true"/>
            <property name="username" value="root"/>
            <property name="password" value="w@2014221317b"/>
        </bean>
    
        <!--多数据源配置-->
        <bean id="multiDataSource" class="com.ry.project.dataSouces.DynamicDataSource">
            <property name="targetDataSources">
                <map>
                    <entry key="dataSource0" value-ref="dataSource0"></entry>
                </map>
            </property>
            <property name="defaultTargetDataSource" ref="dataSource1"></property>
        </bean>
    
        <!--<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">-->
            <!--<property name="dataSource" ref="multiDataSource"/>-->
            <!--&lt;!&ndash;<property name="configLocation" value="classpath:mybatis-config.xml"/>&ndash;&gt;-->
            <!--<property name="mapperLocations" value="classpath*:/mapper/User.xml"/>-->
        <!--</bean>-->
    
         <!--会话工厂-->
        <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="multiDataSource"/>
            <!--<property name="configLocation" value="classpath:mybatis-config.xml"/>-->
            <property name="plugins">
                <array>
                    <bean class="com.github.pagehelper.PageInterceptor">
                        <property name="properties">
                            <!--使用下面的方式配置参数,一行配置一个 -->
                            <value>
                                helperDialect=mysql
                                reasonable=true
                                supportMethodsArguments=true
                                params=count=countSql
                                autoRuntimeDialect=true
                            </value>
                        </property>
                    </bean>
                </array>
            </property>
            <property name="mapperLocations" value="classpath:mapper/*.xml" />
        </bean>
    
        <!--mybatis扫描 映射-->
        <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.ry.project.mapper"/>
            <property name="sqlSessionFactoryBeanName" value="sessionFactory"/>
        </bean>
    
    <!--事务管理-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="multiDataSource"/>
        </bean>
    
    </beans>
    View Code

    在spring配置文件中加上这个Order管控事务与AOP顺序问题,防止实物卡住数据源无法切换:

        <tx:annotation-driven transaction-manager="transactionManager" order="2"/>

    下面是我的java代码:相关类引用网友:

    https://blog.csdn.net/u013034378/article/details/82469368
     1 package com.ry.project.dataSouces;
     2 
     3 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
     4 
     5 public class DynamicDataSource extends AbstractRoutingDataSource {
     6 
     7     /* ThreadLocal,叫线程本地变量或线程本地存储。
     8      * ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
     9      * 这里使用它的子类InheritableThreadLocal用来保证父子线程都能拿到值。
    10      */
    11     private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>();
    12 
    13     /**
    14      * 设置dataSourceKey的值
    15      * @param dataSource
    16      */
    17     public static void setDataSourceKey(String dataSource) {
    18         dataSourceKey.set(dataSource);
    19     }
    20     /**
    21      * 清除dataSourceKey的值
    22      */
    23     public static void toDefault() {
    24         dataSourceKey.remove();
    25     }
    26 
    27     @Override
    28     protected Object determineCurrentLookupKey() {
    29         return dataSourceKey.get();
    30     }
    31     /**
    32      * 返回当前dataSourceKey的值
    33      */
    34 
    35 
    36 }
    View Code
     1 package com.ry.project.dataSouces;
     2 
     3 import java.lang.annotation.*;
     4 
     5 @Target({ElementType.METHOD,ElementType.TYPE})
     6 @Retention(RetentionPolicy.RUNTIME)
     7 @Documented
     8 public @interface DynamicRoutingDataSource {
     9     String value() default "dataSource1";//本文默认dataSource
    10 }
    View Code
     1 package com.ry.project.dataSouces;
     2 
     3 import org.aspectj.lang.JoinPoint;
     4 import org.aspectj.lang.annotation.After;
     5 import org.aspectj.lang.annotation.Aspect;
     6 import org.aspectj.lang.annotation.Before;
     7 import org.aspectj.lang.annotation.Pointcut;
     8 import org.springframework.core.Ordered;
     9 import org.springframework.stereotype.Component;
    10 
    11 import java.lang.reflect.Method;
    12 
    13 @Aspect
    14 @Component
    15 public class HandlerDataSourceAop implements Ordered {
    16 
    17     /**
    18      * @within匹配类上的注解
    19      * @annotation匹配方法上的注解
    20      */
    21     @Pointcut("@within(com.ry.project.dataSouces.DynamicRoutingDataSource)||@annotation(com.ry.project.dataSouces.DynamicRoutingDataSource)")
    22     public void pointcut(){}
    23 
    24     @Before(value = "pointcut()")
    25     public void beforeOpt(JoinPoint joinPoint) throws NoSuchMethodException {
    26         /** 先查找方法上的注解,没有的话再去查找类上的注解
    27          *-----------------------------------------------------------------------
    28          * 这里使用的是接口的模式,注解在实现类上,所以不能使用如下方式获取目标方法的对象,
    29          * 因为该方式获取的是该类的接口或者顶级父类的方法的对象.
    30          * MethodSignature methodSignature = (MethodSignature)point.getSignature();
    31          * Method method = methodSignature.getMethod();
    32          * DynamicRoutingDataSource annotation = method.getAnnotation(DynamicRoutingDataSource.class);
    33          * 通过上面代码是获取不到方法上的注解的,如果真要用上面代码来获取,可以修改aop代理模式,修改为cglib代理
    34          * 在xml配置文件修改为<aop:aspectj-autoproxy proxy-target-class="true" /> ,
    35          * proxy-target-class属性true为cglib代理,默认false为jdk动态代理 。
    36          * ---------------------------------------------------------
    37          * 本文使用是jdk动态代理, 这里使用反射的方式获取方法
    38          */
    39         //反射获取Method 方法一
    40         Object target = joinPoint.getTarget();
    41         Class<?> clazz = target.getClass();
    42         Method[] methods = clazz.getMethods();
    43         DynamicRoutingDataSource annotation = null;
    44         for (Method method : methods) {
    45             if (joinPoint.getSignature().getName().equals(method.getName())) {
    46                 annotation = method.getAnnotation(DynamicRoutingDataSource.class);
    47                 if (annotation == null) {
    48                     annotation = joinPoint.getTarget().getClass().getAnnotation(DynamicRoutingDataSource.class);
    49                     if (annotation == null) {
    50                         return;
    51                     }
    52                 }
    53             }
    54         }
    55 
    56 
    57 //             反射获取Method 方法二
    58 //                Object[] args = joinPoint.getArgs();
    59 //                Class<?>[] argTypes = new Class[joinPoint.getArgs().length];
    60 //                for (int i = 0; i < args.length; i++) {
    61 //                    argTypes[i] = args[i].getClass();
    62 //                }
    63 //                Method method = joinPoint.getTarget().getClass().getMethod(joinPoint.getSignature().getName(), argTypes);
    64 //                DynamicRoutingDataSource annotation = method.getAnnotation(DynamicRoutingDataSource.class);
    65 //                if (annotation == null) {
    66 //                    annotation = joinPoint.getTarget().getClass().getAnnotation(DynamicRoutingDataSource.class);
    67 //                    if (annotation == null) {
    68 //                        return;
    69 //                    }
    70 //                }
    71 
    72         String dataSourceName = annotation.value();
    73         DynamicDataSource.setDataSourceKey(dataSourceName);
    74         System.out.println("切到" + dataSourceName + "数据库");
    75     }
    76     @After(value="pointcut()")
    77     public void afterOpt(){
    78         DynamicDataSource.toDefault();
    79         System.out.println("切回默认数据库");
    80     }
    81 
    82     @Override
    83     public int getOrder() {
    84         return 1;
    85     }
    86 }
    View Code
    一点点学习,一丝丝进步。不懈怠,才不会被时代淘汰
  • 相关阅读:
    easyui combobox 的取值问题
    关于Java中集合的讲解~
    面试中经常会被问到的70个问题
    80端口被NT kernel & System 占用pid 4
    java中try{}catch{}和finally{}的执行顺序问题
    Java中基本数据类型的存储方式和相关内存的处理方式(java程序员必读经典)
    String,StringBuffer和StringBuilder三者的讲解
    String是基本的数据类型吗?
    jdk中那些常见的类不能被继承的
    关于java中getClass()和getSuperClass()的讲解
  • 原文地址:https://www.cnblogs.com/wangbiaohistory/p/12545415.html
Copyright © 2020-2023  润新知