MyBATIS插件原理第二篇 Mapper运行原理

本文解析了MyBIS如何利用动态代理实现接口调用,重点介绍了MapperProxy和MapperMethod类的工作原理,揭示了仅使用Mapper接口即可执行SQL的秘密。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://blog.youkuaiyun.com/ykzhen2015/article/details/50314509


我们目前在MyBATIS中,我们知道MyBATIS的Mapper是一个接口,而不是一个实体类。在Java中接口是没有办法运行的。那么它是怎么运行的呢?

有了第一篇的基础,我们可以大胆的想象——它是通过动态代理运行,没有错真实的情况就是这样的。

让我们看看mybatis是怎么实现这个动态代理的:

[java]  view plain  copy
  1. /** 
  2.  *    Copyright 2009-2015 the original author or authors. 
  3.  * 
  4.  *    Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  *    you may not use this file except in compliance with the License. 
  6.  *    You may obtain a copy of the License at 
  7.  * 
  8.  *       http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  *    Unless required by applicable law or agreed to in writing, software 
  11.  *    distributed under the License is distributed on an "AS IS" BASIS, 
  12.  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  *    See the License for the specific language governing permissions and 
  14.  *    limitations under the License. 
  15.  */  
  16. package org.apache.ibatis.binding;  
  17.   
  18. import java.io.Serializable;  
  19. import java.lang.reflect.InvocationHandler;  
  20. import java.lang.reflect.Method;  
  21. import java.util.Map;  
  22.   
  23. import org.apache.ibatis.reflection.ExceptionUtil;  
  24. import org.apache.ibatis.session.SqlSession;  
  25.   
  26. /** 
  27.  * @author Clinton Begin 
  28.  * @author Eduardo Macarron 
  29.  */  
  30. public class MapperProxy<T> implements InvocationHandler, Serializable {  
  31.   
  32.   private static final long serialVersionUID = -6424540398559729838L;  
  33.   private final SqlSession sqlSession;  
  34.   private final Class<T> mapperInterface;  
  35.   private final Map<Method, MapperMethod> methodCache;  
  36.   
  37.   public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {  
  38.     this.sqlSession = sqlSession;  
  39.     this.mapperInterface = mapperInterface;  
  40.     this.methodCache = methodCache;  
  41.   }  
  42.   
  43.   @Override  
  44.   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  45.     if (Object.class.equals(method.getDeclaringClass())) {  
  46.       try {  
  47.         return method.invoke(this, args);  
  48.       } catch (Throwable t) {  
  49.         throw ExceptionUtil.unwrapThrowable(t);  
  50.       }  
  51.     }  
  52.     final MapperMethod mapperMethod = cachedMapperMethod(method);  
  53.     return mapperMethod.execute(sqlSession, args);  
  54.   }  
  55.   
  56.   private MapperMethod cachedMapperMethod(Method method) {  
  57.     MapperMethod mapperMethod = methodCache.get(method);  
  58.     if (mapperMethod == null) {  
  59.       mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());  
  60.       methodCache.put(method, mapperMethod);  
  61.     }  
  62.     return mapperMethod;  
  63.   }  
  64.   
  65. }  
这便是mybaitis处理对象的方法,我们可以看到invoke方法。我们知道一旦mapper是一个代理对象,那么它就会运行到invoke方法里面,invoke首先判断是否一个类,显然这里mapper是一个接口,不是类所以判定失败。那么跟着就会生成MapperMethod对象,它是通过cachedMapperMethod方法对其初始化的,然后执行execute方法,把sqlSession和当前运行的参数传递进去。

于是让我们看看这个execute方法的源码:

[java]  view plain  copy
  1. package org.apache.ibatis.binding;  
  2.   
  3.   
  4. public class MapperMethod {  
  5.   
  6.   private final SqlCommand command;  
  7.   private final MethodSignature method;  
  8.   
  9.   public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {  
  10.     this.command = new SqlCommand(config, mapperInterface, method);  
  11.     this.method = new MethodSignature(config, method);  
  12.   }  
  13.   
  14.   public Object execute(SqlSession sqlSession, Object[] args) {  
  15.     Object result;  
  16.     if (SqlCommandType.INSERT == command.getType()) {  
  17.       Object param = method.convertArgsToSqlCommandParam(args);  
  18.       result = rowCountResult(sqlSession.insert(command.getName(), param));  
  19.     } else if (SqlCommandType.UPDATE == command.getType()) {  
  20.       Object param = method.convertArgsToSqlCommandParam(args);  
  21.       result = rowCountResult(sqlSession.update(command.getName(), param));  
  22.     } else if (SqlCommandType.DELETE == command.getType()) {  
  23.       Object param = method.convertArgsToSqlCommandParam(args);  
  24.       result = rowCountResult(sqlSession.delete(command.getName(), param));  
  25.     } else if (SqlCommandType.SELECT == command.getType()) {  
  26.       if (method.returnsVoid() && method.hasResultHandler()) {  
  27.         executeWithResultHandler(sqlSession, args);  
  28.         result = null;  
  29.       } else if (method.returnsMany()) {  
  30.         <span style="color:#FF0000;">result = executeForMany(sqlSession, args);//我们主要看看这个方法</span>  
  31.       } else if (method.returnsMap()) {  
  32.         result = executeForMap(sqlSession, args);  
  33.       } else {  
  34.         Object param = method.convertArgsToSqlCommandParam(args);  
  35.         result = sqlSession.selectOne(command.getName(), param);  
  36.       }  
  37.     } else if (SqlCommandType.FLUSH == command.getType()) {  
  38.         result = sqlSession.flushStatements();  
  39.     } else {  
  40.       throw new BindingException("Unknown execution method for: " + command.getName());  
  41.     }  
  42.     if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {  
  43.       throw new BindingException("Mapper method '" + command.getName()   
  44.           + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");  
  45.     }  
  46.     return result;  
  47.   }  
  48.  ........  
  49.   //方法还是很多,我们不需要全看就看一个很常用的查询返回多条记录的  
  50.   private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {  
  51.     List<E> result;  
  52.     Object param = method.convertArgsToSqlCommandParam(args);  
  53.     if (method.hasRowBounds()) {  
  54.       RowBounds rowBounds = method.extractRowBounds(args);  
  55.       result = sqlSession.<E>selectList(command.getName(), param, rowBounds);  
  56.     } else {  
  57.       <span style="color:#FF0000;">result = sqlSession.<E>selectList(command.getName(), param);</span>  
  58.     }  
  59.     // issue #510 Collections & arrays support  
  60.     if (!method.getReturnType().isAssignableFrom(result.getClass())) {  
  61.       if (method.getReturnType().isArray()) {  
  62.         return convertToArray(result);  
  63.       } else {  
  64.         return convertToDeclaredCollection(sqlSession.getConfiguration(), result);  
  65.       }  
  66.     }  
  67.     return result;  
  68.   }  
  69.   .......  
  70.  }  


好这里我们看到,MapperMethod 类采用命令模式运行,然后根据上下文跳转可能跳转到许多方法中取,我们不需要全部明白,我们可以看到里面的executeForMany方法,再看看它的实现,实际上它最后就是通过sqlSession对象去运行对象的SQL而已。


至此,相信大家已经了解了MyBATIS为什么只用mappper接口便能够运行sql,因为mapperd的xml文件的命名空间对应的便是这个接口全路径,那么它根据全路径和方法名,便能够绑定起来,通过动态代理技术,让这个接口跑起来。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值