Mybatis source code - Mapper implementation class

Mybatis mapper implementation class

abstract

Speaking of Mybatis, we all know that it is a persistence layer framework for interacting with the database. It can provide a customizable database query interface and encapsulate the query details. It is an excellent framework for us to focus on business development.

But when it comes to dynamic agents, most of the students who have just come out may be a little confused, because in my work, I have been asked by my colleagues who have just joined the work more than once: "where is the implementation of Mapper interface under that package? Why can't I find it?". Then I will tell him without hesitation: "the implementation class of Mapper interface is generated by dynamic agent technology and is placed in memory, which you can't see". Then they returned to the station with a question mark on their face.

Next, let's see how Mybatis generates Mapper implementation classes and puts them into memory through dynamic proxy technology. It can generate implementation classes without writing code, and it can also connect to the database.

Knowledge preparation - dynamic agent

As for dynamic agent technology, there are a lot of relevant interpretations on the Internet. Let's take a look at what the big guys on the Internet say first

Know What is dynamic agent?

Dynamic agent practice

Small trial ox knife

It's better to watch more than to knock. Let's see how to obtain the implementation class of an interface based on dynamic agent technology.

Before we start, let's sort out the requirements

1. Define an interface and write a sayHello() method in it

2. Obtain the implementation class of interface based on dynamic agent technology

3. Implement the sayHello method

Well, if you know what to do, come and work

  • Define interface, define method
interface CustomInterface {
    void sayHello();
}
  • Get implementation class based on dynamic agent

Please note that this step is the most important and difficult to understand, but we don't have to worry. We'll do it step by step

  1. Writing implementation classes

    Although our interface can not write implementation classes, the logic of method implementation also needs to be specified. We need to implement the important interface of jdk dynamic agent__ InvocationHandler__ Interface. There is only one method of the interface that needs to be implemented by us, and our method implementation logic can be written in it

class CustomInterfaceProxy implements InvocationHandler{

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName());
        return null;
    }
}
  1. Generates the implementation class of the specified interface
        /**
         *  Generate an implementation class and use an interface to receive the implementation class
         *  Proxy.newProxyInstance Method needs to receive three parameters
         *  The first parameter: we need to pass a class loader instance. Here, if we need to generate an implementation class for that, we need to pass the class loader of that interface
         *  The second parameter: we need to pass a Class array. Here, we directly pass in the type information of the interface that needs proxy
         *  The third parameter: an InvocationHandler implementation class needs to be passed
         *
         *  By interpreting the three parameters of the method, we can roughly understand that the method loads the type information of the interface through the class loader of the interface,
         *  Then bind with the implementation class of InvocationHandler, and then you will get an implementation class of the specified interface
         *
         */
        CustomInterface customInterface = (CustomInterface) Proxy.newProxyInstance(CustomInterface.class.getClassLoader(), new Class[]{CustomInterface.class}, new CustomInterfaceProxy());

        //Call method
        customInterface.sayHello();

Execution result after calling method

sayHello is printed. This output is printed in the InvocationHandler implementation class we wrote. The execution code is as follows

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println(method.getName());
    return null;
}

In this implementation class, we can see that the method name is printed instead of the sayHello method. No hurry, let's add another method to the interface

  1. Verify the running mode of dynamic agent
interface CustomInterface {

    void sayHello();

    //Add a summation method
    Integer sum(Integer v1,Integer v2);
}

We call this method

        //Call summation method
        Integer v1 = 1;
        Integer v2 = 2;
        Integer sum = customInterface.sum(v1, v2);
        System.out.println(String.format("Calling summation methods: summation parameters%s,%s, Summation result:%s ",v1,v2,sum));

Then we rewrite the invoke implementation in the InvocationHandler implementation class

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        String name = method.getName();
        System.out.println(method.getName());

        //If it's a summation method
        if(name.equals("sum")) {
            Integer v1 = (Integer) args[0];
            Integer v2 = (Integer) args[1];
            return v1 + v2;
        }
        return null;
    }

	// Or so
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        String name = method.getName();
        System.out.println(method.getName());

        //If it is the summation method of CustomInterface
        if(CustomInterface.class.getMethod("sum",Integer.class,Integer.class).equals(method)) {
            Integer v1 = (Integer) args[0];
            Integer v2 = (Integer) args[1];
            return v1 + v2;
        }

        return null;
    }

