前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >mybatis解读篇

mybatis解读篇

作者头像
用针戳左手中指指头
发布2021-01-29 10:39:39
8100
发布2021-01-29 10:39:39
举报
文章被收录于专栏:学习计划

文章目录

mybatis初始化原理

传统的查数据库方式,没有现在的方便;

我简单写一个以回顾一下这些东西。

代码语言:javascript
复制
 private static final String URL = "jdbc:mysql://localhost:3306/ali?useSSl=false&zeroDateTimeBehavior=CONVERT_TO_NULL&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";

    private static final String USERNAME = "root";

    private static final String PAASWORD = "root";

    private static final Logger logger = LoggerFactory.getLogger(TraditionConnection.class);

    public static Connection connection() throws ClassNotFoundException, SQLException {
        Class.forName(Driver.class.getName());
        return DriverManager.getConnection(URL, USERNAME, PAASWORD);
    }

    public static  List<Map<String, Object>> menuQuery(String sql, Object... args) throws SQLException, ClassNotFoundException {
        logger.info("执行的sql: {}", sql);
        logger.info("参数:{}", args);
        Connection connection = connection();
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        for (int i = 0; i < args.length; i++) {
            preparedStatement.setObject(i + 1, args[i]);
        }
        List<Map<String, Object>> result = new ArrayList<>();
        ResultSet resultSet = preparedStatement.executeQuery();
        while (resultSet.next()) {
            Map<String, Object> obj = new HashMap<>();
            obj.put("name", resultSet.getString("name"));
            obj.put("id", resultSet.getInt("id"));
            result.add(obj);
        }
        close(connection, preparedStatement, resultSet);
        logger.info("结果数据条数:{}", result.size());
        return result;
    }

    public static void close(Connection connection, Statement statement, ResultSet resultSet) throws SQLException {
        if (resultSet != null) {
            resultSet.close();
        }
        if (statement != null) {
            statement.close();
        }
        if (connection != null) {
            connection.close();
        }


    }
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        String query = "select * from menu where name = ?";
        List<Map<String, Object>> result = menuQuery(query, "菜单测试");
        System.out.println("结果: " + JSON.toJSONString(result));
    }

传统方式需要加载驱动,声明sql等操作,而现在的框架都像上面一样进行了各种封装,使得我们使用更加方便,下面来看看mybatis的方式。

mybatis初始化的入口是sqlSessionFactory.build();

代码语言:javascript
复制
InputStream is = new FileInputStream(ResourceUtils.getFile("classpath:Mybaties.xml"));
SqlSessionFactoryBuilder s = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = s.build(is);
代码语言:javascript
复制
// xml解析器
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 通过parse解析xml配置
return build(parser.parse());

这里对应配置文件下的每一个节点,然后再由每一个方法去解析对应的节点,先不说设计模式,这样的作法比较美观、简洁、易读,平常开发的时候,也应该像这种;比如写serviceImpl时,虽然功能逻辑,条理清晰,顺着写也没毛病,代码行数也差不多七八十至百行,但这样的代码可以更简介优化,看看有没有功能、逻辑,可以提取成一个方法,而在主方法里,只看到你哪一行代码做了什么,下面做了什么,整体逻辑更加清晰,在排错时也能更得心应手。

代码语言:javascript
复制
 private void parseConfiguration(XNode root) {
    try {
      // 读取配置节点(连接数据库的配置)
      propertiesElement(root.evalNode("properties"));
        // 配置运行参数
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
        // 配置类别名
      typeAliasesElement(root.evalNode("typeAliases"));
        // 配置插件
      pluginElement(root.evalNode("plugins"));
        // 查询到的结果映射到目标类可以由objectFactory执行
      objectFactoryElement(root.evalNode("objectFactory"));
        // 对象包装工厂
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        // 反射工厂
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // 运行环境配置
      environmentsElement(root.evalNode("environments"));
        // 数据库提供者
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        // 类型处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
        // mapper类配置
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

只看主要方法;

mybatis可以配置别名,有两种方式,一种是通过typeAliaes下的typeAlias标签,一种是通过package标签,对应mapper类上使用Aias注解配置就行,使用package标签时,他会扫描对应包下的class对象,

不使用package标签,而使用typeAlias标签的话,他会为每个对象添加别名,添加别名的方式就是放的一个map中保存。

代码语言:javascript
复制
private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
          // 扫描package标签下的对象
        if ("package".equals(child.getName())) {
          String typeAliasPackage = child.getStringAttribute("name");
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {
            // 按配置的每个对象添加别名
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          try {
            Class<?> clazz = Resources.classForName(type);
            if (alias == null) {
              typeAliasRegistry.registerAlias(clazz);
            } else {
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }

在注册mapper时(这里的注册就是放到一个map中管理),都会经过一个方法hasMapper(type),已经注册过的mapper,会抛出异常,不知道有没有小伙伴遇到过(多人开发的时候,心心相惜起了相同的名字,也或是间隔时间太久都忘了还有这个名称的类);在注册就会创建mapperProxy对象放到map中。

代码语言:javascript
复制
 private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
            // mappers标签下,使用package标签的话,会扫描包路径下的文件,批量注册mapper,
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
            // 3个属性只能配置一个,
          if (resource != null && url == null && mapperClass == null) {
              // 相对路径解析mapper.xml文件
            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) {
              // 接口信息解析(这里的逻辑和上面package标签里的一样)
            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.");
          }
        }
      }
    }
  }

这里是使用package标签后和配置了mapperClass属性走的逻辑;虽然这里不一样,但是都是解析mapper,所以,最终的逻辑都是一样的,只是这里还不是最底层的东西,我们看这里只有添加mapper,和MapperAnnotationBuilder.parse(),底层的注册就埋在里面的某个地方。

代码语言:javascript
复制
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // 解析mapper
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

进入到里面,有这么一段,它会先判断是否加载过这个类,没有才进行解析,毕竟解析也是消耗资源的,从逻辑上来说也是必须的。

代码语言:javascript
复制
 public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
        // 加载xml(加载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();
  }

