SpringBoot采用集成多个Mybatis框架实现多JDBC数据源 + 多数据源本地事务

1. 原理

1.1 实现多数据源的原理

通过启动多个SqlSessionFactoryBean,每个SqlSessionFactoryBean对应一个datasource和指定位置的mapper.xml文件,就可以实现多个数据源了。而不用切换数据源,不用实现AbstractRoutingDataSource

1.2 多数据源本地事务的原理

在多数据源下,涉及到多个数据库的写入。Spring的声明式事务在一次请求线程中只能对一个数据源进行控制。因为一个DataSourceTransactionManager无法完成对多数据源的控制

需要多个DataSourceTransactionManager可以完成,但@Transactional注解无法管理多个数据源,这里我们通过变通的方式让@Transactional管理多个DataSourceTransactionManager

将每个datasource都分别和一个DataSourceTransactionManager进行绑定。然后可以通过嵌套事务的方式进行调用

2. Mysql数据准备

分别创建write_db1.user和write_db2.user

mysql> create database write_db1;
Query OK, 1 row affected (0.14 sec)

mysql> create database write_db2;
Query OK, 1 row affected (0.01 sec)

mysql> create table write_db1.user (
    -> id bigint(20) auto_increment not null comment '主键ID',
    -> name varchar(30) null default null comment '姓名',
    -> primary key (id)
    -> );
Query OK, 0 rows affected, 1 warning (0.29 sec)

mysql> 
mysql> create table write_db2.user (
    -> id bigint(20) auto_increment not null comment '主键ID',
    -> name varchar(30) null default null comment '姓名',
    -> primary key (id)
    -> );
Query OK, 0 rows affected, 1 warning (0.04 sec)

mysql> 

3. pom.xml依赖

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.31</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.15</version>
        </dependency>

        <!-- 支持spring 2.5.3 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>

	  <!-- 用于编程式事务 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

4. application.properties配置

指定了datasource1和datasource2两个DataSource

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

# 用于读的数据库
spring.datasource.datasource1.url=jdbc:mysql://192.168.28.12:3306/write_db1
spring.datasource.datasource1.username=root
spring.datasource.datasource1.password=Root_123
spring.datasource.datasource1.driver-class-name=com.mysql.cj.jdbc.Driver

# 用于写的数据库
spring.datasource.datasource2.url=jdbc:mysql://192.168.28.12:3306/write_db2
spring.datasource.datasource2.username=root
spring.datasource.datasource2.password=Root_123
spring.datasource.datasource2.driver-class-name=com.mysql.cj.jdbc.Driver

5. 创建两个SqlSessionFactory

5.1 DataSource1Config

其实就是将datasource1和指定的Mapper接口、Mapper.xml文件进行绑定。然后将datasource1TransactionManager和datasource1绑定,datasource1TransactionTemplate和datasource1TransactionManager进行绑定

说明:

  • 通过@MapperScan注解,指定Mapper接口的位置和SqlSessionFactory的名称
  • 指定了要连接的数据源datasource1
  • 指定了Mapper.xml文件的位置
package com.hh.springboottest.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;


@Configuration
@MapperScan(basePackages = {"com.hh.springboottest.mapper.datasource1"},
        sqlSessionFactoryRef = "datasource1SqlSessionFactory")
public class DataSource1Config {

    @ConfigurationProperties(prefix = "spring.datasource.datasource1")
    // 向IOC容器添加name = dataSource1的DataSource
    @Bean
    public DataSource dataSource1() {
        DruidDataSource druidDataSource = new DruidDataSource();

        return druidDataSource;
    }


