什么是MyBatis
MyBatis是支持定制化SQL、存儲過程以及高級映射的優秀的持久層框架。MyBatis 避免了幾乎所有的 JDBC 代碼和手工設置參數以及抽取結果集。MyBatis 使用簡單的 XML 或注解來配置和映射基本體,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記錄。
MyBatis簡單示例
雖然在使用MyBatis時一般都會使用XML文件,但是本文為了分析程序的簡單性,簡單的測試程序將不包含XML配置,該測試程序包含一個接口、一個啟動類:
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User selectUser(int id);
}
public class Test2 {
public static void main(String[] args) {
SqlSessionFactory sqlSessionFactory = initSqlSessionFactory();
SqlSession session = sqlSessionFactory.openSession();
try {
User user = (User) session.selectOne(
"org.mybatis.example.UserMapper.selectUser", 1);
System.out.println(user.getUserAddress());
System.out.println(user.getUserName());
} finally {
session.close();
}
}
private static SqlSessionFactory initSqlSessionFactory() {
DataSource dataSource = new PooledDataSource("com.mysql.jdbc.Driver",
"jdbc:mysql://127.0.0.1:3306/jdbc", "root", "");
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development",
transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(UserMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(configuration);
return sqlSessionFactory;
}
}
UserMapper是一個接口,我們在構建sqlSessionFactory時通過configuration.addMapper(UserMapper.class)把該接口注冊進了sqlSessionFactory中。從上面的代碼中我們可以看出,要使用MyBatis,我們應該經過以下步驟:1、創建sqlSessionFactory(一次性操作);2、用sqlSessionFactory對象構造sqlSession對象;3、調用sqlSession的相應方法;4、關閉sqlSession對象。
在main方法中,我們沒有配置sql,也沒有根據查詢結果拼接對象,只需在調用sqlSession方法時傳入一個命名空間以及方法參數參數即可,所有的操作都是面向對象的。在UserMapper接口中,我們定制了自己的sql,MyBatis把書寫sql的權利給予了我們,方便我們進行sql優化及sql排錯。
JDBC基礎回顧
直接使用JDBC是很痛苦的,JDBC連接數據庫包含以下幾個基本步驟:1、注冊驅動 ;2、建立連接(Connection);3、創建SQL語句(Statement);4、執行語句;5、處理執行結果(ResultSet);6、釋放資源,示例代碼如下:
public static void test() throws SQLException{
// 1.注冊驅動
Class.forName("com.mysql.jdbc.Driver");
// 2.建立連接 url格式 - JDBC:子協議:子名稱//主機名:端口/數據庫名?屬性名=屬性值&…
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", "");
// 3.創建語句
Statement st = conn.createStatement();
// 4.執行語句
ResultSet rs = st.executeQuery("select * from user");
// 5.處理結果
while (rs.next()) {
User user = new User(rs.getObject(1), rs.getObject(2));
}
// 6.釋放資源
rs.close();
st.close();
conn.close();
}
可以看到與直接使用JDBC相比,MyBatis為我們簡化了很多工作:
1、把創建連接相關工作抽象成一個sqlSessionFactory對象,一次創建多次使用;
2、把sql語句從業務層剝離,代碼邏輯更加清晰,增加可維護性;
3、自動完成結果集處理,不需要我們編寫重復代碼。
但是,我們應該知道的是,框架雖然能夠幫助我們簡化工作,但是框架底層的代碼肯定還是最基礎的JDBC代碼,因為這是Java平台連接數據庫的通用方法,今天我將分析一下MyBatis源碼,看看MyBatis是如何把這些基礎代碼封裝成一個框架的。
MyBatis調用流程
我們最終調用的是sqlSession對象上的方法,所以我們先跟蹤sqlSession的創建方法:sqlSessionFactory.openSession(),最終這個方法會調用到DefaultSqlSessionFactory的以下方法:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
最終返回的對象是一個DefaultSqlSession對象,在調試模式下,我們看到autoCommit為false,executor為CachingExecutor類型,在CachingExecutor里面有屬性delegate,其類型為simpleExecutor:

現在,我們跟進DefaultSqlSession的selectOne()方法,查看該方法的調用流程,selectOne()方法又會調用selectList()方法:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
可以看到要得到查詢結果,最終還是要調用executor上的query方法,這里的executor是CachingExecutor實例,跟進程序得到如下代碼:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
MyBatis框架首先生成了一個boundSql和CacheKey,在boundSql中包含有我們傳入的sql語句:

生成boundSql和CacheKey后會調用一個重載函數,在重載函數中,我們會檢測是否有緩存,這個緩存是MyBatis的二級緩存,我們沒有配置,那么直接調用最后一句delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql),前面說過這個delagate其實就是simpleExecutor,跟進去查看一下:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) throw new ExecutorException("Executor was closed.");
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear(); // issue #601
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache(); // issue #482
}
}
return list;
}
關鍵代碼是以下三行:
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
首先嘗試從localCache中根據key得到List,這里的localCache是MyBatis的一級緩存,如果得不到則調用queryFromDatabase()從數據庫中查詢:
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
其中關鍵代碼是調用doQuery()代碼,SimpleExecutor的doQuery()方法如下:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
調用了prepareStatement方法,該方法如下:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
handler.parameterize(stmt);
return stmt;
}
終於,我們看到熟悉的代碼了,首先得到Connection,然后從Connection中得到Statement,同時在調試模式下我們看到,我們的sql語句已經被設置到stmt中了:

