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

news2024/11/28 10:44:39

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

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

先上代码,再扯淡

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

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

#jdbc configure

connection.url=${db.url}
connection.username=${db.username}
connection.password=${db.password}
connection.driver=${connection.driver_class}

#删掉此配置 退回单数据源
connection.url2=${tradding.db.url2}
connection.username2=${tradding.db.username2}
connection.password2=${tradding.db.password2}
connection.driver2=${connection.driver_class2}

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

package cn.xxx.datasource;

import java.lang.annotation.*;

/**
 * @author zj
 * @creatTime 2022-11-22
 * @description
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceAnnotation {

    String value();

    String primary = "primary";

    String secend= "secend";

}

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

package cn.xxx.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @author zj
 * @creatTime 2022-11-18
 * @description 配置多个数据源
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 数据源标识,保存在线程变量中,避免多线程操作数据源时互相干扰
     */
    private static final ThreadLocal<String> key = new ThreadLocal<String>();

    @Override
    protected Object determineCurrentLookupKey() {
        return key.get();
    }

    /**
     * 设置数据源
     *
     * @param dataSource 数据源名称
     */
    public static void setDataSource(String dataSource) {
        key.set(dataSource);
    }

    /**
     * 获取数据源
     *
     * @return
     */
    public static String getDatasource() {
        return key.get();
    }

    /**
     * 清除数据源
     */
    public static void clearDataSource() {
        key.remove();
    }

}

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

package cn.xxx.datasource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author zj
 * @creatTime 2022-11-22
 * @description
 */
@Component
public class DynamicDataSourceAspect implements MethodBeforeAdvice, AfterReturningAdvice {

