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

目录

  • 1. 原理
    • 1.1 实现多数据源的原理
    • 1.2 多数据源本地事务的原理
  • 2. Mysql数据准备
  • 3. pom.xml依赖
  • 4. application.properties配置
  • 5. 创建两个SqlSessionFactory
    • 5.1 DataSource1Config
    • 5.1 DataSource2Config
  • 6. 动态数据源测试
    • 6.1 创建User类
    • 6.2 Mapper接口实现
    • 6.3 Mapper.xml实现
    • 6.4 Service实现
    • 6.5 测试
  • 7. AOP + 自定义注解实现事务(未测试)

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依赖

        
            mysql
            mysql-connector-java
            8.0.31
        

        
            com.alibaba
            druid
            1.2.15
        

        
        
            com.baomidou
            mybatis-plus-boot-starter
            3.5.2
        

	  
        
            org.springframework.boot
            spring-boot-starter-aop
        

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





    
    
        INSERT INTO user(id, name) VALUES(#{id}, #{name})
    


resources/mapper/datasource2/Datasource2UserMapper.xml





    
    
        INSERT INTO user(id, name) VALUES(#{id}, #{name})
    


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());

    }

}

你可能感兴趣的:(#,SpringBoot,spring,boot,mybatis,集成多个mybatis框架,多JDBC数据源,多数据源本地事务)