这里的type是我们定义的mapper类对象,type.getName()拿到的就是类全名,如:com.lry.mybatis.MenuMapper,那么这里的解析有可以理解为,当我们的mapper.xml是放在resources/mapper目录下的,那么这里的inputStream一定是null,因为它解析的路径是:resources/com/lry/mybatis/MenuMapper.xml,这就是默认的解析路径,即使你配置了注解sql,在对应路径下有xml配置,也会加载到配置。

代码语言:javascript
复制
 private void loadXmlResource() {
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
        // 读取的路径是mapper接口对应的包名下的xml,也是默认读取路径。
        // 如:com.lry.mybatis.dao.MenuMapper.xml
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      InputStream inputStream = null;
      try {
          // 如果在resource下有对应包名的xml,就会加载到,inputStream就不会为null
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e) {
        // ignore, resource is not required
      }
      if (inputStream != null) {
          // xml 解析,如果是配置xml sql,不是注解sql就走parse
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
  }

那么即配置类注解sql,也配置了xml sql,会执行哪一个?

答案是:报异常!

解析xml后,进入这个方法解析注解sql,然后在assistant.addMappedStatement()中注册方法时抛出异常;

代码语言:javascript
复制
void parseStatement(Method method) {
    Class<?> parameterTypeClass = getParameterType(method);
    LanguageDriver languageDriver = getLanguageDriver(method);
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
      // 省略...
        // 
      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          // ParameterMapID
          null,
          parameterTypeClass,
          resultMapId,
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          // TODO gcode issue #577
          false,
          keyGenerator,
          keyProperty,
          keyColumn,
          // DatabaseID
          null,
          languageDriver,
          // ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : null);
    }
  }

这有个问题,就是它都把xml解析了,注解也解析了,然后在注册sql声明的时候抛异常,感觉多此一举啊,既然让使用者指定了注解解析方式,自己还加一个默认解析,然后又不给注册上去,不知道设计者是怎么考虑的。

再回头看xml解析,loadXmlResource方法里的parse和mapperElement方法里的parse一样,都是下面的代码。

