这一篇文章主要MyBatis拿到配置文件之后,即获取到数据源配置信息和SQL语句之后,然后再从源码解读分析MyBatis是如何进行数据库连接和SQL语句执行。关于MyBatis是如何解析mybatis-config.xml文件,获取到数据源的请看这篇文章:
Mybatis源码解析之数据源和SQL构建
根据官网介绍结合自己的理解,MyBatis的流程图如下所示:
根据流程图,在上篇文章中创建了一个测试类,并且详细介绍了MyBatis是如何解析配置文件,获取到数据源信息及SQL语句的。下面来看一下上一篇文章中的测试类:
public class TestMyBatis {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
//获取MyBatis配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//通过SqlSessionFactoryBuilder的build()方法获取SqlSessionFactory 对象
//此时已将MyBatis配置文件的相关配置已获取到,即Configuration类已获取到值
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过SqlSessionFactory的openSession()方法获取SqlSession 对象
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
//通过SqlSession对象执行SQL语句
List<BaseUserInfoDO> result = sqlSession.selectList("com.mybatis.DemoMapper.queryBaseUserInfoDO");
System.out.println(result.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
}
}
其中下面的语句的已在上篇文章中详细解析过了。
//已解析
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
因此本篇文章主要解析以下语句,来探查MyBatis底层是如下进行数据库连接和执行SQL语句的。
//创建SQL会话
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行SQL查询语句
List<BaseUserInfoDO> result = sqlSession.selectList("com.mybatis.DemoMapper.queryBaseUserInfoDO");
创建SqlSession其实就是根据Confiuration对象实例化一个SQL执行器,一个执行器里面需要包含数据源信息和事务管理器,然后根据Confiuration对象和实例化的SQL执行器创建一个SqlSession对象,数据库连接和SQL语句的执行都在这个SqlSession里面进行操作。
创建SqlSession对象的执行语句如下所示:
//已解析结束
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//根据sqlSessionFactory对象创建一个SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
其中openSession() 方法的源码如下图所示,已省略无关代码:
public class DefaultSqlSessionFactory implements SqlSessionFactory {
//openSession()方法的入口
public SqlSession openSession() {
//调用openSessionFromDataSource()方法创建一个SqlSession对象
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
/**
* 此方法就是根据之前的configuration对象获取数据源信息,
* 然后根据数据源信息和配置的事务管理器类型新建一个JDBC事务管理器,
* 然后根据新建的事务管理器和配置的执行器类型新建一个SQL执行器,
* 随后根据configuration和执行器实例化一个SqlSession 对象
**/
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
//新建一个事务管理器
Transaction tx = null;
try {
//获取Environment对象,里面包含数据源信息和事务管理器类型
final Environment environment = configuration.getEnvironment();
//获取事务管理器工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//根据数据源信息和事务管理器类型实例化一个JDBC事务管理器对象
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//根据事务管理器和执行器类型,实例化一个SQL执行器。执行器类型为Simple类型
final Executor executor = configuration.newExecutor(tx, execType, autoCommit);
//创建一个SqlSession对象返回
return new DefaultSqlSession(configuration, executor);
} 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();
}
}
}
这里看一下 configuration.newExecutor()方法的源码,可以从源码上了解SQL执行器的初始化过程,无关代码已省略。
public class Configuration {
//newExecutor()方法的入口
public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
//判断执行类型是否为空,如果为空,则赋值为默认的执行器类型,否则执行器类型不做改变
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
//初始化一个执行器
Executor executor;
//根据配置的执行器类型,实例化一个执行器
//这里可以看到共有三种执行器类型,分别为BATCH、REUSE、SIMPLE
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
//判断一级缓存是否开启,一级缓存默认是开启的
if (cacheEnabled) {
//如果开启,则缓存中的SQL执行器也被设置为当前执行器类型
executor = new CachingExecutor(executor, autoCommit);
}
//设置拦截器,就是在进行SQL语句执行之前可以在拦截器中做一些SQL语句方面的修改
executor = (Executor) interceptorChain.pluginAll(executor);
//返回执行器
return executor;
}
}
此时SqlSession对象已创建,那么就到了最激动人心的时刻了,看看MyBatis是如何进行根据SqlSession进行数据库连接以及SQL语句的执行的吧。
根据官网介绍,MyBatis关于数据库连接以及Sql语句的执行都是根据JDBC规范来的,如果你对JDBC很熟悉的话,那么你对以下代码应该会感到很熟悉。JDBC操作数据库的步骤一般如下:
//使用JDBC驱动器,根据数据源进行数据库连接
Connection conn = DriverManager.getConnection(url, user, password);
//根据SQL语句创建一个Statement对象,常用的一般都是预编译的PreparedStatement
PreparedStatement ps = conn.prepareStatement(sql);
//用StateMent执行SQL语句
ResultSet rs= ps.executeQuery("select * from user");
接下来就让我们一起来看一下,MyBatis是如何对JDBC进行封装,同时也验证一下MyBatis内部是否真的就是JDBC的执行过程。那么现在就Ddbug下面这个语句的执行过程,深入源码进行验证:
List<BaseUserInfoDO> result = sqlSession.selectList("com.mybatis.DemoMapper.queryBaseUserInfoDO");
上面这个语句就是告诉MyBatis需要执行DemoMapper类的queryBaseUserInfoDO()方法。DefaultSqlSession这个类是SqlSession类的子类,这个类里面包含了JDBC四种StateMent
(select|insert|update|delete) 以及其它的一些方法,比如获取**Mapper.java接口、获取Configuration类等一些方法,具体方法内容,有兴趣的同学可以自行去查阅一下,这里只看测试类用到的方法,省略其它无关代码。下面来看一下sqlSession.selectList()内部源码内容:
public class DefaultSqlSession implements SqlSession {
/**
*selectList()方法的入口
**/
public <E> List<E> selectList(String statement) {
//调用下面一个方法
return this.<E>selectList(statement, null);
}
/**
* 这个方法增加了一个默认分页的类:RowBounds
* 起始位置: offset = 0;
* 查询多少条记录? limit = 最大的整数值。
**/
public <E> List<E> selectList(String statement, Object parameter) {
//增加了一个分页的类:RowBounds
return this.<E>selectList(statement, parameter, RowBounds.DEFAULT);
}
/**
* 这个方法就是SQL语句执行的入口
**/
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//这个方法里面就是根据传进来的参数:"com.mybatis.DemoMapper.queryBaseUserInfoDO"
//从Configuration类来获取MappedStatement对象
//MappedStatement对象包含了StateMentType(Prepared)和SqlType(Select)以及具体的SQL语句
MappedStatement ms = configuration.getMappedStatement(statement);
//然后执行器执行查询语句
//参数分别为:(MappedStatement对象,queryBaseUserInfoDO方法所需参数,分页对象
//,查询结果解析器(就是将JDBC对象转换为JAVA对象))
List<E> result = executor.<E>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();
}
}
}
那么执行器是怎么连接数据库然后执行SQL语句的呢?Debug进去看一下executor.query()方法的源码实现:
/**
* 这个执行器是采用的缓存执行器类,具体的执行器类型其实还是SIMPLE类型
* 因为MyBait默认一级缓存是开启的,所以在创建会话的时候,执行器具体类变更为CachingExecutor
**/
public class CachingExecutor implements Executor {
/**
* executor.query()方法的入口
* 这个方法主要是获取SQL语句以及创建一个缓存键
* 这个缓存建对应的值就是此次的查询结果
**/
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//根据参数获取需要执行的具体SQL语句
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 {
//获取MappedStatement的缓存对象
Cache cache = ms.getCache();
//因为是第一次查询,所以缓存里面肯定是空的,
//如果是第二次查询的话,且当前数据未被改动,那么这个缓存对象肯定不为空,
//就直接根据StateMent从缓存中取出结果,不需要进行数据连接、查询等一系列操作
//极大的提高了查询的响应速率
if (cache != null) {
//根据StateMent对象查询是否需要刷新缓存,其实就是验证数据是否修改过,
//如果数据已经被修改,则MyBatis会删除缓存
flushCacheIfRequired(ms);
//判断条件:是否需要使用缓存以及处理处理器是否为空
if (ms.isUseCache() && resultHandler == null) {
//查询是否是Callable的StateMent对象
ensureNoOutParams(ms, key, parameterObject, boundSql);
//数据是否已经更改?没有更改则直接查询缓存,否则查询数据库
if (!dirty) {
//利用读锁进行加锁,因为DefaultSqlSession是线程不安全的
cache.getReadWriteLock().readLock().lock();
try {
//根据前面创建的缓存key值,获取缓存值
@SuppressWarnings("unchecked")
List<E> cachedList = (List<E>) cache.getObject(key);
//如果缓存不为空则返回缓存值
if (cachedList != null) return cachedList;
} finally {
//释放读锁
cache.getReadWriteLock().readLock().unlock();
}
}
//这一步其实就是线程进来的时候还有缓存,但是在查询缓存的时候,由于数据变更导致缓存被删
//所以只能悲催的去查询数据库
List<E> 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);
}
}
delegate.query()方法源码内容如下:
public abstract class BaseExecutor implements Executor {
/**
* query()方法入口
* 此方法只是做一些条件判断,并且再一次查询局部缓存容器,看是否有缓存值
* 因为DefaultSqlSession是线程不安全的
**/
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());
//判断当前会话是否已关闭,关闭则抛出异常,因为DefaultSqlSession是线程不安全的,
//所以多线程情况下会有这种情况
if (closed) throw new ExecutorException("Executor was closed.");
//判断是否需要刷新缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
//初始化返回结果
List<E> list;
try {
//这个变量类似于版本号,当前线程执行数据库操作前加1,结束后减1
queryStack++;
//如果结果处理器为空,则再一次查询缓存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//如果缓存结果不为空,则更新此次Statement的缓存参数
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//如果为空,则直接查询数据库
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
//将变量减1,变成0,表示操作完成
queryStack--;
}
//....省略部分代码
//返回结果
return list;
}
/**
*queryFromDatabase()方法查询数据
* 这个方法调用的doQuery()就是我们本次要找寻的答案了。
**/
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//返回结果
List<E> list;
//存储当前的缓存唯一键key和执行器持有者放入localCache对象
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//查询数据库
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
//移除前面存储的key-value对象
localCache.removeObject(key);
}
//将缓存唯一键key和查询结果放入本地缓存中
localCache.putObject(key, list);
//判断StatementType是否是CALLABLE类型的,就是是否需要调用存储过程
if (ms.getStatementType() == StatementType.CALLABLE) {
//如果是则将缓存键key和参数放入localOutputParameterCache对象中
localOutputParameterCache.putObject(key, parameter);
}
//返回结果
return list;
}
}
其实doQuery()方法查询数据库最终用到的还是SimpleExcutor执行器去操作数据库的,Simple执行器也是最初在配置文件中进行配置的执行器,到现在终于要掀开MyBatis进行数据库操作的神秘面纱,也可以验证一下它到底是不是利用JDBC操作数据库的。doQuery()源码如下:
/**
* SimpleExecutor执行器
**/
public class SimpleExecutor extends BaseExecutor {
/**
*doQuery()方法查询数据库
*进行数据库连接并执行SQL语句
**/
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
//StateMent对象
Statement stmt = null;
try {
//获取配置信息
Configuration configuration = ms.getConfiguration();
//获取Statement处理器,StateMent处理器包含了:SQL语句、执行器、结果处理器、参数处理器等等
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
//调用下面的prepareStatement()方法返回一个PrepareStatement对象
//同时也进行数据库连接和SQL预编译,如果编译不通过则抛出异常
stmt = prepareStatement(handler, ms.getStatementLog());
//Statement处理器执行SQL语句进行查询
return handler.<E>query(stmt, resultHandler);
} finally {
//关闭此次Statement的执行
closeStatement(stmt);
}
}
/**
* 进行数据库连接,同时也进行Sql语句预编译
**/
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
//Statement对象
Statement stmt;
//进行数据库连接
Connection connection = getConnection(statementLog);
//进行SQL预编译处理
stmt = handler.prepare(connection);
//设置sql参数
handler.parameterize(stmt);
//返回StateMent对象
return stmt;
}
}
下面通过源码来看一下数据库的具体连接,即看一下getConnection()方法的具体内容
/**
* 下面的这四个方法就是数据库连接的整个过程,
* 可以看到最后一个方法就是调用JDBC的连接方法,进行数据库连接的
* 到此也证明了我们猜想的正确性
**/
protected Connection getConnection(Log statementLog) throws SQLException {
//在当前事务中创建一个数据库连接
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog);
} else {
//返回连接
return connection;
}
}
//打开一个连接
public Connection getConnection() throws SQLException {
//如果连接为空
if (connection == null) {
//新建一个连接
openConnection();
}
//返回这个连接对象
return connection;
}
/**
*根据数据源信息进行数据库连接
**/
protected void openConnection() throws SQLException {
//用数据源进行数据库连接
connection = dataSource.getConnection();
//事务级别设置
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
//设置自动提交事务,默认为false
setDesiredAutoCommit(autoCommmit);
}
/**
* 调用JDBC方法进行数据库连接
**/
public Connection getConnection() throws SQLException {
//调用数据库驱动创建一个连接
return DriverManager.getConnection(this.jdbcUrl, this.createProps((String)null, (String)null));
}
至此,整个数据库连接结束,也证明了MyBaits最底层其实就是调用JDBC方法进行数据库连接的。数据库连接结束后,肯定就是执行SQL语句了。那么接下来就看一下MyBatis是否也是采用JDBC方式进行SQL语句执行的呢?
第3节已经完成了数据库的连接了,在数据库连接结束之后,因为配置文件配置的是SIMPLE执行器,它默认的Statement是PrepareStatement,所以在执行SQL语句之前会进行预编译处理,预编译处理的语句如下所示:
//Statement对象
Statement stmt;
//进行数据库连接 第3节已解析
Connection connection = getConnection(statementLog);
//进行SQL预编译处理
stmt = handler.prepare(connection);
prepare()方法源码如下所示:
/**
* 方法入口 省略部分次要代码
**/
public Statement prepare(Connection connection) throws SQLException {
Statement statement = null;
//预编译SQL,编译通过则返回PreparedStatement对象,否则抛出异常
statement = instantiateStatement(connection);
//设置超时时间
setStatementTimeout(statement);
//设置查询多少行
setFetchSize(statement);
//返回PreparedStatement对象
return statement;
}
/**
* instantiateStatement():预编译SQL方法
**/
protected Statement instantiateStatement(Connection connection) throws SQLException {
//获取SQL语句
String sql = boundSql.getSql();
//是否要返回生成的主键,在Mysql新增(insert)的SQL语句中会执行下面的语句
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
}
//结果处理器类型不为空,则执行下面的语句
else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
//本次测试类执行下面的预编译SQL方法
else {
return connection.prepareStatement(sql);
}
}
通过源码可以看到上面预编译过程和JDBC的预编译过程一致,预编译SQL结束后,肯定就是执行SQL语句了,Sql语句的执行是通过Statement处理器进行的,如下所示:
Configuration configuration = ms.getConfiguration();
//获取Statement处理器,StateMent处理器包含了:SQL语句、执行器、结果处理器、参数处理器等等
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
//数据库连接和SQL预编译结束
stmt = prepareStatement(handler, ms.getStatementLog());
//Statement处理器执行SQL语句进行查询
return handler.<E>query(stmt, resultHandler);
现在就来看一下handler.query() 方法的源码,验证SQL语句的执行是否和JDBC的执行过程一致,源码内容如下:
/**
* 执行SQL语句入口
**/
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//将Statement对象强转成PreparedStatement对象
PreparedStatement ps = (PreparedStatement) statement;
//执行SQL语句,查询结果保存在PreparedStatement对象中:
//这个执行过程确实和JDBC执行过程一致
//JDBC: ps.executeQuery(sql)
ps.execute();
//解析查询出来的结果:将JDBC类型转换成JAVA类型
return resultSetHandler.<E> handleResultSets(ps);
}
现在来看一下MyBatis是如何将查询出来的JDBC结果转换成对应JAVA类型的吧,resultSetHandler.handleResultSets() 方法源码如下:
/**
* 解析查询结果: 已省略部分代码
**/
public List<Object> handleResultSets(Statement stmt) throws SQLException {
//实例化结果集
final List<Object> multipleResults = new ArrayList<Object>();
//获取执行SQL返回的结果类型集合, 比如测试类返回的是:BaseUserInfoDO类
final List<ResultMap> resultMaps = mappedStatement.getResultMaps();
//SQL执行返回的结果类型集合长度
int resultMapCount = resultMaps.size();
//结果集计数器,遍历结果集用
int resultSetCount = 0;
//获取执行SQL的结果,返回的结果集都在这个对象里面
ResultSet rs = stmt.getResultSet();
//遍历结果集
while (rs != null && resultMapCount > resultSetCount) {
//获取第一种结果类型
final ResultMap resultMap = resultMaps.get(resultSetCount);
//获取查询结果列的缓存,里面存放的是查询的列和列的JDBC类型及对应的JAVA类型
ResultColumnCache resultColumnCache = new ResultColumnCache(rs.getMetaData(), configuration);
//解析返回的结果
handleResultSet(rs, resultMap, multipleResults, resultColumnCache);
//获取下一个结果类型,测试类只有一个结果类型:BaseUserInfoDO类
rs = getNextResultSet(stmt);
resultSetCount++;
}
//返回结果
return collapseSingleResultList(multipleResults);
}
/**
* 解析查询结果 已省略部分代码
**/
protected void handleResultSet(ResultSet rs, ResultMap resultMap, List<Object> multipleResults, ResultColumnCache resultColumnCache) throws SQLException {
//新建结果集对象
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
//解析SQL查询出来的所有的行信息:即所有的结果集,这个时候结果集还是JDBC类型
handleRowValues(rs, resultMap, defaultResultHandler, rowBounds, resultColumnCache);
//将解析后的结果集添加到这个结果对象中:这个时候结果集对应的就是JAVA类型了
multipleResults.add(defaultResultHandler.getResultList());
}
/**
* 遍历结果集合
**/
protected void handleRowValues(ResultSet rs, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultColumnCache resultColumnCache) throws SQLException {
//实例化一个结果对象
final DefaultResultContext resultContext = new DefaultResultContext();
//遍历查询出来的所有结果:即数据库表的所有行数据
while (shouldProcessMoreRows(rs, resultContext, rowBounds)) {
//获取结果集对象:即BaseUserInfoDO类
final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rs, resultMap, null);
//解析每一行的结果(一个BaseUserInfoDO对象):
//即把数据表中每一行的每一个JDBC字段转换成JAVA字段并赋值
Object rowValue = getRowValue(rs, discriminatedResultMap, null, resultColumnCache);
//将这个对象添加到结果集中
callResultHandler(resultHandler, resultContext, rowValue);
}
}
/**
* 解析每一行的值:即把数据表中每一行的每一个JDBC字段转换成JAVA字段并赋值
**/
protected Object getRowValue(ResultSet rs, ResultMap resultMap, CacheKey rowKey, ResultColumnCache resultColumnCache) throws SQLException {
final ResultLoaderMap lazyLoader = instantiateResultLoaderMap();
//获取BaseUserInfoDO类对象
Object resultObject = createResultObject(rs, resultMap, lazyLoader, null, resultColumnCache);
//获取BaseUserInfoDO类对象
final MetaObject metaObject = configuration.newMetaObject(resultObject);
//获取BaseUserInfoDO类对象的成员变量
final List<String> unmappedColumnNames = resultColumnCache.getUnmappedColumnNames(resultMap, null);
//解析BaseUserInfoDO对象每一个成员变量值
applyAutomaticMappings(rs, unmappedColumnNames, metaObject, null, resultColumnCache)
//返回结果
return resultObject;
}
/**
*把数据表中每一行的每一个JDBC字段转换成BaseUserInfoDO类的成员变量并且赋值
**/
protected boolean applyAutomaticMappings(ResultSet rs, List<String> unmappedColumnNames, MetaObject metaObject, String columnPrefix, ResultColumnCache resultColumnCache) throws SQLException {
//核心代码:遍历BaseUserInfoDO类的每一个成员变量,然后进行赋值
for (String columnName : unmappedColumnNames) {
//根据成员变量类型获取相应的类型解析器
final TypeHandler<?> typeHandler = resultColumnCache.getTypeHandler(propertyType, columnName);
//根据相应的类型解析器,将查询出来的JDBC类型值转换成对应的JAVA类型值
final Object value = typeHandler.getResult(rs, columnName);
//对字段进行赋值
metaObject.setValue(property, value);
}
}
}
至此整个SQL语句执行结束,现在看一下查询出来的结果,即下列语句的查询结果:
List<BaseUserInfoDO> result = sqlSession.selectList("com.mybatis.DemoMapper.queryBaseUserInfoDO");
此时的结果和数据库中的结果一致。
有兴趣的同学可以自己跟着流程自己动手操作一遍。可以理解的更深入一些。
总结: MyBatis内部其实就是对JDBC进行封装,然后操作数据库,执行SQL语句。所以使用MyBatis操作数据库,几乎可以免除所有的jdbc代码以及参数设置和结果集转换的工作。
DefaultSqlSession是线程安全的吗?
答:DefaultSqlSession不是线程安全的,SqlSessionManager和Spring的SqlSessionTemplate是线程安全。
MyBatis是如何进行分页的?
答:MyBatis有一个内置的分页对象RowBounds,默认的是使用这个对象进行分页的。当然也可以使用sql进行分页,也可以配置拦截器进行分页。
说说你理解的MyBatis的设计模式有几种?
答:工厂模式:SqlSessionFactory。构建模式:build;代理模式:Proxy;装饰模式:Executor;等等