    Logger log = LoggerFactory.getLogger(DynamicDataSourceAspect.class);


    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        //这里做一个判断,有使用DataSourceAnnotation注解时才关闭数据源,有一个主要的数据源,就没有必要每次都去关闭
        if (method.isAnnotationPresent(DataSourceAnnotation.class)) {
            DynamicDataSource.clearDataSource();
            log.debug("数据源已关闭");
        }

    }

    /**
     * 拦截目标方法,获取由@DataSourceAnnotation指定的数据源标识,设置到线程存储中以便切换数据源
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        if (method.isAnnotationPresent(DataSourceAnnotation.class)) {
            DataSourceAnnotation dataSourceAnnotation = method.getAnnotation(DataSourceAnnotation.class);
            DynamicDataSource.setDataSource(dataSourceAnnotation.value());
            log.debug("数据源切换为:" + DynamicDataSource.getDatasource());
        }
    }
}

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

  <!-- 数据源1 -->
    <bean id="dataSourcePrimary" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 基本属性 url、user、password -->
        <property name="url" value="${connection.url}"/>
        <property name="username" value="${connection.username}"/>
        <property name="password" value="${connection.password}"/>

        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="${druid.initialSize}"/>
        <property name="minIdle" value="${druid.minIdle}"/>
        <property name="maxActive" value="${druid.maxActive}"/>

        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="${druid.maxWait}"/>
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" />

        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />

        <property name="validationQuery" value="${druid.validationQuery}" />
        <property name="testWhileIdle" value="${druid.testWhileIdle}" />
        <property name="testOnBorrow" value="${druid.testOnBorrow}" />
        <property name="testOnReturn" value="${druid.testOnReturn}" />
        <!-- 配置监控统计拦截的filters -->
        <property name="filters" value="${druid.filters}" />
    </bean>

    <!-- 数据源2 -->

    <bean id="dataSourceSecend" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 基本属性 url、user、password -->
        <property name="url" value="${connection.url2}"/>
        <property name="username" value="${connection.username2}"/>
        <property name="password" value="${connection.password2}"/>

        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="${druid.initialSize}"/>
        <property name="minIdle" value="${druid.minIdle}"/>
        <property name="maxActive" value="${druid.maxActive}"/>

        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="${druid.maxWait}"/>
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" />

        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />

        <property name="validationQuery" value="${druid.validationQuery}" />
        <property name="testWhileIdle" value="${druid.testWhileIdle}" />
        <property name="testOnBorrow" value="${druid.testOnBorrow}" />
        <property name="testOnReturn" value="${druid.testOnReturn}" />
        <!-- 配置监控统计拦截的filters -->
        <property name="filters" value="${druid.filters}" />
    </bean>


  <!-- 动态数据源配置  如果之前没有这块那这个id直接用原数据源的名称,然后把原来数据源重命名,这样有些配置就不会漏改。。-->
    <bean id="dataSource" class="cn.pinming.datasource.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <!-- 主库-1 -->
                <entry key="primary" value-ref="dataSourcePrimary"/>
                <!-- 副库-2 -->
                <entry key="secend" value-ref="dataSourceSecend"/>
            </map>
        </property>
        <!--默认数据源-->
        <property name="defaultTargetDataSource" ref="dataSourcePrimary"/>
    </bean>

六、配置事务和AOP

 <!--事务管理器配置-->

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="doReweight" propagation="REQUIRES_NEW"/>
            <tx:method name="doClear*" propagation="REQUIRES_NEW"/>
            <tx:method name="doSend*" propagation="REQUIRES_NEW"/>
            <tx:method name="doBatchSave*" propagation="REQUIRES_NEW"/>
            <tx:method name="time*" propagation="REQUIRES_NEW"/>

            <!--hibernate4必须配置为开启事务 否则 getCurrentSession()获取不到-->
            <tx:method name="get*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="count*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="find*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="search*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="select*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="package*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>


    <bean id="dynamicDataSourceAspect" class="cn.pinming.datasource.DynamicDataSourceAspect">
    </bean>

    <!--设置切面  com.test.service.*.impl.*()-->
    <aop:config proxy-target-class="true">
        <aop:pointcut id="myPointcut" expression="execution(* cn.xxx.test.service.TestSourceService.*(..))"/>
        <aop:advisor advice-ref="dynamicDataSourceAspect" pointcut-ref="myPointcut" order="1"/>
        <!--把事务控制在Service层-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut" order="2"/>
    </aop:config>

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

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

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

controller层:

/**
 * @author zj
 * @creatTime 2022-11-18
 * @description
 */
@Controller
public class TestController {

    @Autowired
    private TPProjectMapper projectMapper;
    @Autowired
    private TestSourceService testSourceService;

    @RequestMapping("/ocx/saveOrUpdateTest")
    @ResponseBody
    public Result saveOrUpdateTest(){
        Result result = new Result();
        //先测试插入 监督库
        TPProject project = new TPProject();
        project.setProjectId(111221212L);
        project.setProjectName("test测试多数据源——监督库");
        ContextFacade.initEntity(project);
        projectMapper.insert(project);
        //再测试插入 交易库
        testSourceService.saveOrUpdateTest();
        //再测试修改 监督库
        TPProject project1 = projectMapper.selectByPrimaryKey(111221212L);
        project1.setProjectId(111221212L);
        project1.setProjectName("test测试多数据源——监督库__二次修改结果");
        projectMapper.updateByPrimaryKey(project1);

        return result;
    }


}

service层:

package cn.xxx.test.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author zj
 * @creatTime 2022-11-18
 * @description
 */
@Service
public class TestSourceService {

    @Autowired
    private TPProjectMapper tpProjectMapper;

    @DataSourceAnnotation(DataSourceAnnotation.secend)
    public Result saveOrUpdateTest(){
        Result result = new Result();
        TPProject project = new TPProject();
        project.setProjectId(111221213L);
        project.setProjectName("test测试多数据源——交易库");
        ContextFacade.initEntity(project);
        tpProjectMapper.insert(project);
        return result;
    }
}

八、结果

主库:

副库:

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

九、Other

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

2、aop:pointcut的配置说明

