MybatisPlus多数据源及事务解决思路

关于多数据源解决方案

目前在SpringBoot框架基础上多数据源的解决方案大多手动创建多个DataSource,后续方案有三:

  1. 继承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,使用AOP切面注入相应的数据源 ,但是这种做法仅仅适用单Service方法使用一个数据源可行,如果单Service方法有多个数据源执行会造成误读。
  2. 通过DataSource配置 JdbcTemplateBean,直接使用 JdbcTemplate操控数据源。
  3. 分别通过DataSource创建SqlSessionFactory并扫描相应的Mapper文件和Mapper接口。

MybatisPlus的多数据源

我通过阅读源码,发现MybatisPlus的多数据源解决方案正是AOP,继承了org.springframework.jdbc.datasource.AbstractDataSource,有自己对ThreadLocal的处理。通过注解切换数据源。也就是说,MybatisPlus只支持在单Service方法内操作一个数据源,毕竟官网都指明——“强烈建议只注解在service实现上”。

而后,注意看com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder,也就是MybatisPlus是如何切换数据源的。

重点看:

/**
 * 为什么要用链表存储(准确的是栈)
 * <pre>
 * 为了支持嵌套切换,如ABC三个service都是不同的数据源
 * 其中A的某个业务要调B的方法,B的方法需要调用C的方法。一级一级调用切换,形成了链。
 * 传统的只设置当前线程的方式不能满足此业务需求,必须模拟栈,后进先出。
 * </pre>
 */
 private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new ThreadLocal() {
 @Override
 protected Object initialValue() {
  return new ArrayDeque();
 }
 };

这段话翻译为大家都能懂得的意思就是“可以同时操控多个数据源”。那么,在MYSQL中,有语法为schemaName+. +tableName,如此一来就不会误走数据源了。

我继续看MybatisPlus是如何利用mybatis本身的ORM机制将实体类自动映射以及生成SQL语句的(这里插一句,MybatisPlus的源码易读懂,写的很不错)。无意看到了注解com.baomidou.mybatisplus.annotation.TableName中的schema,如果在类上加schema,在生成SQL语句时就会生成schemaName+. +tableName格式。

MybatisPlus多数据源事务(JTA

简单说明一下JTA

JTA包括事务管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager ) 两部分, 可以将资源管理器看做任意类型的持久化数据存储;事务管理器则承担着所有事务参与单元的协调与控制。

JTA只是提供了一个接口,并没有提供具体的实现。

不过Atomikos对其进行了实现,而后SpringBoot将其进行了整合,对其进行了托管,很方便开发者拿来即用。

其中事务管理器的主要部分为UserTransaction 接口,开发人员通过此接口在信息系统中实现分布式事务;而资源管理器则用来规范提供商(如数据库连接提供商)所提供的事务服务,它约定了事务的资源管理功能,使得 JTA 可以在异构事务资源之间执行协同沟通。

通常接入JTA步骤(目的就是让JTAUserTransaction接管驱动为分布式的数据源,通常为AtomikosDataSourceBean):

  • 配置好AtomikosDataSourceBean
  • AtomikosDataSourceBean交给SqlSessionFactory
  • 配置UserTransaction事务管理。

但是我们用的是MybatisPlus,我们需要做的是接管MybatisPlus每一个数据源的配置,然后再把数据源依次交给MybatisPlus进行管理。

看看MybatisPlus是怎么进行多数据源配置的,源码里有这几个地方需要重点看一下:

  • com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider,这个就是MybatisPlus多数据源配置的方式,利用HashMap来装载。
  • com.baomidou.dynamic.datasource.DynamicDataSourceCreator,这个是每个数据源的配置方式。

其中com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider实现了接口com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider,是该接口的默认的实现。也就是说我们只需要实现该接口,自己配置多数据源以及每个数据源的驱动,成为该接口的默认实现就OK。

实现该接口,配置多数据源:

package xxx.xxx.xxx.config;

import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

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

/**
 * @author : zuoyu
 * @description : 接管MybatisPlus多数据源至Atomikos管理
 * @date : 2020-06-01 16:36
 **/
@Service
@Primary
public class DynamicDataSourceProviderImpl implements DynamicDataSourceProvider {

 /**
  * 配置文件数据的松散绑定
  */
 private final DynamicDataSourceProperties properties;

 /**
  * Atomikos驱动数据源创建
  */
 private final AtomikosDataSourceCreator atomikosDataSourceCreator;

