一、概述
項目採用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;
}
}
希望大家調試順利~