文档章节

mybatis源码解析6---MappedStatement解析

o
 osc_y8yehimr
发布于 2019/03/21 21:39
字数 1754
阅读 21
收藏 0

精选30+云产品,助力企业轻松上云!>>>

MappedStatement类位于mybatis包的org.apache.ibatis.mapping目录下,是一个final类型也就是说实例化之后就不允许改变

MappedStatement对象对应Mapper.xml配置文件中的一个select/update/insert/delete节点,描述的就是一条SQL语句,属性如下:

 

 1   private String resource;//mapper配置文件名,如:UserMapper.xml
 2   private Configuration configuration;//全局配置
 3   private String id;//节点的id属性加命名空间,如:com.lucky.mybatis.dao.UserMapper.selectByExample
 4   private Integer fetchSize;
 5   private Integer timeout;//超时时间
 6   private StatementType statementType;//操作SQL的对象的类型
 7   private ResultSetType resultSetType;//结果类型
 8   private SqlSource sqlSource;//sql语句
 9   private Cache cache;//缓存
10   private ParameterMap parameterMap;
11   private List<ResultMap> resultMaps;
12   private boolean flushCacheRequired;
13   private boolean useCache;//是否使用缓存,默认为true
14   private boolean resultOrdered;//结果是否排序
15   private SqlCommandType sqlCommandType;//sql语句的类型,如select、update、delete、insert
16   private KeyGenerator keyGenerator;
17   private String[] keyProperties;
18   private String[] keyColumns;
19   private boolean hasNestedResultMaps;
20   private String databaseId;//数据库ID
21   private Log statementLog;
22   private LanguageDriver lang;
23   private String[] resultSets;

 

其中StatementType指操作SQL对象的类型,是个枚举类型,值分别为:

STATEMENT(直接操作SQL,不进行预编译),

PREPARED(预处理参数,进行预编译,获取数据),

CALLABLE(执行存储过程)

ResultSetType指返回结果集的类型,也是个枚举类型,值分别为:

FORWARD_ONLY:结果集的游标只能向下滚动

SCROLL_INSENSITIVE:结果集的游标可以上下移动,当数据库变化时当前结果集不变

SCROLL_SENSITIVE:结果集客自由滚动,数据库变化时当前结果集同步改变

言归正传,现在我们知道一个MappedStatement对象对应一个mapper.xml中的一个SQL节点,而Mapper.xml文件是初始化Configuration对象的时候进行解析加载的,则说明MappedStatement对象就是在初始化Configuration对象的时候创建的,并且是final类型不可更改。

之前我们知道Configuration对象的初始化过程,是通过XMLConfigBuilder类的parse方法进行初始化的,现在来看下是如何初始化MappedStatement对象的,Configuration对象初始化代码如下:

 1 public Configuration parse() {
 2     if (parsed) {
 3       throw new BuilderException("Each XMLConfigBuilder can only be used once.");
 4     }
 5     parsed = true;
 6     parseConfiguration(parser.evalNode("/configuration"));
 7     return configuration;
 8   }
 9 
10   private void parseConfiguration(XNode root) {
11     try {
12       Properties settings = settingsAsPropertiess(root.evalNode("settings"));
13       //issue #117 read properties first
14       propertiesElement(root.evalNode("properties"));
15       loadCustomVfs(settings);
16       typeAliasesElement(root.evalNode("typeAliases"));
17       pluginElement(root.evalNode("plugins"));
18       objectFactoryElement(root.evalNode("objectFactory"));
19       objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
20       reflectorFactoryElement(root.evalNode("reflectorFactory"));
21       settingsElement(settings);
22       // read it after objectFactory and objectWrapperFactory issue #631
23       environmentsElement(root.evalNode("environments"));
24       databaseIdProviderElement(root.evalNode("databaseIdProvider"));
25       typeHandlerElement(root.evalNode("typeHandlers"));
26       mapperElement(root.evalNode("mappers"));//MappedStatement对象的初始化
27     } catch (Exception e) {
28       throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
29     }
30   }

 

