介绍下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 }
源码中关键类的介绍:
上面的方法走完后下辖一步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&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true"/>-->
<!--<property name="user" value="wangbiao"/>-->
<!--<property name="password" value="w@2014221317b"/>-->
<!--<!–默认为0,单位为秒,表示在连接池中未被使用的连接最长存活多久不被移除–>-->
<!--<property name="maxIdleTime" value="3600"/>-->
<!--<!–默认为3表示连接池中任何时候可以存放的连接最小数量。–>-->
<!--<property name="minPoolSize" value="1"/>-->
<!--<!– 默认为15,表示连接池中任何时候可以存放的连接最大数量。–>-->
<!--<property name="maxPoolSize" value="5"/>-->
<!--<!–默认为3,表示初始化连接池时获取的连接个数。该数值在miniPoolSize和maxPoolSize之间。–>-->
<!--<property name="initialPoolSize" value="2"/>-->
<!--<!–表示当连接池中连接用完时,客户端调用getConnection获取连接等待的时间 如果超时,则抛出SQLException异常。特殊值0表示无限期等待–>-->
<!--<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&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false&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&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false&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"/>-->
<!--<!–<property name="configLocation" value="classpath:mybatis-config.xml"/>–>-->
<!--<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>
在spring配置文件中加上这个Order管控事务与AOP顺序问题,防止实物卡住数据源无法切换:
<tx:annotation-driven transaction-manager="transactionManager" order="2"/>
下面是我的java代码:相关类引用网友:
https://blog.****.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 }
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 }
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 }