代码语言:javascript
复制
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
代码语言:javascript
复制
public void parse() {
    // 判断是否加载过
    if (!configuration.isResourceLoaded(resource)) {
        // 解析mapper节点
      configurationElement(parser.evalNode("/mapper"));
        // 将已解析的xml文件添加记录
      configuration.addLoadedResource(resource);
        // 绑定命名空间
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

解析mapper节点

代码语言:javascript
复制
private void configurationElement(XNode context) {
    try {
        // 当前mapper.xml的命名空间,即对应的mapper类的全类名
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
        // builderAssistant就相当于当前读取的mapper节点的一个对象副本
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
        // 这个很少写,参数映射
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        // 这个经常写,结果集映射
      resultMapElements(context.evalNodes("/mapper/resultMap"));
        // sql节点,通常会把一些通用的动态sql放到sql标签中,通过include标签静态引入
      sqlElement(context.evalNodes("/mapper/sql"));
        // 解析mapper方法
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

这个参数解析的不是很复杂,就解析了节点然后将解析后的结果放到parametermappings中,这里有一个typeHandler的东西,这个放后面讲,是个很有用的东西。

代码语言:javascript
复制
 @SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);

然后是解析resultMap节点;

resultMapNode.getStringAttribute方法解析节点属性,默认值为null。该方法在解析时会被递归调用,所以它被比较通用,其实标签属性也差不多。

代码语言:javascript
复制
 private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
     // 异常上下文ErrorContext,用于记录mybatis的错误信息,然后打印
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
     // 下面是解析resultmap属性,
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    String extend = resultMapNode.getStringAttribute("extends");
     // 自动映射,当我们没有写resultMap这个标签映射字段时,mybatis会帮我们去做映射
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    Class<?> typeClass = resolveClass(type);
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
    resultMappings.addAll(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
        // 指定构造函数的标签节点
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
          // 判别器,这个东西用法很有趣,但好像不怎么有用,我就没用到过
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
          // 解析result、collection
        List<ResultFlag> flags = new ArrayList<ResultFlag>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
          // buildResultMappingFromContext方法里有递归调用这个方法。
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

上面的代码就是解析类似下面的xml:

代码语言:javascript
复制
    <resultMap id="NodeDefStylePortMap" type="com.lry.mybatis.model.MenuBo" extends="MenuMap">
        <collection property="menus" javaType="java.util.ArrayList" ofType="com.lry.mybatis.model.Menu">
            <result property="id" column="id" jdbcType="INTEGER"/>
            <result property="name" column="name" jdbcType="VARCHAR"/>
        </collection>
        <discriminator javaType="">
            <case value=""></case>
        </discriminator>
    </resultMap>

然后是解析sql,其实这里也就是解析实例sql的标签属性,然后放到map里,没有其他解析。

代码语言:javascript
复制
 private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
    for (XNode context : list) {
      String databaseId = context.getStringAttribute("databaseId");
      String id = context.getStringAttribute("id");
      id = builderAssistant.applyCurrentNamespace(id, false);
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
        sqlFragments.put(id, context);
      }
    }
  }

之后是解析mapper方法对应的标签(select、update、insert、delete);一连串的解析标签属性;

代码语言:javascript
复制
 public void parseStatementNode() {
     // 一连串的解析属性
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
     // sql语句的类型
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
     // select 和其他语句不一样,所以这里会设定一个isSelect,分开解析
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // 解析selectKey标签,并生成SelectKeyGenerator
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // 解析sql
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
     // 这里判断有没有keyGenerator,如果是有selectKey标签,解析的时候会生成一个selectGenerator 
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
        // 没有selectKey标签,就解析useGeneratedKeys属性
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }
// 添加映射语句
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

上面有一个statementType这个是sql语句的类型,他有三个值:STATEMENT, PREPARED, CALLABLE;

STATEMENT:sql语句,字符串拼接,对应的是${}

PREPARED:预处理sql,preparedStatement 的默认类型,对应的是#{}

CALLABLE:存储过程,callableStatement

insert返回id,有不同,mysql可以通过useGeneratedKeys可以返回自增id,也可以通过selectKey标签,Oracle就只能通过selectKey标签了,selectKey标签也和select标签一样,会添加到mapppedStatement里面(如下图);根据代码顺序,设置新增返回id的方式也有优先级,如果设置了selectKey标签,那么设置的useGeneratedKeys就会失效。

在这里插入图片描述
在这里插入图片描述

mapper的别名不区分大小写:它代码里有:alias.toLowerCase(Locale.ENGLISH); 他会把mapper注册到一个别名库中。

解析sql是下面这句,它这里解析会将#{}这部分替换成?,然后在后面执行的时候通过这个sqlsource 做预编译处理。

代码语言:javascript
复制
 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
代码语言:javascript
复制
public SqlSource parseScriptNode() {
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource = null;
    if (isDynamic) {
        // 动态sql解析
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
        // 静态sql
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

这里它会区分是否是动态sql,即是否含有动态标签,如<if></if> 、<where></where> 、${}这些。

代码语言:javascript
复制
protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<SqlNode>();
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
          // 这里进行区分动态sql
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return new MixedSqlNode(contents);
  }

解析完的sql,会把变量替换成占位符的方式,比如:select * from menu where id = ? and name = ${name}

代码语言:javascript
复制
 public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }

解析/mapper之后,就是resultmap/cache/statement这些,这边

代码语言:javascript
复制
private void parsePendingResultMaps() {
    Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
    synchronized (incompleteResultMaps) {
      Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
      while (iter.hasNext()) {
        try {
          iter.next().resolve();
          iter.remove();
        } catch (IncompleteElementException e) {
          // ResultMap is still missing a resource...
        }
      }
    }
  }

mybatis执行流程

getMapper方法进行跟踪

代码语言:javascript
复制
 MenuMapper ndao = getSqlSessionFactory().getMapper(MenuMapper.class);

1. 委托configuration获取mapper

代码语言:javascript
复制
return configuration.<T>getMapper(type, this);

2. configuration通过mapperRegistry(type,sqlSession)获取mapper

代码语言:javascript
复制
mapperRegistry.getMapper(type, sqlSession);

3. MapperRegistry.getMapper(type, sqlSession)

代码语言:javascript
复制
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
     // 拿到ProxyFactory,这个是在初始化时创建添加的
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        // 传入SQLSession创建代理对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
代码语言:javascript
复制
public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
代码语言:javascript
复制
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

MapperProxy里的invoke方法,

代码语言:javascript
复制
 @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 如果是默认的object的方法就直接执行,不去重写
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
          // 如果是默认方法(接口可以定义default方法),就绑定到proxy对象上
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
     // 代理对象是需要消耗资源的,所以代理的方法都会被放到缓存中,
      // 这里的mapperMethod,是做过封装的,可以在后面取到mybatis需要的参数
    final MapperMethod mapperMethod = cachedMapperMethod(method);
      // 接口执行的方法
    return mapperMethod.execute(sqlSession, args);
  }

4. mapperMethod构造(mapper参数解析)

这里cachedMapperMethod(method);这里将method封装为mybatis需要的对象,里面还有一个比较重要的东西,就是它new的MethodSignature,里面它有一属性paramNameResolver,通过它解析参数:

代码语言:javascript
复制
this.paramNameResolver = new ParamNameResolver(configuration, method);
代码语言:javascript
复制
  public ParamNameResolver(Configuration config, Method method) {
      // 获取参数类型
    final Class<?>[] paramTypes = method.getParameterTypes();
      // 参数注解
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
      // 有序map
    final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
      // 参数计数
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
        // 这个是一个判断参数是否是RowBounds  ResultHandler 类的子类或接口,百度比较详细
        // 这里就是出现特殊的参数,就直接退出了,就不会保存这类型的参数
      if (isSpecialParameter(paramTypes[paramIndex])) {
        continue;
      }
      String name = null;
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        if (annotation instanceof Param) {
            // 拿到@Param注解,并将@Param的value值给
          hasParamAnnotation = true;
          name = ((Param) annotation).value();
          break;
        }
      }
      if (name == null) {
          // 没有@Param,就取实际名称,这里的isUseActualParamName默认true
        if (config.isUseActualParamName()) {
          name = getActualParamName(method, paramIndex);
        }
        if (name == null) {
          // 如果实际名称也取不到,就用map的大小,一般也不会出现这样的情况
          name = String.valueOf(map.size());
        }
      }
        // 这里用索引为key,后面的取值都是按参数顺序遍历的,所以这里以索引为键,然后再放到SortedMap中
      map.put(paramIndex, name);
    }
    names = Collections.unmodifiableSortedMap(map);
  }

