• SSM框架使用多数据源(druid连接池)


     最近有个数据归集的需求用到了多数据源,在业务库保存后同时向归集库插入或数据。之前好像还没做过这块的东西,简单记录下防止下次又忘记了~

    踩过的几个坑都是某些知识点不熟悉导致的,而且都是框架配置相关的..

    先上代码,再扯淡

    两个库都是mysql,不同数据库应该就是配置不一样,使用的druid数据库连接池

    一、修改properties配置文件中的数据库信息

    1. #jdbc configure
    2. connection.url=${db.url}
    3. connection.username=${db.username}
    4. connection.password=${db.password}
    5. connection.driver=${connection.driver_class}
    6. #删掉此配置 退回单数据源
    7. connection.url2=${tradding.db.url2}
    8. connection.username2=${tradding.db.username2}
    9. connection.password2=${tradding.db.password2}
    10. connection.driver2=${connection.driver_class2}

    二、创建@DataSourceAnnotation 注解使用于aop进行数据源的切换

    1. package cn.xxx.datasource;
    2. import java.lang.annotation.*;
    3. /**
    4. * @author zj
    5. * @creatTime 2022-11-22
    6. * @description
    7. */
    8. @Target({ElementType.TYPE, ElementType.METHOD})
    9. @Retention(RetentionPolicy.RUNTIME)
    10. @Documented
    11. public @interface DataSourceAnnotation {
    12. String value();
    13. String primary = "primary";
    14. String secend= "secend";
    15. }

    三、创建动态数据源DynamicDataSource继承AbstractRoutingDataSource

    1. package cn.xxx.datasource;
    2. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    3. /**
    4. * @author zj
    5. * @creatTime 2022-11-18
    6. * @description 配置多个数据源
    7. */
    8. public class DynamicDataSource extends AbstractRoutingDataSource {
    9. /**
    10. * 数据源标识,保存在线程变量中,避免多线程操作数据源时互相干扰
    11. */
    12. private static final ThreadLocal key = new ThreadLocal();
    13. @Override
    14. protected Object determineCurrentLookupKey() {
    15. return key.get();
    16. }
    17. /**
    18. * 设置数据源
    19. *
    20. * @param dataSource 数据源名称
    21. */
    22. public static void setDataSource(String dataSource) {
    23. key.set(dataSource);
    24. }
    25. /**
    26. * 获取数据源
    27. *
    28. * @return
    29. */
    30. public static String getDatasource() {
    31. return key.get();
    32. }
    33. /**
    34. * 清除数据源
    35. */
    36. public static void clearDataSource() {
    37. key.remove();
    38. }
    39. }

    四、创建一个aop切面作用于目标方法上

    1. package cn.xxx.datasource;
    2. import org.slf4j.Logger;
    3. import org.slf4j.LoggerFactory;
    4. import org.springframework.aop.AfterReturningAdvice;
    5. import org.springframework.aop.MethodBeforeAdvice;
    6. import org.springframework.stereotype.Component;
    7. import java.lang.reflect.Method;
    8. /**
    9. * @author zj
    10. * @creatTime 2022-11-22
    11. * @description
    12. */
    13. @Component
    14. public class DynamicDataSourceAspect implements MethodBeforeAdvice, AfterReturningAdvice {
    15. Logger log = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
    16. @Override
    17. public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
    18. //这里做一个判断,有使用DataSourceAnnotation注解时才关闭数据源,有一个主要的数据源,就没有必要每次都去关闭
    19. if (method.isAnnotationPresent(DataSourceAnnotation.class)) {
    20. DynamicDataSource.clearDataSource();
    21. log.debug("数据源已关闭");
    22. }
    23. }
    24. /**
    25. * 拦截目标方法,获取由@DataSourceAnnotation指定的数据源标识,设置到线程存储中以便切换数据源
    26. */
    27. @Override
    28. public void before(Method method, Object[] objects, Object o) throws Throwable {
    29. if (method.isAnnotationPresent(DataSourceAnnotation.class)) {
    30. DataSourceAnnotation dataSourceAnnotation = method.getAnnotation(DataSourceAnnotation.class);
    31. DynamicDataSource.setDataSource(dataSourceAnnotation.value());
    32. log.debug("数据源切换为:" + DynamicDataSource.getDatasource());
    33. }
    34. }
    35. }

    五、spring-config.xml配置多数据源

    1. <bean id="dataSourcePrimary" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    2. <property name="url" value="${connection.url}"/>
    3. <property name="username" value="${connection.username}"/>
    4. <property name="password" value="${connection.password}"/>
    5. <property name="initialSize" value="${druid.initialSize}"/>
    6. <property name="minIdle" value="${druid.minIdle}"/>
    7. <property name="maxActive" value="${druid.maxActive}"/>
    8. <property name="maxWait" value="${druid.maxWait}"/>
    9. <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" />
    10. <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />
    11. <property name="validationQuery" value="${druid.validationQuery}" />
    12. <property name="testWhileIdle" value="${druid.testWhileIdle}" />
    13. <property name="testOnBorrow" value="${druid.testOnBorrow}" />
    14. <property name="testOnReturn" value="${druid.testOnReturn}" />
    15. <property name="filters" value="${druid.filters}" />
    16. bean>
    17. <bean id="dataSourceSecend" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    18. <property name="url" value="${connection.url2}"/>
    19. <property name="username" value="${connection.username2}"/>
    20. <property name="password" value="${connection.password2}"/>
    21. <property name="initialSize" value="${druid.initialSize}"/>
    22. <property name="minIdle" value="${druid.minIdle}"/>
    23. <property name="maxActive" value="${druid.maxActive}"/>
    24. <property name="maxWait" value="${druid.maxWait}"/>
    25. <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" />
    26. <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />
    27. <property name="validationQuery" value="${druid.validationQuery}" />
    28. <property name="testWhileIdle" value="${druid.testWhileIdle}" />
    29. <property name="testOnBorrow" value="${druid.testOnBorrow}" />
    30. <property name="testOnReturn" value="${druid.testOnReturn}" />
    31. <property name="filters" value="${druid.filters}" />
    32. bean>
    33. <bean id="dataSource" class="cn.pinming.datasource.DynamicDataSource">
    34. <property name="targetDataSources">
    35. <map key-type="java.lang.String">
    36. <entry key="primary" value-ref="dataSourcePrimary"/>
    37. <entry key="secend" value-ref="dataSourceSecend"/>
    38. map>
    39. property>
    40. <property name="defaultTargetDataSource" ref="dataSourcePrimary"/>
    41. bean>

    六、配置事务和AOP

    1. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    2. <property name="dataSource" ref="dataSource" />
    3. bean>
    4. <tx:advice id="txAdvice" transaction-manager="transactionManager">
    5. <tx:attributes>
    6. <tx:method name="doReweight" propagation="REQUIRES_NEW"/>
    7. <tx:method name="doClear*" propagation="REQUIRES_NEW"/>
    8. <tx:method name="doSend*" propagation="REQUIRES_NEW"/>
    9. <tx:method name="doBatchSave*" propagation="REQUIRES_NEW"/>
    10. <tx:method name="time*" propagation="REQUIRES_NEW"/>
    11. <tx:method name="get*" propagation="REQUIRED" read-only="true"/>
    12. <tx:method name="count*" propagation="REQUIRED" read-only="true"/>
    13. <tx:method name="find*" propagation="REQUIRED" read-only="true"/>
    14. <tx:method name="search*" propagation="REQUIRED" read-only="true"/>
    15. <tx:method name="select*" propagation="REQUIRED" read-only="true"/>
    16. <tx:method name="package*" propagation="REQUIRED" read-only="true"/>
    17. <tx:method name="*" propagation="REQUIRED"/>
    18. tx:attributes>
    19. tx:advice>
    20. <bean id="dynamicDataSourceAspect" class="cn.pinming.datasource.DynamicDataSourceAspect">
    21. bean>
    22. <aop:config proxy-target-class="true">
    23. <aop:pointcut id="myPointcut" expression="execution(* cn.xxx.test.service.TestSourceService.*(..))"/>
    24. <aop:advisor advice-ref="dynamicDataSourceAspect" pointcut-ref="myPointcut" order="1"/>
    25. <aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut" order="2"/>
    26. aop:config>

    这里需要注意的是order值越小,优先级越高,所以切换数据源order的值要比事务切面的值小,否则会出现数据源切换失败!

    七、切换数据源,实际使用

    在需要切换为非默认数据的方法上加@DataSourceAnnotation(DataSourceAnnotation.secend)就可以完成数据源的切换了。

    controller层:

    1. /**
    2. * @author zj
    3. * @creatTime 2022-11-18
    4. * @description
    5. */
    6. @Controller
    7. public class TestController {
    8. @Autowired
    9. private TPProjectMapper projectMapper;
    10. @Autowired
    11. private TestSourceService testSourceService;
    12. @RequestMapping("/ocx/saveOrUpdateTest")
    13. @ResponseBody
    14. public Result saveOrUpdateTest(){
    15. Result result = new Result();
    16. //先测试插入 监督库
    17. TPProject project = new TPProject();
    18. project.setProjectId(111221212L);
    19. project.setProjectName("test测试多数据源——监督库");
    20. ContextFacade.initEntity(project);
    21. projectMapper.insert(project);
    22. //再测试插入 交易库
    23. testSourceService.saveOrUpdateTest();
    24. //再测试修改 监督库
    25. TPProject project1 = projectMapper.selectByPrimaryKey(111221212L);
    26. project1.setProjectId(111221212L);
    27. project1.setProjectName("test测试多数据源——监督库__二次修改结果");
    28. projectMapper.updateByPrimaryKey(project1);
    29. return result;
    30. }
    31. }

    service层:

    1. package cn.xxx.test.service;
    2. import org.springframework.beans.factory.annotation.Autowired;
    3. import org.springframework.stereotype.Service;
    4. /**
    5. * @author zj
    6. * @creatTime 2022-11-18
    7. * @description
    8. */
    9. @Service
    10. public class TestSourceService {
    11. @Autowired
    12. private TPProjectMapper tpProjectMapper;
    13. @DataSourceAnnotation(DataSourceAnnotation.secend)
    14. public Result saveOrUpdateTest(){
    15. Result result = new Result();
    16. TPProject project = new TPProject();
    17. project.setProjectId(111221213L);
    18. project.setProjectName("test测试多数据源——交易库");
    19. ContextFacade.initEntity(project);
    20. tpProjectMapper.insert(project);
    21. return result;
    22. }
    23. }

    八、结果

    主库:

    副库:

     这里为了方便用了同一个表,只是不同数据库,所以mapper和sql也一样的,实际根据需求来就行。

    九、Other

    1、SpringAOP切面类不运行的问题 ,注意配置写在spring-config.xml而不是spring-mvc.xml中。

    2、aop:pointcut的配置说明

    其中expression="execution(* com.aop.service..*(..))"的配置规则如下:

    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
    execution(方法的操作权限    返回值类型模式    方法所在的包        方法名 (参数名)  异常)

    参数名(参数模式)稍微复杂一些:
    ()                 匹配不带参数的方法,
    (..)               匹配任何数量(零个或多个)参数。
    (*)                模式匹配采用任何类型的一个参数的方法
    (*,String)     匹配一个带有两个参数的方法。第一个可以是任何类型,而第二个必须是String


    返回值,方法名,参数名,必须有,其他可选


    执行任何公共方法:
    execution(public * *(..))


    执行名称以以下开头的任何方法set:
    execution(* set*(..))


    执行AccountService接口定义的任何方法:
    execution(* com.xyz.service.AccountService.*(..))


    执行service包中定义的任何方法:
    execution(* com.xyz.service.*.*(..))


    执行服务包或其子包中定义的任何方法:
    execution(* com.xyz.service..*.*(..))

    问题:在controller层调用不同的service可以正常切换数据源,但是在service层调用另一个数据源就会失效。 原因是AbstractRoutingDataSource  加事务导致切换数据源失败。。 

    解决可参考:SpringBoot多数据源切换详解,以及开启事务后数据源切换失败处理_zzhongcy的博客-CSDN博客_动态数据源事务失效

  • 相关阅读:
    评价聚类的方法
    【Git】:远程仓库操作
    计算机视觉项目-文档扫描OCR识别
    SpringBoot的测试方案
    期刊论文如何降低重复率?
    【2023复旦微电子提前批笔试题】~ 题目及参考答案
    【触想智能】工业触摸显示器在户外使用需要注意哪些问题?
    无涯教程-JavaScript - COUPNUM函数
    wangEditor富文本编辑器的调用开发实录(v5版本、多个编辑器、php后端上传视频阿里云OSS、编辑HTML回显)
    Babel 插件通关秘籍
  • 原文地址:https://blog.csdn.net/sunon_/article/details/127987035