前言
开心一刻
本人幼教老师, 冬天戴帽子进教室, 被小朋友看到, 这时候, 有个小家伙对我说: 老师你的帽子太丑, 赶紧摘了吧. 我逗他: 那你好好学习, 以后给老师买个漂亮的? 这孩子想都没想立刻回答: 等我赚钱了, 带你去韩国整形
简单示例
我们先来看一个纯粹的 mybatis 示例 (不集成 spring 等其他框架), 代码很简单, 结构如下
完整代码地址: https://gitee.com/youzhibing/mybatis ;mapper 层和我们平时说的 dao 层指的是同一个内容, 都是数据库操作的封装, 但是在没有集成 mybatis 时, dao 层的接口都是需要我们手动去写其实现类, 可在上图中我们却发现: 我们并没有手动去实现 PersonMapper 接口, 但工程却能实实在在的查询数据库, 获取我们需要的数据, 如下图所示
从上图我们发现, PersonMapper 实例是一个代理对象, 我们操作的其实是 PersonMapper 的代理实现; 也就是说不用我们手动去实现 PersonMapper 接口, mybatis 会动态生成 PersonMapper 的代理实例, 然后由代理实例完成数据库的操作
那么问题来了, mybatis 是何时, 何地, 如何生成 mapper 代理实例的呢? 我们接着往下看
源码分析
针对上述问题, 我们来跟下 mybatis 源码
SqlSessionFactory 的创建
XMLConfigBuilder 解析 Mybatis 配置文件 (mybatis-config.xml), 将配置文件中各个属性解析到 Configuration 实例中, 然后以 Configuration 实例构建 SqlSessionFactory(实际是 DefaultSqlSessionFactory); 其中 parseConfiguration 方法是解析的具体过程, 有兴趣的可以更深一步的去探究
- /**
- * root 是以 configuration 标签开始的文档树
- * 解析配置文件中的各个标签, 并存放到 Configuration 实例对应的属性中
- * 解析完成之后, 配置文件中的内容全部解析到了 Configuration 实例中
- * @param root
- */
- private void parseConfiguration(XNode root) {
- try {
- //issue #117 read properties first
- propertiesElement(root.evalNode("properties")); // 解析配置文件中的 properties 标签
- Properties settings = settingsAsProperties(root.evalNode("settings")); // 解析配置文件中的 settings 标签
- loadCustomVfs(settings);
- typeAliasesElement(root.evalNode("typeAliases")); // 解析配置文件中的 typeAliases 标签
- pluginElement(root.evalNode("plugins")); // 解析配置文件中的 plugins 标签
- objectFactoryElement(root.evalNode("objectFactory")); // 解析配置文件中的 objectFactory 标签
- objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // 解析配置文件中的 objectWrapperFactory 标签
- reflectorFactoryElement(root.evalNode("reflectorFactory")); // 解析配置文件中的 reflectorFactory 标签
- settingsElement(settings);
- // read it after objectFactory and objectWrapperFactory issue #631
- environmentsElement(root.evalNode("environments")); // 解析配置文件中的 environments 标签
- databaseIdProviderElement(root.evalNode("databaseIdProvider")); // 解析配置文件中的 databaseIdProvider 标签
- typeHandlerElement(root.evalNode("typeHandlers")); // 解析配置文件中的 typeHandlers 标签
- mapperElement(root.evalNode("mappers")); // 解析配置文件中的 mappers 标签
- } catch (Exception e) {
- throw new BuilderException("Error parsing SQL Mapper Configuration. Cause:" + e, e);
- }
- }
- View Code
上述代码中的 mapperElement(root.evalNode("mappers")); 是不是很诱人? 与我们的 mapper 有关系, 是不是在这里就生成了 mapper 的代理实例, 还是只是读取了 mapper 配置文件的内容? 暂时还不敢肯定, 那么我们跟进去看看
其中有两个方法值得重点关注下, 具体如下, 里面的注释可以重点看下, 有兴趣的可以更进一步的跟进去
- public void parse() {
- if (!configuration.isResourceLoaded(resource)) {
- configurationElement(parser.evalNode("/mapper")); // 解析映射文件 Person.xml
- configuration.addLoadedResource(resource);
- bindMapperForNamespace(); // 将 mapper 与 namespace 绑定起来; 将 PersonMapper 接口与 MapperProxyFactory 关联起来
- }
- parsePendingResultMaps(); // 解析 Configuration 的 incompleteResultMaps 到 Configuration 的 resultMaps
- parsePendingCacheRefs(); // 解析 Configuration 的 incompleteCacheRefs 到 Configuration 的 cacheRefMap
- parsePendingStatements(); // 解析 Configuration 的 incompleteStatements 到 Configuration 的 mappedStatements
- }
- /**
- * context 是映射文件: Person.xml 的文档树, 以 mapper 标签开始
- * 解析映射文件中的各个标签, 并存放到 MapperBuilderAssistant 实例对应的属性中
- */
- private void configurationElement(XNode context) {
- try {
- String namespace = context.getStringAttribute("namespace"); // 解析 mapper 标签的 namespace 属性
- if (namespace == null || namespace.equals("")) {
- throw new BuilderException("Mapper's namespace cannot be empty");
- }
- builderAssistant.setCurrentNamespace(namespace); // namespace 属性值解析到 Configuration 的 mapperRegistry 中
- cacheRefElement(context.evalNode("cache-ref")); // 解析 cache-ref 标签到 Configuration 的 cacheRefMap 中
- cacheElement(context.evalNode("cache")); // 解析 cache 标签到 Configuration 的 caches 中
- parameterMapElement(context.evalNodes("/mapper/parameterMap")); // 解析 parameterMap 标签到 Configuration 的 parameterMaps 中
- resultMapElements(context.evalNodes("/mapper/resultMap")); // 解析 resultMap 标签到 Configuration 的 resultMaps 中
- sqlElement(context.evalNodes("/mapper/sql")); // 解析 sql 标签到 XMLMapperBuilder 的 sqlFragments 中
- buildStatementFromContext(context.evalNodes("select|insert|update|delete")); // 解析 select|insert|update|delete 标签到 Configuration 的 mappedStatements 中
- } catch (Exception e) {
- throw new BuilderException("Error parsing Mapper XML. The XML location is'" + resource + "'. Cause:" + e, e);
- }
- }
- View Code
此时 SqlSessionFactory 已经创建, 但 PersonMapper 的代理实例还没有创建; 期间准备了很多东西, 包括读取配置文件和映射文件的内容, 并将其放置到 Configuration 实例的对应属性中
SqlSession 的创建
实例化了 Transaction(JdbcTransaction),Executor(SimpleExecutor) 和 SqlSession(DefaultSqlSession), 此时 mapper 代理实例仍未被创建
Mapper 代理对象的创建
可以看到, 最终还是利用了 JDK 的动态代理
- protected T newInstance(MapperProxy<T> mapperProxy) {
- // 利用 JDK 的动态代理生成 mapper 的代理实例
- return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
- }
生成了 mapper 的代理实例, 后续就可以利用此代理实例进行数据库的操作了
总结
1, 我们用 mytabis 操作数据库, 有一个固定流程: 先创建 SqlSessionFactory, 然后创建 SqlSession, 然后再创建获取 mapper 代理对象, 最后利用 mapper 代理对象完成数据库的操作; 一次数据库操作完成后需要关闭 SqlSession;
2, 创建 SqlSessionFactory 实例的过程中, 解析 mybatis 配置文件和映射文件, 将内容都存放到 Configuration 实例的对应属性中; 创建 SqlSession 的过程中, 有创建事务 Transaction, 执行器 Executor, 以及 DefaultSqlSession;Mapper 代理对象的创建, 利用的是 JDK 的动态代理, InvocationHandler 是 MapperProxy, 后续 Mapper 代理对象方法的执行都会先经过 MapperProxy 的 invoke 方法;
3, 很多细节没有讲到, 但大体流程就是这样; 另外提下, 实际应用中, mybatis 往往不会单独使用, 绝大多数都是集成在 spring 中; 关于在 spring 的集成下, mapper 代理对象的创建过程请期待我的下篇博文
来源: https://www.cnblogs.com/youzhibing/p/10451680.html