SqlSession
SqlSession是Mybatis的核心接口之一,对外提供常用的数据库操作api。mybatis提供了两个SqlSession的实现,其中最常用的是DefaultSqlSession。
SqlSession的代码如下
/**
* 接口层,也是开发人员使用mybatis去操作sql所使用的主要的接口
*
* @author Clinton Begin
*/
public interface SqlSession extends Closeable {
/**
* 查询sql单条数据
*
* @param <T> 返回的数据类型
* @param statement sql
* @return Mapped object
*/
<T> T selectOne(String statement);
/**
* 指定sql并传入实参去查单条数据
*
* @param <T> 返回的数据类型
* @param statement 预编译的带有?的sql
* @param parameter 用户传入的实参,与前面sql绑定
* @return Mapped object
*/
<T> T selectOne(String statement, Object parameter);
/**
* 执行sql查询多条数据
*
* @param <E> 返回的数据类型
* @param statement sql
* @return List of mapped object
*/
<E> List<E> selectList(String statement);
/**
* 指定sql并传入实参去查多条数据
*
* @param <E> 返回的数据类型
* @param statement 预编译的带有问号的sql
* @param parameter 用户传入的实参,与前面的sql绑定
* @return List of mapped object
*/
<E> List<E> selectList(String statement, Object parameter);
/**
* 使用预编译的sql,指定传入的实参以及结果集范围
* 查询指定范围的所有数据
*
* @param <E> 返回的数据类型
* @param statement 预编译的带有问号的sql
* @param parameter 用户传入的实参,与前面的sql绑定
* @param rowBounds 指定查询范围
* @return List of mapped object
*/
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
/**
* 执行sql,返回map对象
*
* @param <K> the returned Map keys type
* @param <V> 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 <K> the returned Map keys type
* @param <V> 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 <K> the returned Map keys type
* @param <V> 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 <T> the returned cursor element type.
* @param statement Unique identifier matching the statement to use.
* @return Cursor of mapped objects
*/
<T> Cursor<T> selectCursor(String statement);
/**
* A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
*
* @param <T> 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
*/
<T> Cursor<T> selectCursor(String statement, Object parameter);
/**
* A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
*
* @param <T> 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
*/
<T> Cursor<T> 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<BatchResult> flushStatements();
/**
* 关闭SqlSession
*/
@Override
void close();
/**
* 清空 缓存
*/
void clearCache();
/**
* Retrieves current configuration.
*
* @return Configuration
*/
Configuration getConfiguration();
/**
* 使用type获取对应的Mapper
*
* @param <T> the mapper type
* @param type Mapper interface class
* @return a mapper bound to this SqlSession
*/
<T> T getMapper(Class<T> 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);
// 创建DefaultSqlSession
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();
}
}
/**
* 通过用户提供的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 transactions
autoCommit = 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;
// 使用动态代理去创建 SqlSession
this.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
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取当前线程的SqlSession
final 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的,虽然这个比较简单,但是也是相当重要的一个模块。