这个方法,会优先去@Param注解中设置的名称,如果没有才会去设置实际参数名称。

5. 实际执行通过SQLSession执行对应方法

代码语言:javascript
复制
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

6. 执行前解析方法参数

在委托给SQLSession之前,他会先进行参数的一个解析,method.convertArgsToSqlCommandParam(args)

代码语言:javascript
复制
public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    // 判断参数是否存在
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
        // 没有注解,且只有一个就返回
      return args[names.firstKey()];
    } else {
        // 存放参数对象的
      final Map<String, Object> param = new ParamMap<Object>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
          // 这里将names里存的参数名,放入map中,
        param.put(entry.getValue(), args[entry.getKey()]);
          // 这里是按照参数顺序,设置固定的参数名,param1开始递增:(param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
        // 这里是防止重复添加
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }

那么如果mapper方法的参数有注解,这里param返回的值就包含注解设置的名称和这里默认的(param1, param2,...)名称,没有参数注解,就是实际参数名称和(param1, param2,...)。这里他会返回3种结果:

  • null 即没有参数
  • 参数对象 有且只有一个参数
  • map对象, key为参数名,值为参数对象,key可以是主键@Param的值,也可以是(param1, param2,...)

7. SQLSession里的执行又丢给executor

代码语言:javascript
复制
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        // 拿到对应的mapper方法声明
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

在这里它会对参数进行一个封装,先判断这个object(参数对象),上一步它对参数进行了解析,会返回3种值,只有第二种会出现符合这里的collectionarray

那么返回只有一个对象的,这里会再封装一遍,是list的话,就在包一层map,key为“list”,是array的话,key为“array”,最后结果就很统一了,参数集合都是一个map。