Output results

At this point, we can conclude that for InvocationHandler__ invoke__ Method. We can regard this method as the unified entry of all interfaces. We should distinguish this method through the method instance. When we know which method it is, we can know its parameter type, and then write a specific implementation to return the return value required by the method.

Some students may ask that when you write code, you judge the Method name. The Method name can be repeated, or the Method name of other interfaces. Here, students can imagine that when we call the interface Method, we execute the invoke Method, which means that they must have a connection. Next, in a deeper level, you can check the information of the Method instance through Debug, and the args parameter information will find the answer

Practice is the only criterion for testing truth

In the last chapter, we learned how to use jdk dynamic agent technology and understand its basic principle. Let's use this technology to realize a small requirement

The requirements are as follows:

1. Write a method in the interface

2. Verify that the method parameter is null before calling the actual code of the method

We use custominterface Sum (integer V1, integer V2) as an example

Realization idea:

To realize the above requirements, if we are writing implementation classes, we can write the logic of parameter verification before each method enters, and each method needs to write logic similar to if(parameter == null), which will produce a lot of redundant code and poor code readability.

But now we use the dynamic agent technology. We only need to implement the InvocationHandler__ invoke__ Method, all methods will pass through this method, which is helpful for us to write a general parameter verification method in this method. Let's try and experience the magic of dynamic agent technology

1. Write a method to check whether the parameter is empty

    /**
     * Check whether the called method parameter is empty
     * @param method  Method example
     * @param args    List of arguments passed
     */
    private void checkParameterHaNull(Method method,Object[] args) {
        //Gets the number of parameters for the method
        int parameterCount = method.getParameterCount();
        //No processing without parameters
        if(parameterCount == 0) {
            return;
        }
        //Get the parameter encapsulation data of the method
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameterCount; i++) {
            Parameter parameter = parameters[i];
            //The basic data type does not check whether it is empty
            if (checkBaseType(parameter)) {
                continue;
            }
            //If the corresponding parameter is empty
            if(args[i] == null) {
                String name = method.getName();
                String msg = "method" + name + "The first" + i + "Parameters" + parameter.getName() + "Cannot be empty";
                throw new RuntimeException(msg);
            }
        }
    }

2. If there is a basic data type, we need to check whether it is a basic data type separately

/**
 * Verify whether this parameter is a parameter of basic data type
 * @param parameter     Parameter information based on reflection encapsulation
 * @return              boolean   true-It's false. - No
 */
private boolean checkBaseType(Parameter parameter) {
    Class<?> type = parameter.getType();
    return Byte.TYPE.equals(type)
            || Short.TYPE.equals(type)
            || Integer.TYPE.equals(type)
            || Long.TYPE.equals(type)
            || Character.TYPE.equals(type)
            || Float.TYPE.equals(type)
            || Double.TYPE.equals(type)
            || Boolean.TYPE.equals(type);

}

3. Call method

4. Call test

The first parameter we set to null

5. Execution results

Summary

In this chapter, we briefly practiced the dynamic proxy technology based on Jdk, but it should be noted that using the dynamic proxy technology of Jdk can only proxy interfaces. If we want to proxy non interface classes, we need to use cglb dynamic proxy technology, and our spring AOP is implemented based on it.

Next, let's strike while the iron is hot and see how Mybatis uses jdk dynamic proxy technology to implement Mapper interface

Mybatis dynamic agent technology practice

summary

mybatis encapsulates how to realize dynamic agent according to its own needs. The encapsulated module package is__ org.apache.ibatis.bingding__ Package, let's take a look at some classes in this package, and then what do these classes do

BindingException.java      
MapperMethod.java			Mapper Method
MapperProxy.java			Mapper Proxy class of interface
MapperProxyFactory.java	     Mapper Agent factory
MapperRegistry.java			Mapper Factory registration	

Interpretation of core classes

Mapper implementation class MapperProxy

First of all, let's introduce the implementation class of Mapper, which is often asked by many new students. In order to facilitate understanding, we first have a general understanding of what this class does.

