Mybatis 源码分析
2021-12-06 00:45:42 0 举报
mybatis 源码流程分析
作者其他创作
大纲/内容
Configuration configuration = MappedStatement.getConfiguration();
使用 parameterHandler 处理 sql 占位符
先 if 判断 <package></package> 节点是否存在,如果不存在,else 再判断 <mapper></mapper> 节点
创建一个 SqlSessionFactoryBuilder
BaseBuilder # 构造方法
获取 mapper 接口的 Class 对象
return connection.prepareStatement(sql);
loadedResources.add(resource);
isDynamic
resource == null && url != null && mapperClass == null
final Environment environment = configuration.getEnvironment();
parsePendingResultMaps();
DynamicSqlSource 负责处理动态 SQL 语句。 使用了 OGNL 表达式,或者使用了 ${} 表达式的 SQL ,所以它是动态的,需要在每次执行# getBoundSql(Object parameterObject) 方法,根据参数,生成对应的 SQL 。
通过 XPathPaser 解析出 configuration 节点并封装为 XNode 对象
cache != null
isFlushCacheRequired() 判断是否需要清空一级缓存,lushCacheRequired 配置为 true,则清空一级缓存
configuration.addLoadedResource(\"namespace:\" + namespace);
处理解析失败的 CacheRef 节点
typeHandlerElement(root.evalNode(\"typeHandlers\"));
Configuration # addMapper
这里详细阐述了让读者思考的第 8个问题,mybatis 的一级缓存和二级缓存是如何工作的
this.mapperInterface = mapperInterface;
parsed = true;
queryStack == 0 && ms.isFlushCacheRequired()
在 XMLConfigBuilder 构造方法里调用重载构造方法
循环 mappers 节点下的 mapper 节点,实例化 XMLMapperBuilder 对象处理节点,parent 即参数,root.evalNode(\"mappers\") 解析结果,是一个XNode
String id = context.getStringAttribute(\"id\")
extends
解析 mybatis-config.xml 全局配置文件,这里最重要的就是解析 mybatis-confi.xml 为 Configuration 对象
connection = dataSource.getConnection();
true
return statement
关闭 statement
objectFactoryElement(root.evalNode(\"objectFactory\"));
Object param = method.convertArgsToSqlCommandParam(args);
String namespace = builderAssistant.getCurrentNamespace();
InputStream inputStream = Resources.getResourceAsStream(resource);
它是 SqlSessionFactory 的缓存,又称为 Mapper 级别的缓存。只要是同一个 SqlSessionFactory 创建的 SqlSession 就共享二级缓存的内容,并且可以操作二级缓存。二级缓存如果要使用的话,需要我们自己手动开启(需要配置的)。
mapperRegistry 是在创建 Configuration 对象的时候就已经初始化了的一个属性,上面有提到过,参数 type 即mapper 接口的 Class 对象
注释
先判断是否存在 resource 属性如果没有,再判断 url最后判断 mapperClass,笔者用的是 resource 属性配置,所以这里以 resource 为例,其他两种都是大同小异
继上一篇 spring 源码分析之后,笔者又马不停蹄的赶出 Mybatis,俺是勤劳的小蜜蜂,飞到花丛中啊........这一幅图,笔者依旧以一个小 demo 作为案例进行分析,如图:
BoundSql 详解
获取 mapper 接口 全限定名 ,即 对应mapper映射文件的 namespace 命名空间
可以看出,全局配置文件只能加载一次,多次加载会报错
cacheRefElement(context.evalNode(\"cache-ref\"))
调用它的重载方法
看是否有必要清空二级缓存
Enumeration<Driver> drivers = DriverManager.getDrivers();
解析<mapper></mapper> 标签中 resultMap 节点
MappedStatement 解析
这个 cache HashMap 就是存放具体数据的地方
SimpleExecutor # doQuery
mybatis 解析 environments 过程
将第一步的属性 props 设置到 datasourceFactory 中
String url = child.getStringAttribute(\"url\");
environmentsElement(root.evalNode(\"environments\"));
SqlSession采用了门面模式方便来让我们使用。他不能跨线程使用,一级缓存生命周期和它一致;基本功能:增删改查基本的操作功能;辅助功能:提交、关闭会话;门面模式:提供一个统一的门面接口API,使得系统更加容易使用。
Mybatis 默认使用 SimpleExcutor,创建 Executor 完成后,会判断 cacheEnabled,这个属性值默认为 true,所以会用装饰器模式,将这个 SimpleExecutor 包装为 CachingExecutor,在执行查询时会走二级缓存,所以这就回答了让读者思考的第 5 个问题,mybatis 二级缓存是否是默认开启?为什么?
注意红框框起来的部分,可以看出,Mybatis 的二级缓存默认使用 LRU 淘汰策略
类型转换器
new Configuration()
声明变量 sqlSource
this.dataSource = new PooledDataSource();
driversIterator.next();
从数据源拿到数据库连接
this.parser = parser;
for
通过构造者模式创建一个 factory 工厂
return builder.parseScriptNode();
MapperProxy # invoke
源码
获取全局配置对象
CachingExecutor # query
Properties settings = settingsAsProperties(root.evalNode(\"settings\"))
清除一级缓存
protected boolean cacheEnabled = true
cachedInvoker(method) 返回一个 PlainMethodInvoker 对象PlainMethodInvoker 是 MapperProxy 的一个静态内部类此对象实现了 MapperMethodInvoker 接口内部维护了一个常量 MapperMethod,创建 MapperMethod 的同时又会创建 SqlCommand,保存 Sql 语句信息,以及sql语句类型(crud)invoke 方法调用的是内部接口 MapperMethodInvoker 的 invoke 方法
closeStatement(stmt)
解析 <mapper></mapper> 标签中 parameterMap 节点
BatchExecutor
configuration.addLoadedResource(resource);
CachingExecutor
至此,SqlSessionFactory 创建完成
DefaultSqlSession # getMapper
解析插件配置
dataSourceElement(XNode context)
font color=\"#f44336\
build(parser.parse())
queryStack++;
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this)
this.configuration = configuration;
创建了一个 mapper 的代理工厂,在之后 mapper 接口所有的代理对象都由这个工厂产生
return newInstance(mapperProxy);
List<User> userList = mapper.findAll();
解析当前标签中的节点中的 sql 语句,并封装为 sqlSource
准备 Statement
从 MappedStatement 中获取 cache,判断二级缓存是否开启,其实就是看 mapper.xml 中有没有配置值<cache/> 标签
statementParser.parseStatementNode()
BoundSql boundSql = MappedStatement.getBoundSql(parameterObject);
false
如果当前调用的方法是 Object 本身的方法,则不做增强
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
String sql = boundSql.getSql()
基本功能:改、查、维护缓存;辅助功能:提交、关闭执行器、批处理刷新;Executor 主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,它会将数据库相关操作委托给 StatementHandler完成。
this.environment = environment;
protected boolean mapUnderscoreToCamelCase;
继承 extends
environments 在 mybatis-config.xml 中的配置
类
pluginElement(root.evalNode(\"plugins\"));
Statement statement = null;
configuration # getMapper
它是 sqlSession 对象的缓存,自带的(不需要配置)不可关闭的. 一级缓存的生命周期与sqlSession一致。
封装处理结果集,并返回查询结果
SimpleExecutor
首先获取方法传入的参数
MappedStatement ms = configuration.getMappedStatement(statement);
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
获取事务工厂
PreparedStatement ps = (PreparedStatement) statement;
setDesiredAutoCommit(autoCommit);
super(configuration)
final XMLStatementBuilder font color=\"#f44336\
将 mapper.xml 映射文件转换为流对象
这个方法最终会返回一个封装好的 configuration 对象,包含了 mybatis-config.xml 全局配置文件以及 mapper 映射文件的所有信息
resultMapElements(context.evalNodes(\"/mapper/resultMap\"));
实例化 XMLMapperBuilder 解析mapper 映射文件,如 XMLConfigBuilder 专职负责解析 mybatis 全局配置文件一样,也提供了一个专门负责解析 mybatis 的解析器
StaticSqlSource 处理包含的 SQL 可能含有 “?” 占位符,可以被数据库直接执行。
获取代理对象后,执行 mapper 代理对象的方法,这里以查询 select 为例
throw new Exception
获取到 需要执行的1sql 语句,让读者思考的第 7 个问题,这里只是直接获取 sql ,实际上 mybatis 获取 sql 的过程非常复杂,在前面就解析了 sql,这里只是直接拿出来
new SqlSessionFactoryBuilder()
mappedStatements 类型是一个 new StrictMap<MappedStatement>StrictMap 是一 Configuration 中的一个静态内部类,继承自 HashMap并重写了 HashMap 的 put 方法。在重写的 put 方法中做了判断,ms.getId() = mapperNamespace + (SELECT | UPDATE | DELETE |INSERT 节点的 id)会判断此 id 是否已经存在,存在则抛出异常,所以这就解决了开头让读者思考的第三个问题为什么 mapper 接口中而方法不能实现重载
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance()
创建 XMLScriptBuilder 对象,负责解析 sql 语句
return configuration;
configuration.addMapper(boundType);
Author:helloworld
MapperProxyFactory # newInstance
resource != null && url == null && mapperClass == null
else
调用 build 重载方法
一级缓存未能命中 ,从数据库中查询
从上图可以看出,用 JDBC 进行数据库查询: 1、首先需要加载数据库驱动,在 java1.6 后,数据库驱动不用手动加载,DriverManager.getConnection 就已经加载了数据库驱动(调用 DriverManager 静态方法需执行类加载,执行静态代码块,在执行静态代码块的时候通过 SPI 机制加载了数据库驱动)。 2、准备好 sql 语句 3、准备传输器 4、执行 sql 语句查询数据库 5、处理返回结果,封装成 javaBean 对象 6、关闭所有连接 而使用 mybatis ,则只需创建一个 1、SqlSessionFactory 工厂 2、获得 SqlSession 3、再拿到 mapper 接口的代理对象 4、用代理对象查询数据。众所周知,mybatis 实现了对 JDBC 的 封装,那它是如何做的封装?在看图之前,请读者先思考几个问题: 1、mybatis 如何加载数据库驱动? 2、mybatis如何获取数据源连接信息? 3、 mapper 接口中的方法为何不能重载? 4、mybatis 是如何将 mapper 接口与 mapper.xml 映射文件进行绑定的? 5、mybatis 二级缓存是否是默认开启?为什么? 6、mybatis 是如何为 mapper 接口创建代理对象的? 7、mybatis如何获取到需要执行的sql语句? 8、mybatis 的一级缓存和二级缓存是如何工作的? 9、mybatis 是什么时候获取的数据库连接?是在 openSession 的时候吗? 10、mybatis是如何完成sql执行的? 11、mybatis如何完成参数映射和结果封装?以上的问题,笔者会在接下来的流程图中,详细用源码解答,笔者也建议,读者最好是从官网上下载 mybatis 源码,用上图的 demo 进行断点跟踪,配合着流程图看,相信看完你一定会有收获这张图是笔者纯手画,如果觉得这张图对您有帮助,不妨赞助一点,点个赞也行,多谢支持!有问题或者建议可以留言,或者加笔者 QQ:275394982 ; WX:rabbitmq0
............
Iterator<Driver> driversIterator = loadedDrivers.iterator();
解析标签拿到完整的 sql 语句(rootSqlNode 包含了完整的 sql 语句),同时判断是否是动态sql,以一个布尔值 isDynamic 作为标记
给当前查询的 sql 准备一个 key,用作一级缓存的 key 值
return list;
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
mapperParser.parse();
XMLScriptBuilder # parseScriptNode
这里为了演示参数,笔者执行了另一个语句,一个 findByUserId(1),以便读者可以更清晰的了解 BoundSql
返回 PrepareStatement,这里就可以看出,跟我们使用 JDBC 进行数据库查询是一样的
在之前的步骤可知,konwMappers 是一个以 Mapper 接口全限定名为 key,MapperProxyFactroy 为值 的 HashMap,konwMappers 记录 mapper 接口和对应的 MapperProxyFactory 之间的关系这里根据传入的 Mapper 接口的 Class 对象,拿到其对应的 MapperProxyFactory
由于 UserMapper 被 JDK 动态代理了,所以首先进入的方法是调用处理器 MapperProxy 的 invoke 方法
二级缓存不为空,则直接返回
return list;
注册 mapper 接口并绑定命名空间
在这里加载数据库驱动
sqlSource.getBoundSql(parameterObject);
this 就是 defaultSqlSession,这里传入一个 sqlSession 是因为 MapperProxy 调用处理器需要,因为 getMapper() 方法,MapperProxyFactory 会返回一个实现了 mapper 接口的代理对象这个代理对象执行 mapper 方法进行数据库操作的时候,调用的是 MapperProxy 调用处理器的 invoke 方法在真正执行查询的时候还是需要 sqlSession,所以 MapperProxy 类需要拿到 sqlSession所以在这里传入 sqlSession 构建 MapperProxy 对象 总结: 为什么要传入 sqlSession? 因为创建 MapperProxy 对象需要 sqlSession 为什么 MapperProxy 对象需要 sqlSession? 因为 MapperProxy 是个调用处理器,在进行代理功能增强的时候需要执行真实目标方法,所以需要拿到 sqlSession
测试代码
mapper 接口的动态代理注册中心
第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。 如果 sqlSession 去执行 commit操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。清空一级缓存: 1、sqlSession销毁 2、执行数据库写操作 3、手动清空,调用 clearLocalCache 方法一级缓存数据结构: 一级缓存是 BaseExecutor 下的一个属性,如图 PerpetualCache 就是一级缓存的实现类,如图
笔者以 mapper 标签为例,进入 false
onfiguration.setEnvironment(environmentBuilder.build());
创建 StatementHandler 对象
namespace != null
String resource = child.getStringAttribute(\"resource\");
Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource);
loadedResources 一个 HashSet,用于存储 mapper 全路径文件名,以及接口全限定名如:{\"mappers/user.xml\",\"namespace:com.qmy.mapper.UserMapper\"}
设置是否自动提交
return new DefaultSqlSessionFactory(config);
DriverManager.getDrivers();调用DriverManager 的getDrinver 方法,也会触发 DriverManager 类加载,执行 DriverManager 的static 代码块
每次创建都会生成一个新的 MapperProxy 对象,MapperProxy 实现了 InvocationHandler 接口,是一个 jdk 动态代理的调用处理器
bindMapperForNamespace()
参数 list 包含了当前 mapper.xml 所有 sql 语句节点
String mapperPackage = child.getStringAttribute(\"name\");
sqlElement(context.evalNodes(\"/mapper/sql\"));
由于是反射创建 UnpooledDataSourceFactory 对象,会触发类加载,执行该类的 static 代码块
Executor
mybatis-config.xml 是 mybatis 的全局配置文件,先将 xml 文件解析成流,以便读取
得到 mapper.xml 映射文件的命名空间,如果namespace 为空,则要抛出异常
statement = instantiateStatement(connection)
factory.setProperties(props);
this.parsed = false
注释箭头
super(new Configuration())
MapperProxy # invoke
解析输入流,即 mybatis-configs.xml 配置文件,XMLConfigBuilder 是 mybatis 专门为解析全局配置文件提供的一个 xml 解析器
从二级缓存中获取数据,不为空直接返回
SqlSessionFactoryBuilder # build(Configuration config)
MapperProxyFactory ---> MapperProxy,通过调用mapper代理工厂对象的 newInstance方法,返回 mapper 代理对象 MapperProxy
MapperBuilderAssistant # addMappedStatement
解析 sql 语句节点
protected ObjectFactory objectFactory = new DefaultObjectFactory()
protected boolean useGeneratedKeys;
解析数据源配置节点 dataSource,封装数据源对象
parameterMapElement(context.evalNodes(\"/mapper/parameterMap\"))
这里解析全局配置文件中的每一个root 节点,并将对应的信息封装到 configuration 对象中
buildStatementFromContext(context.evalNodes(\"select|insert|update|delete\"));
DefaultSqlSessionFactory # openSession
实现 implements
parsePendingStatements();
configuration.addMappedStatement(statement)
写在前面
typeAliasesElement(root.evalNode(\"typeAliases\"))
让读者思考的前 4 个问题 都可以在这一步找到答案
声明 Statement 变量
返回解析成功后的 sqlSouce
解析别名配置
configuration.addMappers(mapperPackage);
返回查询结果
判断二级缓存是否为空,为空,继续往下查询
解析 <mapper></mapper> 标签中cache-ref 节点
实例化 XMLStatementBuilder,专注于解析 sql 语句节点,注意,前面有个循环,也就是说,每一个 select,update,insert,delete 节点都有独立对应的 XMLStatementBuilder 进行解析
SqlSource sqlSource
一级缓存只能在同一个 sqlSession 下使用,sqlSession 一旦关闭或提交,则一级缓存被清空,而二级缓存如上图,可以跨 sqlSession 使用,只要是对同一个 dao(又叫mapper) 的读操作,都可以共用这个二级缓存。当数据库执行写操作的时候,会先清空二级缓存,再清空一级缓存。 二级缓存数据结构 二级缓存是 MappedStatement 类下的一个属性,类型为 Cache,如上图,Cache 只是一个接口,它真实的实现类是 SynchronizedCache。
MixedSqlNode rootSqlNode = parseDynamicTags(context);
解析 xml 中的 setting 配置
执行 sl 语句
是否启用数据库字段到 javaBean property 驼峰映射
Mybatis 二级缓存
InputStream resourceAsStream = Resources.getResourceAsStream(\"mybatis-config.xml\")
resource == null && url == null && mapperClass != null
MappedStatement statement = statementBuilder.build();
return mapperProxyFactory.newInstance(sqlSession);
这一条方法调用链的结构可以看出获取 mapper 代理对象需要用到的对象{ BaseBuilder:{ Configuration:{ MapperRegistry:{ knowMapper:{ Class<?>, (Mapper接口类对象,即 mapper.xml 映射文件的命名空间 ) MapperProxyFactory<>(type) (Mapper接口类对象对应的代理工厂) } } }}由此,解决了开头让读者思考的第 4 个问题,Mapper 接口 是如何与 Mapper.xml 映射文件建立起对应关系的
mapperElement(root.evalNode(\"mappers\"));
configurationElement(XNode,context)
提示箭头
DataSourceFactory dsFactory = dataSourceElement(child.evalNode(\"dataSource\"))
XMLMapperBuilder # parse
flushCacheIfRequired(ms);
driversIterator.hasNext()
循环 list 拿到 sql 节点,即某一个 <select>、<update>、<insert>、<delete>
SqlSource 是一个提供 BoundSql 的接口,也是 MappedStatement 的一个属性(BoundSql,MappedStatement 后面会详解),作用是根据上下文和参数解析生成需要的 sql 看他的继承体系:
return connection
MapperRegistry # getMapper
Connection connection = transaction.getConnection();
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
SqlSession sqlSession = factory.openSession();
propertiesElement(root.evalNode(\"properties\"))
处理解析失败的 sql 语句节点
解析事务管理器节点,封装事务管理器对象
parser.parse() 解析 mybatis-configs 全局配置文件
MapperProxyFactory # 构造方法
newInstance
list == null
解析 mapper 文件
在获取 session 那一步已经解释了为什么是 CachingExecutor 而不是默认的 SimpleExecutor,实际就是用装饰器模式包装了 SimpleExecutor
获取 mapper 标签内的 resource 属性值
做一个非空判断
创建 Executor
SqlSession 详解
利用刚获取的 Connection 创建 Statement
\"package\".equals(child.getName())
Executor 详解
protected final MapperRegistry mapperRegistry = new MapperRegistry(this)
根据命名空间 即 MappedStatement 的 id 值找到对应的 MappedStatement 对象
XNode context= parser.evalNode(\"/mapper\")
将 当前的的sql语句节点(某一个<select>、<update>、<insert>、<delete>)封装构建为 MappedStatement 对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
获取 UserMapper 的动态代理对象
PS:由于本图较大,建议读者缩放一下,ctrl+滚轮 缩放
字符串
如果是以jdbc来实现同样的功能
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
使用二级缓存须知: 不同的namespace(Mapper)下的SQL, 持有的二级缓存是不一致的。也就是说,对于同样的数据库表, 另一个namespace(Mapper)下的UPDATE SQL, 无法清空当前namespace(Mapper)下的二级缓存。也就是说,同样的表的CRUD, 如果启用了二级缓存, 得写在一块。否则会出现缓存脏读
SqlSouce 解析
与读者约定
XMLConfigBuilder # parse
else if
将查询到的结果存入二级缓存
通过getConnection方法来获取一个Connection
return stmt
SqlSessionFactoryBuilder# build(InputStream inputStream)
将结果对象添加到一级缓存
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
clearLocalCache();
查询一级缓存
return resultSetHandler.handleResultSets(ps);
RawSqlSource # getBoundSql
这里就解决了让读者思考的第 9 个问题,mybatis 是在 executor 执行查询的时候,获取连接,并非是 openSession 获取的数据库连接,SqlSession 只是 mybatis 暴露给开发者的接口
XNode context : list
接口
至此,Mybatis 从初始化工厂到执行一次查询的流程结束
配置全局 cache 开关,默认 true,二级缓存配置默认开启
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(.......)
获取 sql 节点id
Connection connection = getConnection(statementLog);
Cache cache = ms.getCache();
String mapperClass = child.getStringAttribute(\"class\")
这里解决了开头让读者思考的第一个和第二个问题,mybatis 是如何加载数据库驱动的 ?mybatis 是如何获取数据源连接信息的?
实现 JDK 动态代理,返回 mapper 接口的代理对象
return lst;
拿到 mapper.xml 映射文件路径,这是 mybatis 提供的三种不同的写法,任何一种都可以
RawSqlSource 负责处理静态 SQL 语句,它们最终会把处理后的 SQL 封装 StaticSqlSource 进行返回。 使用 #{} 表达式,或者不使用任何表达式的情况,所以它是静态的,仅需要在构造方法中,直接生成对应的 SQL 。
parseConfiguration(XNode root)
这个使用的是包扫描方式
获取 mapper 标签内的 url 属性值
重要
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
创建MappedStatementBuilder 构造器
Mybatis 每次创建结果对象的新实例时,都会使用对象工厂 ObjectFactory 去创建 pojo
处理解析失败的 resultMap 节点
builderAssistant.setCurrentNamespace(namespace)
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
localCache.removeObject(key);
main方法
parsePendingCacheRefs();
Properties props = context.getChildrenAsProperties();
ps.execute()
这里就解决了给读者提出的第 6 个问题, mybatis 是如何为 mapper 接口创建代理对象的? 首先,在工厂构建的时候,每一个 mapper 接口会作为键 key 被存放到 一个 knowMappers 的HashMap 里,它对应的值是一个 mapper 代理工厂,一个 mapper 接口对应一个 mapper 代理工厂 Mapper.class ----> MapperProxyFactory<Mapper.class> 此时,并未创建 mapper 接口的代理对象。 待到用户调用 getMapper 方法时,mybatis 会找到 mapper 接口对应的 MapperProxyFactory,在工厂的 newInstance 方法里首先 new 一个调用处理器,即 mapperProxy,在通过 JDK 动态代理创建代理对象
executor 执行查询擦操作
openConnection()
执行顺序,如果存在多个分支箭头,顺序由上到下,从左至右
Object.class.equals(method.getDeclaringClass())
将当前节点封装为 MappedStatement,并添加进 configuration 全局配置对象
ReuseExecutor
StaticSqlSource # getBoundSql
BaseExecutor # query
DriverManager#loadInitialDrivers()
throw new BuilderException(\"Each XMLConfigBuilder can only be used once.\");
handler.parameterize(stmt);
MapperMethod # execute
构造出当前 select | update | delete | insert 语句节点
Interface
在这里之前,都是在解析 mybatis 的全局配置文件从这里之后,开始解析mapper.xml文件
String databaseId = context.getStringAttribute(\"databaseId\")
执行 UnpooledDataSourceFactory 无参构造方法
return statement;
实例化 Statement
ProviderSqlSource 实现 SqlSource 接口,基于方法上的 @ProviderXXX 注解的 SqlSource 实现类。
获取 mapper 标签内的 class 属性值
return sqlSession
parsed
执行 sqlSession 的selectList 方法注意,sqlSession 并不是真实执行查询的对象,它处于接口层,是 mybatis 提供给用户执行 crud 操作的接口,真是执行数据库读写操作的是 Executor
Mybatis 一级缓存
判断一级缓存是否命中
将 parsed 置为 true
UnpooledDataSourceFactory
允许 JDBC 强制生成主键,默认为false,如果设置为true,将强制使用被生成主键,一般用于生成主键 id
String namespace = context.getStringAttribute(\"namespace\")
build(resourceAsStream)
list != null
布尔值,判断是否是动态 sql
boundType = Resources.classForName(namespace);
PooledDataSourceFactory
拿到全面具配置文件中 mappers 标签,并解析,找到所有映射文件,解析 <mappers></mappers> 节点中的所有 映射器---- *Mapper.xml
循环执行后边的流程
TransactionFactory txFactory = transactionManagerElement(child.evalNode(\"transactionManager\"))
封装构建 environment 对象,并将其设置到 configuration 全局配置对象中
XMLStatementBuilder # parseStatementNode
BaseExecutor
XNode child : parent.getChildren()
获取到环境配置
准备一个 Configuration 对象,mybatis-config 我们的全局配置文件最终就要封装成这个对象,在创建的时候初始化属性,即 mybatis-config.xml 的默认配置,这里我挑了几个重要的作讲解
if 条件判断
开始解析 mapper 文件
mapperRegistry.addMapper(type);
这里首先会判断,当前方法执行的是 crud 中的哪一种,笔者这里执行的是 select ,所以会进入到图中高亮的那一行
这是一个标记,表示当前 mybatis 的全局配置文件还未加载过
parseConfiguration(parser.evalNode(\"/configuration\"));
return list
Class
0 条评论
下一页