spring中父子線程共享事務(wù)的實(shí)現(xiàn)
一、原生jdbc的父子線程共用事務(wù)
初始化數(shù)據(jù)庫中的數(shù)據(jù)

1、單線程事務(wù)提交
private static void t1() throws Exception {
Connection conn = getConn();
PreparedStatement stmt1 = null;
PreparedStatement stmt2 = null;
try {
// 1. 禁用自動提交,開啟事務(wù)
conn.setAutoCommit(false);
// 2. 執(zhí)行多個數(shù)據(jù)庫操作
String sql1 = "UPDATE student SET age = age - 1 WHERE id = ?";
stmt1 = conn.prepareStatement(sql1);
stmt1.setInt(1, 1);
stmt1.executeUpdate();
String sql2 = "UPDATE student SET age = age + 1 WHERE id = ?";
stmt2 = conn.prepareStatement(sql2);
stmt2.setInt(1, 2);
stmt2.executeUpdate();
// 3. 事務(wù)回滾, 將會回滾兩個執(zhí)行語句的內(nèi)容
conn.rollback();
// 4. 提交事務(wù)
conn.commit();
System.out.println("Transaction committed successfully.");
} catch (SQLException e) {
// 發(fā)生異常時回滾事務(wù)
if (conn != null) {
try {
conn.rollback();
System.out.println("Transaction rolled back.");
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
close(stmt1, stmt2);
}
}
獲取數(shù)據(jù)庫連接和釋放連接
private static Connection getConn() throws Exception {
String url = "jdbc:mysql://localhost:3306/test";
String username = "root";
String password = "123456";
// 1. 獲取數(shù)據(jù)庫連接
return DriverManager.getConnection(url, username, password);
}
private static void close(AutoCloseable... closeables) {
for (AutoCloseable closeable : closeables) {
if (closeable != null) {
try {
closeable.close();
} catch (Exception e) {
// ... do something
e.printStackTrace();
}
}
}
}
執(zhí)行完后, 數(shù)據(jù)庫中數(shù)據(jù)如下, 可以看到兩條語句都執(zhí)行成功

2、父子線程提交事務(wù)
還原數(shù)據(jù)庫中的數(shù)據(jù)如下

private static void t2() throws Exception {
Connection conn = getConn();
PreparedStatement stmt1 = null;
try {
// 1. 禁用自動提交,開啟事務(wù)
conn.setAutoCommit(false);
// 2. 父線程執(zhí)行
String sql1 = "UPDATE student SET age = age - 1 WHERE id = ?";
stmt1 = conn.prepareStatement(sql1);
stmt1.setInt(1, 1);
stmt1.executeUpdate();
// 3. 子線程執(zhí)行
Thread t1 = new Thread(() -> {
String sql2 = "UPDATE student SET age = age + 1 WHERE id = ?";
try {
PreparedStatement stmt2 = conn.prepareStatement(sql2);
stmt2.setInt(1, 2);
stmt2.executeUpdate();
// 子線程回滾
conn.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
});
t1.start();
// 等t1執(zhí)行完
t1.join();
// 4. 提交事務(wù)
conn.commit();
System.out.println("Transaction committed successfully.");
} catch (SQLException e) {
// 發(fā)生異常時回滾事務(wù)
if (conn != null) {
try {
conn.rollback();
System.out.println("Transaction rolled back.");
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
close(stmt1);
}
}
執(zhí)行之后看結(jié)果

age沒有發(fā)生變化, 也就是說子線程回滾了, 主線程中的提交不會生效, 達(dá)到了異步任務(wù)中事務(wù)共享的效果。
二、jdbcTemplate實(shí)現(xiàn)父子共用事務(wù)
以jdbcTemplate的update方法為例, 看看內(nèi)部實(shí)現(xiàn)
// ... 只看核心update方法
protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss)
throws DataAccessException {
return updateCount(execute(psc, ps -> {
try {
// 賦值
if (pss != null) {
pss.setValues(ps);
}
// 使用PreparedStatement執(zhí)行
int rows = ps.executeUpdate();
if (logger.isTraceEnabled()) {
logger.trace("SQL update affected " + rows + " rows");
}
return rows;
}
finally {
// ...
}
}, true));
}
繼續(xù)看execute
private <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action, boolean closeResources)
throws DataAccessException {
// 從數(shù)據(jù)源中獲取一個鏈接
Connection con = DataSourceUtils.getConnection(obtainDataSource());
PreparedStatement ps = null;
try {
// ...省略部分代碼
// 執(zhí)行
T result = action.doInPreparedStatement(ps);
return result;
}
catch (SQLException ex) {
//...
}
finally {
// 關(guān)閉資源
if (closeResources) {
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
// 關(guān)閉Statement
JdbcUtils.closeStatement(ps);
// 回收鏈接(連接池的場景下是對連接的引用數(shù)-1, 并不會直接釋放連接)
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
}
這里是從連接池中獲取一個鏈接, 然后執(zhí)行對應(yīng)的db操作, 這里重點(diǎn)看一下 Connection con = DataSourceUtils.getConnection(obtainDataSource());
DataSourceUtils.getConnection的核心邏輯如下
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
// 上下文中獲取當(dāng)前數(shù)據(jù)源中的連接句柄
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
// 1.連接句柄存在 并且 2(句柄中有鏈接 或者 是事務(wù)同步)
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
// 引用數(shù)+1
conHolder.requested();
// 直接獲取連接
return conHolder.getConnection();
}
// 從數(shù)據(jù)源中獲取連接
Connection con = fetchConnection(dataSource);
// ... 省略事務(wù)同步處理邏輯
return con;
}
從這個方法可以知道兩點(diǎn)
- 如果當(dāng)前上下文中存在連接就用該連接
- 不存在上下文中的連接就從數(shù)據(jù)源中獲取
清楚了這個邏輯后, 我們來構(gòu)建多線性下使用事務(wù)的場景
@Autowired
private DataSource dataSource;
@Autowired
private JdbcTemplate jdbcTemplate;
private void jdbcTemplateTest() throws Exception {
Connection con = DataSourceUtils.getConnection(dataSource);
con.setAutoCommit(false);
ConnectionHolder holderToUse = new ConnectionHolder(con);
// 在jdbcTemplate執(zhí)行update之前將數(shù)據(jù)庫連接放到上下文中
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
String sql1 = "UPDATE student SET age = age - 1 WHERE id = ?";
jdbcTemplate.update(sql1, "1");
Thread t1 = new Thread(() -> {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
String sql2 = "UPDATE student SET age = age + 1 WHERE id = ?";
jdbcTemplate.update(sql2, "2");
try {
con.rollback();
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
t1.start();
t1.join();
con.commit();
con.setAutoCommit(true);
}
注意這里需要在jdbcTemplate執(zhí)行update之前調(diào)用bindResource操作, 因為上面介紹的update里面會先從上下文中獲取數(shù)據(jù)庫連接, 在子線程中也需要執(zhí)行bindResource將連接綁定到子線程中, 這樣父子線程就用的同一個連接了。
注意這里是使用Connection直接調(diào)用的setAutoCommit控制手動提交, 所以用完后記得設(shè)置為自動提交
三、PlatformTransactionManager 管理事務(wù)下的父子線程共享事務(wù)
簡單demo
先看下一般的單線程下的手動事務(wù)
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private TransactionDefinition definition;
private void manualTx() {
String sql1 = "UPDATE student SET age = age - 1 WHERE id = ?";
// 開啟手動事務(wù)
TransactionStatus status = this.transactionManager.getTransaction(definition);
try {
jdbcTemplate.update(sql1, "1");
throw new RuntimeException("錯誤了");
} catch (Exception e) {
this.transactionManager.rollback(status);
}
if (!status.isCompleted()) {
this.transactionManager.commit(status);
}
}
這里主要是使用getTransaction方法開啟手動提交, 然后rollback方法回滾事務(wù), commit方法提交事務(wù)
原理剖析
這里我們看看手動開啟方法getTransaction
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// Use defaults if no transaction definition given.
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
// 上下文中獲取事務(wù)
Object transaction = doGetTransaction();
// 判斷是否獲取到了連接和并且事務(wù)由jdbc管理
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(def, transaction, debugEnabled);
}
// 以下是事務(wù)不存在的情況
// 判斷傳播機(jī)制; 這里是必須有事務(wù)的傳播機(jī)制
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
// 需要事務(wù)的傳播機(jī)制
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
// 創(chuàng)建新的事物
try {
return startTransaction(def, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
else {
// 創(chuàng)建空事物
// ....
}
}
核心邏輯
- 如果存在事務(wù), 則使用當(dāng)前事務(wù)
- 如果不存在事務(wù), 并且隔離界別需要開啟事務(wù), 那么創(chuàng)建新事務(wù)
再跟一下doGetTransaction方法
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
我們看到了上面也介紹過的從上下文中獲取連接句柄的方法TransactionSynchronizationManager.getResource
繼續(xù)看startTransaction方法, 創(chuàng)建新的事物
private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
// 開啟事務(wù)
doBegin(transaction, definition);
// 將事物相關(guān)信息放入到上下文中
prepareSynchronization(status, definition);
return status;
}
這里做了兩件事: 1. 開啟事務(wù) 2. 將事物相關(guān)信息放入到上下文中
先看一下開啟事務(wù)doBegin方法
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
// 1.上下文中沒有連接 2.或者需要將事務(wù)資源綁定到上下文中
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
// 創(chuàng)建新的數(shù)據(jù)庫連接
Connection newCon = obtainDataSource().getConnection();
// 標(biāo)記為新創(chuàng)建的數(shù)據(jù)庫連接
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
// 設(shè)置為綁定資源到上下文
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
// 隔離級別
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
txObject.setReadOnly(definition.isReadOnly());
// 設(shè)置為手動提交
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
con.setAutoCommit(false);
}
// 設(shè)置事務(wù)只讀
prepareTransactionalConnection(con, definition);
// 標(biāo)記事務(wù)激活狀態(tài)
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// 新的數(shù)據(jù)庫連接, 就將連接綁定的上下文中
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
}
方法小結(jié)
- 沒事務(wù)就開啟事務(wù)
- 設(shè)置事務(wù)為手動提交
- 將數(shù)據(jù)庫連接放到上下文中
TransactionSynchronizationManager.bindResource
咋們再來看看prepareSynchronization方法, 將事務(wù)信息放到上下文
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
// 新事務(wù)打開了事務(wù)同步
if (status.isNewSynchronization()) {
// 事務(wù)激活狀態(tài)
TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
// 隔離級別
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?
definition.getIsolationLevel() : null);
TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
TransactionSynchronizationManager.initSynchronization();
}
}
在傳播機(jī)制是PROPAGATION_REQUIRED(有事務(wù)就用當(dāng)前事務(wù), 沒有就新建)的場景下我們看一下handleExistingTransaction方法, 用來獲取當(dāng)前事務(wù)
// ... 省略傳播機(jī)制判斷的代碼
if (isValidateExistingTransaction()) {
// 非默認(rèn)隔離級別, 校驗隔離級別
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
// ...拋異常
}
}
// 校驗readOnly
if (!definition.isReadOnly()) {
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
// ...拋異常
}
}
}
// 將事務(wù)信息綁定到線程上下文
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
總結(jié)一下transactionManager.getTransaction方法
- 從當(dāng)前上下文獲取數(shù)據(jù)庫連接句柄
TransactionSynchronizationManager.getResource - 如果存在連接和事務(wù)
- 直接使用當(dāng)前事務(wù)
- 校驗事務(wù)的傳播機(jī)制, 是否只讀
- 將事務(wù)信息放到上下文
- 如果當(dāng)前不存在事務(wù)(
PROPAGATION_REQUIRED場景下)
- 創(chuàng)建新的數(shù)據(jù)庫連接
- 設(shè)置為手動提交
- 綁定數(shù)據(jù)庫連接句柄到上下文
TransactionSynchronizationManager.bindResource - 將事務(wù)信息放到上下文
關(guān)于TransactionStatus的commit方法和rollback方法我們就不看了, 它里面都校驗了事務(wù)是否已經(jīng)激活事務(wù)同步, 可以使用TransactionSynchronizationManager.initSynchronization方法來激活
有了以上信息支撐后, 我們再來看PlatformTransactionManager下的父子線程共享事務(wù)
我們只需要將父線程中的數(shù)據(jù)庫句柄放到子線程, 并且設(shè)置傳播機(jī)制, 是否只讀, 激活事務(wù)同步就可以了;
PlatformTransactionManager下的父子線程使用同一個事務(wù)的demo
private void manualMultiTx() throws Exception {
String sql1 = "UPDATE student SET age = age - 1 WHERE id = ?";
// 開啟手動事務(wù)
TransactionStatus status = this.transactionManager.getTransaction(definition);
// 獲取父線程中綁定的數(shù)據(jù)庫連接句柄
ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
// 因為getTransaction已經(jīng)將ConnectionHolder綁定到當(dāng)前線程(TransactionSynchronizationManager.bindResource),所以這里會直接拿這個連接
jdbcTemplate.update(sql1, "1");
Thread t1 = new Thread(() -> {
/******************** 核心步驟start ***************/
// 將ConnectionHolder綁定到當(dāng)前線程
TransactionSynchronizationManager.bindResource(dataSource, connectionHolder);
// 將事務(wù)相關(guān)信息綁定到現(xiàn)成上下文
// 設(shè)置隔離級別
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(definition.getIsolationLevel());
// 設(shè)置是否只讀
TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
// 激活事務(wù)同步
TransactionSynchronizationManager.initSynchronization();
/******************** 核心步驟end ***************/
System.out.println("線程1執(zhí)行");
jdbcTemplate.update(sql1, "2");
if (!status.isCompleted()) {
this.transactionManager.rollback(status);
}
});
t1.start();
t1.join();
System.out.println("run線程執(zhí)行");
if (!status.isCompleted()) {
this.transactionManager.commit(status);
}
}
其實(shí)這里的將事務(wù)相關(guān)信息綁定到現(xiàn)成上下文直接copy上面的prepareSynchronization方法的內(nèi)容即可
改成這樣更完整
/******************** 核心步驟start ***************/ TransactionSynchronizationManager.bindResource(dataSource, connectionHolder); DefaultTransactionStatus s = (DefaultTransactionStatus) status; // 事務(wù)激活狀態(tài) TransactionSynchronizationManager.setActualTransactionActive(s.hasTransaction()); // 隔離級別, 這里稍微不一樣 TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(definition.getIsolationLevel()); TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly()); TransactionSynchronizationManager.setCurrentTransactionName(definition.getName()); TransactionSynchronizationManager.initSynchronization(); /******************** 核心步驟end ***************/
四、@Transactional注解下的父子線程事務(wù)共享
我們得看到@Transactional注解的增強(qiáng)類TransactionInterceptor中去;
在invoke中調(diào)用了invokeWithinTransaction方法, 我們關(guān)注一下
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// 事務(wù)屬性
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
// 事務(wù)管理器
final TransactionManager tm = determineTransactionManager(txAttr);
// ... 省略一些其它代碼
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
// 切點(diǎn)方法
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
// 這里是JdbcTransactionManager, 也是PlatformTransactionManager實(shí)現(xiàn)類
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// 創(chuàng)建事務(wù)
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// 調(diào)用目標(biāo)方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 這里會rollback
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
// 這里會commit
commitTransactionAfterReturning(txInfo);
return retVal;
}
// ... 省略部分代碼
}
這里我們只需要關(guān)注用來創(chuàng)建事務(wù)的createTransactionIfNecessary方法即可
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
// ... 省略部分代碼
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
status = tm.getTransaction(txAttr);
}
}
// 包裝一下返回
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
回到了上面原理剖析中介紹的PlatformTransactionManager#getTransaction方法了, 也就是說它也會先從線程上下文中獲取數(shù)據(jù)庫連接句柄, 有的話直接用了, 沒有就去創(chuàng)建然后放到當(dāng)前上下文, 和上面介紹的第3節(jié)中的一樣, 這里就不贅述了。
demo和上面的也基本沒有區(qū)別
private void transactionalTest() throws Exception {
// 開啟手動事務(wù)
TransactionStatus status = this.transactionManager.getTransaction(definition);
ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
// 因為getTransaction已經(jīng)將ConnectionHolder綁定到當(dāng)前線程(TransactionSynchronizationManager.bindResource),所以這里會直接拿這個連接
// 主線程中service調(diào)用
myService.tranTest();
Thread t1 = new Thread(() -> {
// 將ConnectionHolder綁定到當(dāng)前線程
TransactionSynchronizationManager.bindResource(dataSource, connectionHolder);
DefaultTransactionStatus s = (DefaultTransactionStatus) status;
// 標(biāo)記當(dāng)前事務(wù)已存在
TransactionSynchronizationManager.setActualTransactionActive(s.hasTransaction());
// 設(shè)置隔離級別
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(definition.getIsolationLevel());
// 設(shè)置是否只讀
TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
TransactionSynchronizationManager.initSynchronization();
System.out.println("線程1執(zhí)行");
// 子線程中service調(diào)用
myService.tranTest();
if (!status.isCompleted()) {
this.transactionManager.rollback(status);
}
});
t1.start();
t1.join();
System.out.println("run線程執(zhí)行");
if (!status.isCompleted()) {
this.transactionManager.commit(status);
}
}
service
@Component
public class MyService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void tranTest() {
String sql1 = "UPDATE student SET age = age - 1 WHERE id = ?";
jdbcTemplate.update(sql1, "2");
}
}
五、總結(jié)
- 父子線程中事務(wù)共享的原理就是要讓一個數(shù)據(jù)庫連接在父子線程中同時使用
- 原生jdbc中, 將數(shù)據(jù)庫連接直接從父線程傳遞到子線程即可
- jdbcTemplate的使用中, 只需將同一個數(shù)據(jù)庫連接同時綁定到父子線程中接口, 使用
TransactionSynchronizationManager.bindResource - 在spring中使用
PlatformTransactionManager開啟手動事務(wù), 除了第三點(diǎn)的需要將數(shù)據(jù)庫連接同時在父線程中綁定transactionManager.getTransaction(definition)和在子線程中綁定TransactionSynchronizationManager.bindResource之外, 還需要將事務(wù)的相關(guān)信息綁定到子線程(詳細(xì)見文章內(nèi)部) - 在spring中使用
PlatformTransactionManager不管是配合jdbcTemplate還是調(diào)用@Transactional使用聲明式原理都是一樣的, 都是得從在子線程中綁定父線程中的相同的事務(wù)信息和連接信息
到此這篇關(guān)于spring中父子線程共享事務(wù)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)spring 父子線程共享事務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于Java從本地文件復(fù)制到網(wǎng)絡(luò)文件上傳
這篇文章主要介紹了關(guān)于Java從本地文件復(fù)制到網(wǎng)絡(luò)文件上傳,File?和?IO?流其實(shí)是很相似的,都是將文件從一個地方轉(zhuǎn)移到另一個地方,這也是流的特點(diǎn)之一,需要的朋友可以參考下2023-04-04
Mybatis-Plus設(shè)置全局或者局部ID自增的實(shí)現(xiàn)
在使用Mybatis-Plus新增的時候,我們往往想要id隨著數(shù)據(jù)庫自增,本文主要介紹了Mybatis-Plus設(shè)置全局或者局部ID自增的實(shí)現(xiàn),具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01
SpringBoot3整合MinIO實(shí)現(xiàn)分布式文件存儲系統(tǒng)
MinIO是一個高性能的分布式對象存儲系統(tǒng),兼容Amazon S3協(xié)議,廣泛用于存儲大量非結(jié)構(gòu)化數(shù)據(jù),SpringBoot3提供了與MinIO的良好集成,可以很方便地將MinIO用于分布式文件存儲,在這篇文章中,我們將介紹如何在SpringBoot3中集成MinIO,搭建一個分布式文件存儲系統(tǒng)2025-07-07
Java基本類型與byte數(shù)組之間相互轉(zhuǎn)換方法
下面小編就為大家?guī)硪黄狫ava基本類型與byte數(shù)組之間相互轉(zhuǎn)換方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-08-08
springboot整合xxl-job的實(shí)現(xiàn)示例
本文主要介紹了springboot整合xxl-job的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
詳解SpringCloud服務(wù)認(rèn)證(JWT)
本篇文章主要介紹了SpringCloud服務(wù)認(rèn)證(JWT),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01
@Autowired自動裝配接口時存在多個實(shí)現(xiàn)的處理過程
文章介紹了在Spring中@Autowired自動裝配接口如果存在多個實(shí)現(xiàn)類時的注入問題,建議通過@Resource或@Qualifier注解按名稱指定注入,避免編譯報錯,并分享了個人實(shí)踐經(jīng)驗2025-10-10