<aop:pointcut expression="execution(* com.aop.service..*(..))" 

其中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..*.*(..))

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/26824.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PyQt5 拖拽与剪贴板

拖拽与剪切板拖拽剪贴板拖拽 基于MIME类型的拖拽数据传输时基于QDrag类的QMimeData对象管理的数据与其对应的MIME类型相关联。 MimeData类函数允许检测和使用方法的MIME类型 判断函数设置函数获取函数MIME类型hasText()text()setText()text/plainhasHtml()html()setHtml()tex…

【Java八股文总结】之SpringBoot

文章目录SpringBoot1、Spring Boot的优点&#xff1f;2、Spring Boot自动配置原理3、如何定义一个SpringBoot Starter&#xff1f;4、SpringBoot启动原理&#xff1f;5、SpringBoot的常用注解Spring Cache1、Spring Cache介绍2、Spring Cache注解Sharding-JDBCSpringBoot 1、S…

关于vector的迭代器失效

目录 关于迭代器失效的判定 1 迭代器指向的位置是野指针&#xff08;全部迭代器失效&#xff09; 原因&#xff1a; 解决 2 erase或者insert之后迭代器被更改了&#xff08;部分迭代器失效&#xff09; 原因 迭代器失效的场景&#xff1a; 改进之后 部分迭代器失效之越…

十年前的AlexNet,今天的NeurIPS 2022时间检验奖

目录&#xff1a;十年前的AlexNet&#xff0c;今天的NeurIPS 2022时间检验奖一、前言二、时间检验奖一、前言 作为当前全球最负盛名的 AI 学术会议之一&#xff0c;NeurIPS 是每年学界的重要事件&#xff0c;通常在每年 12 月举办。大会讨论的内容包含深度学习、计算机视觉、大…

三款“非主流”日志查询分析产品初探

前言 近些年在开源领域&#xff0c;用于构建日志系统的软件有两类典型&#xff1a; Elasticsearch&#xff1a;基于 Lucene 构建倒排索引提供搜索功能&#xff0c;DocValue 存储支持了其统计分析能力。Clickhouse&#xff1a;列式存储是其优秀 OLAP 性能的保障。 这里把上述系…

Windows安装MySQL

目录 1、确认本地是否安装了mysql 2、下载安装包 3、安装mysql 4、修改mysql密码 5、配置环境变量 1、确认本地是否安装了mysql &#xff08;1&#xff09;按【winr】快捷键打开运行&#xff0c;输入services.msc&#xff0c;点击【确定】&#xff1b; &#xff08;2&…

大数据开发和软件开发哪个前景好?

大数据开发和软件开发哪个前景好&#xff1f;大数据开发学习有难度&#xff0c;零基础入门要先学习Java语言打基础&#xff0c;然后进入大数据技术体系的学习&#xff0c;学习Hadoop、Spark、Storm等知识。软件开发工程师根据不同的学科从事的岗位也千差万别。 先说说大数据开…

关于激光雷达传感器分类及简介

关于激光雷达传感器&#xff08;根据特性进行分类介绍&#xff09;0一些激光雷达的厂商总结1 基本介绍2 基本分类及组成2.1 激光雷达的相关分类2.1.1 机械式激光雷达&#xff1a;2.1.2 混合固态激光雷达&#xff1a;2.1.3 固态激光雷达&#xff1a;OPA和Flash固态激光雷达2.2 机…

Linux shell 脚本学习

目录 1.shell编程的基本格式 开头 注释 加可执行权限 执行脚本的三种方法 2.变量 定义变量和清除变量 从键盘获取值(read) 读取多个值 只读变量(readonly) 位置变量 变量运算 3.条件测试 文件测试 字符串测试 数值测试 逻辑操作符 4. if条件语句 if条件语句语法 单…

SPARKSQL3.0-源码剖析全流程导读

