mybatis-3.4.6 mapper解析

系列

  • mybatis-3.4.6 配置介绍
  • mybatis-3.4.6 顶层配置解析
  • mybatis-3.4.6 子配置解析
  • mybatis-3.4.6 mapper解析
  • mybatis-3.4.6 SQL执行流程
  • mybatis-3.4.6 SqlSession执行过程
  • mybatis-3.4.6 缓存介绍
  • mybatis-3.4.6 自增主键
  • mybatis-3.4.6 foreach 自增主键
  • mybatis-3.4.6 事务管理


开篇

  • 这个系列是基于mybatis-3.4.6版本的源码解析,这篇文章主要分析mybatis的mapper配置解析流程。
  • mapper文件是mybatis框架的核心之处,所有的用户sql语句都编写在mapper文件中,所以理解mapper文件对于所有的开发人员来说都是必备的要求。这篇文章 的核心就是分析mapper的解析过程。
  • 由于mapper的配置文件解析逻辑比较复杂,建议阅读附录的参考文章。


mapper文件配置


  



  
  
  


  
  
  


  
  
  

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 如果要同时使用package自动扫描和通过mapper明确指定要加载的mapper,一定要确保package自动扫描的范围不包含明确指定的mapper,否则在通过package扫描的interface的时候,尝试加载对应xml文件的loadXmlResource()的逻辑中出现判重出错,报org.apache.ibatis.binding.BindingException异常,即使xml文件中包含的内容和mapper接口中包含的语句不重复也会出错,包括加载mapper接口时自动加载的xml mapper也一样会出错。
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }


mapperXml属性

SQL映射文件

SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • parameterMap – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
  • sql – 可被其它语句引用的可重用语句块。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查询语句。


Select 元素的属性


        select count(1) from (
            
        ) tmp


2、include节点替换为sqlFragment节点


3、将sqlFragment的子节点(文本节点)insert到sqlFragment节点的前面。


4、移除sqlFragment节点

  • 标签的替换过程如上图所示。


StaticSqlSource

public class StaticSqlSource implements SqlSource {
    // SELECT  user_nick AS userNick FROM imc_user WHERE user_id = ?;
    private final String sql;
    private final List parameterMappings;
    private final Configuration configuration;

    public StaticSqlSource(Configuration configuration, String sql) {
        this(configuration, sql, (List)null);
    }

    public StaticSqlSource(Configuration configuration, String sql, List parameterMappings) {
        this.sql = sql;
        this.parameterMappings = parameterMappings;
        this.configuration = configuration;
    }

    public BoundSql getBoundSql(Object parameterObject) {
        return new BoundSql(this.configuration, this.sql, this.parameterMappings, parameterObject);
    }
}
  • sql表示的是已经将#{}占位符替换为?的SQL语句。
  • parameterMappings保存的是按顺序的参数信息。


addMappedStatement

    public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class parameterType, String resultMap, Class resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) {
        if (this.unresolvedCacheRef) {
            throw new IncompleteElementException("Cache-ref not yet resolved");
        } else {
            id = this.applyCurrentNamespace(id, false);
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
            org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired(((Boolean)this.valueOrDefault(flushCache, !isSelect)).booleanValue()).useCache(((Boolean)this.valueOrDefault(useCache, isSelect)).booleanValue()).cache(this.currentCache);
            ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id);
            if (statementParameterMap != null) {
                statementBuilder.parameterMap(statementParameterMap);
            }

            MappedStatement statement = statementBuilder.build();
            this.configuration.addMappedStatement(statement);
            return statement;
        }
    }


    protected final Map mappedStatements;
    public void addMappedStatement(MappedStatement ms) {
        this.mappedStatements.put(ms.getId(), ms);
    }
  • 保存到Configuration的mappedStatements的数据对象当中。


    mybatis-3.4.6 mapper解析_第1张图片
    mappedStatements

MappedStatement

public final class MappedStatement {

  private String resource;
  private Configuration configuration;
  private String id;
  private Integer fetchSize;
  private Integer timeout;
  private StatementType statementType;
  private ResultSetType resultSetType;
  private SqlSource sqlSource;
  private Cache cache;
  private ParameterMap parameterMap;
  private List resultMaps;
  private boolean flushCacheRequired;
  private boolean useCache;
  private boolean resultOrdered;
  private SqlCommandType sqlCommandType;
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;
}
  • mapper文件或者mapper接口中每个映射语句都对应一个MappedStatement实例,它包含了所有运行时需要的信息比如结果映射、参数映射、是否需要刷新缓存等。


