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 }
源码中关键类的介绍:
上面的方法走完后下辖一步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.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 }
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 }
spring动态切换数据源(一)的更多相关文章
- Spring动态切换数据源及事务
前段时间花了几天来解决公司框架ssm上事务问题.如果不动态切换数据源话,直接使用spring的事务配置,是完全没有问题的.由于框架用于各个项目的快速搭建,少去配置各个数据源配置xml文件等.采用了动态 ...
- Spring动态切换数据源
11 //定义数据源枚举public enum DataSourceKey { master, slave, } 22 /** * 数据源路由 */ @Slf4j public class Dynam ...
- Spring AOP动态切换数据源
现在稍微复杂一点的项目,一个数据库也可能搞不定,可能还涉及分布式事务什么的,不过由于现在我只是做一个接口集成的项目,所以分布式就先不用了,用Spring AOP来达到切换数据源,查询不同的数据库就可以 ...
- Spring + Mybatis 项目实现动态切换数据源
项目背景:项目开发中数据库使用了读写分离,所有查询语句走从库,除此之外走主库. 最简单的办法其实就是建两个包,把之前数据源那一套配置copy一份,指向另外的包,但是这样扩展很有限,所有采用下面的办法. ...
- Spring+Mybatis动态切换数据源
功能需求是公司要做一个大的运营平台: 1.运营平台有自身的数据库,维护用户.角色.菜单.部分以及权限等基本功能. 2.运营平台还需要提供其他不同服务(服务A,服务B)的后台运营,服务A.服务B的数据库 ...
- Spring动态切换多数据源事务开启后,动态数据源切换失效解决方案
关于某操作中开启事务后,动态切换数据源机制失效的问题,暂时想到一个取巧的方法,在Spring声明式事务配置中,可对不改变数据库数据的方法采用不支持事务的配置,如下: 对单纯查询数据的操作设置为不支持事 ...
- Spring Boot 如何动态切换数据源
本章是一个完整的 Spring Boot 动态数据源切换示例,例如主数据库使用 lionsea 从数据库 lionsea_slave1.lionsea_slave2.只需要在对应的代码上使用 Data ...
- 在使用 Spring Boot 和 MyBatis 动态切换数据源时遇到的问题以及解决方法
相关项目地址:https://github.com/helloworlde/SpringBoot-DynamicDataSource 1. org.apache.ibatis.binding.Bind ...
- Spring学习总结(16)——Spring AOP实现执行数据库操作前根据业务来动态切换数据源
深刻讨论为什么要读写分离? 为了服务器承载更多的用户?提升了网站的响应速度?分摊数据库服务器的压力?就是为了双机热备又不想浪费备份服务器?上面这些回答,我认为都不是错误的,但也都不是完全正确的.「读写 ...
随机推荐
- 电子物流中的EDI 应用
电子物流中的EDI 应用 背景 EDI 全称是Electronic data interchange, 即电子数据交换.在传统企业里,很多流程上的操作或者通信一般是由纸质媒介完成的,比如说采购订单.发 ...
- PAT乙级:1061 判断题 (15分)
PAT乙级:1061 判断题 (15分) 题干 判断题的评判很简单,本题就要求你写个简单的程序帮助老师判题并统计学生们判断题的得分. 输入格式: 输入在第一行给出两个不超过 100 的正整数 N 和 ...
- ThinkPHP3.2.3使用PHPExcel类操作excel导出excel
如何导入excel请看:ThinkPHP3.2.3使用PHPExcel类操作excel导入读取excel // 引入PHPExcel类 import("Org.Util.PHPExccel& ...
- POJ3268-最短路径-反向存边
因为是单向边,牛儿来回的路径长度并不相同,所以需要用两次dijkstra,一次正向从x开始dijkstra,再将边全部反向存再来一次. 因为是板子题比较良心n比较小,我们就可以用矩阵来存储啦.如果n比 ...
- 数据库-SQL 语法
数据库-SQL 语法 二十余年如一梦,此身虽在堪惊. 简介:数据库-SQL 语法 一.基础 模式定义了数据如何存储.存储什么样的数据以及数据如何分解等信息,数据库和表都有模式. 主键的值不允许修改,也 ...
- Windows下删除顽固文件夹
参考链接: https://www.cnblogs.com/azbane/p/9808802.html 第一步:修改当前文件夹所有者为管理员 takeown /f * /a /r 第二步:修改管理员权 ...
- 深入刨析tomcat 之---第11篇 how tomcat works( 第15章 ) 如何解析web.xml 文件
writedby 张艳涛 记得当年是学习jsp的时候,写过web.xml中的标签.在之后的springmvc中也是有关于配置mvc 过滤器 和dispatchServlet的标签,之前是看不懂呢!看到 ...
- frame window 和open 的关系
建立一个如下的关系框架 windowA.html <!DOCTYPE html> <html lang="en"> <head> <met ...
- docker上运行mysql服务器
1.搜索MySQL镜像 $ docker search mysql INDEX NAME DESCRIPTION STARS OFFICIAL AUTOMATED docker.io docker.i ...
- 卷积的等变性(equivariant) / 不变性(invariant)
不变性:输入x发生变换,但是F之后的输出不变 \(F(x)=F [\)transform\((x)]\) 池化:近似不变性,当图像发生微小变化,最大池化的输出不变,还是一个池化范围内的max 等变性: ...