代码语言:javascript
复制
  private Object wrapCollection(final Object object) {
    if (object instanceof Collection) {
      StrictMap<Object> map = new StrictMap<Object>();
      map.put("collection", object);
      if (object instanceof List) {
        map.put("list", object);
      }
      return map;
    } else if (object != null && object.getClass().isArray()) {
      StrictMap<Object> map = new StrictMap<Object>();
      map.put("array", object);
      return map;
    }
    return object;
  }

8. 构造sql对象(解析动态sql)

这里BoundSql boundSql = ms.getBoundSql(parameterObject);会去拿绑定的sql对象。

代码语言:javascript
复制
 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
     // 拿绑定的sql对象
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 创建缓存key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

如果是静态sql,那么在加载mapper配置时就加载到configuration里了,而动态sql,它还会进行解析,将#{}变量替换为?,{}替换为变量值,如果sql是这样的:select * from menu where name = #{name} and id = {id},那么这一步就会被转为成:select * from menu where name = ? and id = 1;,之后在调用sqlSourceParser.parse()处理,将sql包装成静态sql对象。

代码语言:javascript
复制
  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
      // 在这里会通过TextSqlNode 和 StaticTextSqlNode 进行处理sql语句,
      // TextSqlNode是动态sql处理对象,它的处理方法和处理 #{} 一样,只是这个替换为了变量值
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
      // 然后在通过sqlSourceParser 再处理一遍,那么放回静态sql对象
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }

9. 先查询二级缓存

他在查询是会尝试从二级缓存中获取,如果没有就会直接查询;可以看到使用缓存和不使用缓存都调用了一个query的重载方法,使用缓存的方式比直接查询多了一些获取缓存的步骤。

代码语言:javascript
复制
 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
        // 刷新缓存
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
          // 直接取缓存
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
            // 缓存是空的才会查表
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
     // 没有使用缓存直接走这里
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

10. 尝试获取一级缓存

一级缓存得到的结果为空,才会调用queryFromDatabase()方法;这里的queryStack类似一个计数,因为可能会连续执行多个sql。

代码语言:javascript
复制
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
        // 尝试获取缓存
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
          // 这个方法针对的是statementType = callable的sql,就是执行存储过程语句的,再处理参数缓存
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
          // 查询数据库
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

当一级缓存没有时执行这个方法;

代码语言:javascript
复制
 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
     // 一级缓存保存
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        // 当sql语句的类型为CALLABLE时,就是存储过程语句执行
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

11. statementHandler执行底层方法

doQuery方法进去后,通过configuration对象获取statementHandler,通过它去执行连接数据库查询结果映射等操作。

代码语言:javascript
复制
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
   return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

12. 设置Statement参数和typeHandler

代码语言:javascript
复制
 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
     // 参数化,在这里进行参数的设置
    handler.parameterize(stmt);
    return stmt;
  }

handler.parameterize(stmt)里面走就是setParameters方法,是下面的方法,它有一个比较有用的地方是typeHandle这个对象。

代码语言:javascript
复制
public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 拿到xml里的参数映射对象
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
            // 获取参数的类型处理器,
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
              // 通过类型处理器转换参数值。如果是自定义的类型处理器,走的就是我们自定义的方法
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

typeHandler运用例子

这边我写了个例子:一个menu对象,属性deleted定义为枚举,然后再定义一个handle类来处理我们的这个属性,修改mapper.xml支持改属性。

我这里的例子不是很严谨,应该定义一个接口的,但这是个例子,就简单一点。

代码语言:javascript
复制
public class Menu implements Serializable {
    private static final long serialVersionUID = 499536196155109971L;
    private Long id;
    private String name;
    private DeleteEnum deleted;
    // 省略setter getter
}

定义DeleteEnum

代码语言:javascript
复制
public enum DeleteEnum {
    DELETED(1, "已删除"),
    UN_DELETED(0, "未删除");

    private Integer id;

    private String desc;
// 省略构造器 和 setter getter
}

定义类型处理器

代码语言:javascript
复制
public class DeleteTypeHandle<E extends DeleteEnum> implements TypeHandler<E> {

    private Class<E> tClass;

    private E[] enums;

