1、前言
mybatis 是有事務(wù)模塊的乙各,mybatis 與 spring 結(jié)合的時候杖狼,spring 實(shí)現(xiàn)了 mybatis 的事務(wù)接口 Transaction披蕉,實(shí)現(xiàn)類為 SpringManagedTransaction。
此類的注釋如下:
- 如果它主要掌管 jdbc 連接的整個生命周期伞访,包括獲取釋放膝藕。
- 如果 spring 的事務(wù)管理器被激活的話,那么它不會做事務(wù)的操作咐扭,他的 commit/rollback/close 不會做任何操作。
- 如果 spring 事務(wù)不激活滑废,那么它會掌管 jdbc 的事務(wù)蝗肪,事實(shí)上,我們一般都用 spring 自己的事務(wù)蠕趁,所以它其實(shí)就拿個連接而已薛闪。
我們可以點(diǎn)進(jìn)去看他的方法,事實(shí)上也是這樣:
/**
* Copyright 2010-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mybatis.spring.transaction;
import static org.springframework.util.Assert.notNull;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.transaction.Transaction;
import org.springframework.jdbc.datasource.ConnectionHolder;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* {@code SpringManagedTransaction} handles the lifecycle of a JDBC connection.
* It retrieves a connection from Spring's transaction manager and returns it back to it
* when it is no longer needed.
* <p>
* If Spring's transaction handling is active it will no-op all commit/rollback/close calls
* assuming that the Spring transaction manager will do the job.
* <p>
* If it is not it will behave like {@code JdbcTransaction}.
*
* @author Hunter Presnall
* @author Eduardo Macarron
*
* @version $Id$
*/
public class SpringManagedTransaction implements Transaction {
private static final Log LOGGER = LogFactory.getLog(SpringManagedTransaction.class);
private final DataSource dataSource;
private Connection connection;
private boolean isConnectionTransactional;
private boolean autoCommit;
public SpringManagedTransaction(DataSource dataSource) {
notNull(dataSource, "No DataSource specified");
this.dataSource = dataSource;
}
/**
* {@inheritDoc}
*/
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
/**
* Gets a connection from Spring transaction manager and discovers if this
* {@code Transaction} should manage connection or let it to Spring.
* <p>
* It also reads autocommit setting because when using Spring Transaction MyBatis
* thinks that autocommit is always false and will always call commit/rollback
* so we need to no-op that calls.
*/
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"JDBC Connection ["
+ this.connection
+ "] will"
+ (this.isConnectionTransactional ? " " : " not ")
+ "be managed by Spring");
}
}
/**
* {@inheritDoc}
*/
@Override
public void commit() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Committing JDBC Connection [" + this.connection + "]");
}
this.connection.commit();
}
}
/**
* {@inheritDoc}
*/
@Override
public void rollback() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Rolling back JDBC Connection [" + this.connection + "]");
}
this.connection.rollback();
}
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws SQLException {
DataSourceUtils.releaseConnection(this.connection, this.dataSource);
}
/**
* {@inheritDoc}
*/
@Override
public Integer getTimeout() throws SQLException {
ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (holder != null && holder.hasTimeout()) {
return holder.getTimeToLiveInSeconds();
}
return null;
}
}
綜上而言俺陋,mybatis 就像個工具人豁延,spring 只需要它解析 mapper 接口、mapper.xml 文件之類的功能(mybatis 主體功能)腊状,對于事務(wù)這種東西诱咏,spring 選擇了自己實(shí)現(xiàn)。那么缴挖,spring 事務(wù)是怎么實(shí)現(xiàn)的呢袋狞?
2、關(guān)于事務(wù)實(shí)現(xiàn)
首先映屋,不管是 spring苟鸯、mybatis 抑或是其他的任何框架類,對于事務(wù)的實(shí)現(xiàn)本質(zhì)上還是利用數(shù)據(jù)源的事務(wù)實(shí)現(xiàn)棚点。就比如 mybatis早处,在最后提交事務(wù)的時候,還是使用 connection.commit()瘫析、connection.rollback()砌梆。可以試著點(diǎn)進(jìn)去 commit 等方法颁股,發(fā)現(xiàn)調(diào)用的是各個數(shù)據(jù)源的實(shí)現(xiàn)(而數(shù)據(jù)源的實(shí)現(xiàn)么库,最后都是數(shù)據(jù)庫 sql 語句的實(shí)現(xiàn),比如一個很簡單的事務(wù)操作語句:
set autocommit = 0甘有;
update xxx set xx = yy ...诉儒;
commit;
如果有人能講清楚這個問題亏掀,而不是說一堆廢話忱反,我想可能更好理解把)泛释。代碼如下:
/**
* Copyright 2009-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.transaction.jdbc;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionException;
/**
* {@link Transaction} that makes use of the JDBC commit and rollback facilities directly.
* It relies on the connection retrieved from the dataSource to manage the scope of the transaction.
* Delays connection retrieval until getConnection() is called.
* Ignores commit or rollback requests when autocommit is on.
*
* @author Clinton Begin
*
* @see JdbcTransactionFactory
*/
public class JdbcTransaction implements Transaction {
private static final Log log = LogFactory.getLog(JdbcTransaction.class);
protected Connection connection;
protected DataSource dataSource;
protected TransactionIsolationLevel level;
// MEMO: We are aware of the typo. See #941
protected boolean autoCommmit;
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
dataSource = ds;
level = desiredLevel;
autoCommmit = desiredAutoCommit;
}
public JdbcTransaction(Connection connection) {
this.connection = connection;
}
@Override
public Connection getConnection() throws SQLException {
if (connection == null) {
openConnection();
}
return connection;
}
@Override
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Committing JDBC Connection [" + connection + "]");
}
connection.commit();
}
}
@Override
public void rollback() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Rolling back JDBC Connection [" + connection + "]");
}
connection.rollback();
}
}
@Override
public void close() throws SQLException {
if (connection != null) {
resetAutoCommit();
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + connection + "]");
}
connection.close();
}
}
protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
try {
if (connection.getAutoCommit() != desiredAutoCommit) {
if (log.isDebugEnabled()) {
log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
}
connection.setAutoCommit(desiredAutoCommit);
}
} catch (SQLException e) {
// Only a very poorly implemented driver would fail here,
// and there's not much we can do about that.
throw new TransactionException("Error configuring AutoCommit. "
+ "Your driver may not support getAutoCommit() or setAutoCommit(). "
+ "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e);
}
}
protected void resetAutoCommit() {
try {
if (!connection.getAutoCommit()) {
// MyBatis does not call commit/rollback on a connection if just selects were performed.
// Some databases start transactions with select statements
// and they mandate a commit/rollback before closing the connection.
// A workaround is setting the autocommit to true before closing the connection.
// Sybase throws an exception here.
if (log.isDebugEnabled()) {
log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
}
connection.setAutoCommit(true);
}
} catch (SQLException e) {
if (log.isDebugEnabled()) {
log.debug("Error resetting autocommit to true "
+ "before closing the connection. Cause: " + e);
}
}
}
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommmit);
}
@Override
public Integer getTimeout() throws SQLException {
return null;
}
}
3、spring 事務(wù)實(shí)現(xiàn)
實(shí)際上温算,spring 的源碼異常龐大復(fù)雜怜校,所以基本上是不可能看完的,光 transaction 都有一個模塊注竿,這邊頂多講講他的實(shí)現(xiàn)思路茄茁。
spring中的事務(wù)實(shí)現(xiàn)從原理上說比較簡單,通過aop在方法執(zhí)行前后增加數(shù)據(jù)庫事務(wù)的操作巩割。
- 1.在方法開始時判斷是否開啟新事務(wù)裙顽,需要開啟事務(wù)則設(shè)置事務(wù)手動提交 set autocommit=0;
- 2.在方法執(zhí)行完成后手動提交事務(wù) commit;
- 3.在方法拋出指定異常后調(diào)用rollback回滾事務(wù)
具體邏輯看 TransactionIntercepto 的 invoke 方法,會發(fā)現(xiàn)有創(chuàng)建事務(wù)宣谈,提交事務(wù)愈犹,出錯會滾的代碼,我也就看了一下闻丑,沒具體細(xì)看漩怎。不過我們使用 @Transactional 開啟事務(wù)的時候,
我們先使用:sudo tcpdump -i lo0 -s 0 -l -w - port 3306 | strings
抓包 mysql 的 sql 打印代碼嗦嗡,會發(fā)現(xiàn)有如下的代碼打友浮:
當(dāng)然,我在實(shí)際操作的過程中發(fā)現(xiàn)侥祭,我不好抓這個包怪得,可能是不熟悉這個軟件把催什。所以我直接去 mysql 的日志查看爆哑,事實(shí)也確實(shí)是類似的(記得開啟 mysql 日志):
4殊者、感想
我覺得大部分人都不會好好講話鲸沮,至少都不會完整的把一個事情講明白往扔。spring 的事務(wù)編程模型確實(shí)很復(fù)雜逾礁,但是我有時候其實(shí)想知道他為啥有效移宅,而不是整個模型是怎樣做的鲫售,因?yàn)槿绻浪麨樯队行Ш竺婢吐芯烤托辛讼跖 R苍S