前置知識
- 簡單介紹 詳解自行g(shù)oogle.
事務(wù)是什么?
- 事務(wù)是一種可靠、一致的方式,訪問和操作數(shù)據(jù)庫中的程序單元
事務(wù)的特性
- 原子性:要么做,要么不做
- 一致性:一致性是指事務(wù)必須使數(shù)據(jù)庫從一個一致性狀態(tài)變換到另一個一致性狀態(tài)
- 持久性:持久性是指一個事務(wù)一旦被提交了,那么對數(shù)據(jù)庫中的數(shù)據(jù)的改變就是永久性的。
- 隔離性:不同的事務(wù)操作的互相不干擾
并發(fā)事務(wù)的問題
- 臟讀:臟讀是指在一個事務(wù)處理過程里讀取了另一個未提交的事務(wù)中的數(shù)據(jù),針對對某一項數(shù)據(jù)
- 不可重復(fù)讀:一個事務(wù)范圍內(nèi)多次查詢卻返回了不同的數(shù)據(jù)值,是由于在查詢間隔,被另一個事務(wù)修改并提交了。
- 幻讀:是指在事務(wù)執(zhí)行過程中多次查詢出來的數(shù)據(jù)數(shù),不一致,比如通過where篩選第二次比第一次多幾條別的事務(wù)新插入的數(shù)據(jù),幻讀針對的是一批數(shù)據(jù)整體
事務(wù)的隔離級別
- READ_UNCOMMITTED 讀未提交:可以讀到?jīng)]有沒有提交事務(wù)做出的數(shù)據(jù)修改內(nèi)容(隔離級別最低,并發(fā)性能高)
- READ_COMMITTED 讀已提交:只能讀到事務(wù)以及提交的數(shù)據(jù)(鎖定正在讀取的行)
- REPEATABLE_READ 可重復(fù)讀:保證在一個事務(wù)中 多次讀同樣的數(shù)據(jù)是一致的。不會被其他事務(wù)所影響(鎖定所讀取的所有行)
- SERIALIZABLE 可串行化:事務(wù)串行化順序執(zhí)行,
Spring事務(wù)
- Spring提供了統(tǒng)一的事務(wù)API來支持不同的資源類型
- Spring也提供了聲明式事務(wù)管理的方式,與業(yè)務(wù)代碼解耦
- 很容易和Spring生態(tài)框架整合
- 支持多資源的事務(wù)管理與同步
1. 事務(wù)抽象
1.1 PlatformTransactionManager
- 事務(wù)管理器:主要提供提交操作、回滾操作以及根據(jù)事務(wù)定義獲取事務(wù)狀態(tài)的統(tǒng)一接口
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
1.2 TransactionDefinition
- 事務(wù)定義:事務(wù)的一些基礎(chǔ)信息,如超時時間、隔離級別、傳播屬性等
事務(wù)傳播機制,也就是在事務(wù)在多個方法的調(diào)用中是如何傳遞的;如:兩個service的方法都是事務(wù)操作,一個事務(wù)方法調(diào)用另一個事務(wù)方法如何進(jìn)行處理?
public interface TransactionDefinition {
// 事務(wù)的傳播機制
// 如果調(diào)用方有事務(wù)則使用調(diào)用方的事務(wù),如果不存在事務(wù)則創(chuàng)建一個事務(wù)
int PROPAGATION_REQUIRED = 0;
// 跟隨調(diào)用方,如果調(diào)用方有 那就用,調(diào)用方?jīng)]有那就不用
int PROPAGATION_SUPPORTS = 1;
// 調(diào)用方的方法必須運行在一個事務(wù)中,不存在事務(wù)則拋出異常
int PROPAGATION_MANDATORY = 2;
// 不管調(diào)用方是否有事務(wù)執(zhí)行,自己都要起一個新事務(wù)。把原先的事務(wù)掛起,這個只在jta的事務(wù)管理器中起作用(事務(wù)是不支持嵌套的)
int PROPAGATION_REQUIRES_NEW = 3;
// 即使調(diào)用方有事務(wù),我也要在非事務(wù)中執(zhí)行,把原先的事務(wù)掛起
int PROPAGATION_NOT_SUPPORTED = 4;
// 絕不在事務(wù)里面執(zhí)行
int PROPAGATION_NEVER = 5;
// 嵌套事務(wù)(實際是不支持的,是利用存盤點來實現(xiàn),把調(diào)用方之前執(zhí)行的存盤點,然后類似于再開啟一個事務(wù)執(zhí)行,執(zhí)行完畢后恢復(fù)存盤點,繼續(xù)執(zhí)行。JDBC3.0以上支持)
int PROPAGATION_NESTED = 6;
// 以下定義的是事務(wù)的隔離機制
// 根據(jù)數(shù)據(jù)庫的默認(rèn)的隔離機制而定
int ISOLATION_DEFAULT = -1;
// 讀未提交
int ISOLATION_READ_UNCOMMITTED = 1; // same as java.sql.Connection.TRANSACTION_READ_UNCOMMITTED;
// 讀已提交
int ISOLATION_READ_COMMITTED = 2; // same as java.sql.Connection.TRANSACTION_READ_COMMITTED;
// 可重復(fù)讀
int ISOLATION_REPEATABLE_READ = 4; // same as java.sql.Connection.TRANSACTION_REPEATABLE_READ;
// 串行化
int ISOLATION_SERIALIZABLE = 8; // same as java.sql.Connection.TRANSACTION_SERIALIZABLE;
// 默認(rèn)超時時間,以數(shù)據(jù)庫設(shè)定為準(zhǔn)
int TIMEOUT_DEFAULT = -1;
// 獲取事務(wù)的傳播屬性
default int getPropagationBehavior() {
return PROPAGATION_REQUIRED;
}
// 獲取事務(wù)的隔離級別
default int getIsolationLevel() {
return ISOLATION_DEFAULT;
}
// 獲取事務(wù)超時時間
default int getTimeout() {
return TIMEOUT_DEFAULT;
}
// 事務(wù)是否只讀。
default boolean isReadOnly() {
return false;
}
// 獲取事務(wù)的名稱
@Nullable
default String getName() {
return null;
}
// 返回一個默認(rèn)事務(wù)定義
static TransactionDefinition withDefaults() {
return StaticTransactionDefinition.INSTANCE;
}
}
1.3 TransactionStatus
- 事務(wù)狀態(tài):事務(wù)的一些狀態(tài)信息,如是否是一個新的事務(wù)、是否已被標(biāo)記為回滾
// Savepoiont就是在Nested這種傳播機制中提供保存點機制來實現(xiàn)嵌套事務(wù),出錯的時候可以選擇恢復(fù)到保存點
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
// 是否有保存點
boolean hasSavepoint();
@Override
void flush();
}
public interface TransactionExecution {
// 是否是一個新事務(wù)
boolean isNewTransaction();
// 設(shè)置事務(wù)回滾
void setRollbackOnly();
// 事務(wù)是否回滾
boolean isRollbackOnly();
// 獲取事務(wù)是否完成
boolean isCompleted();
}
2. PlatformTransactionManager常見的實現(xiàn)
- DataSourceTransactionManager (用于JDBC Template、MyBatis等)
- JpaTransactionManager (用于data-jpa、hibernate等)
- JmsTransactionManager (用于消息中間件等)
- JtaTransactionManager (主要用于分布式事務(wù))
3. Spring單數(shù)據(jù)源事務(wù)實際案例
3.1 環(huán)境說明:
- DataSource: Alibaba Druid
- Database: MySQL 5.7
- SpringBoot: 2.2.2.RELEASE
- ORM: MyBatis
- GitHub: https://github.com/imyiren/transaction-example/tree/master/one-data-source
3.2 實例代碼
- 此案例額基于轉(zhuǎn)賬業(yè)務(wù)實現(xiàn) PlatformTransactionManager則是DataSourceTransactionManager
@Slf4j
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDAO accountDAO;
@Autowired
PlatformTransactionManager transactionManager;
/**
* 聲明式事務(wù)
* propagation = Propagation.REQUIRED (默認(rèn)值就是REQUIRED) 如果調(diào)用方有事務(wù)就直接使用調(diào)用方的事務(wù),如果沒有就新建一個事務(wù)
* transactionManager = "transactionManager" 也是默認(rèn)值
* isolation= Isolation.DEFAULT 隔離級別
* 還有timeout等參數(shù) 可自行查看Transactional的源碼 里面都有說明
* @param sourceAccountId 源賬戶
* @param targetAccountId 目標(biāo)賬戶
* @param amount 金額
* @return 操作結(jié)果信息
*/
@Override
@Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED, rollbackFor = Exception.class, isolation= Isolation.DEFAULT)
public String transferAnnotation(Long sourceAccountId, Long targetAccountId, BigDecimal amount) {
AccountDO sourceAccountDO = accountDAO.selectByPrimaryKey(sourceAccountId);
AccountDO targetAccountDO = accountDAO.selectByPrimaryKey(targetAccountId);
if (null == sourceAccountDO || null == targetAccountDO) {
return "轉(zhuǎn)入或者轉(zhuǎn)出賬戶不存在";
}
if (sourceAccountDO.getBalance().compareTo(amount) < 0) {
return "轉(zhuǎn)出賬戶余額不足";
}
sourceAccountDO.setBalance(sourceAccountDO.getBalance().subtract(amount));
accountDAO.updateByPrimaryKeySelective(sourceAccountDO);
// error("annotation error!");
targetAccountDO.setBalance(targetAccountDO.getBalance().add(amount));
accountDAO.updateByPrimaryKeySelective(targetAccountDO);
return "轉(zhuǎn)賬成功!";
}
@Override
public String transferCode(Long sourceAccountId, Long targetAccountId, BigDecimal amount) {
TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
// 獲取事務(wù) 開始業(yè)務(wù)執(zhí)行
TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
try {
AccountDO targetAccountDO = accountDAO.selectByPrimaryKey(targetAccountId);
AccountDO sourceAccountDO = accountDAO.selectByPrimaryKey(sourceAccountId);
if (null == sourceAccountDO || null == targetAccountDO) {
return "轉(zhuǎn)入或者轉(zhuǎn)出賬戶不存在";
}
error("code error");
if (sourceAccountDO.getBalance().compareTo(amount) < 0) {
return "轉(zhuǎn)出賬戶余額不足";
}
sourceAccountDO.setBalance(sourceAccountDO.getBalance().subtract(amount));
targetAccountDO.setBalance(targetAccountDO.getBalance().add(amount));
accountDAO.updateByPrimaryKeySelective(sourceAccountDO);
accountDAO.updateByPrimaryKeySelective(sourceAccountDO);
// 提交事務(wù)
transactionManager.commit(transaction);
return "轉(zhuǎn)賬成功!";
} catch (Exception e) {
log.error("轉(zhuǎn)賬發(fā)生錯誤,開始回滾,source: {}, target: {}, amount: {}, errMsg: {}",
sourceAccountId, targetAccountId, amount, e.getMessage());
// 報錯回滾
transactionManager.rollback(transaction);
}
return "轉(zhuǎn)賬失敗";
}
@Override
public List<AccountDO> listAll() {
return accountDAO.selectAll();
}
private static void error(String msg) {
throw new RuntimeException(msg);
}
}
3.3 Transactional注解實現(xiàn)事務(wù)
使用注解式事務(wù),Spring會利用代理實現(xiàn)一個代理類,
然后從上下文中得到事務(wù)管理器,開啟一個事務(wù)后執(zhí)行業(yè)務(wù)代碼,
如果滿足你設(shè)置的回滾異常條件,就執(zhí)行rollback
我們調(diào)用的時候是直接調(diào)用的Service的方法
但是Spring在實現(xiàn)的時候,實際是通過AOP Proxy(AOP 代理服務(wù))來調(diào)用Transaction Advisor(做事務(wù)管理的)然后來處理調(diào)用注解式事務(wù)的service方法
- 我們通過接口/api/account/transfer/annotation?source=1&target=2&amount=123 觸發(fā)賬戶1轉(zhuǎn)給賬戶2 123塊錢,轉(zhuǎn)賬操作
- 日志信息(開啟DEBUG日志):
o.s.web.servlet.DispatcherServlet : GET "/api/account/transfer/annotation?source=1&target=2&amount=123", parameters={masked}
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to io.ilss.transaction.onedatasource.web.AccountController#transferAnnotation(Long, Long, String)
o.s.j.d.DataSourceTransactionManager : Creating new transaction with name [io.ilss.transaction.onedatasource.service.impl.AccountServiceImpl.transferAnnotation]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; 'transactionManager',-java.lang.Exception
o.s.j.d.DataSourceTransactionManager : Acquired Connection [com.mysql.jdbc.JDBC4Connection@185f0a96] for JDBC transaction
o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [com.mysql.jdbc.JDBC4Connection@185f0a96] to manual commit
org.mybatis.spring.SqlSessionUtils : Creating a new SqlSession
org.mybatis.spring.SqlSessionUtils : Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
o.m.s.t.SpringManagedTransaction : JDBC Connection [com.mysql.jdbc.JDBC4Connection@185f0a96] will be managed by Spring
i.i.t.o.d.AccountDAO.selectByPrimaryKey : ==> Preparing: select id, nickname, username, `password`, balance, create_time, update_time from account where id = ?
i.i.t.o.d.AccountDAO.selectByPrimaryKey : ==> Parameters: 1(Long)
i.i.t.o.d.AccountDAO.selectByPrimaryKey : <== Total: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42] from current transaction
i.i.t.o.d.AccountDAO.selectByPrimaryKey : ==> Preparing: select id, nickname, username, `password`, balance, create_time, update_time from account where id = ?
i.i.t.o.d.AccountDAO.selectByPrimaryKey : ==> Parameters: 2(Long)
i.i.t.o.d.AccountDAO.selectByPrimaryKey : <== Total: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42] from current transaction
i.i.t.o.d.A.updateByPrimaryKeySelective : ==> Preparing: update account SET nickname = ?, username = ?, `password` = ?, balance = ?, create_time = ?, update_time = ? where id = ?
i.i.t.o.d.A.updateByPrimaryKeySelective : ==> Parameters: 小一(String), xiaoyi(String), 123456(String), 877.00(BigDecimal), 2020-01-09T17:04:28(LocalDateTime), 2020-01-09T17:44:33(LocalDateTime), 1(Long)
i.i.t.o.d.A.updateByPrimaryKeySelective : <== Updates: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42] from current transaction
i.i.t.o.d.A.updateByPrimaryKeySelective : ==> Preparing: update account SET nickname = ?, username = ?, `password` = ?, balance = ?, create_time = ?, update_time = ? where id = ?
i.i.t.o.d.A.updateByPrimaryKeySelective : ==> Parameters: 小二(String), xiaoer(String), 123456(String), 223.00(BigDecimal), 2020-01-09T17:04:40(LocalDateTime), 2020-01-09T17:04:40(LocalDateTime), 2(Long)
i.i.t.o.d.A.updateByPrimaryKeySelective : <== Updates: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
o.s.j.d.DataSourceTransactionManager : Initiating transaction commit
o.s.j.d.DataSourceTransactionManager : Committing JDBC transaction on Connection [com.mysql.jdbc.JDBC4Connection@185f0a96]
o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [com.mysql.jdbc.JDBC4Connection@185f0a96] after transaction
m.m.a.RequestResponseBodyMethodProcessor : Using 'text/html', given [text/html, application/xhtml+xml, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] and supported [text/plain, */*, text/plain, */*, application/json, application/*+json, application/json, application/*+json]
m.m.a.RequestResponseBodyMethodProcessor : Writing ["轉(zhuǎn)賬成功!"]
o.s.web.servlet.DispatcherServlet : Completed 200 OK
- 成功執(zhí)行過程
- GET 請求到 /api/account/transfer/annotation接口 然后調(diào)用service方法
- 根據(jù)寫的Transactional注解的設(shè)置 創(chuàng)建一個新事務(wù)
- 選用JDBC連接 然后創(chuàng)建 SqlSession
- 為SqlSession注冊事務(wù)同步
- SQL操作
- 然后把事務(wù)提交
- 最后釋放資源。完成方法調(diào)用
- 接口返回200
- 通過日志可看出我們使用MyBatis+Druid 配置數(shù)據(jù)源 事務(wù)管理實際是使用的DataSourceTransactionManager.
- 雖然代碼中指定了transactionManager,但實際卻沒有增加任何Bean注冊或者其他有關(guān)DataSource的事務(wù)管理器代碼。這個地方是因為Spring幫你做了,我設(shè)置transactionManager是因為自動裝配的transactionManager名字就是這個。寫出來提醒一下有這個配置,這個配置顧名思義就是配置對這個事務(wù)的管理器實現(xiàn)。簡而言之就是PlatformTransactionManager的指定。后面多數(shù)據(jù)源的時候我們會通過指定不同的transactionManager來實現(xiàn)事務(wù)管理。
- 如果報錯會是怎么樣呢?
- 先看日志(開啟DEBUG日志):
o.s.web.servlet.DispatcherServlet : GET "/api/account/transfer/annotation?source=1&target=2&amount=123", parameters={masked}
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to io.ilss.transaction.onedatasource.web.AccountController#transferAnnotation(Long, Long, String)
o.s.j.d.DataSourceTransactionManager : Creating new transaction with name [io.ilss.transaction.onedatasource.service.impl.AccountServiceImpl.transferAnnotation]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; 'transactionManager',-java.lang.Exception
o.s.j.d.DataSourceTransactionManager : Acquired Connection [com.mysql.jdbc.JDBC4Connection@1b6963ec] for JDBC transaction
o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [com.mysql.jdbc.JDBC4Connection@1b6963ec] to manual commit
org.mybatis.spring.SqlSessionUtils : Creating a new SqlSession
org.mybatis.spring.SqlSessionUtils : Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
o.m.s.t.SpringManagedTransaction : JDBC Connection [com.mysql.jdbc.JDBC4Connection@1b6963ec] will be managed by Spring
i.i.t.o.d.AccountDAO.selectByPrimaryKey : ==> Preparing: select id, nickname, username, `password`, balance, create_time, update_time from account where id = ?
i.i.t.o.d.AccountDAO.selectByPrimaryKey : ==> Parameters: 1(Long)
i.i.t.o.d.AccountDAO.selectByPrimaryKey : <== Total: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
org.mybatis.spring.SqlSessionUtils : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58] from current transaction
i.i.t.o.d.AccountDAO.selectByPrimaryKey : ==> Preparing: select id, nickname, username, `password`, balance, create_time, update_time from account where id = ?
i.i.t.o.d.AccountDAO.selectByPrimaryKey : ==> Parameters: 2(Long)
i.i.t.o.d.AccountDAO.selectByPrimaryKey : <== Total: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
org.mybatis.spring.SqlSessionUtils : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58] from current transaction
i.i.t.o.d.A.updateByPrimaryKeySelective : ==> Preparing: update account SET nickname = ?, username = ?, `password` = ?, balance = ?, create_time = ?, update_time = ? where id = ?
i.i.t.o.d.A.updateByPrimaryKeySelective : ==> Parameters: 小一(String), xiaoyi(String), 123456(String), 754.00(BigDecimal), 2020-01-09T17:04:28(LocalDateTime), 2020-01-09T17:44:33(LocalDateTime), 1(Long)
i.i.t.o.d.A.updateByPrimaryKeySelective : <== Updates: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
o.s.j.d.DataSourceTransactionManager : Initiating transaction rollback
o.s.j.d.DataSourceTransactionManager : Rolling back JDBC transaction on Connection [com.mysql.jdbc.JDBC4Connection@1b6963ec]
o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1b6963ec] after transaction
o.s.web.servlet.DispatcherServlet : Failed to complete request: java.lang.RuntimeException: annotation error!
o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is
java.lang.RuntimeException: annotation error!
.......
o.a.c.c.C.[Tomcat].[localhost] : Processing ErrorPage[errorCode=0, location=/error]
o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error?source=1&target=2&amount=123", parameters={masked}
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)
o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, text/html;q=0.8]
o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 500
- 失敗執(zhí)行過程
- GET 請求到 /api/account/transfer/annotation接口 然后調(diào)用service方法
- 根據(jù)寫的Transactional注解的設(shè)置 創(chuàng)建一個新事務(wù)
- 選用JDBC連接 然后創(chuàng)建 SqlSession
- 為SqlSession注冊事務(wù)同步
- SQL操作:可以從日志看到,執(zhí)行到第一個跟新操作發(fā)生錯誤
- 直接回滾Rolling back JDBC transaction on Connection后打印錯誤日志。后面的第二個更新操作也就沒了
- 方法異常退出,接口500
3.4 編程實現(xiàn)事務(wù)管理
- 編程式事務(wù)主要步驟
- 創(chuàng)建事務(wù)定義TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
- 設(shè)置事務(wù)屬性:如傳播屬性、隔離屬性等
- 通過TransactionManager開啟事務(wù)
- 執(zhí)行業(yè)務(wù)代碼
- 提交事務(wù) / 處理回滾
- 此實例的編程式事務(wù)的執(zhí)行基本和聲明式事務(wù)相同,有興趣可以自行下載代碼自己實現(xiàn)。
// todo: 下一篇多數(shù)據(jù)源事務(wù)管理
關(guān)于我
- 坐標(biāo)杭州,普通本科在讀,計算機科學(xué)與技術(shù)專業(yè),20年6月畢業(yè),瘋狂找工作中。。。。
- 目前處于菜鳥階段,各位大佬輕噴,小弟正在瘋狂學(xué)習(xí)。
- 歡迎大家和我交流鴨?。?!