    public DeleteTypeHandle(Class<E> tClass){
        if (tClass == null) {
            throw new RuntimeException("类型不能为空");
        }
        this.tClass = tClass;
        this.enums = tClass.getEnumConstants();
        if (this.enums == null) {
            throw new RuntimeException("类型不能为空");
        }

    }
    /**
     在执行query时设置参数时执行这个方法
     */
    @Override
    public void setParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter.getId());
    }

    /**
     结果映射时执行这个
     */
    @Override
    public E getResult(ResultSet rs, String columnName) throws SQLException {
        if (!rs.wasNull()) {
            return transform(rs.getInt(columnName));
        }
        return null;
    }
    /**
     结果映射时执行这个
     */
    @Override
    public E getResult(ResultSet rs, int columnIndex) throws SQLException {
        if (!rs.wasNull()) {
            return transform(rs.getInt(columnIndex));
        }
        return null;
    }
    /**
     结果映射时执行这个
     */
    @Override
    public E getResult(CallableStatement cs, int columnIndex) throws SQLException {
        if (!cs.wasNull()) {
            return transform(cs.getInt(columnIndex));
        }
        return null;
    }
    /**
     遍历我们的枚举,找到对应的枚举对象
     */
    private E transform(Integer id) {
        for (E e : enums) {
            if (e.getId().equals(id)) {
                return e;
            }
        }
        throw new RuntimeException("未知枚举类型");
    }
}

mapper.xml里的查询语句修改:

注意这里需要写javaType、typeHandler两个属性

代码语言:javascript
复制
  <select id="querySelective2" resultMap="MenuMap">
        select <include refid="base_column_list"></include>
        from menu
        where name = #{param1,jdbcType=VARCHAR}
        and deleted = #{deleted, javaType=integer,javaType=com.lry.mybatis.config.DeleteEnum, typeHandler=com.lry.mybatis.config.DeleteTypeHandle}
    </select>

结果映射修改:

代码语言:javascript
复制
   <resultMap type="com.lry.mybatis.model.Menu" id="MenuMap">
                 <result property="id" column="id" jdbcType="INTEGER"/>
                 <result property="name" column="name" jdbcType="VARCHAR"/>
                 <result property="deleted" column="deleted" jdbcType="INTEGER" javaType="com.lry.mybatis.config.DeleteEnum" typeHandler="com.lry.mybatis.config.DeleteTypeHandle"/>
            </resultMap>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gCDOBZO3-1610090504610)(C:\Users\ALI\AppData\Roaming\Typora\typora-user-images\image-20201104163345824.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wv0lwDMA-1610090504614)(C:\Users\ALI\AppData\Roaming\Typora\typora-user-images\image-20201104162532392.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EE3RFZQ1-1610090504615)(C:\Users\ALI\AppData\Roaming\Typora\typora-user-images\image-20201104163944155.png)]

13. 结果映射

回到doQuery方法中,通过StatementHandler执行查询方法

代码语言:javascript
复制
handler.<E>query(stmt, resultHandler)
代码语言:javascript
复制
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    // 包装查询项结果
    ResultSetWrapper rsw = getFirstResultSet(stmt);
// 从mapper声明中获取到结果映射,这个在初始化时就放入的了。
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
        // 数据绑定在这里做
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

这里的包装,将一些相关的东西赋值到属性中,想类型处理器、结果集、查询列和列类型。

这里的columnNames存放了,sql查询出的列名,jdbcTypes 存放了对应的列类型。

代码语言:javascript
复制
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
    super();
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.resultSet = rs;
    final ResultSetMetaData metaData = rs.getMetaData();
    final int columnCount = metaData.getColumnCount();
    for (int i = 1; i <= columnCount; i++) {
      columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
      jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
      classNames.add(metaData.getColumnClassName(i));
    }
  }
代码语言:javascript
复制
 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
        // 这里是resultMap有嵌套是才会进入
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
            // 处理查询结果
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
            // 处理结果放到返回结果集合中
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

handleRowValues往里走是下面这段;对读取的行数据,进行赋值,并存储到集合里。

代码语言:javascript
复制
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
    skipRows(rsw.getResultSet(), rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
        // 这里创建结果行对象并设置值
      Object rowValue = getRowValue(rsw, discriminatedResultMap);
        // 存储对象
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
  }
代码语言:javascript
复制
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      if (shouldApplyAutomaticMappings(resultMap, false)) {
          // 自动映射设置,配置了resultType,在这里映射
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
      }
        // resultMap在这里映射
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
  }

分别看看applyAutomaticMappingsapplyPropertyMappings里是什么。

  1. applyAutomaticMappings这个方法没有设置自动映射都会走,所以resultMap和resultType的都走了这个方法。但是如果是设置了resultMap,不会走那个for循环,所以这个方法更像是为了resultType类型而做方法。
代码语言:javascript
复制
 private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
     // 获取未映射的列;resultType会获取到实体对应的列,而如果是resultMap,这里是空的,这个列的名称也起的比较清楚“未自定映射的列”
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }
代码语言:javascript
复制
 private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    final String mapKey = resultMap.getId() + ":" + columnPrefix;
     // 到自动映射里拿
    List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
    if (autoMapping == null) {
      autoMapping = new ArrayList<UnMappedColumnAutoMapping>();
        // 没有设置自动映射,就在这里获取sql查询出来的列,这个列的来源是: ResultSetWrapper rsw = getFirstResultSet(stmt);
      final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
      for (String columnName : unmappedColumnNames) {
        String propertyName = columnName;
       // ...
      }
      autoMappingsCache.put(mapKey, autoMapping);
    }
    return autoMapping;
  }
  1. applyPropertyMappings这个方法是设置里resultMap类型的方法走的。这个方法比resultType的直接一点,拿到属性名和值,直接设置。
代码语言:javascript
复制
 private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
     // 获取映射列
    final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
      String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      if (propertyMapping.getNestedResultMapId() != null) {
        // the user added a column attribute to a nested result map, ignore it
        column = null;
      }
      if (propertyMapping.isCompositeResult()
          || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
          || propertyMapping.getResultSet() != null) {
        Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
        // issue #541 make property optional
        final String property = propertyMapping.getProperty();
        if (property == null) {
          continue;
        } else if (value == DEFERED) {
          foundValues = true;
          continue;
        }
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(property, value);
        }
      }
    }
    return foundValues;
  }

缓存

流程里面说过,他会先查询二级缓存,然后在查询一级缓存,那么这些缓存具体是什么,怎么使用呢?

一级缓存:

本地缓存。它是SQLSession级别的缓存,不需要任何配置,默认开启。

缓存对象是BaseExecutor对象的属性PerpetualCache,在查询是会将结果放到缓存中,一次SQLSession的创建表示一个会话,只要会话不关闭,那么这个缓存就是生效的。

有几种情况会使缓存失效:

  1. 上面说过的,只要SQLSession不断,缓存就存在,所以调用了close()方法会使缓存失效;
  2. 调用了SQLSession的方法clearCache()方法,会清空缓存;
  3. 执行修改操作如update、delete、insert,会清空缓存;

对应的存储源码,在上面已经有了,这里就截个图

在这里插入图片描述
在这里插入图片描述

有一点,下面的查询会看到commit,但是对于一级缓存来说,不用commit也可以进行缓存。

不同的SQLSession,打印两次sql

在这里插入图片描述
在这里插入图片描述

如下结果,两次查询,只有一次sql打印

在这里插入图片描述
在这里插入图片描述

调用clearCache()方法后再查询,打印了两次sql

在这里插入图片描述
在这里插入图片描述

调用异常update,打印了两次查询sql

在这里插入图片描述
在这里插入图片描述

调用一次insert,打印了两次查询sql

在这里插入图片描述
在这里插入图片描述

调用一次delete,打印了两次查询sql

在这里插入图片描述
在这里插入图片描述

二级缓存:

与一级缓存不同,它作用的范围更广,它属于namespace级别的缓存,只要是相同的方法都可以共享。

该功能需要手动配置生效,配置方法:

执行器executor有两个子类,BaseExecutor 和 CachingExecutor,在配置了二级缓存后,走的就是CachingExecutor 这个类的方法。

经过测试后,事务不提交,二级缓存也还存在;

执行insert、delete、update时,缓存也会被刷新;

插件

配置方式

代码语言:javascript
复制
<plugins>
	<plugin interceptor="com.lry.mybatis.config.CustomInterceptor">
</plugin>

官网还是比较详细的:https://mybatis.org/mybatis-3/zh/configuration.html#plugins

mybatis提供Interceptor接口,只要实现并添加到拦截链中,就可以进行拦截,它可以拦截下面几个类:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

注解@Signature里的值也是他们的参数类型,作为方法签名。

它的method可以参考他们的接口方法,注意insert方法底层在executor里是update。

插件原理

而对于这个拦截器一开始看有些复杂,它的底层还是动态代理,捋一捋,mybatis提供了一个插件接口Interceptor,方法对象Invocation,拦截链InterceptorChain,还有一个Plugin 的插件工具类相当于。

它的结构都是高度封装的比较复杂写,刚开始都把绕进去了,我把它简化如下:

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
public interface IPlayGame {

    void play();
}
代码语言:javascript
复制
public interface IPluginBusiness {

    /**
     * 业务实现
     * @param target
     * @param method
     * @param args
     */
    Object intercept(Object target, Method method, Object[] args);

}
代码语言:javascript
复制
public class PlayGame implements IPlayGame{

    @Override
    public void play() {
        System.out.println("打游戏");
    }
}
代码语言:javascript
复制
public class PlayGameHandler implements InvocationHandler {

    private Object target;
    private IPluginBusiness pluginBusiness;

    public PlayGameHandler(Object target, IPluginBusiness pluginBusiness) {
        this.target = target;
        this.pluginBusiness = pluginBusiness;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return pluginBusiness.intercept(target, method, args);
    }
}
代码语言:javascript
复制
public class PluginBusinessImpl implements IPluginBusiness {
    @Override
    public Object intercept(Object target, Method method, Object[] args) {
        System.out.println("插件[1]开始。。。");
        Object invoke = null;
        try {
            invoke = method.invoke(target, args);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        System.out.println("插件[1]结束。");
        return invoke;
    }
}
代码语言:javascript
复制
public class PluginChain {

    private List<IPluginBusiness> pluginBusinesInterfaces = new ArrayList<>();

    public void addPlugin(IPluginBusiness pluginBusinesInterface) {
        pluginBusinesInterfaces.add(pluginBusinesInterface);
    }

    public List<IPluginBusiness> getPluginBusinesInterfaces() {
        return pluginBusinesInterfaces;
    }
}
代码语言:javascript
复制
public class PluginsIncationHandler implements InvocationHandler {

    private Object target;

    public PluginsIncationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("插件的动态代理。。。");
        return method.invoke(target, args);
    }
}
代码语言:javascript
复制
 public static void main() {
        // 1. 构建插件业务对象
        PluginBusinessImpl plugin = new PluginBusinessImpl();
        // 2. 将插件业务对象封装成代理对象
        PluginsIncationHandler pluginHandler = new PluginsIncationHandler(plugin);
        IPluginBusiness proxy = (IPluginBusiness)Proxy.newProxyInstance(plugin.getClass().getClassLoader(), new Class[]{IPluginBusiness.class}, pluginHandler);
        // 3. 将代理对象放到插件链
        PluginChain chain = new PluginChain();
        chain.addPlugin(proxy);

        // 1. 构建插件业务对象
        PluginBusinessImpl2 plugin2 = new PluginBusinessImpl2();
        // 2. 将插件业务对象封装成代理对象
        PluginsIncationHandler pluginHandler2 = new PluginsIncationHandler(plugin2);
        IPluginBusiness proxy2 = (IPluginBusiness)Proxy.newProxyInstance(plugin.getClass().getClassLoader(), new Class[]{IPluginBusiness.class}, pluginHandler2);
        // 3. 将代理对象放到插件链
        chain.addPlugin(proxy2);
        // 4. 将所有的插件都封装到待执行的对象上
        IPlayGame playGame = new PlayGame();
        for (IPluginBusiness pluginBusiness : chain.getPluginBusinesInterfaces()) {
            PlayGameHandler playGameHandler = new PlayGameHandler(playGame, pluginBusiness);
            playGame = (IPlayGame)Proxy.newProxyInstance(PlayGame.class.getClassLoader(), new Class[]{IPlayGame.class}, playGameHandler);
        }
        playGame.play();
    }
在这里插入图片描述
在这里插入图片描述

运行结果如上所示,我们可以随意的插入我们的插件逻辑,从main方法来看它的步骤就很清晰:

  1. 插件接口提供一个插件业务的接口,参数为target, method, args
  2. 实现插件,并生成代理对象
  3. 将插件代理对象放到插件链中
  4. 实现业务对象,
  5. 生成业务对象代理,插件对象作为参数,将invoke方法替换为插件的方法

这样做的一个好处是,功能解耦,并且在实际业务方法前后加上了插件逻辑。

mybatis的优化思路可能是这样的:

  1. 插件的功能独立,只要加入插件就能实现插件前后处理的效果,需要代理实际业务对象,但不能产生依赖,经理少的去关联业务对象的东西,那么就可以把target, method, args传入(intercept方法);
  2. 同时,按功能划分,插件的功能可以提供一个实现对实际业务对象进行代理封装(warp方法);
  3. 作为管理插件的插件链类,必须有添加插件功能,可以额外的增加使插件生效的功能(pluginAll方法);

总结

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/01/08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • mybatis初始化原理
  • mybatis执行流程
    • 1. 委托configuration获取mapper
      • 2. configuration通过mapperRegistry(type,sqlSession)获取mapper
        • 3. MapperRegistry.getMapper(type, sqlSession)
          • 4. mapperMethod构造(mapper参数解析)
            • 5. 实际执行通过SQLSession执行对应方法
              • 6. 执行前解析方法参数
                • 7. SQLSession里的执行又丢给executor
                  • 8. 构造sql对象(解析动态sql)
                    • 9. 先查询二级缓存
                      • 10. 尝试获取一级缓存
                        • 11. statementHandler执行底层方法
                          • 12. 设置Statement参数和typeHandler
                            • typeHandler运用例子
                          • 13. 结果映射
                          • 缓存
                            • 一级缓存:
                              • 二级缓存:
                              • 插件
                                • 配置方式
                                  • 插件原理
                                  • 总结
                                  领券
                                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档