跳至主要內容

SpringBoot与数据库编程-2

wangdx大约 24 分钟

AOP 事务处理

1、
package com.yootk.config;

import org.aspectj.weaver.patterns.HasThisTypePatternTriedToSneakInSomeGenericOrParameterizedTypePatternMatchingStuffAnywhereVisitor;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.interceptor.TransactionInterceptor;

import java.util.HashMap;
import java.util.Map;

public class TransactionConfig { // 事务配置类
    private static final int TRANSACTION_METHOD_TIMEOUT = 5; // 事务处理的超时时间
    private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.yootk..service.*.*(..))";
    @Autowired
    private TransactionManager transactionManager; // 事务管理器
    @Bean("txAdvice")
    public TransactionInterceptor transactionInterceptorConfig() {
        // 配置数据读取事务规则
        RuleBasedTransactionAttribute readOnlyAttribute = new RuleBasedTransactionAttribute();
        readOnlyAttribute.setReadOnly(true); // 只读事务
        readOnlyAttribute.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED); // 非事务运行
        // 配置了数据更新事务规则
        RuleBasedTransactionAttribute requiredAttribute = new RuleBasedTransactionAttribute();
        requiredAttribute.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); // 事务开启
        requiredAttribute.setTimeout(TRANSACTION_METHOD_TIMEOUT); // 事务处理超时时间
        // 配置所有要进行事务处理的方法名称定义
        Map<String, TransactionAttribute> transactionAttributeMap = new HashMap<>();
        transactionAttributeMap.put("add*", requiredAttribute);
        transactionAttributeMap.put("edit*", requiredAttribute);
        transactionAttributeMap.put("delete*", requiredAttribute);
        transactionAttributeMap.put("list*", readOnlyAttribute);
        transactionAttributeMap.put("get*", readOnlyAttribute);
        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
        source.setNameMap(transactionAttributeMap); // 配置方法名称的映射
        TransactionInterceptor interceptor = new TransactionInterceptor(transactionManager, source);
        return interceptor;
    }
    @Bean("txAdvisor")
    public Advisor transactionAdvisor(
            @Autowired TransactionInterceptor interceptor
    ) {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
        return new DefaultPointcutAdvisor(pointcut, interceptor);
    }
}


2、
package com.yootk.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yootk.vo.Member;

import java.util.List;
import java.util.Set;

public interface IMemberService {
    public List<Member> list();
    public Member get(String mid); // 根据ID查询
    public boolean add(Member vo); // 增加数据
    public boolean delete(Set<String> ids); // 数据删除
    public IPage<Member> listSplit(String column, String keyword,
                                   Integer currentPage, Integer lineSize); // 分页查询
    public boolean addBatch(String ... mid); // 批量数据增加
}


3、
package com.yootk.service.impl.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yootk.dao.IMemberDAO;
import com.yootk.service.IMemberService;
import com.yootk.vo.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Set;

@Service
public class MemberServiceImpl implements IMemberService {
    @Autowired
    private IMemberDAO memberDAO;

    @Override
    public List<Member> list() {
        return this.memberDAO.findAll();
    }

    @Override
    public Member get(String mid) {
        return this.memberDAO.selectById(mid);
    }

    @Override
    public boolean add(Member vo) {
        return this.memberDAO.insert(vo) > 0;
    }

    @Override
    public boolean delete(Set<String> ids) {
        return this.memberDAO.deleteBatchIds(ids) == ids.size();
    }

    @Override
    public IPage<Member> listSplit(String column, String keyword, Integer currentPage, Integer lineSize) {
        QueryWrapper<Member> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(column, keyword); // 模糊查询
        int count = this.memberDAO.selectCount(queryWrapper); // 统计数据行数
        IPage<Member> page = new Page<>(currentPage, lineSize, count);
        return this.memberDAO.selectPage(page, queryWrapper);
    }

    @Override
    public boolean addBatch(String... mid) {
        for (String id : mid) {
            Member vo = new Member();
            vo.setMid(id); // 如果id重复则会出现更新异常
            vo.setName("爆可爱的小李老师");
            this.memberDAO.insert(vo);
        }
        return true;
    }
}


4、
package com.yootk.test;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yootk.StartSpringBootDatabaseApplication;
import com.yootk.service.IMemberService;
import com.yootk.vo.Member;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;

import java.util.Date;
import java.util.Set;

@ExtendWith(SpringExtension.class) // 使用JUnit5测试工具
@WebAppConfiguration // 启动WEB运行环境
@SpringBootTest(classes = StartSpringBootDatabaseApplication.class) // 配置程序启动类
public class TestMemberService { // 编写测试类
    @Autowired
    private IMemberService memberService;

    @Test
    public void testFindAll() {
        System.out.println(this.memberService.list());
    }

    @Test
    public void testGet() {
        System.out.println(this.memberService.get("yootk"));
    }

    @Test
    public void testAddBatchRepeatID() {
        this.memberService.addBatch("muyan", "yootk", "lixinghua", "happy-summery");
    }
    @Test
    public void testAddBatchNoRepeatID() {
        this.memberService.addBatch("muyan-happy", "yootk-happy", "lixinghua-happy", "happy-summery");
    }
    @Test
    public void testAdd() {
        Member vo = new Member();
        vo.setMid("yootk - " + Math.random());
        vo.setName("沐言科技");
        vo.setBirthday(new Date());
        vo.setSalary(865.0);
        vo.setContent("www.yootk.com");
        vo.setAge(16);
        System.out.println(this.memberService.add(vo));
    }

    @Test
    public void testSplit() {
        IPage<Member> page = this.memberService.listSplit("name", "沐言", 1, 5);
        System.out.println("总页数:" + page.getPages());
        System.out.println("总记录数:" + page.getTotal());
        System.out.println("响应内容:" + page.getRecords());
    }
    @Test
    public void testDelete() {
        Set<String> ids = Set.of("muyan", "yootk", "lixinghua");
        this.memberService.delete(ids);
    }
}

