spring动态切换数据源

Posted 余生请多指教ANT

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 + "数据库");
以上是关于spring动态切换数据源的主要内容,如果未能解决你的问题,请参考以下文章

Spring主从数据库的配置和动态数据源切换原理

Spring动态配置多数据源的基于spring和ibatis的多数据源切换方案

spring动态切换数据源

Spring Boot 动态数据源(多数据源自动切换)

基于spring的aop实现多数据源动态切换

spring AbstractRoutingDataSource实现动态数据源切换