How to find the implementation class of the mybatis mapper interface – source code analysis

Keywords: mybatis principle, source code, mybatis mapper interface implementation class, proxy mode, dynamic proxy, Java Dynamic Proxy, proxy Newproxyinstance, mapper mapping, mapper implementation

Mybatis is an excellent persistence layer framework, which supports customized SQL, stored procedures and advanced mapping. Mybatis avoids almost all JDBC code and manually setting parameters and getting result sets. When we are using mybaits, we usually only need to define several mapper interfaces, and then write an XML file. We write SQL in the configuration file, and mybatis helps us complete the call of the specific implementation of mapper interface channel. And mapping the results to the model bean.

Many mapper classes we wrote in the project are just an interface. According to the polymorphism of Java, we know that we can use the interface as a formal parameter to determine what the specific implementation object is at run time. However, for mapper interface, we did not write its implementation class! How does mybatis find its implementation class and then complete the specific crud method call? What is the principle?

How does the mapper interface find the implementation class

In order to find out how the mapper interface finds the implementation class, we first recall how mybatis is used, and then analyze it bit by bit according to the actual examples. The use here refers to the separate use of mybatis rather than the integration of spring, because the integration of spring also involves the loading of mapper Dao into the spring container, and spring helps create data source configuration.

Generally, the main steps of using mybatis are:

From a piece of code

Above, we summarized the four steps of using mybatis. These four steps seem very simple, but they are a lot written in code. We might as well remember these four steps first, and then look at the code. It will be easier.

// 1. 
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development",transactionFactory,dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);// 添加Mapper接口
sqlSessionFactory sqlSessionFactory = new sqlSessionFactoryBuilder().build(configuration);
// 2. 
sqlSession session = sqlSessionFactory.openSession();
try {
  // 3. 
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  // 4.
  Blog blog = mapper.selectBlog(1);
} finally {
  session.close();
}

In this code, in part 1, we use java coding to implement sqlsessionfactory, or XML. If you use XML, the first part of the code above is like this:

String resource = "org/mybatis/example/mybatis-config.xml"; // xml内容就不贴了
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory sqlSessionFactory = new sqlSessionFactoryBuilder().build(inputStream);

Our goal this time is to find out "how mapper finds the implementation class". We pay attention to the location of codes 3 and 4 above:

  // 3. 
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  // 4.
  Blog blog = mapper.selectBlog(1);

Here, mapper can call the method selectblog (1), which indicates that mapper is an object because the object has the method behavior implementation. The blogmapper interface cannot be instantiated, and there is no specific method to implement it. We don't define a class to implement the blogmapper interface, but here it just calls session Getmapper(). From this, we can infer that it must be session The implementation class of blogmapper is generated inside the getmapper () method. What technology can generate an implementation class according to the blogmapper interface? Thinking of this, it is easy for programmers with experience in using dynamic agents to think that this must be based on dynamic agent technology. How to implement it? Let's find out according to the source code.

Mapper interface registration

From the above code, we know that the implementation class of the blogmapper interface is from session Getmapper is probably based on dynamic agent technology. Since we can get the blogmapper interface from sqlsession, we must put it in somewhere first, and then sqlsession can generate the proxy class we want. There is a line in the above code:

configuration.addMapper(BlogMapper.class);

The code implementation of this addmapper method is as follows:

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

We see here that mapper is actually added to mapperregistry. Follow up code:

public class MapperRegistry {
  private final Map<Class<?>,MapperProxyFactory<?>> kNownMappers 
                                                  = new HashMap<Class<?>,MapperProxyFactory<?>>();
  
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)); // 注意这里
 
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config,type);
        parser.parse();
        loadCompleted = true;

      } finally {
        if (!loadCompleted) {
          kNownMappers.remove(type);
        }
      }
    }
  }
}

See here, we know the configuration executed above addMapper(BlogMapper.class); In fact, it is finally put into the HashMap, which is called knownmappers. Knowmapers is a private attribute of mapperregistry class, which is a HashMap. The key is the current class object and the value is an instance of mapperproxyfactory.

Here's a summary: mapper interfaces such as blogmapper have been added to a HashMap in mapperregistry. The class object of mapper interface is used as the key, and a mapperproxyfactory instance with mapper interface as the attribute is used as the value. Mapperproxyfactory looks like a factory from its name, which is used to create mapper proxy. Let's keep looking down.

Generation of dynamic proxy class of mapper interface

As we know above, the mapper interface is registered in mapperregistry - placed in the HashMap attribute named knowmappers. When we call the method of mapper interface, it is as follows:

BlogMapper mapper = session.getMapper(BlogMapper.class);