parseConfiguration方法是根据XNode对Configuration对象进行属性赋值,mapperElement方法即解析<mappers>标签中的内容,mapperElement方法源码如下:

 1 private void mapperElement(XNode parent) throws Exception {
 2     //parent是Configuration配置文件中的<mappers>标签
 3     if (parent != null) {
 4        //遍历<mappers>标签下的所有子标签
 5       for (XNode child : parent.getChildren()) {
 6         if ("package".equals(child.getName())) {
 7           //加载package包下的所有mapper
 8           String mapperPackage = child.getStringAttribute("name");
 9           configuration.addMappers(mapperPackage);//加载packege包下的所有mapper
10         } else {
11           //按resource或url或class加载单个mapper
12           String resource = child.getStringAttribute("resource");
13           String url = child.getStringAttribute("url");
14           String mapperClass = child.getStringAttribute("class");
15           if (resource != null && url == null && mapperClass == null) {
16             ErrorContext.instance().resource(resource);
17             InputStream inputStream = Resources.getResourceAsStream(resource);
18             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
19             mapperParser.parse();//解析xml文件流
20           } else if (resource == null && url != null && mapperClass == null) {
21             ErrorContext.instance().resource(url);
22             InputStream inputStream = Resources.getUrlAsStream(url);
23             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
24             mapperParser.parse();//解析xml文件流
25           } else if (resource == null && url == null && mapperClass != null) {
26             Class<?> mapperInterface = Resources.classForName(mapperClass);
27             configuration.addMapper(mapperInterface);//加载指定接口的mapper
28           } else {
29             throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
30           }
31         }
32       }
33     }
34   }

 

可以看出共有三种方法可以加载mapper,一个是批量加载指定package下所有mapper,一个是根据mapper接口路径加载指定mapper,还有一种是解析mapper.xml文件流进行加载,接下来挨个来看下;

先来看个最简单的,根据指定接口加载mapper,也就是configuration.addMapper(mapperInterface)方法,源码如下:

Configuration的addMapper方法

1 public <T> void addMapper(Class<T> type) {
2     mapperRegistry.addMapper(type);
3   }

 

调用了mapperRegistry的addMapper方法。mapperRegistry是Configuration类的一个属性

protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

 

根据MapperRegistry的名字可以理解为此类的作用是mapper的注册中心,用于注册mapper,MapperRegistry源码如下:

 1 package org.apache.ibatis.binding;
 2 
 3 import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
 4 import org.apache.ibatis.io.ResolverUtil;
 5 import org.apache.ibatis.session.Configuration;
 6 import org.apache.ibatis.session.SqlSession;
 7 
 8 import java.util.Collection;
 9 import java.util.Collections;
10 import java.util.HashMap;
11 import java.util.Map;
12 import java.util.Set;
13 
14 public class MapperRegistry {
15 
16   private final Configuration config;//全局配置
17   private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();//已注册的mapper集合
18 
19   public MapperRegistry(Configuration config) {
20     this.config = config;
21   }
22 
23   @SuppressWarnings("unchecked")
24   public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
25     final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
26     if (mapperProxyFactory == null) {
27       throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
28     }
29     try {
30       return mapperProxyFactory.newInstance(sqlSession);
31     } catch (Exception e) {
32       throw new BindingException("Error getting mapper instance. Cause: " + e, e);
33     }
34   }
35   //判断指定mapper是否已经存在
36   public <T> boolean hasMapper(Class<T> type) {
37     return knownMappers.containsKey(type);
38   }
39 
40   //新增一个mapper
41   public <T> void addMapper(Class<T> type) {
42     if (type.isInterface()) {
43       if (hasMapper(type)) {
44         throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
45       }
46       boolean loadCompleted = false;
47       try {
48         knownMappers.put(type, new MapperProxyFactory<T>(type));
49         // It's important that the type is added before the parser is run
50         // otherwise the binding may automatically be attempted by the
51         // mapper parser. If the type is already known, it won't try.
52         MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
53         parser.parse();
54         loadCompleted = true;
55       } finally {
56         if (!loadCompleted) {
57           knownMappers.remove(type);
58         }
59       }
60     }
61   }
62 
63   //获取所有mapper集合
64   public Collection<Class<?>> getMappers() {
65     return Collections.unmodifiableCollection(knownMappers.keySet());
66   }
67 
68   
69   //根据package名称加载包下所有的mapper
70   public void addMappers(String packageName, Class<?> superType) {
71     ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
72     resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
73     Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
74     for (Class<?> mapperClass : mapperSet) {
75       addMapper(mapperClass);
76     }
77   }
78 
79   //根据package批量加载mapper
80   public void addMappers(String packageName) {
81     addMappers(packageName, Object.class);
82   }
83   
84 }
85

 

从源码可看出MapperRegistry就是Mapper的注册中心,有两个属性一个是全局配置Configuration还有一个是已经加载过的mapper集合 knownMappers