多数据源操作简介

单实例数据库应用

  • 传统的项目开发都是基于单数据库实例完成持久化数据存储,这样在 MyBatis 组件进行数据操作时,直接基于-个 DataSource 实例即可实现资源注入

多数据源应用

  • 随着项目的长期运行,数据量也会持续增加,而数据量的增加就会导致单实例数据库的执行性能下降,而此时就需要将-一个完整的数据库拆分为若干个不同的数据库,从而造成项目中存在有多数据源的情况

动态数据源切换

  • 在使用 MyBatis 进行数据操作时,就有可能会出现有若干个 DataSource 实例的情况导致在 MyBatis 框架进行组件依赖配置时出现错误,所以此时就需要通过-个动态数据源决策管理类,根据不同的操作环境切换当前要使用的 DataSource 实例,保证在每一次进行 MyBatis 操作时都只会提供有唯一的一个 DataSource 实例
1、
DROP DATABASE IF EXISTS muyan ;
CREATE DATABASE muyan CHARACTER SET UTF8 ;
USE muyan ;
CREATE TABLE dept(
   did			BIGINT    AUTO_INCREMENT,
   dname  		VARCHAR(50),
   loc			VARCHAR(50),
   flag			VARCHAR(50),
   CONSTRAINT pk_did PRIMARY KEY(did)
) engine=innodb;
-- 增加测试数据
INSERT INTO dept(dname, loc, flag) VALUES ('教学部', '北京', database());
INSERT INTO dept(dname, loc, flag) VALUES ('财务部', '上海', database());
INSERT INTO dept(dname, loc, flag) VALUES ('技术部', '洛阳', database());


2、
DROP DATABASE IF EXISTS yootk ;
CREATE DATABASE yootk CHARACTER SET UTF8 ;
USE yootk ;
CREATE TABLE emp(
   eid			VARCHAR(50),
   ename			VARCHAR(50) ,
   sal			DOUBLE ,
   did			BIGINT ,
   flag			VARCHAR(50),
   CONSTRAINT pk_eid PRIMARY KEY(eid)
) engine=innodb ;
-- 增加雇员测试数据
INSERT INTO emp(eid, ename, sal, did, flag) VALUES
	 	 ('yootk-teacher-a', '沐言科技讲师-A', 5000.00, 1, database());
INSERT INTO emp(eid, ename, sal, did, flag) VALUES
	 	 ('yootk-teacher-b', '沐言科技讲师-B', 5000.00, 1, database());
INSERT INTO emp(eid, ename, sal, did, flag) VALUES
	 	 ('yootk-leader-c', '沐言科技领导', 6000.00, 2, database());
INSERT INTO emp(eid, ename, sal, did, flag) VALUES
	 	 ('yootk-developer-d', '沐言科技工程师-A', 9000.00, 2, database());
INSERT INTO emp(eid, ename, sal, did, flag) VALUES
	 	 ('yootk-developer-e', '沐言科技工程师-B', 9800.00, 2, database());

配置多个 Druid 数据源

多数据源配置

  • 为了便于数据源的配置管理,常见的做法就是将数据库连接池的相关信息通过 application.yml 配置文件进行定义,而对于每一个数据库的连接可以由用户自定义配置项
  • 所有的配置信息如果要想转为 DataSource 接口实例,那么就需要依靠 DruidDataSourceBuilder 工具类实现,而该类由“druid-spring-boot-starter”模块所提供
1、
project('microboot-database') { // 子模块
    dependencies { // 配置子模块依赖
        compile(project(':microboot-common')) // 引入其他子模块
        compile('org.springframework.boot:spring-boot-starter-actuator')
        compile(libraries.'mysql-connector-java')
        compile(libraries.'druid-spring-boot-starter') // 删除掉此依赖库配置
        compile(libraries.'druid') // 添加原始依赖
        compile(libraries.'spring-jdbc')
        compile('org.springframework.boot:spring-boot-starter-aop')
        compile(libraries.'mybatis')
        compile(libraries.'mybatis-spring-boot-starter')
        compile(libraries.'mybatis-plus')
    }
}

