*************************************優雅的分割線 **********************************
分享一波:程序員賺外快-必看的巔峰干貨
如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程
請關注微信公眾號:HB荷包
一個能讓你學習技術和賺錢方法的公眾號,持續更新
*************************************優雅的分割線 **********************************
SimpleExecutor
SqlSession
SqlSession是Mybatis的核心接口之一,對外提供常用的數據庫操作api。mybatis提供了兩個SqlSession的實現,其中最常用的是DefaultSqlSession。
SqlSession的代碼如下
/**
-
接口層,也是開發人員使用mybatis去操作sql所使用的主要的接口
-
@author Clinton Begin
*/
public interface SqlSession extends Closeable {/**
- 查詢sql單條數據
- @param 返回的數據類型
- @param statement sql
- @return Mapped object
*/
T selectOne(String statement);
/**
- 指定sql并傳入實參去查單條數據
- @param 返回的數據類型
- @param statement 預編譯的帶有?的sql
- @param parameter 用戶傳入的實參,與前面sql綁定
- @return Mapped object
*/
T selectOne(String statement, Object parameter);
/**
- 執行sql查詢多條數據
- @param 返回的數據類型
- @param statement sql
- @return List of mapped object
*/
List selectList(String statement);
/**
- 指定sql并傳入實參去查多條數據
- @param 返回的數據類型
- @param statement 預編譯的帶有問號的sql
- @param parameter 用戶傳入的實參,與前面的sql綁定
- @return List of mapped object
*/
List selectList(String statement, Object parameter);
/**
- 使用預編譯的sql,指定傳入的實參以及結果集范圍
- 查詢指定范圍的所有數據
- @param 返回的數據類型
- @param statement 預編譯的帶有問號的sql
- @param parameter 用戶傳入的實參,與前面的sql綁定
- @param rowBounds 指定查詢范圍
- @return List of mapped object
*/
List selectList(String statement, Object parameter, RowBounds rowBounds);
/**
- 執行sql,返回map對象
- @param the returned Map keys type
- @param the returned Map values type
- @param statement Unique identifier matching the statement to use.
- @param mapKey The property to use as key for each value in the list.
- @return Map containing key pair data.
*/
<K, V> Map<K, V> selectMap(String statement, String mapKey);
/**
- 指定sql和實參,返回map
- @param the returned Map keys type
- @param the returned Map values type
- @param statement Unique identifier matching the statement to use.
- @param parameter A parameter object to pass to the statement.
- @param mapKey The property to use as key for each value in the list.
- @return Map containing key pair data.
*/
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
/**
- 指定sql、實參、范圍,返回map
- @param the returned Map keys type
- @param the returned Map values type
- @param statement Unique identifier matching the statement to use.
- @param parameter A parameter object to pass to the statement.
- @param mapKey The property to use as key for each value in the list.
- @param rowBounds Bounds to limit object retrieval
- @return Map containing key pair data.
*/
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
/**
- A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
- @param the returned cursor element type.
- @param statement Unique identifier matching the statement to use.
- @return Cursor of mapped objects
*/
Cursor selectCursor(String statement);
/**
- A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
- @param the returned cursor element type.
- @param statement Unique identifier matching the statement to use.
- @param parameter A parameter object to pass to the statement.
- @return Cursor of mapped objects
*/
Cursor selectCursor(String statement, Object parameter);
/**
- A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
- @param the returned cursor element type.
- @param statement Unique identifier matching the statement to use.
- @param parameter A parameter object to pass to the statement.
- @param rowBounds Bounds to limit object retrieval
- @return Cursor of mapped objects
*/
Cursor selectCursor(String statement, Object parameter, RowBounds rowBounds);
/**
- 將查詢結果通過此處的ResultHandler對象封裝成對應的對象
- @param statement Unique identifier matching the statement to use.
- @param parameter A parameter object to pass to the statement.
- @param handler ResultHandler that will handle each retrieved row
*/
void select(String statement, Object parameter, ResultHandler handler);
/**
- Retrieve a single row mapped from the statement
- using a {@code ResultHandler}.
- @param statement Unique identifier matching the statement to use.
- @param handler ResultHandler that will handle each retrieved row
*/
void select(String statement, ResultHandler handler);
/**
- Retrieve a single row mapped from the statement key and parameter
- using a {@code ResultHandler} and {@code RowBounds}.
- @param statement Unique identifier matching the statement to use.
- @param rowBounds RowBound instance to limit the query results
- @param handler ResultHandler that will handle each retrieved row
*/
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
/**
- 執行insert
- @param statement Unique identifier matching the statement to execute.
- @return int The number of rows affected by the insert.
*/
int insert(String statement);
/**
- Execute an insert statement with the given parameter object. Any generated
- autoincrement values or selectKey entries will modify the given parameter
- object properties. Only the number of rows affected will be returned.
- @param statement Unique identifier matching the statement to execute.
- @param parameter A parameter object to pass to the statement.
- @return int The number of rows affected by the insert.
*/
int insert(String statement, Object parameter);
/**
- 執行update
- @param statement Unique identifier matching the statement to execute.
- @return int The number of rows affected by the update.
*/
int update(String statement);
/**
- Execute an update statement. The number of rows affected will be returned.
- @param statement Unique identifier matching the statement to execute.
- @param parameter A parameter object to pass to the statement.
- @return int The number of rows affected by the update.
*/
int update(String statement, Object parameter);
/**
- 執行delete
- @param statement Unique identifier matching the statement to execute.
- @return int The number of rows affected by the delete.
*/
int delete(String statement);
/**
- Execute a delete statement. The number of rows affected will be returned.
- @param statement Unique identifier matching the statement to execute.
- @param parameter A parameter object to pass to the statement.
- @return int The number of rows affected by the delete.
*/
int delete(String statement, Object parameter);
/**
- 提交事務
*/
void commit();
/**
- Flushes batch statements and commits database connection.
- @param force forces connection commit
*/
void commit(boolean force);
/**
- 回滾事務
*/
void rollback();
/**
- Discards pending batch statements and rolls database connection back.
- Note that database connection will not be rolled back if no updates/deletes/inserts were called.
- @param force forces connection rollback
*/
void rollback(boolean force);
/**
- 將請求刷新到數據庫
- @return BatchResult list of updated records
- @since 3.0.6
*/
List flushStatements();
/**
- 關閉SqlSession
*/
@Override
void close();
/**
- 清空 緩存
*/
void clearCache();
/**
- Retrieves current configuration.
- @return Configuration
*/
Configuration getConfiguration();
/**
- 使用type獲取對應的Mapper
- @param the mapper type
- @param type Mapper interface class
- @return a mapper bound to this SqlSession
*/
T getMapper(Class type);
/**
- 獲取該SqlSession對應的數據庫連接
- @return Connection
*/
Connection getConnection();
}
DefaultSqlSession
在mybatis單獨使用的時候,DefaultSqlSession是最常使用的SqlSession實現。DefaultSqlSession核心字段如下,其中已經過多介紹的類將不再注釋。
private final Configuration configuration;
private final Executor executor;/*** 是否自動提交事務*/
private final boolean autoCommit;/*** 當前緩存是否有臟數據*/
private boolean dirty;
DefaultSqlSession中使用到了策略模式(不知道策略模式的請看我以前的帖子)。DefaultSqlSession扮演了上下文,只是通過executor字段的不同,而選擇不同的Executor去操作數據庫。
DefaultSqlSession為每種SQL操作都提供了大量的重載,對于不同的參數都提供了一個重載方法, 便于開發者去調用。這里只貼出核心的方法,對于重載方法將不進行介紹。
* 根據sql和實參查詢一條數據* @param statement 預編譯的帶有?的sql* @param parameter 用戶傳入的實參,與前面sql綁定* @param <T>* @return*/
@Override
public <T> T selectOne(String statement, Object parameter) {// 調用selectList查詢多條List<T> list = this.selectList(statement, parameter);// 如果查詢到的數據長度1是或者0就正常,否則拋出異常if (list.size() == 1) {return list.get(0);} else if (list.size() > 1) {// 這里的異常信息是不是很熟悉呢throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());} else {return null;}
}/*** 查詢結果封裝成map返回* 閱讀源碼發現,這里的selectMap貌似并不是將結果集按照屬性映射成map* 而是把map當做list去使用。* 查詢出多條數據,使用不同的key去封裝到map* 這里的V應該是這一條數據映射的對象,或者是Map<String, Object>* @param statement Unique identifier matching the statement to use.* @param parameter A parameter object to pass to the statement.* @param mapKey The property to use as key for each value in the list.* @param rowBounds Bounds to limit object retrieval* @param <K>* @param <V>* @return*/
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {// 查詢列表final List<? extends V> list = selectList(statement, parameter, rowBounds);// 創建Map返回集處理器final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<>(mapKey,configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());final DefaultResultContext<V> context = new DefaultResultContext<>();for (V o : list) {// 暫存一下當前結果對象context.nextResultObject(o);// 處理上下文中的結果對象mapResultHandler.handleResult(context);}// 將map返回回去return mapResultHandler.getMappedResults();
}/*** 根據傳入的sql、實參、查詢范圍去查詢一個列表* @param statement 預編譯的帶有問號的sql* @param parameter 用戶傳入的實參,與前面的sql綁定* @param rowBounds 指定查詢范圍* @param <E>* @return*/
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {MappedStatement ms = configuration.getMappedStatement(statement);return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);} finally {ErrorContext.instance().reset();}
}/*** 根據sql、實參、范圍查詢* 將查詢結果交給指定的ResultHandler去處理* @param statement Unique identifier matching the statement to use.* @param parameter* @param rowBounds RowBound instance to limit the query results* @param handler ResultHandler that will handle each retrieved row*/
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {try {MappedStatement ms = configuration.getMappedStatement(statement);executor.query(ms, wrapCollection(parameter), rowBounds, handler);} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);} finally {ErrorContext.instance().reset();}
}@Override
public int update(String statement, Object parameter) {try {dirty = true;MappedStatement ms = configuration.getMappedStatement(statement);return executor.update(ms, wrapCollection(parameter));} catch (Exception e) {throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);} finally {ErrorContext.instance().reset();}
}@Override
public void commit(boolean force) {try {// 提交事務。提交之后將dirty設為false// 此時的緩存中視為沒有臟數據executor.commit(isCommitOrRollbackRequired(force));dirty = false;} catch (Exception e) {throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);} finally {ErrorContext.instance().reset();}
}@Override
public void rollback(boolean force) {try {executor.rollback(isCommitOrRollbackRequired(force));dirty = false;} catch (Exception e) {throw ExceptionFactory.wrapException("Error rolling back transaction. Cause: " + e, e);} finally {ErrorContext.instance().reset();}
}@Override
public List<BatchResult> flushStatements() {try {return executor.flushStatements();} catch (Exception e) {throw ExceptionFactory.wrapException("Error flushing statements. Cause: " + e, e);} finally {ErrorContext.instance().reset();}
}
代碼比較簡單,就不做過多的介紹。
DefaultSqlSessionFactory
DefaultSqlSessionFactory是一個工廠類,提供了兩種創建DefaultSqlSession的方式,一種是通過數據源創建SqlSession,一種是通過用戶傳入的數據庫連接對象來創建SqlSession。另外代碼里有大量的openSession都是用于創建SqlSession對象的,但是其實現都是基于這兩種方式,因此這里只把兩種創建SqlSession的方式的代碼貼出來,如下。
/*** 通過數據源去創建SqlSession* @param execType* @param level* @param autoCommit* @return*/
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {// 獲取environment。這個是mybatis中配置的環境final Environment environment = configuration.getEnvironment();// 根據環境去獲取TransactionFactory對象final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);// 創建Transaction對象tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);// 創建Executor對象final Executor executor = configuration.newExecutor(tx, execType);// 創建DefaultSqlSessionreturn 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();}
}/*** 通過用戶提供的Connection對象去創建* @param execType* @param connection* @return*/
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {try {boolean autoCommit;try {autoCommit = connection.getAutoCommit();} catch (SQLException e) {// Failover to true, as most poor drivers// or databases won't support transactionsautoCommit = true;}final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);final Transaction tx = transactionFactory.newTransaction(connection);final Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);} finally {ErrorContext.instance().reset();}
}
SqlSessionManager
SqlSessionManager同時實現了SqlSession接口和SQLSessionFactory接口,因此它擁有操作數據庫的能力以及創建SqlSession的功能。
SQLSessionManager核心字段如下
private final SqlSessionFactory sqlSessionFactory;/*** localSqlSession中記錄的SqlSession對象的代理對象*/
private final SqlSession sqlSessionProxy;/*** 記錄當前線程的SqlSession對象*/
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();
其中ThreadLocal的作用往往是作為當前線程的上下文,可以為當前線程提供全局變量。對ThreadLocal不了解的朋友也請查看我以前的文章。
SqlSessionManager提供了兩種模式。一種是同一線程每次通過SqlSessionManager對象訪問數據庫時,都會創建一個DefaultSqlSession對象完成數據庫操作,另一種則是使用localSqlSession綁定當前線程的SqlSession,在當前線程中循環使用同一個SqlSession。后者使用往往居多,這也是大家經常說的“SqlSession與線程綁定 ,每個請求都會創建SqlSession”的原因。
sqlSessionProxy是一個代理對象,在SqlSessionmanager的構造方法中使用JDK的動態代理創建完成,代碼如下。
private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {this.sqlSessionFactory = sqlSessionFactory;// 使用動態代理去創建 SqlSessionthis.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(),new Class[]{SqlSession.class},new SqlSessionInterceptor());
}
SqlSessionManager中實現的SqlSession 接口方法,都是直接調用sqlSessionProxy字段記錄的SqlSession代理對象的方法實現的。在創建該代理對象時使用到的SqlSessionInterceptor是SqlSessionManager的內部類,代碼如下。
private class SqlSessionInterceptor implements InvocationHandler {public SqlSessionInterceptor() {// Prevent Synthetic Access}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 獲取當前線程的SqlSessionfinal SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();if (sqlSession != null) {try {// SqlSession不為空就調用真正的SqlSession去完成數據庫的操作return method.invoke(sqlSession, args);} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}} else {try (SqlSession autoSqlSession = openSession()) {// 如果當前線程的SqlSession為空,就創建新的SqlSession對象try {// 使用創建的SqlSession對象完成數據庫操作final Object result = method.invoke(autoSqlSession, args);// 提交事務autoSqlSession.commit();return result;} catch (Throwable t) {autoSqlSession.rollback();throw ExceptionUtil.unwrapThrowable(t);}}}}
}
總結
SqlSession是單體mybatis使用最多的一個接口,可能我們在整合SSM之后就看不到這個接口了,但是其底層實現的時候也是會創建SqlSession的,雖然這個比較簡單,但是也是相當重要的一個模塊。
*************************************優雅的分割線 **********************************
分享一波:程序員賺外快-必看的巔峰干貨
如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程
請關注微信公眾號:HB荷包
一個能讓你學習技術和賺錢方法的公眾號,持續更新
*************************************優雅的分割線 **********************************
SimpleExecutor