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


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

  • 相关阅读:
    jenkins代码自动部署
    jenkins安装
    git图形管理工具
    gitlab自动备份恢复与卸载
    linux下获取外网IP
    网站安全webshell扫描
    jQuery动画效果实现
    form表单中的enctype属性什么意思?
    你那么努力又怎么样!
    话语
  • 原文地址:https://www.cnblogs.com/jackion5/p/9482052.html
Copyright © 2020-2023  润新知