現在Statement對象有了,sql也設置進去了,就只差執行以及對象映射了,繼續跟進代碼,我們會跟蹤到org.apache.ibatis.executor.statement.
PreparedStatementHandler類的executor方法:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
在這里,調用了ps.execute()方法執行sql,接下來調用的resultSetHandler.<E> handleResultSets(ps)方法明顯是對結果集進行封裝,我就不繼續跟進了。
MyBatis的數據庫連接池
上面一部分介紹了MyBatis執行的整體流程,這一部分打算討論一個具體話題:MyBatis的數據庫連接池。
我們知道,每次連接數據庫時都創建Connection是十分耗費性能的,所以我們在寫JDBC代碼時,一般都會使用數據庫連接池,把用過的Connection不是直接關閉,而是放入數據庫連接池中,方便下次復用,開源的數據庫連接池有DBCP、C3P0等,MyBatis也實現了自己的數據庫連接池,在這一節我將探索一下MyBatis實現的數據庫連接池源碼。
跟進上一節的getConnection()方法,我們最終會進入JdbcTransaction的getConnection()方法,getConnection()方法又會調用openConnection()方法,而openConnection()又將調用dataSource的getConnection()方法:
public Connection getConnection() throws SQLException {
if (connection == null) {
openConnection();
}
return connection;
}
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);
}
這里的dataSource是PooledDataSource類型,跟進查看源碼如下:
public Connection getConnection() throws SQLException {
return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}
private PooledConnection popConnection(String username, String password) throws SQLException {
//暫不分析
}
可以看到,在這里我們返回的對象其實已經不是原生的Connection對象了,而是一個動態代理對象,是PooledConnection的一個屬性,所有對對Connection對象的操作都將被PooledConnection攔截,我們可以查看PooledConnection的定義如下:
class PooledConnection implements InvocationHandler {
private static final String CLOSE = "close";
private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };
private int hashCode = 0;
private PooledDataSource dataSource;
private Connection realConnection;
private Connection proxyConnection;
private long checkoutTimestamp;
private long createdTimestamp;
private long lastUsedTimestamp;
private int connectionTypeCode;
private boolean valid;
public PooledConnection(Connection connection, PooledDataSource dataSource) {
this.hashCode = connection.hashCode();
this.realConnection = connection;
this.dataSource = dataSource;
this.createdTimestamp = System.currentTimeMillis();
this.lastUsedTimestamp = System.currentTimeMillis();
this.valid = true;
this.proxyConnection = (Connection) Proxy.newProxyInstance(
Connection.class.getClassLoader(), IFACES, this);
}
public void invalidate() {
valid = false;
}
public boolean isValid() {
return valid && realConnection != null
&& dataSource.pingConnection(this);
}
public Connection getRealConnection() {
return realConnection;
}
public Connection getProxyConnection() {
return proxyConnection;
}
public int getRealHashCode() {
if (realConnection == null) {
return 0;
} else {
return realConnection.hashCode();
}
}
public int getConnectionTypeCode() {
return connectionTypeCode;
}
public void setConnectionTypeCode(int connectionTypeCode) {
this.connectionTypeCode = connectionTypeCode;
}
public long getCreatedTimestamp() {
return createdTimestamp;
}
public void setCreatedTimestamp(long createdTimestamp) {
this.createdTimestamp = createdTimestamp;
}
public long getLastUsedTimestamp() {
return lastUsedTimestamp;
}
public void setLastUsedTimestamp(long lastUsedTimestamp) {
this.lastUsedTimestamp = lastUsedTimestamp;
}
public long getTimeElapsedSinceLastUse() {
return System.currentTimeMillis() - lastUsedTimestamp;
}
public long getAge() {
return System.currentTimeMillis() - createdTimestamp;
}
public long getCheckoutTimestamp() {
return checkoutTimestamp;
}
public void setCheckoutTimestamp(long timestamp) {
this.checkoutTimestamp = timestamp;
}
public long getCheckoutTime() {
return System.currentTimeMillis() - checkoutTimestamp;
}
public int hashCode() {
return hashCode;
}
public boolean equals(Object obj) {
if (obj instanceof PooledConnection) {
return realConnection.hashCode() == (((PooledConnection) obj).realConnection
.hashCode());
} else if (obj instanceof Connection) {
return hashCode == obj.hashCode();
} else {
return false;
}
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
String methodName = method.getName();
if (CLOSE.hashCode() == methodName.hashCode()
&& CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
} else {
try {
if (!Object.class.equals(method.getDeclaringClass())) {
checkConnection();
}
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
private void checkConnection() throws SQLException {
if (!valid) {
throw new SQLException(
"Error accessing PooledConnection. Connection is invalid.");
}
}
}
可以看到這個類暴露了很多接口檢測Connection狀態,例如連接是否有效,連接創建時間最近使用連接等:

這個類實現了InvocationHandler接口,最主要的一個方法如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
} else {
try {
if (!Object.class.equals(method.getDeclaringClass())) {
checkConnection();
}
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
可以看到,PooledConnection會攔截close方法,當客戶端調用close()方法時,程序不會關閉Connection,而是會調用dataSource.pushConnection(this)方法,該方法的實現如下:
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
state.activeConnections.remove(conn);
if (conn.isValid()) {
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
state.idleConnections.add(newConn);
newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
conn.invalidate();
if (log.isDebugEnabled()) {
log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
}
state.notifyAll();
} else {
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
conn.getRealConnection().close();
if (log.isDebugEnabled()) {
log.debug("Closed connection " + conn.getRealHashCode() + ".");
}
conn.invalidate();
}
} else {
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
}
state.badConnectionCount++;
}
}
}
可以看到,首先會把Connection從活躍列表中刪除,然后檢測空閑列表的長度有沒有達到最大長度(默認為5),若沒有達到,把Connection放入空閑鏈表,否則關閉連接。這里的state是一個PoolState對象,該對象定義如下:
public class PoolState {
protected PooledDataSource dataSource;
protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();
protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();
protected long requestCount = 0;
protected long accumulatedRequestTime = 0;
protected long accumulatedCheckoutTime = 0;
protected long claimedOverdueConnectionCount = 0;
protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
protected long accumulatedWaitTime = 0;
protected long hadToWaitCount = 0;
protected long badConnectionCount = 0;
public PoolState(PooledDataSource dataSource) {
this.dataSource = dataSource;
}
public synchronized long getRequestCount() {
return requestCount;
}
public synchronized long getAverageRequestTime() {
return requestCount == 0 ? 0 : accumulatedRequestTime / requestCount;
}
public synchronized long getAverageWaitTime() {
return hadToWaitCount == 0 ? 0 : accumulatedWaitTime / hadToWaitCount;
}
public synchronized long getHadToWaitCount() {
return hadToWaitCount;
}
public synchronized long getBadConnectionCount() {
return badConnectionCount;
}
public synchronized long getClaimedOverdueConnectionCount() {
return claimedOverdueConnectionCount;
}
public synchronized long getAverageOverdueCheckoutTime() {
return claimedOverdueConnectionCount == 0 ? 0 : accumulatedCheckoutTimeOfOverdueConnections / claimedOverdueConnectionCount;
}
public synchronized long getAverageCheckoutTime() {
return requestCount == 0 ? 0 : accumulatedCheckoutTime / requestCount;
}
public synchronized int getIdleConnectionCount() {
return idleConnections.size();
}
public synchronized int getActiveConnectionCount() {
return activeConnections.size();
}
}
可以看到最終我們的Connection對象是放在ArrayList中的,該類還提供一些接口返回連接池基本信息。
好了,現在我們可以回去看看PooledDataSource的popConnection方法了:
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
PooledConnection conn = null;
long t = System.currentTimeMillis();
int localBadConnectionCount = 0;
while (conn == null) {
synchronized (state) {
if (state.idleConnections.size() > 0) {
// Pool has available connection
conn = state.idleConnections.remove(0);
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else {
// Pool does not have available connection
if (state.activeConnections.size() < poolMaximumActiveConnections) {
// Can create new connection
conn = new PooledConnection(dataSource.getConnection(), this);
@SuppressWarnings("unused")
//used in logging, if enabled
Connection realConn = conn.getRealConnection();
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else {
// Cannot create new connection
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > poolMaximumCheckoutTime) {
// Can claim overdue connection
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
state.activeConnections.remove(oldestActiveConnection);
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
oldestActiveConnection.getRealConnection().rollback();
}
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {
// Must wait
try {
if (!countedWait) {
state.hadToWaitCount++;
countedWait = true;
}
if (log.isDebugEnabled()) {
log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
state.wait(poolTimeToWait);
state.accumulatedWaitTime += System.currentTimeMillis() - wt;
} catch (InterruptedException e) {
break;
}
}
}
}
if (conn != null) {
if (conn.isValid()) {
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
conn.setCheckoutTimestamp(System.currentTimeMillis());
conn.setLastUsedTimestamp(System.currentTimeMillis());
state.activeConnections.add(conn);
state.requestCount++;
state.accumulatedRequestTime += System.currentTimeMillis() - t;
} else {
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
}
state.badConnectionCount++;
localBadConnectionCount++;
conn = null;
if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Could not get a good connection to the database.");
}
throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
}
}
}
}
}
if (conn == null) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
return conn;
}
可以看到獲取Connection一共分以下幾種情況:1、如果有空閑Connection,那么直接使用空閑Connection,否則2;2、如果活躍Connection沒有達到活躍Connection的上限,那么創建一個新Connection並返回,否則3;3、如果達到活躍上限,且被檢出的Connection檢出時間過長,那么把該Connection置為失效,新創建一個Connection,否則4;4、等待空閑Connection。
至此,我們就把MyBatis的數據庫連接池代碼整理了一遍,其中有兩個關鍵點:1、檢出的Connection其實不是原生Connection,而是一個代理對象;2、存放Connection的容器是ArrayList,Connection的檢出遵從先進先出原則。
MyBatis的緩存
這篇博客講的很好,mark一下:http://www.cnblogs.com/fangjian0423/p/mybatis-cache.html
MyBatis的事務
首先回顧一下JDBC的事務知識。
JDBC可以操作Connection的setAutoCommit()方法,給它false參數,提示數據庫啟動事務,在下達一連串的SQL命令后,自行調用Connection的commit()方法,提示數據庫確認(Commit)操作。如果中間發生錯誤,則調用rollback(),提示數據庫撤銷(ROLLBACK)所有執行。同時,如果僅想要撤回某個SQL執行點,則可以設置存儲點(SAVEPOINT)。一個示范的事務流程如下:
Connection conn = ...;
Savepoint point = null;
try {
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
stmt.executeUpdate("INSERT INTO ...");
...
point = conn.setSavepoint();
stmt.executeUpdate("INSERT INTO ...");
...
conn.commit();
} catch (SQLException e) {
e.printStackTrace();
if (conn != null) {
try {
if (point == null) {
conn.rollback();
} else {
conn.rollback(point);
conn.releaseSavepoint(point);
}
} catch (SQLException ex) {
ex.printStackTrace();
}
}
} finally {
...
if (conn != null) {
try {
conn.setAutoCommit(true);
conn.close();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
在MyBatis調用流程一節就寫過,在調試模式下,我們看到autoCommit為false,所以每個sqlSession其實都是一個事務,這也是為什么每次做刪、改、查時都必須調用commit的原因。