2、
spring:
  datasource: # 数据源配置
    muyan: # muyan数据库连接配置
      type: com.alibaba.druid.pool.DruidDataSource    # 配置当前要使用的数据源的操作类型
      driver-class-name: com.mysql.cj.jdbc.Driver    # 配置MySQL的驱动程序类
      url: jdbc:mysql://localhost:3306/muyan    # 数据库连接地址
      username: root                # 数据库用户名
      password: mysqladmin            # 数据库连接密码
    yootk: # yootk数据库连接配置
      type: com.alibaba.druid.pool.DruidDataSource    # 配置当前要使用的数据源的操作类型
      driver-class-name: com.mysql.cj.jdbc.Driver    # 配置MySQL的驱动程序类
      url: jdbc:mysql://localhost:3306/yootk    # 数据库连接地址
      username: root                # 数据库用户名
      password: mysqladmin            # 数据库连接密码
    druid: # druid相关配置
      initial-size: 5                # 初始化连接池大小
      min-idle: 10                # 最小维持的连接池大小
      max-active: 50                # 最大支持的连接池大小
      max-wait: 60000                # 最大等待时间(毫秒)
      time-between-eviction-runs-millis: 60000    # 关闭空闲连接间隔(毫秒)
      min-evictable-idle-time-millis: 30000        # 连接最小生存时间(毫秒)
      validation-query: SELECT 1 FROM dual        # 数据库状态检测
      test-while-idle: true            # 申请连接的时候检测连接是否有效
      test-on-borrow: false            # 申请连接时检测连接是否有效
      test-on-return: false            # 归还连接时检测连接是否有效
      pool-prepared-statements: false        # PSCache缓存
      max-pool-prepared-statement-per-connection-size: 20    # 配置PS缓存
      filters: stat, wall, slf4j            # 开启过滤
      stat-view-servlet: # 监控界面配置
        enabled: true    # 启用druid监控界面
        allow: 127.0.0.1    # 设置访问白名单
        login-username: muyan   # 用户名
        login-password: yootk    # 密码
        reset-enable: true  # 允许重置
        url-pattern: /druid/*  # 访问路径
      web-stat-filter: # WEB监控
        enabled: true  # 启动URI监控
        url-pattern: /*   # 跟踪根路径下的全部服务
        exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"    # 跟踪排除
      filter: # Druid过滤器
        slf4j: # 日志
          enabled: true # 启用SLF4j监控
          data-source-log-enabled: true  # 启用数据库日志
          statement-executable-sql-log-enable: true # 执行日志
          result-set-log-enabled: true # ResultSet日志启用
        stat: # SQL监控
          merge-sql: true # 统计时合并相同的SQL命令
          log-slow-sql: true # 当SQL执行缓慢时是否要进行记录
          slow-sql-millis: 1  # 设置慢SQL的执行时间标准,单位:毫秒
        wall: # SQL防火墙
          enabled: true  # 启用SQL防火墙
          config: # 配置防火墙规则
            multi-statement-allow: true # 允许执行批量SQL
            delete-allow: false # 禁止执行删除语句
      aop-patterns: "com.yootk.action.*,com.yootk.service.*,com.yootk.dao.*" # Spring监控


3、
package com.yootk.config;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class DruidMultiDataSourceConfiguration { // 自定义的Druid配置类
    @Bean("druidMuyanDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.muyan")
    public DataSource getMuyanDataSource() {
        return DruidDataSourceBuilder.create().build();
    }
    @Bean("druidYootkDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.yootk")
    public DataSource getYootkDataSource() {
        return DruidDataSourceBuilder.create().build();
    }
}


4、
package com.yootk.test;

import com.yootk.StartSpringBootDatabaseApplication;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;

import javax.sql.DataSource;
import java.sql.SQLException;

@ExtendWith(SpringExtension.class) // 使用JUnit5测试工具
@WebAppConfiguration // 启动WEB运行环境
@SpringBootTest(classes = StartSpringBootDatabaseApplication.class) // 配置程序启动类
public class TestMultiDruidDataSource { // 编写测试类
    @Autowired
    @Qualifier("druidMuyanDataSource") // 设置要注入的Bean名称
    private DataSource muyanDataSource;
    @Autowired
    @Qualifier("druidYootkDataSource") // 设置要注入的Bean名称
    private DataSource yootkDataSource;
    @Test
    public void testDruid() {    // 进行响应测试
        try {
            System.out.println(this.muyanDataSource.getConnection());
            System.out.println(this.yootkDataSource.getConnection());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

动态数据源决策

数据源动态切换

  • 数据库被拆分之后,项目中会保存有若干个不同的数据源,而在进行数据表数据操作时就需要根据功能实现数据源的动态切换,而为了实现这样的功能,在 SpringJDBC 中就提供了-个 AbstractRoutingDataSource 抽象类,利用该类中定义的 determineCurrentLookupKey()方法来决定最终使用那一个数据源
1、
package com.yootk.config;

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

import javax.sql.DataSource;
import java.util.Map;

public class DynamicDataSource extends AbstractRoutingDataSource { // 数据源动态切换
    // 每一次不同的请求线程操作都有可能要使用到自己的DataSource
    private static final ThreadLocal<String> DATASOURCE_CONTEXT_HOLDER = new ThreadLocal<>();
    @Override
    protected Object determineCurrentLookupKey() { // 获取当前的查询结果
        return null;
    }
    static interface DataSourceNames {  // 定义一个数据源的名称接口标记
        String MUYAN_DATASOURCE = "muyan"; // “muyan”数据库的标记
        String YOOTK_DATASOURCE = "yootk"; // “yootk”数据库标记
    }

    /**
     * 构建动态数据源
     * @param defaultTargetDataSource 默认的数据源对象
     * @param targetDataSources 全部的数据源对象
     */
    public DynamicDataSource(DataSource defaultTargetDataSource,
                             Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource); // 调用父类方法
        super.setTargetDataSources(targetDataSources); // 调用父类方法
        super.afterPropertiesSet(); // 属性的设置
    }
    public static void setDataSource(String dataSourceName) {    // 扩充的数据源处理方法
        DATASOURCE_CONTEXT_HOLDER.set(dataSourceName); // 保存当前线程多少数据源名称
    }
    public static String getDataSource() { // 获取数据源的名称标记
        return DATASOURCE_CONTEXT_HOLDER.get();
    }
    public static void clearDataSource() {
        DATASOURCE_CONTEXT_HOLDER.remove(); // 清除当前线程之中保存的数据源名称
    }
}


2、
package com.yootk.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DynamicDataSourceConfig { // 动态数据源配置
    @Bean("dataSource")
    @Primary // 注入DataSource实例的时候此为优先考虑
    @DependsOn({"druidMuyanDataSource", "druidYootkDataSource"}) // 依赖数据源
    public DynamicDataSource getDataSource(
            @Autowired DataSource druidMuyanDataSource,
            @Autowired DataSource druidYootkDataSource
    ) {  // 动态数据源的切换
        Map<Object, Object> targetDataSources = new HashMap<>(5); // 配置一个初始化大小
        targetDataSources.put(DynamicDataSource.DataSourceNames.MUYAN_DATASOURCE, druidMuyanDataSource); // 绑定数据源
        targetDataSources.put(DynamicDataSource.DataSourceNames.YOOTK_DATASOURCE, druidYootkDataSource); // 绑定数据源
        return new DynamicDataSource(druidMuyanDataSource, targetDataSources); // 获取一个动态DataSource实例
    }
}