This class is a class that implements the InvocationHandler interface and abstracts the execution process of all methods in the Mapper interface. A MapperProxy instance represents an implementation class of the Mapper interface. In other words, it is the implementation class of the Mapper interface.

This is a bit abstract. Let's look at the core properties and core methods

Core attribute
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  //SqlSession object, accessing database
  private final SqlSession sqlSession;
  //Class type information, that is, the class instance of Mapper interface
  private final Class<T> mapperInterface;
  //Mapper method corresponds to various methods of XML we write
  // Map is used for one-to-one correspondence, which is why the Mapper interface method name should be the same as the id of the sql tag of the corresponding XML file
  // The Map here is actually a ConcurrentHashMap. A Mapper interface maintains a methodCache
  private final Map<Method, MapperMethod> methodCache;
}
Core method

Construction method

//methodCache is passed in by MapperProxyFactory  
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

Call Mapper method

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //After the proxy, this invoke method will be called when all Mapper methods are called
    //Not all methods need to be executed. If this method is a general method in Object (toString, hashCode, etc.), it does not need to be executed
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    //It's optimized here. Find MapperMethod in the cache
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //implement
    return mapperMethod.execute(sqlSession, args);
  }

Cache MethodMapper

  //Find MapperMethod in cache
  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      //Go to new if you can't find it
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
Summary

Why is it over? This is Mapper's implementation class? How do so many Mapper methods execute without seeing any logic?

Yes, this is Mapper's implementation class, which is based on dynamic proxy technology. In fact, it just calls the execute method of MeperMethod in the invoke method.

Therefore, to understand the specific logic, we still need to go deep into MapperMethod, but the specific code implementation logic is not our focus this time. The execution logic of Mybatis is is very complex. We will encounter the bottom of JDBC. The core components of Mybatis, Cache, ResultSetHandler, Executor and ResultMap, have been made by students for a long time, So let's first tell the students how Mybatis generates and implements classes

So let's take a look at MapperProxyFactory, the producer of MapperProxy

Mapper implementation class factory - MapperProxyFactory

MapperProxyFactory, first of all, according to the class name, we know that this is a class designed using factory mode. Its responsibility is to generate MapperProxy, which is actually the implementation class of Mapper interface and based on dynamic proxy. We should understand that it is very simple. Of course, its code is also very simple. I don't believe you see

Core attribute
/**
 * @author Lasse Voss
 */
/**
 * Mapper agent factory
 */
public class MapperProxyFactory<T> {

  private static Logger logger = LoggerFactory.getLogger(MapperProxyFactory.class); 
 // Mapper interface type information, and MapperProxy 	 mapperInterface
  private final Class<T> mapperInterface;
  // Mapper interface method corresponds to the tags defined by Mabatis's XML select, delete, etc
  // Here we can see that a ConsurrentHashMap is directly created here
  // Its function is to pass it to MapperProxy. It is very clever here that it maintains a reference based on a factory, and the container is empty during initialization,
  // However, when the corresponding Mapper calls the method, the mapping will be added to the container
  // That is, a Mapper interface corresponds to a MapperProxyFactory, a methodCache corresponds to multiple mapperproxies
  // Simply put, multiple mapperproxies share a methodCache by reference
  // The advantage of this is that lazy loading can be realized. After the first loading, there is no need to load the second time  
  private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
 // The method is omitted here   
}
Core method
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
  //Using the dynamic agent built in JDK to generate the mapper
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

//Get Mapper implementation class
public T newInstance(SqlSession sqlSession) {
  // Please note that always passing the methodCache to MapperProxy confirms that multiple mapperproxies share a methodCache
  final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}
summary

That's how Mapper's implementation class was created?

Ha ha, yes, it was created in this way, but Mybatis is encapsulated based on its own implementation

Well, you'll know where Mapper's implementation class is when you're here. Of course, if you need a real understanding, you need to go through the knowledge about Java reflection. This knowledge is really important.

Register Mapper - MapperRegistry

In the previous chapter, we talked about how Mapper implementation class factory classes generate implementation classes, and also told the students to have a knowledge of Java reflection. We will use this class this time, which is about class loading

