Author: TaoYuanming of the Western Wei Dynasty
Blog: https://blog.springlearn.cn/
TaoYuanming of the Western Wei Dynasty
Don't laugh at young people's dreams of the Jianghu. Who doesn't dream of the Jianghu
1, Architecture analysis
Mapper in Mybatis is usually just an interface, so why can it perform data operations? That must be based on agency, no need to say. Before understanding how Mybatis implements proxy, let's have a general look at its architecture, have a general understanding of these key classes, and know where it is located.
In this article, we only go deep into the lower agent layer to learn how the lower mybatis performs agent operations, and the final execution of sql will be studied in the next execution process.
2, Source code analysis
First of all, don't panic. Looking at the above figure, the agent process of Mybatis is still relatively simple. Now let's mainly look at what each core class is used for.
2.1 MapperProxyFactory
- The code in the Proxy factory is relatively simple, which is to create Proxy objects using Proxy.
- The generated proxy methods are directly cached in MethodCache.
public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>(); // Jdk Proxy. You can see that the main logic is in MapperProxy protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
2.2 MapperProxy
The proxy logic of MapperProxy is also very simple. See the figure for the following three capabilities.
The core processing code is selected and commented below.
public class MapperProxy<T> implements InvocationHandler, Serializable { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // Object method is executed directly if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { // Other methods generate proxy methods return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } private MapperMethodInvoker cachedInvoker(Method method) throws Throwable { try { return MapUtil.computeIfAbsent(methodCache, method, m -> { // If it is the default method if (m.isDefault()) { try { if (privateLookupInMethod == null) { // Syntax parsing generation proxy method for generating java8 return new DefaultMethodInvoker(getMethodHandleJava8(method)); } else { // //Syntax parsing and proxy generation method for generating java9 return new DefaultMethodInvoker(getMethodHandleJava9(method)); } } catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); } } else { // Not the default method, generate proxy method MapperMethod return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); } }); } catch (RuntimeException re) { Throwable cause = re.getCause(); throw cause == null ? re : cause; } } }
2.3 PlainMethodInvoker
As mentioned earlier, there are three main scenarios for proxy methods.
- Object method direct method invoker(this, args);
- The method modified by the default keyword is DefaultMethodInvoker
- The more important proxy Invoker to execute sql is PlainMethodInvoker
PlainMethodInvoker is the specific implementation class that really needs sql processing. The real proxy logic is in MapperMethod.
private static class PlainMethodInvoker implements MapperMethodInvoker { private final MapperMethod mapperMethod; public PlainMethodInvoker(MapperMethod mapperMethod) { super(); this.mapperMethod = mapperMethod; } @Override public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { return mapperMethod.execute(sqlSession, args); } }
MapperMethod, simply look at the process. There are two important implementation classes, which are used to judge the sql type, process the method parameters (resolve the @Param parameters) and finally hand them over to SqlSession for execution. I'll take the agency here
The process has been clarified. But we haven't seen how to assemble parameters in sql and how to call the jdbc interface of database. This part. Let's move on to the next one Part 05: Mybatis' SQL execution process analysis
public class MapperMethod { private final SqlCommand command; private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); } public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: 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; } }