在正式说明之前,我们拿最原生的代码,也就是读取指定目录下的config.xml文件的形式,显式创建会话的形式来讲解源码,为什么这么做呢?因为不管是SpringMVC、SpringBoot集成Mybatis还是MgbatisPlus它们都是对Mybatis的一层又一层的封装,但是底层原理不变,比如SqlSession,这个会话对象是一直都存在的,所以我们从原生代码开始,也就是下面的第一段代码
在下面的文章中,说明了一下Mybatis的架构设计,在最后也说明了Mybatis的一个运行流程,所以在这里,对这下面这篇文档的流程来讲解Mybatis系列之架构设计原理_阿小冰的博客-CSDN博客Mybatis系列之架构设计原理https://blog.csdn.net/qq_38377525/article/details/123642061
加载配置并初始化配置信息源码剖析
//1、读取指定目录下的Mybatis主配置文件
Inputstream inputstream = Resources.getResourceAsStream("mybatis-config.xml");
//2、初始化配置文件
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
上述代码中的步骤2,是主要代码,是拿到主配置文件的路径之后,直接初始化,这一步是下面CURD所有操作的前提,我们着重分析一下这个build(),走进源码看一下
SqlSessionFactoryBuilder类中定义了一个build方法的调用
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
我们点进去看一下具体实现,具体实现也在这个类中,源码如下:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//mybatis中专门解析mybatis配置文件的一个类
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//调用重载方法,返回值是一个配置Configuration对象
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
这里我们需要注意一下,Mybatis在初始化的时候,会自动把Mybatis的配置信息全部加载到内存里,使用Configuration实例来维护,这个类在org.apache.ibatis.session包下,带着大家看一下上述代码最后return的build方法中的入参是怎么分解的
首先,点击进入parse()这个方法,来到XMLConfigBuilder类,这个类是专门处理XML文件的,点击进来之后首先看到的是如下源码,是用来做分析前的一个校验准备的
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//拿到配置文件,获取根节点configuration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
然后我们进入parseConfiguration方法,也在当前类中,源码如下:
private void parseConfiguration(XNode root) {
try {
//解析settings标签
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//解析properties标签
propertiesElement(root.evalNode("properties"));
//加载自定义的VFS实现类
loadCustomVfs(settings);
//解析tupeAliases标签
typeAliasesElement(root.evalNode("typeAliases"));
//解析plugins标签
pluginElement(root.evalNode("plugins"));
//解析objectFactory标签
objectFactoryElement(root.evalNode("objectFactory"));
//解析objectWrapperFactory标签
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//解析reflectionFactory标签
reflectionFactoryElement(root.evalNode("reflectionFactory"));
//将settings传给Configuration
settingsElement(settings);
//解析environments标签
environmentsElement(root.evalNode("environments"));
//解析databaseIdProvider标签
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//解析typeHandlers标签
typeHandlerElement(root.evalNode("typeHandlers"));
//解析mappers标签
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
这个源码主要是对主配置文件做标签解析,获取对应的配置值,解析的这些key,在Configuration类中也都存在,为的就是解析之后转换成Configuration对象,在Configuration类中包含一个mappedStatements缓存,主要是保存一个完整的SQL,这里需要讲解一下这个MappedStatement类
在上述代码中,解析的最后一步是对mappers进行解析,这个是我们写sql的xml文件中最外层的一个标签,获取它,主要是为了获取它作用域下的所有标签,其实点进去,它就是再去解析<select>/<update>/<insert>/<delete>等标签,生成一个又一个的MappedStatement对象,看下面的sql定义
<select id="getUserById" resultType="com.cb.entity.User">
select * from user where id=#{id}
</select>
在Configuration类中,用一个Map来接收这些MappedStatement,Key其实就是namespace+id,value=MappedStatement对象,我们来看一下MappedStatement对象的属性
public final class MappedStatement {
private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
//以下代码省略...
}
看着这些属性是不是很熟悉?其实对应的就是我们写自定义sql时候各个CURD标签中的属性:resultMap、id等等,然后对我们上面写的<select>进行解析,获取一个MappedStatement对象,初始化到Configuration中
到这里,xml解析完成之后,主配置文件、sql维护的xml都已经解析完毕了,得到了一个最终的Configuration对象,拿到这个对象就要回到上面的build方法重载,上面的SqlSessionFactoryBuilder定义了很多build的重载方法,初始化到这里,就要用到下面的重载方法,完成初始化
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
处理操作请求
其实就是执行SQL,然后讲一下SQL是怎么执行的
最主要的还是SqlSession,它是一个接口类,主要是实现类是DefaultSqlSession,还有一个已经被弃用了,就不做赘述了,SqlSession它是Mybatis用于和数据库交互的最父级类(最顶级类),一般情况下,它会结合ThreadLocal绑定使用,一个会话使用一个SqlSession,会话结束就释放资源close
我们看一下DefaultSqlSession类:
public class DefaultSqlSession implements SqlSession {
//配置类
private Configuration configuration;
//执行器
private Executor executor;
//以下代码省略...
}
Configuration类已经没啥可说的了,我们来看执行器Executor:
执行器Executor
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement ms, Object parameter) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
List<BatchResult> flushStatements() throws SQLException;
void commit(boolean required) throws SQLException;
void rollback(boolean required) throws SQLException;
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
boolean isCached(MappedStatement ms, CacheKey key);
void clearLocalCache();
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
Transaction getTransaction();
void close(boolean forceRollback);
boolean isClosed();
void setExecutorWrapper(Executor executor);
}
执行器主要的三个实现类:
1、BatchExecutor:重用语句并执行批量更新
2、ReuseExecutor:重用预处理语句-prepared statements
3、SimpleExecutor:默认的普通执行器
然后我们获取sqlSession实例
SqlSession sqlSession = factory.openSession();
List<User> list =sqlSession.selectList("com.cb.mapper.UserMapper.getUserList");
我们看一下openSession()主要做了哪些工作,首先DefaultSqlSessionFactory中有很多openSession的重载方法,但都调用了openSessionFromDataSource方法,只是入参不一样,看一下openSessionFromDataSource源码逻辑
/*
* execType:执行器类型
* level:事务隔离级别
* autoCommit:是否开启事务
*/
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);
//返回SqlSession实例
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();
}
}
因为上面声明的openSession要执行的是查询列表的操作,所以我们看一下openSession提供的selectList方法的实现逻辑
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//获取到具体的一个MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
//调用执行器中的query方法来进行查询,rowBounds用来分页,wrapCollection用来封装参数
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到这里是已经拼接为一个可以直接拿到数据库执行的sql语句了,所以接下来我们剖析一下执行器是怎么执行sql的
我们进入executor.query()
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//封装sql
BoundSql boundSql = ms.getBoundSql(parameter);
//获取缓存
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
//执行查询-需进一步讲解
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
接下来的query()代码,在之前的一篇mybatis的缓存文章中有写到Mybatis系列之一级/二级缓存_阿小冰的博客-CSDN博客中有讲解,这里我们再来一遍:
@SuppressWarnings("unchecked")
@Override
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();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
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,看源码:
@Override
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();
//1、传入参数创建StatementHanlder对象来执行查询
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//2、创建jdbc中的statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
//3、交给StatementHandler来处理逻辑
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
上述代码的步骤3,需要进一步再讲解一下,看他是怎么创建的
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//从连接池获得连接,这个方法最后会调用openConnection方法,可以自己研究一下哈
Connection connection = getConnection(statementLog);
//建立连接会话
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
这个逻辑比较长,最后也只是创建了一个StatementHandler对象,然后把入参之类的传给它,它来完成对数据库的查询,最终返回一个List结果集
以上的执行器的代码很关键,所以需要总结一下:
1、执行器根据传递的参数,完成SQL的动态解析,生成一个BoundSql对象,让StatementHandler获取并使用
2、结合一级二级缓存,为查询创建了缓存,提高了查询效率
3、创建JDBC的Statement连接对象,然后传递给StatementHandler对象,它去执行并返回结果
所以,到这里我们就需要讲一下StatementHandler对象的作用
StatementHandler
它主要有两个功能
1、我们在写SQL的时候,会用到#{},这个在Mybatis分析之后会转换为?,举例:select * from user where id = #{id},转换之后是 select * from user where id = ?,StatementHandler会通过paramterize方法对这些占位符?进行参数填充
2、StatementHandler通过List query方法(当然还有其他方法,我上面的例子就是查询集合)来执行Statement,然后将Statement对象返回的resultSet封装成对应的List,最后,通过工厂和反射等机制,获取到我们resultMap指定返回的数据类型,将Statement返回的集合转换为对应的数据类型并返回给客户端
具体代码就不展示了,了解主要机制就可以了