新增一个mapper的方法就是addMapper,就是向knownsMappers集合中put一条新的mapper记录,key就是mapper的类名全称,value是这个mapper的代理工厂;

分析到这里,发现Configuration对象初始化的时候会解析所有的xml文件中配置的所有mapper接口,并添加到Configuration的mapper集合knowMappers中,但是貌似还没有MappedStatement的影子,也没有看到哪里解析了mapper.xml配置。

不用急,上面源码的第52行就是了,52到54行的意思目前还没有看源码,但是先猜测下:52行是通过Configuration对象和mapper类来构造一个MapperAnnotationBuilder对象,通过字面意思是Mapper的构建类,而第53行的parse(),应该就是解析mapper.xml文件的,第54行标记加载完成,

只有当mapper接口和mapper.xml匹配成功才能叫做是加载成功,所以下一章篇就再来看看MappedStatement是如何创建的。

总结:MappedStatement类就是对应的Mapper.xml中的一个sql语句

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
源码分析Mybatis MappedStatement的创建流程

上文源码分析Mybatis MapperProxy创建流程重点阐述MapperProxy的创建流程,但并没有介绍.Mapper.java(UserMapper.java)是如何与Mapper.xml文件中的SQL语句是如何建立关联的。本文将重点接开这...

丁威
2019/09/17
0
0
Mybatis一二级缓存实现原理与使用指南

Mybatis 与 Hibernate 一样,支持一二级缓存。一级缓存指的是 Session 级别的缓存,即在一个会话中多次执行同一条 SQL 语句并且参数相同,则后面的查询将不会发送到数据库,直接从 Session ...

丁威
2019/09/23
0
0
mybatis 源码赏析(一)sql解析篇

本系列主要分为三部分,前两部分主要分析mybatis的实现原理,最后一部分结合spring,来看看mybtais是如何与spring结合的就是就是mybatis-spring的源码。 相较于spring,mybatis源码算是比较容...

osc_tnf99tdd
2018/08/24
4
0
Mybatis工作原理

近来想写一个mybatis的分页插件,但是在写插件之前肯定要了解一下mybatis具体的工作原理吧,于是边参考别人的博客,边看源码就开干了。 核心部件: SqlSession、Executor、StatementHandler、...

osc_xlt7v4t5
2019/02/25
2
0
mybatis源码学习(一) 原生mybatis源码学习

最近这一周,主要在学习mybatis相关的源码,所以记录一下吧,算是一点学习心得 个人觉得,mybatis的源码,大致可以分为两部分,一是原生的mybatis,二是和spring整合之后的mybatis源码学习(...

osc_iqtexsjp
2019/11/30
12
0

没有更多内容

加载失败,请刷新页面

加载更多

科技人文丨玻璃心:承受阈值与表达

大家好,我是SKODE。 有趣的灵魂,聊科技人文。 本系列博客地址:传送门 本文转载自B站:安慰记传送门 玻璃心是网络用语,意思是: 对负面事件的接受度很低 还有对别人可能给出的负面评价非常...

osc_u9mt0sus
27分钟前
20
0
迅睿CMS 游客不允许上传附件

游客不允许上传附件 迅睿CMS系统:https://www.xunruicms.com/ 本文档原文地址:https://www.xunruicms.com/doc/752.html...

迅睿CMS-PHP开源CMS程序
28分钟前
7
0
代理,注解,接口和实现类的小测验

* retention : 保留* policy : 策略 ps : 简单测试了一下手写代理,jdk动态代理,和cglib动态代理,根据其不同的结果分析其原理 一:测试目的 主要想看一下不同的代理模式对于目标类中方法上注...

岁一
28分钟前
6
0
V-Ray 5 For 3ds Max 正式发布:超越渲染 - 知乎

15个新功能,V-Ray5助你时间更节省,渲染更出色! 作者:ChaosGroup VRay 5 For 3ds Max 已正式发布! 2分钟视频,抢先预览新功能↓ 知乎视频 www.zhihu.com V-Ray 5 for 3ds Max 新增功能 ...

osc_o9u1um45
28分钟前
0
0
毕业的笑容和悲伤永远是校园的回忆

校园的风轻轻的拂过我的脸庞,风儿显得更加凉爽, 开满火红的凤凰树,染遍了校园的每个角落, 晚上那枝头蝉儿的竞相鸣奏,唱满了令人不舍的毕业歌, 它们彷彿告诉了我们要毕业了。 毕业典礼那...

瑾123
29分钟前
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部