springmvc 利用AOP实现多数据源动态切换

多数据源说白了就是项目里连接多个数据库;常见于分库查询(主从库),分库操作-增删改查(多库多表)等。

下面说下具体步骤:

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

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猩猩之火

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值