3、
package com.yootk.config;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Slf4j // 为了便于观察,将所有的数据全部都以日志的形式输出
@Aspect // 是一个切面处理类
@Order(-100) // 让这个切面处理类的执行顺序可以高一些
public class DataSourceAspect { // 数据源的切面处理类
    @Before("execution(* com.yootk.action.muyan..*.*(..))") // 在每次操作之前进行切换处理
    public void switchMuyanDataSource() {   // 切换指定的数据源
        DynamicDataSource.setDataSource(DynamicDataSource.DataSourceNames.MUYAN_DATASOURCE); // 设置数据源的名称
        log.info("数据源切换到“MUYAN”:{}", DynamicDataSource.getDataSource()); // 日志输出
    }
    @Before("execution(* com.yootk.action.yootk..*.*(..))") // 在每次操作之前进行切换处理
    public void switchYootkDataSource() {   // 切换指定的数据源
        DynamicDataSource.setDataSource(DynamicDataSource.DataSourceNames.YOOTK_DATASOURCE); // 设置数据源的名称
        log.info("数据源切换到“YOOTK”:{}", DynamicDataSource.getDataSource()); // 日志输出
    }
}


4、
package com.yootk.action.muyan;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.sql.DataSource;

@RestController
@Slf4j
public class DeptAction {
    @Autowired
    private DataSource dataSource; // 应该根据当前的程序包自动的切换
    @RequestMapping("/dept_datasource")
    public Object getDataSource() throws Exception { // 获取数据源的信息
        log.info("【MUYAN】数据源:{}", this.dataSource); // 日志输出
        return this.dataSource.getConnection().getCatalog(); // 获取分类日志
    }
}


5、
package com.yootk.action.yootk;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.sql.DataSource;

@RestController
@Slf4j
public class EmpAction {
    @Autowired
    private DataSource dataSource; // 应该根据当前的程序包自动的切换
    @RequestMapping("/emp_datasource")
    public Object getDataSource() throws Exception { // 获取数据源的信息
        log.info("【YOOTK】数据源:{}", this.dataSource); // 日志输出
        return this.dataSource.getConnection().getCatalog(); // 获取分类日志
    }
}


6、
localhost/dept_datasource
localhost/emp_datasource

MyBatisPlus 整合多数据源

基于 DAO 层实现切面管理

  • 现在已经成功的实现了 DataSource 数据源的动态切换,而在数据库开发中 DataSource 仅仅完成的是数据库连接的获取,而真正的数据操作还是需要通过 MyBatisPlus 来完成,所以此时可以基于 DAO 层来实现切面管理
1、
package com.yootk.vo;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;

@Data
public class Dept { // 描述部门信息类
    @TableId(type = IdType.AUTO) // 自动增长ID
    private Long did; // 部门ID
    private String dname; // 部门名称
    private String loc; // 部门为孩子
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String flag; // 标记数据库的米可能构成
}


2、
package com.yootk.vo;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;

@Data
public class Emp {
    @TableId(type = IdType.ASSIGN_ID) // 手工配置
    private String eid;
    private String ename;
    private Double sal;
    private Long did;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String flag;
}


3、
package com.yootk.config.mybatis;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.yootk.vo.Dept;
import com.yootk.vo.Emp;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class FlagMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("【FlagMetaObjectHandler.insertFill()】目标类型:{}", metaObject.getOriginalObject().getClass().getName());
        if (Dept.class.equals(metaObject.getOriginalObject().getClass())) { // 当前操作的是部门类
            this.setFieldValByName("flag", "【INSERT-FILL】muyan", metaObject);
        } else if (Emp.class.equals(metaObject.getOriginalObject().getClass())) {   // 当前操作的是雇员类型
            this.setFieldValByName("flag", "【INSERT-FILL】yootk", metaObject);
        }
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("【FlagMetaObjectHandler.updateFill()】目标类型:{}", metaObject.getOriginalObject().getClass().getName());
        if (Dept.class.equals(metaObject.getOriginalObject().getClass())) { // 当前操作的是部门类
            this.setFieldValByName("flag", "【UPDATE-FILL】muyan", metaObject);
        } else if (Emp.class.equals(metaObject.getOriginalObject().getClass())) {   // 当前操作的是雇员类型
            this.setFieldValByName("flag", "【UPDATE-FILL】yootk", metaObject);
        }
    }
}


4、
package com.yootk.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.yootk.config.mybatis.FlagMetaObjectHandler;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import javax.sql.DataSource;

@Configuration
public class MybatisPlusConfig { // Mybatis拦截器配置
    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    @Bean
    public MybatisPlusInterceptor getMybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(
                new PaginationInnerInterceptor(DbType.MYSQL) // 使用MySQL分页实现
        );
        return interceptor;
    }
    @Bean("mybatisSqlSessionFactoryBean")
    public MybatisSqlSessionFactoryBean getMybatisSqlSessionFactoryBean(
            @Autowired DataSource dataSource, // 要使用的数据源
            @Value("${mybatis-plus.config-location}") Resource configLocation, // 资源文件路径
            @Value("${mybatis-plus.type-aliases-package}") String typeAliasesPackage, // 扫描别名
            @Value("${mybatis-plus.mapper-locations}") String mapperLocations, // Mapping映射路径
            @Value("${mybatis-plus.global-config.db-config.logic-not-delete-value}") String logicNotDeleteValue,
            @Value("${mybatis-plus.global-config.db-config.logic-delete-value:}") String logicDeleteValue
    ) throws Exception {
        MybatisSqlSessionFactoryBean mybatisPlus = new MybatisSqlSessionFactoryBean();
        mybatisPlus.setDataSource(dataSource); // 配置项目中要使用的数据源
        mybatisPlus.setVfs(SpringBootVFS.class); // 配置程序的扫描类
        mybatisPlus.setTypeAliasesPackage(typeAliasesPackage); // 扫描包的别名
        Resource[] mappings = this.resourcePatternResolver.getResources(mapperLocations);
        mybatisPlus.setMapperLocations(mappings);
        GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig(); // 数据配置
        dbConfig.setLogicNotDeleteValue(logicNotDeleteValue); // 未删除时的数据内容
        dbConfig.setLogicDeleteValue(logicDeleteValue); // 删除时的数据内容
        GlobalConfig globalConfig = new GlobalConfig(); // 定义全局配置
        globalConfig.setDbConfig(dbConfig); // 全局配置项
        globalConfig.setMetaObjectHandler(new FlagMetaObjectHandler());
        mybatisPlus.setGlobalConfig(globalConfig);
        return mybatisPlus;
    }
}