SPARKSQL-源码剖析全流程导读 一、处理流程 spark从一条sql语句一步一步转换成物理执行结果&#xff0c;这中间需要经历几个阶段&#xff0c;如下图&#xff1a; 二、各阶段概述 1、Unresolved-未解析阶段 此阶段主要做了两件事&#xff1a; 1、将sql字符串通过antrl4转化…

元数据管理系统

数据治理工具–元数据系统数据服务基础能力之元数据管理元数据管理系统设计 1.元数据概述 1.1 介绍 如果想建设好元数据系统&#xff0c;需要理解元数据系统的相关概念&#xff0c;如数据、数据模型、元数据、元模型、ETL、数据血缘等等。 首先&#xff0c;要清楚数据的定义…

python_函数

一、函数介绍组织好的&#xff0c;可重复使用的&#xff0c;用来实现特定功能的代码段例如&#xff1a;len() 函数--Python内置函数# 已组织好的、可重复使用&#xff0c;针对特定功能 def my_len(data):count 0for i in data:count 1print(f"字符串{data}的长度是{coun…

WebRTC实现一个网页在线录制视频

电脑录制视频几乎不会用到&#xff0c;当有需要的时候就各种找软件找工具&#xff0c;并且都会找免费的。现在市场上肯定有很多符合需求&#xff0c;只是那么偶尔的情况下&#xff0c;而且使用场景不是那么多要求的情况下&#xff0c;一个网页在线直接录屏是不是非常nice。 很…

40Java Runtime类

目录 Runtime类 1.概述 2.常见方法 (1).getRuntime返回环境对象 (2).exit停止虚拟机 (3).availableProcessors返回线程数 (4). maxMemory返回获得最大内存 (5).totalMemory返回已经获得内存 (6).freeMemory返回剩余内存 (7).exec运行cmd命令 Runtime类 1.概述 ​ Run…

双亲委派模型机制

文章目录类加载器findClassloadClassJDK双亲委派的破坏第一次破坏第二次破坏tomcat热部署JDK9最新改动双亲委派机制是当类加载器需要加载某一个.class字节码文件时&#xff0c;则首先会把这个任务委托给他的上级类加载器&#xff0c;递归这个操作&#xff0c;如果上级没有加载该…

卷积神经网络<二>keras实现多分支输入VGG

VGG的模型图 VGG使用Keras实现 这里的代码借鉴了VGG实现Keras&#xff0c;但是这段代码不支持多通道&#xff0c;并且vgg函数的扩展性不好。下面修改一下&#xff0c;方便进行多分支图片输入的建立&#xff0c;以及更见方便的调参。 # from keras.models import from keras.l…

MyBatis介绍

MyBatis介绍 MyBatis 是一款优秀的持久层框架&#xff0c;它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息&#xff0c;将接口和 Java 的 POJOs(Plain Ordi…

PMP考试自学可以吗?(含PMP备考资料)

当然是可以的&#xff0c;只要解决了“报考的35学时”这个问题&#xff0c;就只剩怎么备考的问题了。 在一般情况下&#xff0c;建议备考一到三个月&#xff0c;别给自己太长或太短的备考时间&#xff0c;前者坚持不下来&#xff0c;后者备考时间太少&#xff0c;来不及备考充…

戴尔大步进军经典量子计算混合模型

​ &#xff08;图片来源&#xff1a;网络&#xff09; 戴尔正将量子计算机融入传统IT的基础架构中&#xff0c;并向新型加速计算机开放了数据中心。这家服务器制造商为传统服务器基础设施创建了一个蓝图&#xff0c;以满足量子系统的独特需求&#xff0c;量子系统速度要比经典…

基于物联网的汽车爆胎预警系统

本设计是基于物联网的汽车爆胎预警系统的设计与实现设计&#xff0c;主要实现以下功能&#xff1a; 1&#xff0c;主机用LCD1602显示温度、气压和距离&#xff1b; 2&#xff0c;主从机间通过ZigBee进行数据的传输&#xff1b; 3&#xff0c;从机检测轮胎气压&#xff0c;温度…