    @Bean
    @Primary
    public SqlSessionFactory datasource1SqlSessionFactory(@Qualifier("dataSource1") DataSource dataSource) throws Exception {

        final SqlSessionFactoryBean datasource1SqlSessionFactoryBean = new SqlSessionFactoryBean();
        // 指定数据源
        datasource1SqlSessionFactoryBean.setDataSource(dataSource);
        // 指定数据源对应的mapper.xml文件
        datasource1SqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/datasource1/*.xml"));

        return datasource1SqlSessionFactoryBean.getObject();
    }

    @Bean
    @Primary
    public DataSourceTransactionManager datasource1TransactionManager() {
        DataSourceTransactionManager datasource1TransactionManager =
                new DataSourceTransactionManager();

        datasource1TransactionManager.setDataSource(dataSource1());
        return datasource1TransactionManager;
    }

    @Bean
    public TransactionTemplate datasource1TransactionTemplate() {
        return new TransactionTemplate(datasource1TransactionManager());
    }


}

5.1 DataSource2Config

package com.hh.springboottest.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = {"com.hh.springboottest.mapper.datasource2"},
        sqlSessionFactoryRef = "datasource2SqlSessionFactory")
public class DataSource2Config {


    @ConfigurationProperties(prefix = "spring.datasource.datasource2")
    // 向IOC容器添加name = dataSource2的DataSource
    @Bean
    public DataSource dataSource2() {
        DruidDataSource druidDataSource = new DruidDataSource();

        return druidDataSource;
    }


    @Bean
    public SqlSessionFactory datasource2SqlSessionFactory(@Qualifier("dataSource2") DataSource dataSource) throws Exception {

        final SqlSessionFactoryBean datasource2SqlSessionFactoryBean = new SqlSessionFactoryBean();
        // 指定数据源
        datasource2SqlSessionFactoryBean.setDataSource(dataSource);
        // 指定数据源对应的mapper.xml文件
        datasource2SqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/datasource2/*.xml"));

        return datasource2SqlSessionFactoryBean.getObject();
    }


    @Bean
    public DataSourceTransactionManager datasource2TransactionManager() {
        DataSourceTransactionManager datasource2TransactionManager =
                new DataSourceTransactionManager();

        datasource2TransactionManager.setDataSource(dataSource2());
        return datasource2TransactionManager;
    }


    @Bean
    public TransactionTemplate datasource2TransactionTemplate() {
        return new TransactionTemplate(datasource2TransactionManager());
    }

}

6. 动态数据源测试

6.1 创建User类

package com.hh.springboottest.myController;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString
public class User {

    private Long id;
    private String name;

}

6.2 Mapper接口实现

Datasource1UserMapper.java

package com.hh.springboottest.mapper.datasource1;

import com.hh.springboottest.myController.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface Datasource1UserMapper {


    public void saveUser(User user);

}

Datasource2UserMapper.java

package com.hh.springboottest.mapper.datasource2;

import com.hh.springboottest.myController.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface Datasource2UserMapper {


    public void saveUser(User user);
}

6.3 Mapper.xml实现

resources/mapper/datasource1/Datasource1UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hh.springboottest.mapper.datasource1.Datasource1UserMapper">

    <!-- public void saveUser(User user); -->
    <insert id="saveUser" parameterType="com.hh.springboottest.myController.User">
        INSERT INTO user(id, name) VALUES(#{id}, #{name})
    </insert>

</mapper>

resources/mapper/datasource2/Datasource2UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hh.springboottest.mapper.datasource2.Datasource2UserMapper">

    <!-- public void saveUser(User user); -->
    <insert id="saveUser" parameterType="com.hh.springboottest.myController.User">
        INSERT INTO user(id, name) VALUES(#{id}, #{name})
    </insert>

</mapper>

6.4 Service实现

Service接口实现

package com.hh.springboottest.service;

import com.hh.springboottest.myController.User;

public interface UserService {

    // 向datasource1的数据库插入数据
    public void datasource1SaveUser(User user);

    // 向datasource2的数据库插入数据
    public void datasource2SaveUser(User user);

    // 用于调用datasource1SaveUser和datasource2SaveUser
    public void saveMultiUser();

    // 嵌套在saveMultiUser里面进行调用
    public void saveMultiUserInner();
}

ServiceImpl实现类

说明:

  • 编程式事务:通过两层lambda表达式嵌套,实现两个数据源的统一事务管理,对异常进行捕获,然后手动进行回滚
  • 声明式事务:通过两层事务注解方法进行嵌套,实现两个数据源的统一事务管理。不能进行异常捕获,自动进行回滚,然后抛出异常
package com.hh.springboottest.service.impl;

import com.hh.springboottest.mapper.datasource1.Datasource1UserMapper;
import com.hh.springboottest.mapper.datasource2.Datasource2UserMapper;
import com.hh.springboottest.myController.User;
import com.hh.springboottest.service.UserService;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    Datasource1UserMapper datasource1UserMapper;

    @Autowired
    Datasource2UserMapper datasource2UserMapper;


    @Autowired
    TransactionTemplate datasource1TransactionTemplate;

    @Autowired
    TransactionTemplate datasource2TransactionTemplate;


    /**
     * @Description: 向datasource1的数据库插入数据
     *
     * @Params:    [user]
     * @Return:    void
     */
    @Override
    public void datasource1SaveUser(User user) {
        datasource1UserMapper.saveUser(user);
    }

    /**
     * @Description: 向datasource2的数据库插入数据
     *
     * @Params:    [user]
     * @Return:    void
     */
    @Override
    public void datasource2SaveUser(User user) {
        datasource2UserMapper.saveUser(user);
    }


    /**
    * @Description: 用于调用datasource1SaveUser和datasource2SaveUser  
    *      
    * 编程式事务方式。被0除,回滚所以插入的数据
    *
    * @Params:    []
    * @Return:    void
    */
//    public void saveMultiUser() {
//        datasource1TransactionTemplate.execute((datasource1Status)->{
//            datasource2TransactionTemplate.execute((datasource2Status)->{
//                try {
//                    datasource1SaveUser(new User(1L, "read_name1"));
//                    datasource2SaveUser(new User(1L, "write_name1"));
//                    datasource1SaveUser(new User(2L, "read_name2"));
//                    datasource2SaveUser(new User(2L, "write_name2"));
//                    Integer d = 1 / 0;
//                } catch (Exception e) {
//                    e.printStackTrace();
//                    datasource1Status.setRollbackOnly();
//                    datasource2Status.setRollbackOnly();
//                    return false;
//                }
//                return true;
//            });
//            return true;
//        });
//    }


    /**
     * @Description: 用于调用datasource1SaveUser和datasource2SaveUser
     *
     * 声明式事务方式。被0除,回滚所以插入的数据
     *
     * @Params:    []
     * @Return:    void
     */
    @Transactional(transactionManager = "datasource1TransactionManager")
    public void saveMultiUser() {
        UserService currentUserService = (UserService) AopContext.currentProxy();
        currentUserService.saveMultiUserInner();
    }


    /**
    * @Description: 嵌套在saveMultiUser里面进行调用  
    * 
    * @Params:    []
    * @Return:    void
    */
    @Transactional(transactionManager = "datasource2TransactionManager")
    public void saveMultiUserInner() {
        datasource1SaveUser(new User(1L, "read_name1"));
        datasource2SaveUser(new User(1L, "write_name1"));
        datasource1SaveUser(new User(2L, "read_name2"));
        datasource2SaveUser(new User(2L, "write_name2"));
        Integer myDiv = 1 / 0;
    }

}

6.5 测试

说明:

