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
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
-
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; } }
- 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
- 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