5、
package com.yootk.dao.muyan;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yootk.vo.Dept;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface IDeptDAO extends BaseMapper<Dept> {
}


6、

package com.yootk.dao.yootk;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yootk.vo.Emp;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface IEmpDAO extends BaseMapper<Emp> {
}

7、
package com.yootk.config;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Slf4j // 为了便于观察,将所有的数据全部都以日志的形式输出
@Aspect // 是一个切面处理类
@Order(-100) // 让这个切面处理类的执行顺序可以高一些
public class DataSourceAspect { // 数据源的切面处理类
    @Before("execution(* com.yootk.dao.muyan..*.*(..))") // 在每次操作之前进行切换处理
    public void switchMuyanDataSource() {   // 切换指定的数据源
        DynamicDataSource.setDataSource(DynamicDataSource.DataSourceNames.MUYAN_DATASOURCE); // 设置数据源的名称
        log.info("数据源切换到“MUYAN”:{}", DynamicDataSource.getDataSource()); // 日志输出
    }
    @Before("execution(* com.yootk.dao.yootk..*.*(..))") // 在每次操作之前进行切换处理
    public void switchYootkDataSource() {   // 切换指定的数据源
        DynamicDataSource.setDataSource(DynamicDataSource.DataSourceNames.YOOTK_DATASOURCE); // 设置数据源的名称
        log.info("数据源切换到“YOOTK”:{}", DynamicDataSource.getDataSource()); // 日志输出
    }
}


8、
package com.yootk.service;

public interface ICompanyService {
    public Map<String, Object> list() ;
}


9、
package com.yootk.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yootk.dao.muyan.IDeptDAO;
import com.yootk.dao.yootk.IEmpDAO;
import com.yootk.service.ICompanyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class CompanyServiceImpl implements ICompanyService {
    @Autowired
    private IDeptDAO deptDAO;
    @Autowired
    private IEmpDAO empDAO;
    @Override
    public Map<String, Object> list() {
        Map<String, Object> map = new HashMap<>();
        map.put("allDepts", this.deptDAO.selectList(new QueryWrapper<>()));
        map.put("allEmps", this.empDAO.selectList(new QueryWrapper<>()));
        return map;
    }
}

10、
package com.yootk.test;

import com.yootk.StartSpringBootDatabaseApplication;
import com.yootk.service.ICompanyService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;

@ExtendWith(SpringExtension.class) // 使用JUnit5测试工具
@WebAppConfiguration // 启动WEB运行环境
@SpringBootTest(classes = StartSpringBootDatabaseApplication.class) // 配置程序启动类
public class TestCompanyService { // 编写测试类
    @Autowired
    private ICompanyService companyService;
    @Test
    public void testList() {    // 进行响应测试
        System.out.println(this.companyService.list());
    }
}

INFO [] 12944 --- [    Test worker] com.yootk.test.TestCompanyService        : Started TestCompanyService in 5.315 seconds (JVM running for 6.665)
INFO [] 12944 --- [    Test worker] com.yootk.config.DataSourceAspect        : 数据源切换到“MUYAN”:muyan
INFO [] 12944 --- [    Test worker] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
DEBUG [] 12944 --- [    Test worker] com.yootk.dao.muyan.IDeptDAO.selectById  : ==>  Preparing: SELECT did,dname,loc,flag FROM dept WHERE did=?
DEBUG [] 12944 --- [    Test worker] com.yootk.dao.muyan.IDeptDAO.selectById  : ==> Parameters: 1(Integer)
DEBUG [] 12944 --- [    Test worker] com.yootk.dao.muyan.IDeptDAO.selectById  : <==      Total: 1
INFO [] 12944 --- [    Test worker] com.yootk.config.DataSourceAspect        : 数据源切换到“YOOTK”:yootk
INFO [] 12944 --- [    Test worker] com.alibaba.druid.pool.DruidDataSource   : {dataSource-2} inited
DEBUG [] 12944 --- [    Test worker] com.yootk.dao.yootk.IEmpDAO.selectList   : ==>  Preparing: SELECT eid,ename,sal,did,flag FROM emp
DEBUG [] 12944 --- [    Test worker] com.yootk.dao.yootk.IEmpDAO.selectList   : ==> Parameters:
DEBUG [] 12944 --- [    Test worker] com.yootk.dao.yootk.IEmpDAO.selectList   : <==      Total: 5
{allDepts=Dept(did=1, dname=教学部, loc=北京, flag=muyan), allEmps=[Emp(eid=yootk-developer-d, ename=沐言科技工程师-A, sal=9000.0, did=2, flag=yootk), Emp(eid=yootk-developer-e, ename=沐言科技工程师-B, sal=9800.0, did=2, flag=yootk), Emp(eid=yootk-leader-c, ename=沐言科技领导, sal=6000.0, did=2, flag=yootk), Emp(eid=yootk-teacher-a, ename=沐言科技讲师-A, sal=5000.0, did=1, flag=yootk), Emp(eid=yootk-teacher-b, ename=沐言科技讲师-B, sal=5000.0, did=1, flag=yootk)]}

JTA 分布式事务简介

