When using Mybatis, you may have a question: why can you only write Mapper interface to operate database?
Its main idea is to use dynamic agent to generate implementation class, and then cooperate with SQL statement in xml Mapping file to access database. Let's take a look at the source code.
binding module core class
- Mapper registry: the registry of MapperProxyFactory
- MapperProxyFactory: the dynamic proxy factory class corresponding to mapper interface. Mapper interface and MapperProxyFactory factory class are one-to-one correspondence
- MapperProxy: Mapper interface enhancer, through which the database access is realized
- MapperMethod: for the wrapper class of insert, update, delete, select, flush node methods, it completes the operation on the database through sqlSession
Agent initialization
bindMapperForNamespace
In Mybatis source code (2), we will find that the last step when the configuration file is parsed is to call org.apache.ibatis.builder.xml.xmlmapperbuilder ා bindmapperfornamespace method. The main function of this method is to register the MapperProxyFactory of Mapper interface into MapperRegistry according to the namespace attribute. The source code is as follows:
private void bindMapperForNamespace() { // Get the namespace property (corresponding to the full class name of Mapper interface) String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { //ignore, bound type is not required } if (boundType != null) { // Prevent duplicate loading if (!configuration.hasMapper(boundType)) { // Spring may not know the real resource name so we set a flag // to prevent loading again this resource from the mapper interface // look at MapperAnnotationBuilder#loadXmlResource configuration.addLoadedResource("namespace:" + namespace); // Register the dynamic proxy factory of Mapper interface in MapperRegistry configuration.addMapper(boundType); } } } }
- Read the namespace property to get the full class name of Mapper interface
- Load Mapper interface into memory according to full class name
- Determine whether to load Mapper interface repeatedly
- Call the addMapper method of Mybatis configuration class to complete the following steps
addMapper
Org.apache.ibatis.session.configuration ා addmapper this method directly calls the org.apache.ibatis.binding.mapperregistry ා addmapper method to complete the registration.
public <T> void addMapper(Class<T> type) { // Must be an interface if (type.isInterface()) { if (hasMapper(type)) { // Prevent duplicate registration throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { // Create MapperProxyFactory proxy factory class according to the interface class knownMappers.put(type, new MapperProxyFactory<>(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 { // Remove Mapper in case of loading exception if (!loadCompleted) { knownMappers.remove(type); } } } }
- Determine whether the loading type is an interface
- Duplicate registration verification. If the verification fails, a BindingException exception will be thrown.
- Create MapperProxyFactory proxy factory class according to the interface class
- Remove Mapper in case of loading exception
Get the corresponding Mapper object
In the quick start of Mybatis source code (1), you can get Mapper objects through sqlSession as follows:
PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
getMapper get proxy object
sqlSession.getMapper(PersonMapper.class) finally calls org.apache.ibatis.binding.mapperregistry ා getmapper method, and finally returns the proxy object of the PersonMapper interface. The source code is as follows:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // Get the corresponding agent factory according to the type final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { // Create a new proxy object according to the factory class, and return return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
- Get the corresponding agent factory according to the type
- Create a new proxy object according to the factory class, and return
newInstance create proxy object
Each Mapper interface corresponds to a MapperProxyFactory factory class. MapperProxyFactory creates proxy objects through JDK dynamic proxy. The proxy object of Mapper interface is method level, so new proxy objects need to be created every time the database is accessed. The source code is as follows:
protected T newInstance(MapperProxy<T> mapperProxy) { // Using JDK dynamic agent to generate agent instance return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy); } public T newInstance(SqlSession sqlSession) { // Mapper's enhancer final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
- Get mapperproxy first
- Using JDK dynamic proxy to generate proxy objects according to the enhancer
Decompilation result of proxy class
import com.sun.proxy..Proxy8; import com.xiaolyuh.domain.model.Person; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy8 extends Proxy implements Proxy8 { private static Method m3; ... public $Proxy8(InvocationHandler var1) throws { super(var1); } ... public final Person selectByPrimaryKey(Long var1) throws { try { return (Person)super.h.invoke(this, m3, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } static { try { m3 = Class.forName("com.sun.proxy.$Proxy8").getMethod("selectByPrimaryKey", Class.forName("java.lang.Long")); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
From the decompilation results of the proxy class, we can directly call the invoke method of the enhancer to access the database.
Execution method
By decompiling the proxy object, we can find that all access to the database is implemented in the enhancer org. Apache. Ibatis. Binding. Mapperproxy ා invoke.
invoke
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // If the method of Object itself is not enhanced if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } // Determine whether it is the default method else if (method.isDefault()) { if (privateLookupInMethod == null) { return invokeDefaultMethodJava8(proxy, method, args); } else { return invokeDefaultMethodJava9(proxy, method, args); } } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // Get MapperMethod object from cache final MapperMethod mapperMethod = cachedMapperMethod(method); // Execute MapperMethod return mapperMethod.execute(sqlSession, args); }
- If the method of Object itself is not enhanced
- Determine whether it is the default method
- Get MapperMethod object from cache
- Execute MapperMethod
The agent phase of Mybatis is finished here, mapperMethod.execute(sqlSession, args); this code has already started to operate data, query data and result set mapping through sqlSession.
Flow chart
summary
Through the above source code analysis, it can be found that Mybatis interface oriented programming is implemented through the JDK dynamic agent mode. The main execution process is as follows:
- After the initialization of the mapping file, register the MapperProxyFactory class of the corresponding Mapper interface to MapperRegistry.
- Every time the database is operated, sqlSession obtains the agent class of Mapper interface through MapperProxyFactory
- The agent class calls MapperMethod, the encapsulation class of SQL node in XML Mapping file, to complete the operation of data through MapperProxy.