mapperPackage解析过程

  public void addMappers(String packageName, Class superType) {
    // packageName的值cn.edu.example.mapper
    ResolverUtil> resolverUtil = new ResolverUtil>();
    // 负责查找该package下所有的mapper的class文件
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    // 针对每一个mapper文件执行addMapper操作
    Set>> mapperSet = resolverUtil.getClasses();
    for (Class mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

  public  void addMapper(Class type) {
    if (type.isInterface()) {
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory(type));
        // 通过MapperAnnotationBuilder的parse方法去实现加载
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
  • resolverUtil负责将packageName转为对应的目录,如cn.edu.example.mapper转为cn/edu/example/mapper。遍历该目录下所有的class文件得到mapper的文件。
  • 针对每mapper的class文件通过MapperAnnotationBuilder进行解析。
public class MapperAnnotationBuilder {

  public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      // 负责加载mapper的xml文件
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

  private void loadXmlResource() {
    // 在相同的目录下查找对应的xml文件
    // xml文件和mapper的dao文件需要保持名字的一致
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      InputStream inputStream = null;
      try {
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e) {
        // ignore, resource is not required
      }
      if (inputStream != null) {
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
  }
}
  • MapperAnnotationBuilder的parse通过loadXmlResource来加载mapper.xml文件。
  • loadXmlResource的就是在同级目录下找到对应的Mapper.xml文件。
  • 通过XMLMapperBuilder的parse负责解析对应的Mapper.xml文件。
└── cn
    └── edu
        └── example
            ├── mapper
            │   ├── UserMapper.class
            │   └── UserMapper.xml
  • 以packageName为cn.edu.example.mapper作为package为例。
  • 先遍历cn/edu/example/mapper目录下的class文件并进行加载。
  • 再遍历对应的cn/edu/example/mapper/UserMapper.xml走xml的解析流程。


MappedStatement注册

public class Configuration {
    protected final Map mappedStatements = new StrictMap("Mapped Statements collection");

    public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class parameterType, String resultMap, Class resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) {
        if (this.unresolvedCacheRef) {
            throw new IncompleteElementException("Cache-ref not yet resolved");
        } else {
            id = this.applyCurrentNamespace(id, false);
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
            org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired(((Boolean)this.valueOrDefault(flushCache, !isSelect)).booleanValue()).useCache(((Boolean)this.valueOrDefault(useCache, isSelect)).booleanValue()).cache(this.currentCache);
            ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id);
            if (statementParameterMap != null) {
                statementBuilder.parameterMap(statementParameterMap);
            }

            MappedStatement statement = statementBuilder.build();
            this.configuration.addMappedStatement(statement);
            return statement;
        }
    }


    protected final Map mappedStatements;
    public void addMappedStatement(MappedStatement ms) {
        this.mappedStatements.put(ms.getId(), ms);
    }
}
  • mapper文件解析的核心本质是将mapper.java文件的接口和xml的SQL生成MappedStatement的映射关系保存到Configuration当中。
  • Configuration的mappedStatements维持xml配置文件的namespace和对应mappedStatement映射关系。
  • mappedStatements的key为method维度,内部同时维持cn.edu.example.mapper.UserMapper.getById和getById两个key。


MapperProxyFactory注册

public class XMLMapperBuilder extends BaseBuilder {

  private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // namespace为dao的命名空间如cn.edu.example.mapper.UserMapper
          configuration.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);
        }
      }
    }
  }
}

public class Configuration {

  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

  public  void addMapper(Class type) {
    mapperRegistry.addMapper(type);
  }
}

public class MapperRegistry {

  public  void addMapper(Class type) {
    if (type.isInterface()) {

      boolean loadCompleted = false;

      try {
        knownMappers.put(type, new MapperProxyFactory(type));
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
}


public class MapperProxyFactory {
  private final Class mapperInterface;
  private final Map methodCache = new ConcurrentHashMap();

  public MapperProxyFactory(Class mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
}
  • bindMapperForNamespace的核心是往Configuration的MapperRegistry保存mapper的接口和对应的MapperProxyFactory代理类。
  • MapperProxyFactory作用负责生成dao的代理类。
  • MapperProxyFactory的作用是创建MapperProxy的代理类。
  • knownMappers的key是接口维度,如cn.edu.example.mapper.UserMapper。


参考文章

  • 源码参考
  • mybatis官网介绍
  • 深入理解mybatis原理
  • Mybatis3.4.x技术内幕
  • mybatis 3.x源码深度解析与最佳实践

你可能感兴趣的:(mybatis-3.4.6 mapper解析)