单数据库事务控制

  • 传统关系型数据库最重要的一个技术特点在于支持事务处理,即:每一个数据库连接之中都可以都基于各自的 Session 实现数据库更新操作的事务处理(commit、rollback.,同时在 JDBC 内部也直接提供有相应的事务处理方法,在事务处理时 savepoint)会将所有的更新操作保存在事务处理缓存之中,每当通过 commit 提交时才会将缓存中的更新进行应用,而如果在进行数据操作时出现了问题,则可以通过程序的控制方式实现数据回滚处理

分布式事务管理

  • 在项目中引入了多数据源管理之后一个业务就有可能会进行不同的数据库更新处理那么必然在项目中会出现有多个不同数据库连接,所以传统的事务处理将无法正常使用

JTA

  • 为了便于事务处理的规范化配置,在 JavaEE 中提供了 JTA(Java Transaction API、事务处理 API)服务支持,该服务允许应用程序执行分布式事务处理,可以在多个网络资源上访问并更新数据,这样极大增强了 JDBC 程序的处理能力,由于 JTA 仅仅是一个技术标准,所以在进行分布式事务处理时就需要引入 Atomikos 开源组件来实现具体的事务管理操作。

TransactionEssentias 与 ExtremeTransactions

  • ExtremeTransactions 商业收费版和 TransactionEssentials 开源版本都实现了 JTA/XA 规范中的事务管理器的相关接口,同时针对于 JDBC 以及 JMS 都有提供有良好的封装处理,但是 ExtremeTransactions 收费版本要额外支持 TCC(Try、Confirm、Cancel)远程调用(RMI/HOP/SOAP)技术的支持

XA 协·议

  • XA 是由 X/Open 组织提出的分布式事务的规范。XA 规范主要定义了(全局)事务管理器(Transaction Manager)和(同部)资源管理器(Resource Manager)之间的接口,常用的主流关系型数据库产品已实现了 XA 接口标准。XA 是一个基于二阶段提交的具体实现,在进行分布式事务处理中主要分为预备(Prepare)和提交(Commit)两个处理阶段,这两个阶段的具体作用如下:
    • 预备(Prepare)阶段:TM 事务协调者向所有 RM 资源管理者发送预备(Prepare)指令,询问是否可以执行,RM 资源管理者返回可执行或不可执行提交
    • (Commit)阶段:所有 RM 资源管理者都返回可执行,则向所有 RM 资源管理者发送 COMMIT 指令。如果有一个 RM 资 源管理者返回不可执行,则向所有 RM 发送 ROLLBACK 指令

atomikos 官网open in new window

AtomikosDataSourceBean

AtomikosDataSourceBean 配置结构

  • 分布式事务管理中需要明确的将所有数据源的事务处理统一交由 Atomikos 组件负责处理,这样就必须将项目中所使用到数据源类型变更为“AtomikosDataSourceBean'该类为 DataSource 子接口,同时在该类中需要明确的传入一个支持有 XA 机制的 DataSource 类名称,由于本次使用 Druid 实现了数据源管理,所以此处需要配置“DruidXADataSource”类型,程序的实现结构如图所示
1、
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jta-atomikos
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-jta-atomikos', version: '2.4.6'

2、
project('microboot-database') { // 子模块
    dependencies { // 配置子模块依赖
        compile(project(':microboot-common')) // 引入其他子模块
        compile('org.springframework.boot:spring-boot-starter-actuator')
        compile(libraries.'mysql-connector-java')
        compile(libraries.'druid-spring-boot-starter') // 删除掉此依赖库配置
        compile(libraries.'druid') // 添加原始依赖
        compile(libraries.'spring-jdbc')
        compile('org.springframework.boot:spring-boot-starter-aop')
        compile(libraries.'mybatis')
        compile(libraries.'mybatis-spring-boot-starter')
        compile(libraries.'mybatis-plus')
        compile('org.springframework.boot:spring-boot-starter-jta-atomikos')
    }
}