Here, let's track session The code implementation of getmapper () method. Here sqlsession is an interface. It has two implementation classes, one is defaultsqlsession and the other is sqlsessionmanager. Here we use defaultsqlsession Why defaultsqlsession? Because defaultsqlsession is configured in the build () method of sqlsessionfactorybuilder that we call when initializing sqlsessionfactory, we enter the defaultsession class to see its impact on session How to implement getmapper (blogmapper. Class):

public class DefaultsqlSession implements sqlSession {
  private Configuration configuration;  
  
    @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type,this); //最后会去调用MapperRegistry.getMapper
  }
}

As shown in the code, getmapper here calls configuration Getmapper, in fact, this step finally calls mapperregistry. We already know that mapperregistry stores a HashMap. Let's continue to track it. Then the get here must get data from this HashMap. Let's look at the code:

public class MapperRegistry {
  
private final Map<Class<?>,MapperProxyFactory<?>> kNownMappers = new HashMap<Class<?>,MapperProxyFactory<?>>();// Mapper 映射
  
public <T> T getMapper(Class<T> type,sqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory =
                                  (MapperProxyFactory<T>) kNownMappers.get(type);
    
    try {
      return mapperProxyFactory.newInstance(sqlSession); // 重点看这里
    } catch (Exception e) {
    }
  }
}

We call the session getMapper(BlogMapper.class); It will eventually get to the above method, which is based on the class object of BlogMapper, and uses it as key to find the corresponding value MapperProxyFactory (BlogMapper) object in kNowMappers and then call the newInstance () method of the object. According to the name, we can guess that this method creates an object. The code is as follows:

public class MapperProxyFactory<T> { //映射器代理工厂

  private final Class<T> mapperInterface;
  private Map<Method,MapperMethod> methodCache = new ConcurrentHashMap<Method,MapperMethod>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
  // 删除部分代码,便于阅读

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    //使用了JDK自带的动态代理生成映射器代理类的对象
    return (T) Proxy.newProxyInstance(
             mapperInterface.getClassLoader(),new Class[] { mapperInterface },mapperProxy);
  }

  public T newInstance(sqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession,mapperInterface,methodCache);
    return newInstance(mapperProxy);
  }

}

When you see this, it's clear that it's finally through proxy Newproxyinstance generates a proxy object for blogmapper. In order to implement mapper interface, mybatis uses proxy mode. Specifically, JDK dynamic proxy is used The three elements of the proxy class generated by the newproxyinstance method are:

In proxy mode, the logic of method call is really completed in the proxy class (mapperproxy). We post the mapperproxy code as follows:

public class MapperProxy<T> implements InvocationHandler,Serializable {// 实现了InvocationHandler
  
  @Override
  public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
    //代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
   
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this,args);  //  注意1
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }

    final MapperMethod mapperMethod = cachedMapperMethod(method); // 使用了缓存
    //执行CURD
    return mapperMethod.execute(sqlSession,args); // 注意2
  }    
  
  
}

The blog we called blog = mapper selectBlog(1); In fact, the invoke method of mapperproxy will be called in the end. In this code, the if statement first judges whether the method we want to call comes from the object class. This means that if we call the toString () method, we don't need to do proxy enhancement, but directly call the original method Just invoke (). The enhanced call -- mappemethod. Net -- is executed only when a method such as selectblog () is called execute(sqlSession,args); This is a code logic.

Mappermethod execute(sqlSession,args); This sentence will eventually be added, deleted, modified and queried. The code is as follows:

  public Object execute(sqlSession sqlSession,Object[] args) {
    Object result;
    if (sqlCommandType.INSERT == command.getType()) {         //insert  处理,调用sqlSession的insert
      Object param = method.convertArgsTosqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(),param));
    } else if (sqlCommandType.UPDATE == command.getType()) { // update
      Object param = method.convertArgsTosqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(),param));
    } else if (sqlCommandType.DELETE == command.getType()) {   // delete
      Object param = method.convertArgsTosqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(),param));
    } else if (sqlCommandType.SELECT == command.getType()) {
      // 删除部分代码 
    } else {
      throw new BindingException("UnkNown execution method for: " + command.getName());
    }
     // 删除部分代码
    return result;
  }

The next level is to execute JDBC, get links, execute, get resultsets, parse resultsets and map them into JavaBeans.

So far, we have found out that blog = mapper selectBlog(1); During the process of calling the blogmapper interface to get the database data, how mybaitis generates the implementation class for the interface and where to start the final crud call. In fact, if we are calling blog, blog = mapper selectBlog(1); Before printing out the mapper object obtained from slqsession, you will see that the output is roughly as follows:

com.sun.proxy.$Proxy17

Dynamic proxy, right? Java dynamic proxy is wonderful.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>