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 }
源码中关键类的介绍:
上面的方法走完后下辖一步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 + "数据库");以上是关于spring动态切换数据源的主要内容,如果未能解决你的问题,请参考以下文章