多数据源说白了就是项目里连接多个数据库;常见于分库查询(主从库),分库操作-增删改查(多库多表)等。
下面说下具体步骤:
1.配置db.properties文件
pay.jdbc.url=jdbc:mysql://192.168.0.2:3306/pay?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
pay.jdbc.password=root
pay.jdbc.username=root
model.jdbc.url=jdbc:mysql://192.168.0.2:3306/model?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
model.jdbc.password=root
model.jdbc.username=root
2.1在spring配置文件中配置多个数据源
<!-- 配置数据源 使用的是Druid数据源 -->
<bean name="dsPay" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${pay.jdbc.url}" />
<property name="username" value="${pay.jdbc.username}" />
<property name="password" value="${pay.jdbc.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="${pool.initialSize}" />
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="${pool.maxActive}" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${pool.minIdle}" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${pool.maxWait}" />
<property name="poolPreparedStatements" value="${pool.poolPreparedStatements}" />
<property name="maxPoolPreparedStatementPerConnectionSize"
value="${pool.maxPoolPreparedStatementPerConnectionSize}" />
<!-- 用来检测有效sql -->
<property name="validationQuery" value="${pool.validationQuery}" />
<property name="testOnBorrow" value="${pool.testOnBorrow}" />
<property name="testOnReturn" value="${pool.testOnReturn}" />
<property name="testWhileIdle" value="${pool.testWhileIdle}" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${pool.timeBetweenEvictionRunsMillis}" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${pool.minEvictableIdleTimeMillis}" />
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="${pool.removeAbandoned}" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="${pool.removeAbandonedTimeout}" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="${pool.logAbandoned}" />
<!-- 监控数据库 -->
<property name="filters" value="${pool.filters}" />
</bean>
<!-- 配置数据源 使用的是Druid数据源 -->
<bean name="dsModel" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${model.jdbc.url}" />
<property name="username" value="${model.jdbc.username}" />
<property name="password" value="${model.jdbc.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="${pool.initialSize}" />
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="${pool.maxActive}" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${pool.minIdle}" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${pool.maxWait}" />
<property name="poolPreparedStatements" value="${pool.poolPreparedStatements}" />
<property name="maxPoolPreparedStatementPerConnectionSize"
value="${pool.maxPoolPreparedStatementPerConnectionSize}" />
<!-- 用来检测有效sql -->
<property name="validationQuery" value="${pool.validationQuery}" />
<property name="testOnBorrow" value="${pool.testOnBorrow}" />
<property name="testOnReturn" value="${pool.testOnReturn}" />
<property name="testWhileIdle" value="${pool.testWhileIdle}" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${pool.timeBetweenEvictionRunsMillis}" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${pool.minEvictableIdleTimeMillis}" />
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="${pool.removeAbandoned}" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="${pool.removeAbandonedTimeout}" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="${pool.logAbandoned}" />
<!-- 监控数据库 -->
<property name="filters" value="${pool.filters}" />
</bean>
2.2 配置数据源选择器
<!--配置多数据源选择器-->
<bean id="dataSource" class="lzs.common.datasource.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="dsModel" value-ref="dsModel"/>
</map>
</property>
<!--默认数据源-->
<property name="defaultTargetDataSource" ref="dsPay"/>
</bean>
2.3 配置数据源选择器
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 取得当前使用那个数据源。
*/
@Override
protected Object determineCurrentLookupKey() {
return DataSourceHolder.getDataSources();
}
}
public class DataSourceHolder {
/* ThreadLocal,叫线程本地变量或线程本地存储。
* ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
* 这里使用它的子类InheritableThreadLocal用来保证父子线程都能拿到值。
*/
private static final ThreadLocal<String> dataSources = new InheritableThreadLocal<>();
public static void setDataSources(String dataSource) {
dataSources.set(dataSource);
}
public static String getDataSources() {
return dataSources.get();
}
//还原数据源
public static void clearDataSource() {
return dataSources.remove();
}
}
对于ThreadLocal不太清楚的可以看这个大神的博客《ThreadLocal是什么》http://www.linhao007.com/2017/04/13/01/
3.1手动切换使用
public static void main(String[] args) {
/**
* 数据源切换标识 与spring-db.xml 配置文件中的key 配置一致
*/
DataSourceHolder.setDataSources("dsPay");
//业务逻辑编写
DataSourceHolder.clearDataSource();//切换的时候最好清空一下
DataSourceHolder.setDataSources("dsModel");
//业务逻辑编写
DataSourceHolder.clearDataSource();//
}
3.2手动切换在正式业务里不建议用,那就可以利用Sping的AOP 实现动态切换数据源,本例是aop的切入点设置在了service层,可以根据自己的业务进行调整
<!--利用aop的切入功能拦截-->
<bean id="dataSourceExchange" class="lzs.common.datasource.DataSourceExchange"/>
<aop:config proxy-target-class="true">
<aop:aspect ref="dataSourceExchange" order="1">
<!-- <aop:pointcut id="dataSourcePointcut" expression="execution(* lzs.user.*.service.*.*(..)) || execution(* lzs.model.*.service.*.*(..))"/> -->
<aop:pointcut id="dataSourcePointcut" expression="execution(* lzs.model.*.service.*.*(..))"/>
<aop:before pointcut-ref="dataSourcePointcut" method="before"/>
<aop:after pointcut-ref="dataSourcePointcut" method="after"/>
</aop:aspect>
</aop:config>
import org.aspectj.lang.JoinPoint;
public class DataSourceExchange {
/**
*执行之前的方法
* @param point
*/
public void before(JoinPoint point) {
//获取目标对象的类类型 名称,形如://lzs.model.authcode.service.impl.xxServiceImpl
String nameClass = point.getTarget().getClass().getName().toString();
if(nameClass.contains(".model.")){
DataSourceHolder.setDataSources("dsModel");
System.out.println("切换库model=="+DataSourceHolder.getDataSources());
}
}
/**
* 执行后将数据源置为空
*/
public void after() {
DataSourceHolder.setDataSources(null);
System.out.println("还原默认库=="+DataSourceHolder.getDataSources());
}
这样配置完后,当在controller层调用model包下的service时,就会触发切换数据源到dsModel,使用完后 会自动清空还原默认的数据源。
4.后记,踩过的坑:
以上弄好后启动项目测试,发现AOP织入不成功,前置通知根本就没有执行!!!这个问题坑了我一天!!后来网上各种搜资料发现有这么说的:
这是由于Spring与SpringMVC是2个不同的父子容器, @Aspect如果被spring容器加载的话,而@Controller注解的这些类的实例化以及注入却是由SpringMVC来完成。 @Aspect如果被spring容器加载的时候,可能Spring MVC容器还未初始化, Controller类还未初始化,所以无法正常织入。
解决办法是:把aop的配置()放到springmvc的配置文件中。
重启后,发现可以了。。
参考博客:
http://www.linhao007.com/2017/11/23/01/
https://blog.csdn.net/weixin_44364953/article/details/86309803