[Mybatis source code analysis] Use of dynamic agents (Javassist, CGLIB, JDK dynamic agents)

First, let’s talk about what this blog is about?

I didn’t want to write this blog, because the dynamic proxy implementation of Mybatis for Mapper is also very simple, that is, using the JDK dynamic proxy, calling the method in its interface to the method calling sqlSession, and then The previous articleMybatis query process can be analyzed and strung together. At most, you need to pay attention to how Mybatis handles parameters.

But I found that Mybatis also introduced the CGLIB dynamic proxy library, why? I have the following questions?

  • Why doesn't the proxy mapper use CGLIB dynamic proxies?
  • Why do we not use CGLIB dynamic proxy XOR or the dynamic proxy provided by JDK to implement lazy loading when processing the dynamic proxy of mapping objects, but introduce the javassist proxy library?

In addition to source code analysis, this blog will let you know how the Mybatis agent Mapper handles parameters. It will also explain the two issues clearly to you.

Mybatis uses dynamic proxies in two places. One is a dynamic proxy Mapper object for abstract manipulation of the database by the outside world. The other is dynamic mapping of return value objects to achieve lazy loading.

Mybatis processing parameter source code analysis

  public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    if (args == null || paramCount == 0) {
      return null;
    }
    if (!hasParamAnnotation && paramCount == 1) {
      Object value = args[names.firstKey()];
      return wrapToMapIfCollection(value, useActualParamName ? names.get(names.firstKey()) : null);
    } else {
      // 针对没有使用 @Param 注解和含多个参数的
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        // key 对应的是参数名,或者说@Param中的字符串,Value是对应args中的值
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        // 通用的参数名称,ORM映射需要的名称,需要和填充的相对于
        final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }

It encapsulates names internally, which will indicate the name corresponding to each parameter. If the  @Param annotation is used, the corresponding parameter name will be The value corresponding to the Value property in @Param will be used.

If there are multiple parameters or the @Param annotation is used, it will encapsulate the name->corresponding parameter value into a map The object is then returned. In order to avoid parameters that are not annotated with @Param, it will add parameter values ​​corresponding to common names, that is, similar to param1, param2.....

Then after you know how to convert the parameters in the method into corresponding ones, you can string together what was said above.

Also the difference between ${} and #{}?

When using the executor to prepare to execute the corresponding SQL——

MappedStatement.getBoundSql(param) will be called, which is to call SqlSource.getBoundSql(param)——

What we call ${} actually corresponds to TextSqlNode when parsing dynamic SQL. During the execution of getBoundSql, ${} will be replaced with the corresponding parameter value, and #{} will be replaced with ? , this time you will get the real sql fragment——

At this time, after the sql fragment is precompiled, you can get the PreparedStatement manipulation object - you can get it to carry out the execution process mentioned in the previous article.

Why Javassist dynamic proxy is used to implement lazy loading

First, we need to analyze the timing when it uses the dynamic proxy.

When executing the query, it then determines whether fetchType=lazy is configured and there is a subquery. If so, use a dynamic proxy to process the return value object. Query is an operation that is executed with external calls. It is executed at runtime instead of when the project is loaded. It can be understood that the Mapper agent is executed, and then the corresponding query is executed internally.

If you seize this opportunity, you will know that using the dynamic proxy is implemented at runtime.

Then the proxy process implemented by Mybatis is to first generate a proxy class, and then the proxy class will inherit the actual class and implement the corresponding interface. The actual proxy object is the object of the generated proxy class. In other words, the bytecode of this proxy class must be generated at runtime, so using Javassist is a good choice compared to other dynamic proxy libraries.

For example, suppose there is a User object. When lazy loading is enabled, MyBatis will dynamically generate a UserProxy class as a proxy for the User object. The UserProxy class inherits from the User class and implements the User interface (if any). In the UserProxy class, methods for accessing lazy-loaded properties, including getter methods, are overridden.

Why does the Mapper agent use the dynamic agent that comes with JDK?

This is actually very simple, because the Mapper we define is an interface, but the related API that comes with JDK to solve dynamic proxies is to handle interfaces, which is definitely the most direct solution. As for the introduction of CGLIB dependency, it is to meet user needs. If users want to use CGLIB to implement Mapper's dynamic proxy, they can configure it. as follows:

<configuration>
  <settings>
    <setting name="proxyFactory" value="org.apache.ibatis.executor.CglibProxyFactory" />
  </settings>
  ...
</configuration>

Therefore, the introduction of CGLIB dependency is to allow you to switch freely. Note that it is a switching solution for the proxy process of generating Mapper proxy objects. As for lazy loading, Javassist is the best~

Guess you like

Origin blog.csdn.net/qq_63691275/article/details/132734294