  • EnableTransactionManagement:开启事务控制功能
  • EnableAspectJAutoProxy注解表示开启AOP功能,exposeProxy为true表示让proxy被AOP框架当做ThreadLocal进行暴露,以便通过org.springframework.aop.framework.AopContext类进行获取,默认关闭,不能通过本类调用本类
package com.hh.springboottest;


import com.hh.springboottest.myController.User;
import com.hh.springboottest.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Slf4j
@SpringBootTest
@EnableTransactionManagement   
@EnableAspectJAutoProxy(exposeProxy = true)
public class MyApplicationTest {



    @Autowired
    UserService userService;

    @Test
    public void saveMultiUserTest() {
        userService.saveMultiUser();
    }

}

运行程序,结果如下:

2022-12-13 22:04:29.857  INFO 21468 --- [           main] com.alibaba.druid.pool.DruidDataSource   : {dataSource-2} inited

java.lang.ArithmeticException: / by zero

	at com.hh.springboottest.service.impl.UserServiceImpl.saveMultiUserInner(UserServiceImpl.java:117)
......省略部分......

数据库未插入一条数据,因为回滚了

7. AOP + 自定义注解实现事务(未测试)

这里只做了一部分的记录,并未运行进行测试

自定注解,然后通过多线程的方式执行多个事务方法

package com.hh.springboottest.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


@Component
@Aspect
public class MultiTransactionAop {

