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语句