一、概述
项目采用spring-boot 2.1.6,mybatis,并采用了mybatis-plus中间件,数据库为mysql5.6。
二、实现过程
2.1 实现多数据源
根据网上的大部分人的分享,我大概理清了思路,大致是这样:
1.写一个Holder通过改变ThreadLocal来直接修改数据源的名称
2.写一个注解,再在aop里配置一下,需要切换时通过注解来完成
这两种实现其实都是大同小异的,知道其实现原理才是最重要的。
第一步、创建数据源上下文分配对象:DataSourceContextHolder
package com.daodianlai.commons.jdbc;
import lombok.extern.slf4j.Slf4j;
/**
* DataSourceContextHolder
*
* @author yangqisheng
* @date 2019/08/08
*/
@Slf4j
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
/**
* 设置数据库
*
* @param dataBase 数据库名称
*/
public static void setDataBase(String dataBase) {
log.debug("切换到{" + dataBase + "}数据库");
contextHolder.set(dataBase);
}
/**
* 获取当前数据库名称
*
* @return 数据库名称
*/
public static String getDataBase() {
return (contextHolder.get());
}
/**
* 清除已设置的数据库
*/
public static void clearDataData() {
log.debug("清除{" + contextHolder.get() + "}数据库");
contextHolder.remove();
}
}
第二步、实现路由切换AbstractRoutingDataSource
package com.daodianlai.commons.jdbc;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态数据源路由切换
*
* @author yangqisheng
* @date 2019/08/08
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataBase();
}
}
下一步是创建多数据源,因为采用了Mybatis-Plus所以将数据源配置放在了MybatisPlusConfig里。
package com.daodianlai.commons.config;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.daodianlai.commons.jdbc.DynamicDataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* MybatisPlusConfig
*
* @author yangqisheng
* @date 2019/07/17
*/
@Configuration
@MapperScan(basePackages = {
"com.daodianlai.*.dao.mapper",
"com.daodianlai.*.*.dao.mapper"
})
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
/**
* 获取默认到店来数据源
*
* @return 到店来数据源
*/
@Bean(name = "defaultDataSource")
@ConfigurationProperties(prefix = "spring.datasource.default")
public DataSource defaultDataSource() {
return DataSourceBuilder.create().build();
}
/**
* 获取aswpx数据源
*
* @return 微评选数据源
*/
@Bean(name = "aswpxDataSource")
@ConfigurationProperties(prefix = "spring.datasource.aswpx")
public DataSource aswpxDataSource() {
return DataSourceBuilder.create().build();
}
/**
* 获取hhd数据源
*
* @return hhd数据源
*/
@Bean(name = "hhdDataSource")
@ConfigurationProperties(prefix = "spring.datasource.hhd")
public DataSource hhdDataSource() {
return DataSourceBuilder.create().build();
}
/**
* 动态数据源: 通过AOP在不同数据源之间动态切换
*
* @return 动态数据源
*/
@Primary
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource(
@Qualifier("defaultDataSource") DataSource defaultDataSource,
@Qualifier("aswpxDataSource") DataSource aswpxDataSource,
@Qualifier("hhdDataSource") DataSource hhdDataSource) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 默认数据源
dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);
// 配置多数据源
Map<Object, Object> dataSourceMap = new HashMap();
dataSourceMap.put("defaultDataSource", defaultDataSource());
dataSourceMap.put("aswpxDataSource", aswpxDataSource);
dataSourceMap.put("hhdDataSource", hhdDataSource);
//设置数据源Map
dynamicDataSource.setTargetDataSources(dataSourceMap);
return dynamicDataSource;
}
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory() throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dynamicDataSource(defaultDataSource(), aswpxDataSource(), hhdDataSource()));
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setJdbcTypeForNull(JdbcType.NULL);
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(false);
sqlSessionFactory.setConfiguration(configuration);
//添加插件
sqlSessionFactory.setPlugins(new Interceptor[]{
paginationInterceptor()
});
return sqlSessionFactory.getObject();
}
}
最后一步就是定义拦截器,切换数据源
1.默认数据源 DefultDataSourceAspect
2.aswpx数据源 AswpxDataSourceAspect
3. hhd数据源 HhdDataSourceAspect
下面给一下具体实现:
package com.daodianlai.commons.jdbc;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* DefaultDataSourceAspect
*
* @author yangqisheng
* @date 2019/08/08
*/
@Aspect
@Order(-1)
@Component
public class AswpxDataSourceAspect {
@Pointcut("execution(* com.daodianlai.marketing.dao.mapper.*Mapper.*(..))")
public void defaultAspect() {
}
@Before("defaultAspect()")
public void before(JoinPoint defaultAspect) {
// 切换数据源
DataSourceContextHolder.setDataSource("defaultDataSource");
}
@After("aswpxAspect()")
public void after(JoinPoint defaultAspect) {
// 清除数据源
DataSourceContextHolder.clearDataSource();
}
}
package com.daodianlai.commons.jdbc;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* AswpxDataSourceAspect
*
* @author yangqisheng
* @date 2019/08/08
*/
@Aspect
@Order(-1)
@Component
public class AswpxDataSourceAspect {
@Pointcut("execution(* com.daodianlai.aswpx.dao.mapper.*Mapper.*(..))")
public void aswpxAspect() {
}
@Before("aswpxAspect()")
public void before(JoinPoint aswpxAspect) {
// 切换数据源
DataSourceContextHolder.setDataSource("aswpxDataSource");
}
@After("aswpxAspect()")
public void after(JoinPoint aswpxAspect) {
// 清除数据源
DataSourceContextHolder.clearDataSource();
}
}
package com.daodianlai.commons.jdbc;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* HhdDataSourceAspect
*
* @author yangqisheng
* @date 2019/08/08
*/
@Aspect
@Order(-1)
@Component
public class HhdDataSourceAspect {
@Pointcut("execution(* com.daodianlai.zpi.hhd.dao.mapper.*Mapper.*(..))")
public void hhdAspect() {
}
@Before("hhdAspect()")
public void before(JoinPoint hhdAspect) {
// 切换数据源
DataSourceContextHolder.setDataSource("hhdDataSource");
}
@After("hhdAspect()")
public void after(JoinPoint hhdAspect) {
// 清除数据源
DataSourceContextHolder.clearDataSource();
}
}
至此已经完成了多数据源的配置,下面给出数据源配置:
spring:
application:
name: daodianlai-marketing
aop:
proxy-target-class: true
auto: true
datasource:
default:
jdbc-url: jdbc:mysql://localhost:3306/dapdianlai?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
aswpx:
jdbc-url: jdbc:mysql://localhost:3306/aswpx?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
hhd:
jdbc-url: jdbc:mysql://localhost:3306/hhd?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: longsu
driver-class-name: com.mysql.cj.jdbc.Driver
“重点解读一下jdbc-url”
正常我们设置数据源的时候用的都是url,这里为什么用的是jdbc-url呢?我是在这篇文章里找到的答案,有兴趣可以去看一下。
jdbcUrl is required with driverClassName错误解决
https://blog.csdn.net/newbie_907486852/article/details/81391525
好了,我做了系统测试,运行正常,那么多数据源这事做完了吗?
答案是“没有”!
当我在service的方法上加了一个事务注解后,报错了,代码如下
@Transactional(rollbackFor = {Exception.class})
public void updateData() {
aswpxMapper.excute("update t_user set num = num + 1");
daodianlaiMapper.excute("update t_visit set num = num + 1");
}
系统报了以下错误:
### Cause: java.sql.SQLSyntaxErrorException: Table 'daodianlai.t_visit' doesn't exist
很明显,是数据源没切回来,那么什么原因呢?
这篇文章讲得很详细,感兴趣的可以移步去看看
spring项目中多数据源无法切换的原因分析
https://blog.csdn.net/puhaiyang/article/details/88013712
所以,怎么才能实现切换动态数据源还能控制事务呢?
我在这篇文章里找到了方案,并自己成功实现了。
springboot+mybatis解决多数据源切换事务控制不生效的问题
https://blog.csdn.net/gaoshili001/article/details/79378902
下面我给出最终的实现代码,多数据源,分布式事务代码。
采用的是jpa+atomikos方案
根据Spring官方介绍,要想自己控制事务,则必须实现Transaction接口,所以我们来创建动态数据源事务实现类DynamicDataSourceTransaction
DynamicDataSourceTransaction.java
package com.daodianlai.commons.jdbc;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.transaction.Transaction;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.util.Assert;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* 动态数据源事务
*
* @author yangqisheng
* @date 2019/08/09
*/
@Slf4j
public class DynamicDataSourceTransaction implements Transaction {
private final DataSource dataSource;
private Connection defaultConnection;
private String dataBaseName;
private ConcurrentMap<String, Connection> dynamicConnectionMap;
private boolean isConnectionTransactional;
private boolean autoCommit;
public DynamicDataSourceTransaction(DataSource dataSource) {
Assert.notNull(dataSource, "No DataSource specified");
this.dataSource = dataSource;
this.dynamicConnectionMap = new ConcurrentHashMap<>();
this.dataBaseName = DataSourceContextHolder.getDataBase();
}
@Override
public Connection getConnection() throws SQLException {
String dataBase = DataSourceContextHolder.getDataBase();
if (dataBase.equals(dataBaseName)) {
if (defaultConnection != null) {
return defaultConnection;
}
openMainConnection();
dataBaseName = dataBase;
return defaultConnection;
} else {
if (!dynamicConnectionMap.containsKey(dataBase)) {
try {
Connection conn = dataSource.getConnection();
dynamicConnectionMap.put(dataBase, conn);
} catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
}
}
return dynamicConnectionMap.get(dataBase);
}
}
private void openMainConnection() throws SQLException {
this.defaultConnection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.defaultConnection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.defaultConnection, this.dataSource);
if (log.isDebugEnabled()) {
log.debug(
"JDBC Connection ["
+ this.defaultConnection
+ "] will"
+ (this.isConnectionTransactional ? " " : " not ")
+ "be managed by Spring");
}
}
@Override
public void commit() throws SQLException {
if (this.defaultConnection != null && !this.isConnectionTransactional && !this.autoCommit) {
if (log.isDebugEnabled()) {
log.debug("Committing JDBC Connection [" + this.defaultConnection + "]");
}
this.defaultConnection.commit();
for (Connection connection : dynamicConnectionMap.values()) {
connection.commit();
}
}
}
@Override
public void rollback() throws SQLException {
if (this.defaultConnection != null && !this.isConnectionTransactional && !this.autoCommit) {
if (log.isDebugEnabled()) {
log.debug("Rolling back JDBC Connection [" + this.defaultConnection + "]");
}
this.defaultConnection.rollback();
for (Connection connection : dynamicConnectionMap.values()) {
connection.rollback();
}
}
}
@Override
public void close() {
DataSourceUtils.releaseConnection(this.defaultConnection, this.dataSource);
for (Connection connection : dynamicConnectionMap.values()) {
DataSourceUtils.releaseConnection(connection, this.dataSource);
}
}
@Override
public Integer getTimeout() {
return null;
}
}
重写动态数据源事务管理工厂
DynamicDataSourceTransactionFactory.java
package com.daodianlai.commons.jdbc;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import javax.sql.DataSource;
/**
* 重写动态数据源事务管理工厂
*
* @author yangqisheng
* @date 2019/08/09
*/
public class DynamicDataSourceTransactionFactory extends SpringManagedTransactionFactory {
@Override
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new DynamicDataSourceTransaction(dataSource);
}
}
当然最后一步,我们完全的重写了MybatisPlusConfig
新MybatisPlusConfig.java
package com.daodianlai.commons.config;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.daodianlai.commons.jdbc.DynamicDataSource;
import com.daodianlai.commons.jdbc.DynamicDataSourceTransactionFactory;
import com.mysql.cj.jdbc.MysqlXADataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
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 org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.sql.DataSource;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
/**
* MybatisPlusConfig
*
* @author yangqisheng
* @date 2019/07/17
*/
@Configuration
@EnableTransactionManagement
@MapperScan(basePackages = {
"com.daodianlai.*.dao.mapper",
"com.daodianlai.*.*.dao.mapper"
})
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
@Primary
@Bean(name = "defaultDataSourceProperties")
@Qualifier("defaultDataSourceProperties")
@ConfigurationProperties(prefix = "spring.datasource.default")
public DataSourceProperties defaultDataSourceProperties() {
return new DataSourceProperties();
}
/**
* 获取默认到店来数据源
*
* @return 到店来数据源
*/
@Bean(name = "defaultDataSource", initMethod = "init", destroyMethod = "close")
@ConfigurationProperties(prefix = "spring.datasource.default")
public DataSource defaultDataSource() throws SQLException {
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXaDataSource.setUrl(defaultDataSourceProperties().getUrl());
mysqlXaDataSource.setUser(defaultDataSourceProperties().getUsername());
mysqlXaDataSource.setPassword(defaultDataSourceProperties().getPassword());
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("default");
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setMaxPoolSize(20);
return xaDataSource;
}
@Bean(name = "aswpxDataSourceProperties")
@Qualifier("aswpxDataSourceProperties")
@ConfigurationProperties(prefix = "spring.datasource.aswpx")
public DataSourceProperties aswpxDataSourceProperties() {
return new DataSourceProperties();
}
/**
* 获取微评选数据源
*
* @return 微评选数据源
*/
@Bean(name = "aswpxDataSource", initMethod = "init", destroyMethod = "close")
@ConfigurationProperties(prefix = "spring.datasource.aswpx")
public DataSource aswpxDataSource() throws SQLException {
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXaDataSource.setUrl(aswpxDataSourceProperties().getUrl());
mysqlXaDataSource.setUser(aswpxDataSourceProperties().getUsername());
mysqlXaDataSource.setPassword(aswpxDataSourceProperties().getPassword());
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("aswpx");
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setMaxPoolSize(20);
return xaDataSource;
}
@Bean(name = "hhdDataSourceProperties")
@Qualifier("hhdDataSourceProperties")
@ConfigurationProperties(prefix = "spring.datasource.hhd")
public DataSourceProperties hhdDataSourceProperties() {
return new DataSourceProperties();
}
/**
* 获取慧惠多数据源
*
* @return 慧惠多数据源
*/
@Bean(name = "hhdDataSource", initMethod = "init", destroyMethod = "close")
@ConfigurationProperties(prefix = "spring.datasource.hhd")
public DataSource hhdDataSource() throws SQLException {
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXaDataSource.setUrl(hhdDataSourceProperties().getUrl());
mysqlXaDataSource.setUser(hhdDataSourceProperties().getUsername());
mysqlXaDataSource.setPassword(hhdDataSourceProperties().getPassword());
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("hhd");
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setMaxPoolSize(20);
return xaDataSource;
}
/**
* 动态数据源: 通过AOP在不同数据源之间动态切换
*
* @return 动态数据源
*/
@Primary
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource(
@Qualifier("defaultDataSource") DataSource defaultDataSource,
@Qualifier("aswpxDataSource") DataSource aswpxDataSource,
@Qualifier("hhdDataSource") DataSource hhdDataSource) throws SQLException {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 默认数据源
dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);
// 配置多数据源
Map<Object, Object> dataSourceMap = new HashMap();
dataSourceMap.put("defaultDataSource", defaultDataSource());
dataSourceMap.put("aswpxDataSource", aswpxDataSource);
dataSourceMap.put("hhdDataSource", hhdDataSource);
//设置数据源Map
dynamicDataSource.setTargetDataSources(dataSourceMap);
return dynamicDataSource;
}
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory() throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
//设置动态数据源
sqlSessionFactory.setDataSource(dynamicDataSource(defaultDataSource(), aswpxDataSource(), hhdDataSource()));
//设置动态数据源事务管理
sqlSessionFactory.setTransactionFactory(new DynamicDataSourceTransactionFactory());
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setJdbcTypeForNull(JdbcType.NULL);
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(false);
sqlSessionFactory.setConfiguration(configuration);
//添加插件
sqlSessionFactory.setPlugins(new Interceptor[]{
paginationInterceptor()
});
return sqlSessionFactory.getObject();
}
@Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
}
@Bean(name = "userTransactionManager", initMethod = "init", destroyMethod = "close")
public TransactionManager userTransactionManager() {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(true);
return userTransactionManager;
}
// 默认事务管理器
@Bean(name = "transactionManager")
@DependsOn({"userTransaction", "userTransactionManager"})
public PlatformTransactionManager transactionManager() throws Throwable {
UserTransaction userTransaction = userTransaction();
TransactionManager userTransactionManager = userTransactionManager();
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, userTransactionManager);
jtaTransactionManager.setAllowCustomIsolationLevels(true);
return jtaTransactionManager;
}
}
最后做了测试,完美切换数据源,并实现事务回滚。
package com.daodianlai.task;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* MoreTranTask
*
* @author yangqisheng
* @date 2019/08/09
*/
@Slf4j
@Component
public class MoreTranTask {
@Autowired
private MoreTranService moreTranService;
@Scheduled(cron = "0 * * * * ?")
public void run() {
log.info(">>>>【多事务测试】开始");
moreTranService.updateData();
log.info(">>>>【多事务测试】结束");
}
}
package com.daodianlai.task;
import com.daodianlai.aswpx.dao.mapper.AswpxMapper;
import com.daodianlai.commons.jdbc.DataSourceContextHolder;
import com.daodianlai.zpi.sdcbd.dao.mapper.DaodianlaiMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* MoreTranService
*
* @author yangqisheng
* @date 2019/08/09
*/
@Slf4j
@Service
public class MoreTranService {
@Autowired
private AswpxMapper aswpxMapper;
@Autowired
private DaodianlaiMapper daodianlaiMapper;
@Transactional(
transactionManager = "transactionManager",
propagation = Propagation.REQUIRED, readOnly = false,
rollbackFor = {Exception.class})
public void updateData() {
aswpxMapper.excute("update t_user set num = num + 1");
daodianlaiMapper.excute("update t_visit set num = num + 1");
//此处会报错,引发事务回滚
int a = 1 / 0;
}
}
希望大家调试顺利~