    // ComboTransaction类需要自己定义,还未定义
    private final ComboTransaction comboTransaction;

    @Autowired
    public MultiTransactionAop(ComboTransaction comboTransaction) {
        this.comboTransaction = comboTransaction
    }

    @Pointcut("within(com.hh.springboottest.service.impl.*)")
    public void pointCut() {

    }

    @Around("pointCut() && @annotation(multiTransactional)")
    public Object inMultiTransactions(ProceedingJoinPoint pjp, MultiTransactional multiTransactional) {
        return comboTransaction.inCombinedTx(() -> {
            try {
                return pjp.proceed();       // 执行目标方法
            } catch (Throwable throwable) {
                if (throwable instanceof RuntimeException) {
                    throw (RuntimeException) throwable;
                }
                throw new RuntimeException(throwable);
            }
        }, multiTransactional.value());

    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 是一个用于快速构建 Java 应用程序的框架。它可以与多种其他框架和组件进行整合,以实现更丰富的功能。在这里,我们将讨论如何使用 Spring Boot 整合 Druid、MyBatis、JTA 分布式事务以及多数据,同时使用 AOP 注解实现动态切换。 首先,我们可以在 Spring Boot 中集成 Druid 数据。Druid 是一个高性能的 JDBC 连接池,可以提供监控和统计功能。我们可以通过在 pom.xml 文件中添加相关的依赖,并在 application.properties 文件中配置数据信息,来实现 Druid 的集成。 接下来,我们可以整合 MyBatis 框架,它是一种优秀的持久化解决方案。我们可以使用 MyBatis 来操作数据库,并将其与 Druid 数据进行整合。为此,我们需要在 pom.xml 文件中添加 MyBatisMyBatis-Spring 的依赖,并配置 MyBatis 的相关配置文件。 此外,我们还可以使用 JTA(Java Transaction API)实现分布式事务。JTA 可以在分布式环境中协调多个参与者的事务操作。我们可以在 pom.xml 文件中添加 JTA 的依赖,并在 Spring Boot 的配置文件中配置 JTA 的相关属性,以实现分布式事务的支持。 在实现数据时,我们可以使用 Spring Boot 的 AbstractRoutingDataSource 来实现动态切换数据。这个类可以根据当前线程或其他条件选择不同的数据来进行数据操作。我们可以通过继承 AbstractRoutingDataSource 并实现 determineCurrentLookupKey() 方法来指定当前数据的 key。然后,在配置文件中配置多个数据,并将数据注入到 AbstractRoutingDataSource 中,从而实现动态切换。 最后,我们可以使用 AOP(Aspect Oriented Programming)注解来实现动态切换。AOP 是一种编程范式,可以通过在代码中插入特定的切面(Aspect)来实现横切关注点的处理。我们可以在代码中使用注解来标记需要切换数据的方法,然后使用 AOP 技术来拦截这些方法,并根据注解中指定的数据信息来进行数据的切换。 综上所述,通过整合 Druid、MyBatis、JTA 分布式事务以及多数据,并使用 AOP 注解实现动态切换,我们可以在 Spring Boot 中实现强大而灵活的应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值