Core attribute
/**
 * @author Clinton Begin
 * @author Eduardo Macarron
 * @author Lasse Voss
 */
/**
 * Mapper register
 *
 */
public class MapperRegistry {
 // Mybatis configuration information class, which contains all mybatis configuration information
 // It is similar to data source configuration, transaction manager, plug-in, alias registration information, etc. we will delve into this class in Mybatis configuration
 // This class runs through almost the whole life cycle of Mybatis   
  private Configuration config;
  //Put all the added mappings into HashMap
  // Here we can see that this Class only maintains the mapping relationship between one Class instance and MapperProxyFactory
  // Obviously, the key value of knownMappers is the Class information of Mapper interface
  // Again, there is always only one Class instance information in a virtual machine, that is, a Java application  
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
  }
Core method
  //Add Mapper to implement Class factory through Class type instance
  public <T> void addMapper(Class<T> type) {
    //mapper must be an interface! Will be added
    if (type.isInterface()) {
      if (hasMapper(type)) {
        //If it is added repeatedly, an error will be reported
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // Directly create a MapperProxyFactory and put it into the container  
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        //If there are exceptions during loading, you need to delete the mapper from mybatis. This method is ugly. Is it a last resort?
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }



  /** 
  * Add Mapper to implement class factory through package name
   * @since 3.2.2
   */
  public void addMappers(String packageName, Class<?> superType) {
    //Find all superType classes under the package
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

  @SuppressWarnings("unchecked")
  //Return Mapper implementation class
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      // After obtaining the Mapper implementation class factory, directly create one and return it  
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }


Summary

Here, in fact, we have learned about the creation and acquisition methods of the whole Mapper interface. The most important thing is the Java foundation, which means higher-level abstraction and higher-level encapsulation.

In fact, for Mapper operations, Class type instances run through to the end, from adding a Mapper implementation Class factory to obtaining a Mapper implementation Class, and the implementation method is very simple, that is, the HashMap we most commonly use, which is quite rewarding

The actual executor of Mapper interface method - MapperMethod

Here, we don't delve into the implementation of this class. The complexity of this class is unclear in a few words. We can simply take a look at its core methods

Core method
//implement
public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  //You can see that there are four situations during execution: insert|update|delete|select, which call the four types of methods of SqlSession respectively
  if (SqlCommandType.INSERT == command.getType()) {
    logger.info("implement Insert");
    Object param = method.convertArgsToSqlCommandParam(args);
    result = rowCountResult(sqlSession.insert(command.getName(), param));
  } else if (SqlCommandType.UPDATE == command.getType()) {
    logger.info("implement Update");
    Object param = method.convertArgsToSqlCommandParam(args);
    result = rowCountResult(sqlSession.update(command.getName(), param));
  } else if (SqlCommandType.DELETE == command.getType()) {
    logger.info("implement Delete");
    Object param = method.convertArgsToSqlCommandParam(args);
    result = rowCountResult(sqlSession.delete(command.getName(), param));
  } else if (SqlCommandType.SELECT == command.getType()) {
    logger.info("implement Select");
    if (method.returnsVoid() && method.hasResultHandler()) {
      //If there is a result processor
      executeWithResultHandler(sqlSession, args);
      result = null;
    } else if (method.returnsMany()) {
      //If the result has more than one record
      result = executeForMany(sqlSession, args);
    } else if (method.returnsMap()) {
      //If the result is map
      result = executeForMap(sqlSession, args);
    } else {
      //Otherwise, it's a record
      Object param = method.convertArgsToSqlCommandParam(args);
      result = sqlSession.selectOne(command.getName(), param);
    }
  } else {
    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;
}
Summary

Hey, hey, is it a bit like JDBC? Yes, we are about to touch the bottom layer. We need to pick up the knowledge of JDBC to understand the next content

summary

The building module of Mybatis is an example for us to learn the technology of dynamic proxy. Its implementation method is simple and easy to understand, but the most important thing is that we should write by hand and Debug more. It is the starting point for learning dynamic proxy technology and can also help us understand the implementation of spring AOP.

study hard and make progress every day

Keywords: Java Java framework

Added by kuliksco on Fri, 28 Jan 2022 13:47:07 +0200