3、
spring:
  datasource: # 数据源配置
    muyan: # muyan数据库连接配置
      type: com.alibaba.druid.pool.xa.DruidXADataSource    # 配置当前要使用的数据源的操作类型
      driver-class-name: com.mysql.cj.jdbc.Driver    # 配置MySQL的驱动程序类
      url: jdbc:mysql://localhost:3306/muyan    # 数据库连接地址
      username: root                # 数据库用户名
      password: mysqladmin            # 数据库连接密码
    yootk: # yootk数据库连接配置
      type: com.alibaba.druid.pool.xa.DruidXADataSource    # 配置当前要使用的数据源的操作类型
      driver-class-name: com.mysql.cj.jdbc.Driver    # 配置MySQL的驱动程序类
      url: jdbc:mysql://localhost:3306/yootk    # 数据库连接地址
      username: root                # 数据库用户名
      password: mysqladmin            # 数据库连接密码
    druid: # druid相关配置
      initial-size: 5                # 初始化连接池大小
      min-idle: 10                # 最小维持的连接池大小
      max-active: 50                # 最大支持的连接池大小
      max-wait: 60000                # 最大等待时间(毫秒)
      time-between-eviction-runs-millis: 60000    # 关闭空闲连接间隔(毫秒)
      min-evictable-idle-time-millis: 30000        # 连接最小生存时间(毫秒)
      validation-query: SELECT 1 FROM dual        # 数据库状态检测
      test-while-idle: true            # 申请连接的时候检测连接是否有效
      test-on-borrow: false            # 申请连接时检测连接是否有效
      test-on-return: false            # 归还连接时检测连接是否有效
      pool-prepared-statements: false        # PSCache缓存
      max-pool-prepared-statement-per-connection-size: 20    # 配置PS缓存
      filters: stat, wall, slf4j            # 开启过滤
      stat-view-servlet: # 监控界面配置
        enabled: true    # 启用druid监控界面
        allow: 127.0.0.1    # 设置访问白名单
        login-username: muyan   # 用户名
        login-password: yootk    # 密码
        reset-enable: true  # 允许重置
        url-pattern: /druid/*  # 访问路径
      web-stat-filter: # WEB监控
        enabled: true  # 启动URI监控
        url-pattern: /*   # 跟踪根路径下的全部服务
        exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"    # 跟踪排除
      filter: # Druid过滤器
        slf4j: # 日志
          enabled: true # 启用SLF4j监控
          data-source-log-enabled: true  # 启用数据库日志
          statement-executable-sql-log-enable: true # 执行日志
          result-set-log-enabled: true # ResultSet日志启用
        stat: # SQL监控
          merge-sql: true # 统计时合并相同的SQL命令
          log-slow-sql: true # 当SQL执行缓慢时是否要进行记录
          slow-sql-millis: 1  # 设置慢SQL的执行时间标准,单位:毫秒
        wall: # SQL防火墙
          enabled: true  # 启用SQL防火墙
          config: # 配置防火墙规则
            multi-statement-allow: true # 允许执行批量SQL
            delete-allow: false # 禁止执行删除语句
      aop-patterns: "com.yootk.action.*,com.yootk.service.*,com.yootk.dao.*" # Spring监控


4、
package com.yootk.config;

import com.atomikos.jdbc.AtomikosDataSourceBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import javax.sql.DataSource;
import java.util.Properties;

@Configuration
public class DruidDataSourceConfig {
    // 对应的application.yml配置项前缀:spring.datasource.druid.xx(xx是具体的配置子项)
    private static final String DRUID_POOL_PREFIX = "spring.datasource.druid.";
    private static final String DATABASE_MUYAN_DRUID_PREFIX = "spring.datasource.muyan.";
    private static final String DATABASE_YOOTK_DRUID_PREFIX = "spring.datasource.yootk.";
    @Bean("druidMuyanDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.muyan")
    public DataSource getMuyanDataSource(
            @Autowired Environment env) { // 数据源的配置
        AtomikosDataSourceBean dataSourceBean = new AtomikosDataSourceBean();
        dataSourceBean.setXaDataSourceClassName(env.getProperty(DATABASE_MUYAN_DRUID_PREFIX + "type"));
        dataSourceBean.setUniqueResourceName("muyan");
        Properties properties = build(env, DATABASE_MUYAN_DRUID_PREFIX, DRUID_POOL_PREFIX);
        dataSourceBean.setXaProperties(properties); // 保存所有的配置属性
        return dataSourceBean;
    }
    @Bean("druidYootkDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.yootk")
    public DataSource getYootkDataSource(
            @Autowired Environment env) { // 数据源的配置
        AtomikosDataSourceBean dataSourceBean = new AtomikosDataSourceBean();
        dataSourceBean.setXaDataSourceClassName(env.getProperty(DATABASE_YOOTK_DRUID_PREFIX + "type"));
        dataSourceBean.setUniqueResourceName("yootk");
        Properties properties = build(env, DATABASE_YOOTK_DRUID_PREFIX, DRUID_POOL_PREFIX);
        dataSourceBean.setXaProperties(properties); // 保存所有的配置属性
        return dataSourceBean;
    }
    private Properties build(Environment env,
                             String databasePrefix, String druidPrefix) {
        Properties prop = new Properties();
        prop.put("url", env.getProperty(databasePrefix + "url"));
        prop.put("username", env.getProperty(databasePrefix + "username"));
        prop.put("password", env.getProperty(databasePrefix + "password"));
        prop.put("driverClassName", env.getProperty(
                databasePrefix + "driverClassName", ""));
        prop.put("initialSize", env.getProperty(
                druidPrefix + "initial-size", Integer.class));
        prop.put("maxActive", env.getProperty(druidPrefix + "max-active", Integer.class));
        prop.put("minIdle", env.getProperty(druidPrefix + "min-idle", Integer.class));
        prop.put("maxWait", env.getProperty(druidPrefix + "max-wait", Integer.class));
        prop.put("poolPreparedStatements", env.getProperty(
                druidPrefix + "pool-prepared-statements", Boolean.class));
        prop.put("maxPoolPreparedStatementPerConnectionSize",
                env.getProperty(druidPrefix +
                        "max-pool-prepared-statement-per-connection-size", Integer.class));
        prop.put("maxPoolPreparedStatementPerConnectionSize",
                env.getProperty(druidPrefix +
                        "max-pool-prepared-statement-per-connection-size", Integer.class));
        prop.put("validationQuery", env.getProperty(druidPrefix + "validation-query"));
        prop.put("testOnBorrow", env.getProperty(
                druidPrefix + "test-on-borrow", Boolean.class));
        prop.put("testOnReturn", env.getProperty(
                druidPrefix + "test-on-return", Boolean.class));
        prop.put("testWhileIdle", env.getProperty(
                druidPrefix + "test-while-idle", Boolean.class));
        prop.put("timeBetweenEvictionRunsMillis",
                env.getProperty(druidPrefix +
                        "time-between-eviction-runs-millis", Integer.class));
        prop.put("minEvictableIdleTimeMillis", env.getProperty(druidPrefix +
                "min-evictable-idle-time-millis", Integer.class));
        return prop;
    }
}


5、
package com.yootk.test;

import com.yootk.StartSpringBootDatabaseApplication;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;

import javax.sql.DataSource;
import java.sql.SQLException;

@ExtendWith(SpringExtension.class) // 使用JUnit5测试工具
@WebAppConfiguration // 启动WEB运行环境
@SpringBootTest(classes = StartSpringBootDatabaseApplication.class) // 配置程序启动类
public class TestMultiDruidDataSource { // 编写测试类
    @Autowired
    @Qualifier("druidMuyanDataSource") // 设置要注入的Bean名称
    private DataSource muyanDataSource;
    @Autowired
    @Qualifier("druidYootkDataSource") // 设置要注入的Bean名称
    private DataSource yootkDataSource;
    @Test
    public void testDruid() {    // 进行响应测试
        try {
            AbstractRoutingDataSource s;
            System.out.println(this.muyanDataSource.getConnection());
            System.out.println(this.yootkDataSource.getConnection());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

多数据源事务管理

1、
package com.yootk.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.DataSourceUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.ConcurrentHashMap;

// 当前的项目都是基于MyBatis / MyBatisPlus开发的,所以此时一定要基于MyBatis标准开发事务管理器
@Slf4j
public class MultiDataSourceTransaction
        implements org.apache.ibatis.transaction.Transaction {// 多数据源事务管理器
    private DataSource dataSource; // 事务是需要有DataSource支持
    private Connection currentConnection; // 当前的数据库连接
    private boolean autoCommit; // 是否要进行自动提交启用
    private boolean isConnectionTransactional; // 是否要启用事务
    private ConcurrentHashMap<String, Connection> otherConnectionMap; // 保存其他的Connection对象
    private String currentDatabaseName; // 当前数据库名称
    public MultiDataSourceTransaction(DataSource dataSource) {
        this.dataSource = dataSource; // 保存数据元
        this.otherConnectionMap = new ConcurrentHashMap<>(); // 保存其他数据库连接
        this.currentDatabaseName = DynamicDataSource.getDataSource(); // 获取当前操作的数据库名称
    }
    private void openMainConnection() throws SQLException { // 打开一个连接
        // 通过当前得到的DataSource接口实例来获取一个Connection接口实例
        this.currentConnection = DataSourceUtils.getConnection(this.dataSource);
        this.autoCommit = this.currentConnection.getAutoCommit(); // 获取当前的是否自动提交的状态
        this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.currentConnection, this.dataSource);
        log.info("当前数据库连接:{}、事务支持状态:{}。", this.currentConnection, this.isConnectionTransactional);
    }
    @Override
    public Connection getConnection() throws SQLException { // 获取数据库连接
        // 存在有数据源的前提下才可以实现连接的获取,那么首先要判断是否有数据源存在
        String datasourceName = DynamicDataSource.getDataSource(); // 获取当前数据源名称
        if (null == datasourceName || datasourceName.equals(this.currentDatabaseName)) {    // 现在的数据源为当前使用的数据库
            if (this.currentConnection != null) {   // 当前存在有数据库连接
                return this.currentConnection; // 返回当前的连接
            } else {    // 如果当前的数据源没有开启过连接
                openMainConnection(); // 开启一个数据库连接
                this.currentDatabaseName = datasourceName; // 保存当前的数据库名称
                return this.currentConnection; // 返回当前的连接
            }
        } else {    // 没有连接
            if (!this.otherConnectionMap.containsKey(datasourceName)) { // 没有当前这个数据源的名称存在
                Connection conn = dataSource.getConnection(); // 获取数据库连接
                this.otherConnectionMap.put(datasourceName, conn); // 保存连接
            }
            return this.otherConnectionMap.get(datasourceName);
        }
    }

    @Override
    public void commit() throws SQLException { // 数据库事务提交
        // 当前存在有Connection接口实例,同时没有开启自动的事务提交,并且存在有支持事务的连接
        if (this.currentConnection != null && !this.isConnectionTransactional && !this.autoCommit) {
            log.info("数据库事务提交,当前数据库连接:{}", this.currentConnection);
            this.currentConnection.commit(); // 提交当前的数据库事务
            for (Connection connecion : this.otherConnectionMap.values()) { // 控制其它的数据库连接
                connecion.commit(); // 保证其他的连接提交事务
            }
        }
    }

    @Override
    public void rollback() throws SQLException { // 事务回滚
        if (this.currentConnection != null && !this.isConnectionTransactional && !this.autoCommit) {
            log.info("数据库事务回滚,当前数据库连接:{}", this.currentConnection);
            this.currentConnection.rollback(); // 回滚当前的数据库事务
            for (Connection connecion : this.otherConnectionMap.values()) { // 控制其它的数据库连接
                connecion.rollback(); // 保证其他的连接提交回滚
            }
        }
    }

    @Override
    public void close() throws SQLException { // 事务关闭
        DataSourceUtils.releaseConnection(this.currentConnection, this.dataSource);
        for (Connection connecion : this.otherConnectionMap.values()) { // 控制其它的数据库连接
            DataSourceUtils.releaseConnection(connecion, this.dataSource);
        }
    }

    @Override
    public Integer getTimeout() throws SQLException { // 超时配置
        return 500;
    }
}


2、
package com.yootk.config;

import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;

import javax.sql.DataSource;

public class MultiDataSourceTransactionFactory extends SpringManagedTransactionFactory {
    @Override
    public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
        return new MultiDataSourceTransaction(dataSource); // 事务控制类
    }
}


3、
package com.yootk.config;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.transaction.SystemException;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;

public class XADruidTransactionManager { // XZA事务管理器
    // UserTransaction可以保证在当前线程下的所有数据库操作都使用同一个Connection接口实例
    // 最终可以在进行事务提交或回滚的时候保证事务操作的原子性
    @Bean(name = "userTransaction")
    public UserTransaction getUserTransaction() throws SystemException {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(1000); // 超时配置
        return userTransactionImp;
    }
    @Bean(name="atomikosTransactionManager")
    public TransactionManager getTransactionManager() {
        UserTransactionManager transactionManager = new UserTransactionManager();
        transactionManager.setForceShutdown(false); // 关闭强制退出
        return transactionManager;
    }
    @Bean("transactionManager")
    @DependsOn({"userTransaction", "atomikosTransactionManager"})
    public PlatformTransactionManager getPlatformTransactionManager(
            UserTransaction userTransaction,
            TransactionManager atomikosTransactionManager) {
        return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
    }
}

MyBatis 整合分布式事务

demo


上次编辑于: