集成Mybatis框架及事务控制
数据分布在不同的数据库中,根据业务拆分了数据,应用没有拆分.
一个公司多个子项目,不同业务各用各的数据库,涉及数据共享...
为了解决数据库的读性能瓶颈(读比写性能更高, 写锁会影响读阻塞, 从而影响读的性能)
当读与写都放在一个数据库中操作, 在高并发的情况下, 会出现读锁与写锁互斥的情况, 因为写锁具有排他性, 当对一个数据进行写操作时, 会上一把写锁, 如果同时又对此数据进行读操作, 此时读操作的连接会被阻塞, 等待写操作完成, 写锁被释放后, 才会进行读操作.此时就需要对数据库进行读写分离
很多数据库用主从架构, 即 一台主数据库服务器, 对外提供增删改业务, 一台或多台从数据库服务器, 主要进行读的操作. 在读写分离中, 主从库的数据是一致的. 数据更新操作都在主库上完成, 主库将数据变更信息同步给从库, 查询操作交给从库, 分担主库的压力
实现读写分离可以借助中间件(ShardingSphere, mycat, mysql-proxy, TDDL....) , 应用将数据操作请求发送给中间件, 由中间件根据读写请求进行分发, 当没有搭建读写分离的基础设施时, 就需要自行实现
spring框架中, spring-jdbc模块提供了AbstractRoutingDataSource, 其内部可以包含多个DataSource, 然后在运行时来动态的访问哪个数据库
这种方式访问数据库的架构图如下所示:
应用直接操作的是AbstractRoutingDataSource的子类, 子类重写特定方法, 装载配置, 设置标识,告诉AbstractRoutingDataSource访问哪个数据库, 然后由AbstractRoutingDataSource从配置的数据源(DataSource1, DataSource2...)中选择一个, 访问对应的数据库
底层原理如下(方式二):
/*****
* @author sunwul
* @date 2022/2/15 9:53
* @description 数据库操作类型
*/
public enum DatabaseOperation {
Read(OperationConstant.OPERATION_TYPE_R, "读取"),
Write(OperationConstant.OPERATION_TYPE_W, "写入");
private String name;
private String desc;
DatabaseOperation(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
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;
import java.sql.SQLException;
/*****
* @author sunwul
* @date 2022/2/14 19:32
* @description 数据源配置
*/
@Configuration
public class DatasourceConfig {
@Bean("dataSource_write")
@ConfigurationProperties(prefix = "spring.datasource.datasource-write")
public DataSource dataSource_write() {
// 通过绑定全局配置,拿到属性后自动创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
@Bean("dataSource_read")
@ConfigurationProperties(prefix = "spring.datasource.datasource-read")
public DataSource dataSource_read() throws SQLException {
// 通过绑定全局配置,拿到属性后自动创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
}
# 配置文件
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
# druid连接池
druid:
initial-size: 1
min-idle: 1
max-active: 20
test-on-borrow: true
validation-query: SELECT 1 FROM DUAL
# 写库
datasource-write:
url: jdbc:mysql://localhost:3306/write?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2b8
username: root
password: 123
driver-class-name: com.mysql.cj.jdbc.Driver
# 读库
datasource-read:
url: jdbc:mysql://localhost:3306/read?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2b8
username: root
password: 123
driver-class-name: com.mysql.cj.jdbc.Driver
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import sunwul.datasource.emun.DatabaseOperation;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/*****
* @author sunwul
* @date 2022/2/21 16:20
* @description 动态数据源
* 继承AbstractRoutingDataSource抽象类
* - 重写determineCurrentLookupKey()方法, 返回当前数据源标识
* - 重写afterPropertiesSet()方法, 在Bean初始化时装载配置[设置数据源列表,默认数据源], 调用父类方法, 使配置生效
*
* 此方法继承了Spring框架的抽象类, 只需要重写determineCurrentLookupKey()与afterPropertiesSet()方法
* 其它方法使用Spring的默认实现, 与Spring框架更契合
*/
@Component
@Primary // 作为主要注入Bean, 当出现相同类型的Bean时,会使用含此注解的Bean
public class DynamicDatasource extends AbstractRoutingDataSource {
// 当前使用的数据源标识 线程安全(多线程对这个name进行同时读写时,会出现单独或篡改的情况)
public static ThreadLocal<String> name = new ThreadLocal<>();
// 写
@Autowired
DataSource dataSource_write;
// 读
@Autowired
DataSource dataSource_read;
/**
* 返回当前数据源标识
*/
@Override
protected Object determineCurrentLookupKey() {
return name.get();
}
/**
* 初始化设置
* 1. 设置数据源列表
* 2. 设置默认的数据源
* 3. 调用父类的afterPropertiesSet方法, 使设置生效
* (具体可以查看父类afterPropertiesSet方法,可以看到此方法中判断了数据源列表[必要,否则会异常]和默认数据源[非必要])
*/
@Override
public void afterPropertiesSet() {
// 所有数据源Map 将数据源标识与数据源put进来
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DatabaseOperation.Write.getName(), dataSource_write);
targetDataSources.put(DatabaseOperation.Read.getName(), dataSource_read);
// targetDataSources 初始化所有数据源
super.setTargetDataSources(targetDataSources);
// defaultTargetDataSource 设置默认的数据源
super.setDefaultTargetDataSource(dataSource_write);
// 调用父类的 afterPropertiesSet 方法, 使设置生效
super.afterPropertiesSet();
}
}
mybatis会通过Executor来执行数据库操作, 那么我们只需要拦截Executor中的方法,就可以在mybatis执行操作的时候进行增强
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import sunwul.datasource.db.DynamicDatasource;
import sunwul.datasource.emun.DatabaseOperation;
import java.util.Properties;
/*****
* @author sunwul
* @date 2022/2/21 21:21
* @description mybatis插件的动态数据源切换
* * @Intercepts 标明这是一个mybatis插件(固定写法,插件是基于动态代理的)
* * @Signature 设置要代理的对象, 方法, 参数列表(参数列表可以参考Executor源码中对应方法的参数列表)
* 在mybatis执行操作的时候进行增强, mybatis会通过Executor来执行数据库操作(增删改/查 对应Executor中的update/query方法)
* 因此,只需要代理Executor中的update与query方法
*/
// @Component // 只需要成为SpringBean, 在MybatisAutoConfiguration实例化时,会扫描到此Bean,自动注入 (这里不使用注解的方式,新建一个配置类创建Bean,便于管理)
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class})
})
public class DynamicDataSourcePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取当前方法的参数列表
Object[] objects = invocation.getArgs();
// 根据Executor类的源码可知, 不管是update还是query方法,参数列表中第一个参数都是MappedStatement
// MappedStatement中封装了一个完整的SQL所对应的元素(参考我们平时写的**Mapper.xml, 有命令类型,id,返回结果类型...)
MappedStatement ms = (MappedStatement) objects[0];
// 根据SQL类型改变数据源标识,这样在执行数据库命令时,动态数据源就会根据数据源标识切换到对应数据源
if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
// 读
DynamicDatasource.name.set(DatabaseOperation.Read.getName());
} else {
// 写
DynamicDatasource.name.set(DatabaseOperation.Write.getName());
}
return invocation.proceed();
}
/**
* 在新建可代理对象时,会通过此方法来决定是返回目标对象本身还是代理对象
*/
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
// 参考Interceptor类源码
return Plugin.wrap(target, this);
}
return target;
}
/**
* 此方法会在Configuration初始化当前的Interceptor时执行
*/
@Override
public void setProperties(Properties properties) {
}
}
import org.apache.ibatis.plugin.Interceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import sunwul.datasource.common.plugin.DynamicDataSourcePlugin;
/*****
* @author sunwul
* @date 2022/2/21 21:53
* @description
*/
@Configuration
public class MybatisConfig {
/**
* 自定义的Bean会在MybatisAutoConfiguration实例化钱创建完成
* 在MybatisAutoConfiguration实例化时,会自动扫描并注入所用到的所有Bean,此时就注入了我们自定义的Bean
*/
@Bean
public Interceptor dynamicDataSourcePlugin() {
return new DynamicDataSourcePlugin();
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import sunwul.datasource.entity.TAdmin;
import sunwul.datasource.service.TAdminService;
import java.util.List;
/*****
* @author sunwul
* @date 2022/2/21 21:54
* @description
*/
@RestController
public class TestController {
@Autowired
TAdminService tAdminServiceImpl;
@RequestMapping("/sel")
public List<TAdmin> sel_admin() {
// 切换数据源标识 - 不在需要手动切换数据源标识, 使用Mybatis插件代理Executor拦截特定方法来自动切换数据源标识
// DynamicDatasource.name.set(DatabaseOperation.Read.getName());
return tAdminServiceImpl.sel_admin();
}
@RequestMapping("/selOne")
public TAdmin selOne_admin() {
// DynamicDatasource.name.set(DatabaseOperation.Read.getName());
return tAdminServiceImpl.sel_admin().get(0);
}
@RequestMapping("/add/{name}")
public Integer add_admin(@PathVariable String name) {
// DynamicDatasource.name.set(DatabaseOperation.Write.getName());
return tAdminServiceImpl.add_admin(name);
}
}
当一个项目中有多个子模块, 但数据库根据业务进行拆分了, 此时,可以使用AOP+自定义注解的方式, 根据注解信息来判断当前数据库操作使用的数据源标识, 提供给动态数据源切换对应数据源
/*****
* @author sunwul
* @date 2022/2/23 9:16
* @description 操作类型常量
*/
public class OperationConstant {
// 读
public static final String OPERATION_TYPE_R = "R";
// 写
public static final String OPERATION_TYPE_W = "W";
}
import sunwul.datasource.emun.OperationConstant;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/*****
* @author sunwul
* @date 2022/2/22 17:11
* @description 自定义注解 数据源注解
* * @Target 注解目标,指定注解可以声明在方法与类上
* * @Retention 保留方式,设置为保留策略中的"运行时",这样就可以通过反射来判断注解是否存在
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
// 用于保存数据源标识, 提供给动态数据源使用 默认写库
String value() default OperationConstant.OPERATION_TYPE_W;
}
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import sunwul.datasource.annotaion.DataSource;
import sunwul.datasource.db.DynamicDatasource;
/*****
* @author sunwul
* @date 2022/2/23 9:23
* @description AOP
* * 不管声明在方法还是类上, 有一个核心问题, 切换数据源的时机必须要在数据库操作前
* * 由此可知, 只能使用前置通知或者环绕通知
*/
@Component
@Aspect
public class DynamicDataSourceAspect {
// 前置
@Before("within(sunwul.datasource.controller.*) && @annotation(dataSource)")
public void before(JoinPoint point, DataSource dataSource){
String value = dataSource.value();
DynamicDatasource.name.set(value);
System.out.println("注解获取的数据源标识: " + value);
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import sunwul.datasource.annotaion.DataSource;
import sunwul.datasource.emun.OperationConstant;
import sunwul.datasource.entity.TAdmin;
import sunwul.datasource.service.TAdminService;
import java.util.List;
/*****
* @author sunwul
* @date 2022/2/21 21:58
* @description
*/
@RestController
public class TestController {
@Autowired
TAdminService tAdminServiceImpl;
@DataSource(OperationConstant.OPERATION_TYPE_R)
@RequestMapping("/sel")
public List<TAdmin> sel_admin() {
// 切换数据源标识 - 不在需要手动切换数据源, 使用AOP拦截特定注解及属性切换数据源
// DynamicDatasource.name.set(DatabaseOperation.Read.getName());
return tAdminServiceImpl.sel_admin();
}
@DataSource(OperationConstant.OPERATION_TYPE_R)
@RequestMapping("/selOne")
public TAdmin selOne_admin() {
// DynamicDatasource.name.set(DatabaseOperation.Read.getName());
return tAdminServiceImpl.sel_admin().get(0);
}
@DataSource(OperationConstant.OPERATION_TYPE_W)
@RequestMapping("/add/{name}")
public Integer add_admin(@PathVariable String name) {
// DynamicDatasource.name.set(DatabaseOperation.Write.getName());
return tAdminServiceImpl.add_admin(name);
}
}
通过集成多个Mybatis框架来实现多数据源, 这种方式不涉及到数据源的切换
其原理如下:
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
/*****
* @author sunwul
* @date 2022/2/28 10:12
* @description 工厂工具类
*/
public class FactoryUtil_manyMybatis {
/**
* 创建Mybatis SqlSessionFactory
*
* @param dataSource 数据源
* @param mapperPath mapper.xml文件存放路径
* @param isPrintLog 是否开启控制台日志 true-是 false-否
* @return SqlSessionFactory
* @throws Exception ex
*/
public static SqlSessionFactory createFactory(DataSource dataSource, String mapperPath, boolean isPrintLog) throws Exception {
final SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// 指定数据源
sqlSessionFactoryBean.setDataSource(dataSource);
System.out.println("指定数据源:" + dataSource.getConnection().getMetaData().getURL());
// 指定Mapper.xml文件
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources(mapperPath));
System.out.println("扫描并装载Mapper.xml文件: " + mapperPath);
// 启用控制台日志
if (isPrintLog) {
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setLogImpl(StdOutImpl.class);
System.out.println("启用控制台日志...");
sqlSessionFactoryBean.setConfiguration(configuration);
}
return sqlSessionFactoryBean.getObject();
}
}
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
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 sunwul.datasource.common.FactoryUtil_manyMybatis;
import javax.sql.DataSource;
import java.sql.SQLException;
/*****
* @author sunwul
* @date 2022/2/25 9:22
* @description
* * 指定扫描的包
* * 指定使用的sqlSessionFactory
*/
@Configuration
@MapperScan(basePackages = "sunwul.datasource.mapper.read", sqlSessionFactoryRef = "sqlSessionFactory_r")
public class Read_MybatisConfig_manyMybatis {
/**
* 读库的数据源
*
* @return DataSource
*/
@Bean("dataSource_read")
@ConfigurationProperties(prefix = "spring.datasource.datasource-read")
public DataSource dataSource_read() {
System.out.println("==============读取配置信息,创建DataSource=============");
// 通过绑定全局配置,拿到属性后自动创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
/**
* 配置读库的 SqlSessionFactory
*
* @return SqlSessionFactory
* @throws Exception ex
*/
@Bean
public SqlSessionFactory sqlSessionFactory_r() throws Exception {
return FactoryUtil_manyMybatis.createFactory(dataSource_read(), "classpath:mapper/read/*Mapper_manyMybatis.xml", true);
}
}
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
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 sunwul.datasource.common.FactoryUtil_manyMybatis;
import javax.sql.DataSource;
/*****
* @author sunwul
* @date 2022/2/25 11:27
* @description
*/
@Configuration
@MapperScan(basePackages = "sunwul.datasource.mapper.write", sqlSessionFactoryRef = "sqlSessionFactory_w")
public class Write_MybatisConfig_manyMybatis {
/**
* 写库的数据源
*
* @return DataSource
*/
@Bean("dataSource_write")
@ConfigurationProperties(prefix = "spring.datasource.datasource-write")
public DataSource dataSource_write() {
System.out.println("==============读取配置信息,创建DataSource=============");
// 通过绑定全局配置,拿到属性后自动创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
/**
* 配置写库的 SqlSessionFactory
*
* @return SqlSessionFactory
* @throws Exception ex
*/
@Bean
@Primary
public SqlSessionFactory sqlSessionFactory_w() throws Exception {
return FactoryUtil_manyMybatis.createFactory(dataSource_write(), "classpath:mapper/write/*Mapper_manyMybatis.xml", true);
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import sunwul.datasource.entity.TAdmin_manyMybatis;
import java.util.List;
import java.util.stream.Collectors;
/*****
* @author sunwul
* @date 2022/2/28 9:33
* @description
*/
@RestController
public class TestController_manyMybatis {
@Autowired
// @Qualifier("TAdminMapper_R")
sunwul.datasource.mapper.read.TAdminMapper_manyMybatis mapper_r;
@Autowired
// @Qualifier("TAdminMapper_W")
sunwul.datasource.mapper.write.TAdminMapper_manyMybatis mapper_w;
@RequestMapping("/sel")
public List<TAdmin_manyMybatis> toSel(){
return mapper_r.sel_admin();
}
@RequestMapping("/selOne/{id}")
public TAdmin_manyMybatis toSelOne(@PathVariable("id") Integer id){
return mapper_r.sel_admin().stream().filter(s -> s.getId() == id).distinct().collect(Collectors.toList()).get(0);
}
@RequestMapping("/add/{name}")
public Integer toAdd(@PathVariable("name") String name){
return mapper_w.add_admin(name);
}
}
在多数据源的情况下, 由于设计到多个数据库的读写. 一旦发生异常就可能会导致数据不一致的情况 在这种情况希望使用事务进行回退, 但是Spring的声明式事务在一次请求线程中只能使用一个数据源进行控制
对于多源数据库来说:
- 单一事务管理器(TransactionManager)无法切换数据源, 需要配置多个TransactionManager
- @TransactionManager 是无法管理多个数据源的, 如果想真正实现多源数据库的事务控制, 肯定需要分布式事务.
以下仅仅只是多数据源事务控制的一种变通方式
编程式事务可以在一个方法中对不同的数据源进行事务控制,当存在异常时,对所有数据库的操作都会回滚
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import sunwul.datasource.common.FactoryUtil_springTran;
import javax.sql.DataSource;
/*****
* @author sunwul
* @date 2022/2/25 9:22
* @description
* * 指定扫描的包
* * 指定使用的sqlSessionFactory
*/
@Configuration
@MapperScan(basePackages = "sunwul.datasource.mapper.read", sqlSessionFactoryRef = "sqlSessionFactory_r")
public class Read_MybatisConfig_springTran {
/**
* 读库的数据源
*
* @return DataSource
*/
@Bean("dataSource_read")
@ConfigurationProperties(prefix = "spring.datasource.datasource-read")
public DataSource dataSource_read() {
System.out.println("==============读取配置信息,创建DataSource=============");
// 通过绑定全局配置,拿到属性后自动创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
/**
* 配置读库的 SqlSessionFactory
*
* @return SqlSessionFactory
* @throws Exception ex
*/
@Bean
public SqlSessionFactory sqlSessionFactory_r() throws Exception {
return FactoryUtil_springTran.createFactory(dataSource_read(), "classpath:mapper/read/*Mapper_springTran.xml", true);
}
/**
* 读库的事务管理
*
* @return DataSourceTransactionManager
*/
@Bean
public DataSourceTransactionManager dataSourceTransactionManager_r() {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource_read());
return dataSourceTransactionManager;
}
/**
* 事务模板 - Spring编程式事务
*
* @return TransactionTemplate
*/
@Bean
public TransactionTemplate transactionTemplate_r() {
return new TransactionTemplate(dataSourceTransactionManager_r());
}
}
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
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.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import sunwul.datasource.common.FactoryUtil_springTran;
import javax.sql.DataSource;
/*****
* @author sunwul
* @date 2022/2/25 11:27
* @description
* * 指定扫描的包
* * 指定使用的sqlSessionFactory
*/
@Configuration
@MapperScan(basePackages = "sunwul.datasource.mapper.write", sqlSessionFactoryRef = "sqlSessionFactory_w")
public class Write_MybatisConfig_springTran {
/**
* 写库数据源
*
* @return DataSource
*/
@Bean("dataSource_write")
@ConfigurationProperties(prefix = "spring.datasource.datasource-write")
public DataSource dataSource_write() {
System.out.println("==============读取配置信息,创建DataSource=============");
// 通过绑定全局配置,拿到属性后自动创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
/**
* 配置写库的 SqlSessionFactory
* 整合
*
* @return SqlSessionFactory
* @throws Exception ex
*/
@Bean
@Primary
public SqlSessionFactory sqlSessionFactory_w() throws Exception {
return FactoryUtil_springTran.createFactory(dataSource_write(), "classpath:mapper/write/*Mapper_springTran.xml", true);
}
/**
* 写库的事务管理
*
* @return DataSourceTransactionManager
*/
@Bean
@Primary
public DataSourceTransactionManager dataSourceTransactionManager_w() {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource_write());
return dataSourceTransactionManager;
}
/**
* 事务模板 - Spring编程式事务
*
* @return TransactionTemplate
*/
@Bean
public TransactionTemplate transactionTemplate_w() {
return new TransactionTemplate(dataSourceTransactionManager_w());
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import sunwul.datasource.entity.TAdmin_springTran;
import sunwul.datasource.service.TAdminService_springTran;
import java.util.List;
/**
* @author sunwul
* @date 2022/3/10 22:25:46
* @description
*/
@Service
public class TAdminServiceImpl_springTran implements TAdminService_springTran {
@Autowired
sunwul.datasource.mapper.read.TAdminMapper_springTran TAdminMapper_R;
@Autowired
sunwul.datasource.mapper.write.TAdminMapper_springTran TAdminMapper_W;
@Autowired
TransactionTemplate transactionTemplate_w;
@Autowired
TransactionTemplate transactionTemplate_r;
@Override
public List<TAdmin_springTran> sel_admin() {
return TAdminMapper_R.sel_admin();
}
/**
* 添加数据, 制造异常,触发事务
* 由于此时写库的事务管理添加了@Primary注解, 因此此时只有写库的事务会生效
* 此时会因为数据库操作的顺序出现以下几种情况:
* 1. 当写库操作或读库操作位于异常代码之前时: 读库正常插入数据, 写库回滚操作
* 2. 当读库操作位于异常代码之前,写库操作位于异常代码之后时: 读库正常插入数据, 不会执行到写库操作, 直接抛出异常
* 3. 当写库操作位于异常代码之前,读库操作位于异常代码之后时: 写库回滚操作, 不会执行到读库操作, 直接抛出异常
* 综上:
* 开启了对应数据库的事务管理后, 当出现异常情况时, 会回滚对应的数据库操作
*/
@Transactional
// @Override
public Integer add_admin_bak(String name) {
TAdminMapper_W.add_admin(name);
TAdminMapper_R.add_admin(name);
int a = 1 / 0;
return a;
}
/**
* 通过Spring编程式事务对不同数据源进行事务控制
*/
@Override
public Integer add_admin(String name) {
transactionTemplate_w.execute((status_w) -> {
transactionTemplate_r.execute((status_r) -> {
try {
TAdminMapper_W.add_admin(name);
TAdminMapper_R.add_admin(name);
int a = 1 / 0; // 制造异常,触发事务
} catch (Exception e) {
e.printStackTrace();
status_w.setRollbackOnly(); // 调用写库的回滚
status_r.setRollbackOnly(); // 调用读库的回滚
return false;
}
return true;
});
return true;
});
return 999;
}
}
声明式事务通过注解来进行事务控制,但由于@Transactional注解无法同时使用多个,因此只能通过Spring的代理对象来解决这个问题
...等待探索
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。