 public DynamicDataSourceProviderImpl(DynamicDataSourceProperties properties, AtomikosDataSourceCreator atomikosDataSourceCreator) {
  this.properties = properties;
  this.atomikosDataSourceCreator = atomikosDataSourceCreator;
 }

 @Override
 public Map<String, DataSource> loadDataSources() {
  Map<String, DataSourceProperty> dataSourcePropertiesMap = properties.getDatasource();
  Map<String, DataSource> dataSourceMap = new HashMap<>(dataSourcePropertiesMap.size() * 2);
  for (Map.Entry<String, DataSourceProperty> item : dataSourcePropertiesMap.entrySet()) {
   String pollName = item.getKey();
   DataSourceProperty dataSourceProperty = item.getValue();
   dataSourceProperty.setPollName(pollName);
   dataSourceMap.put(pollName, atomikosDataSourceCreator.createDataSource(dataSourceProperty));
  }
  return dataSourceMap;
 }
}

Atomikos驱动数据源创建:

package xxx.xxx.xxx.config;

import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;

/**
 * @author : zuoyu
 * @description : 事务数据源
 * @date : 2020-06-01 17:30
 **/
@Component
public class AtomikosDataSourceCreator {
 /**
  * 创建数据源
  *
  * @param dataSourceProperty 数据源信息
  * @return 数据源
  */
 public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
  MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
  mysqlXaDataSource.setUrl(dataSourceProperty.getUrl());
  mysqlXaDataSource.setPassword(dataSourceProperty.getPassword());
  mysqlXaDataSource.setUser(dataSourceProperty.getUsername());
  AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
  xaDataSource.setXaDataSource(mysqlXaDataSource);
  xaDataSource.setMinPoolSize(5);
  xaDataSource.setBorrowConnectionTimeout(60);
  xaDataSource.setMaxPoolSize(20);
  xaDataSource.setXaDataSourceClassName(dataSourceProperty.getDriverClassName());
  xaDataSource.setTestQuery("SELECT 1 FROM DUAL");
  xaDataSource.setUniqueResourceName(dataSourceProperty.getPollName());
  return xaDataSource;
 }
}

配置JTA事务管理器:

package xxx.xxx.xxx.config;

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

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

/**
 * @author : zuoyu
 * @description : 分布式事务配置
 * @date : 2020-06-01 17:55
 **/
@Configuration
@EnableTransactionManagement
public class TransactionManagerConfig {

 @Bean(name = "userTransaction")
 public UserTransaction userTransaction() throws Throwable {
  UserTransactionImp userTransactionImp = new UserTransactionImp();
  userTransactionImp.setTransactionTimeout(10000);
  return userTransactionImp;
 }

 @Bean(name = "atomikosTransactionManager")
 public TransactionManager atomikosTransactionManager() throws Throwable {
  UserTransactionManager userTransactionManager = new UserTransactionManager();
  userTransactionManager.setForceShutdown(false);
  return userTransactionManager;
 }

 @Bean(name = "transactionManager")
 @DependsOn({"userTransaction", "atomikosTransactionManager"})
 public PlatformTransactionManager transactionManager() throws Throwable {
  return new JtaTransactionManager(userTransaction(), atomikosTransactionManager());
 }
}

如此,即可

这样一来便可解决MybatisPlus多数据源的误走,且支持多数据源下的事务问题。

做任何事情,重要的是思路,而不是搬砖。

到此这篇关于MybatisPlus多数据源及事务解决思路的文章就介绍到这了,更多相关MybatisPlus多数据源事务内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • springboot集成mybatisPlus+多数据源的实现示例

    该项目主要实现mybatisplus.多数据源.lombok.druid的集成 主要参考 https://mp.baomidou.com/guide/quick-start.html 项目地址:https://github.com/Blankwhiter/mybatisplus-springboot release1.0 项目结构: 一.创建表以及测试数据 CREATE TABLE user ( id VARCHAR(32) NOT NULL COMMENT '主键ID', name VARCH

  • SpringBoot整合MyBatisPlus配置动态数据源的方法

    MybatisPlus特性 •无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑 •损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作 •强大的 CRUD 操作:内置通用 Mapper.通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求 •支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错 •支持多种数据库:支持 MySQL.MariaDB.Ora

  • MybatisPlus多数据源及事务解决思路

    关于多数据源解决方案 目前在SpringBoot框架基础上多数据源的解决方案大多手动创建多个DataSource,后续方案有三: 继承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,使用AOP切面注入相应的数据源 ,但是这种做法仅仅适用单Service方法使用一个数据源可行,如果单Service方法有多个数据源执行会造成误读. 通过DataSource配置 JdbcTemplateBean,直接使用 Jdb

  • Spring Boot 多数据源处理事务的思路详解

    目录 1. 思路梳理 2. 代码实践 2.1 案例准备 2.2 开始整活 LoadDataSource.java 3. 总结 首先我先声明一点,本文单纯就是技术探讨,要从实际应用中来说的话,我并不建议这样去玩分布式事务.也不建议这样去玩多数据源,毕竟分布式事务主要还是用在微服务场景下. 好啦,那就不废话了,开整. 1. 思路梳理 首先我们来梳理一下思路. 在上篇文章中,我们是一个微服务,在 A 中分别去调用 B 和 C,当 B 或者 C 有一个执行失败的时候,就去回滚.B 和 C 都是调用远程的

  • PHP新建类问题分析及解决思路

    下面先给大家分析php新建类的问题 index.php文件 function __autoload($_className) { require $_className.'.class.php'; } //新建类?? if (isset($_GET['index'])) { $m=new Main($_GET['index']); }else{ $m=new Main(); } include $m->ui(); main.class.php文件 class Main{ private $ind

  • 求帮忙修改个php curl模拟post请求内容后并下载文件的解决思路

    下面代码使用curl模拟post请求链接后直接显示出了文件内容,如何修改成不显示内容而直接下载请求到的.torrent格式文件呢 function curl_post($header,$data,$url) { $ch = curl_init(); $res= curl_setopt ($ch, CURLOPT_URL,$url); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); curl_setopt($ch, CURLOPT_SSL_VERI

  • 分享ThinkPHP3.2中关联查询解决思路

    不废话了,直接给大家贴代码了,代码很简单,都是比较常见的sql语句,具体内容请看下文. CREATE TABLE `test_avatar` ( `uid` int(11) unsigned NOT NULL DEFAULT '0', `avatar` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`uid`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; INSERT INTO `test_avatar` VALUE

  • bootstrap datepicker 与bootstrapValidator同时使用时选择日期后无法正常触发校验的解决思路

    一.前言 使用bootstrap-datepicker和bootstrapValidator也有一段时间了,在工作中发现两者同时使用时会出现的一种问题,当选择完日期后,并不会正确校验该字段.为了更加直观的展现问题,上图一张. 可以看出,当选择完日期后,校验结果并没有达到预期,是因为bootstrapValidator插件默认情况下,不会重复校验一个已经标记为验证通过或验证不通过的字段.so ,当开始触发校验后,没有通过校验,当正确选择日期后,并不会刷新校验结果,就会导致数据无法正常提交,当手动把

  • 浅谈C#手机号换成111XXXX1111 这种显示的解决思路

    如下所示: String subStr=phone.Substring(3,4); oldcellphone.Text = phone.Replace(subStr,"****"); /// <summary> /// 替换手机号中间四位为* /// </summary> /// <param name="phoneNo"></param> /// <returns></returns> pro

  • EasyUI的TreeGrid的过滤功能的解决思路

    写在最前面 这个星期一直在纠结easyui的treegrid的过滤功能,原因呢,自然是项目中一个莫名奇妙的需求. easyui虽说是后端程序员的前端框架,但是说句实话,除去api,让我直接写里面的节点信息的话,还真是无从下手,在这里先对前端的同学膜拜下. 说下需求吧,最近一个项目中有个界面使用的easyui的treegrid展示,起初是没什么问题的,但是随着数据的导入发现实际操作上十分不便.原因呢是开发的时候treegrid并没有做分页的处理,目前测试环境数据达到456条,想从中找到一条然后处理

  • Tomcat无法加载css和js等静态资源文件的解决思路

    解决思路有两个 一是,你使用了Apache服务器,html不交给Tomcat处理,所以你找不到Html等静态资源,所以你先停掉阿帕奇,然后只用Tomcat猫试试. 二是,像我一样,使用了Jetty开发程序,但是打war包的时候忘记干掉web.xml中的jetty修改静态资源的代码.如下,干掉即可. 复制代码 代码如下: <servlet> <servlet-name>default</servlet-name> <!-- <servlet-class>

  • 详解Android TextView属性ellipsize多行失效的解决思路

    本文介绍了Android TextView属性ellipsize多行失效的解决思路,分享给大家,具体如下: 多余文字显示省略号的常规做法 android:ellipsize="end" //省略号显示在末尾 android:ellipsize="middle" //省略号显示在中间 但是设置android:maxLines="2" 以后,ellipsize的值end有效,middle无效,本方法解决middle无效的问题 /** * 字符串显示到

随机推荐