文章目录
- 版本
- 背景
- 实现步骤
- 核心类
- MapperRegistry
- MapperProxyFactory
- MapperProxy
- MapperMethodInvoker
- MapperMethod
- 属性
- SqlCommand
- MethodSignature
- 方法
- execute
- convertArgsToSqlCommandParam
- 总结
版本
mybatis版本3.5.7
背景
我们在使用mybatis进行数据库crud时通常都是通过*Mapper.java对象的方法进行操作的,那sql语句怎么和对应的方法绑定的,mybatis是怎么知道返回结果是多个、单个或者是其他的,*Mapper是个接口,它是怎么实例化调用方法的。这些一系列问题背后怎么实现的,都是跟binding模块相关的。
实现步骤
要实现以上操作有三个步骤:
1、根据sql语句类型和参数选择调用不同的方法
2、通过找到命名空间和方法名
3、传递参数
完成以上三个主要步骤就绑定成功了,要完成以上步骤需要下面几个核心类。
核心类
MapperRegistry
MapperRegistry 是mapper 接口和对应的代理对象工厂的注册中心。MapperRegistry 内部维护着一个Map对象,key是mapper接口的Class对象,value是MapperProxyFactory。
当需要获取mapper接口对象时就需要调用SqlSession#getMapper方法,getMapper是从configuration对象的MapperRegistry获取一个代理对象工厂。
SqlSession#getMapper:
public <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this); }
MapperRegistry#getMapper
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 { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
在配置加载阶段会往Configuration对象的属性mapperRegistry中添加mapper接口和代理工厂映射,
添加入口是Configuration#addMappers和Configuration#addMapper,前者是把某个包下的mapper接口添加进去,后者只是添加单独一个mapper接口,最终都是调用MapperRegistry#addMapper。
MapperRegistry#addMapper
/** * 把mapper 接口添加到 knownMappers 中注册中心 * @param type * @param <T> */ public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { // 建立mapper 和 MapperProxyFactory 的连接 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. // 解析接口上的注解信息并添加到configuration对象中 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
MapperProxyFactory
MapperProxyFactory是MapperProxy的工厂类,专门用来生产MapperProxy的。
MapperProxyFactory:
public class MapperProxyFactory<T> { /** * mapper接口的Class对象 */ private final Class<T> mapperInterface; /** * key: mapper接口的一个方法的Method对象 * value: 对应的MapperMethodInvoker对象 */ private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return mapperInterface; } public Map<Method, MapperMethodInvoker> getMethodCache() { return methodCache; } /** * 创建mapperInterface接口的实现类的代理对象 * @param mapperProxy * @return */ @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { T proxyInstance = (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy); return proxyInstance; } public T newInstance(SqlSession sqlSession) { //创建MapperProxy对象 final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
mapper接口的代理类形如org.apache.ibatis.binding.MapperProxy@3d5c822d
MapperProxy
MapperProxy是在MapperProxyFactory#newInstance(SqlSession sqlSession)方法中生成实例对象的。
MapperProxy实现了InvocationHandler,使用的jdk的动态代理,对mapper接口进行了增强。
既然MapperProxy实现了InvocationHandler接口,那就从invoke开始看起。
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //如果是Object类声明的方法 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } }
接下来看cachedInvoker方法:
/** * 缓存method对应的MapperMethodInvoker对象 * @param method * @return * @throws Throwable */ private MapperMethodInvoker cachedInvoker(Method method) throws Throwable { try { // A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372 // It should be removed once the fix is backported to Java 8 or // MyBatis drops Java 8 support. See gh-1929 MapperMethodInvoker invoker = methodCache.get(method); if (invoker != null) { return invoker; } return methodCache.computeIfAbsent(method, m -> { //如果是接口的default方法生成DefaultMethodInvoker if (m.isDefault()) { try { if (privateLookupInMethod == null) { return new DefaultMethodInvoker(getMethodHandleJava8(method)); } else { return new DefaultMethodInvoker(getMethodHandleJava9(method)); } } catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); } } else { //生成非default方法MethodInvoker return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); } }); } catch (RuntimeException re) { Throwable cause = re.getCause(); throw cause == null ? re : cause; } }
MapperMethodInvoker
MapperMethodInvoker是MapperProxy的内部类。
MapperMethodInvoker对于接口中非default方法来说它是MapperMethod的调用者,它的实现PlainMethodInvoker里面维护着一个MapperMethod,真正的执行者还是MapperMethod。
DefaultMethodInvoker内部就直接调用了default方法。重点还是要看PlainMethodInvoker
PlainMethodInvoker
/** * 接口中非default方法的调用器 */ 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 { //调用mapperMethod对象执行sql return mapperMethod.execute(sqlSession, args); } }
PlainMethodInvoker的invoke方法内部也没有对参数做什么别的逻辑操作而是直接转发给MapperMethod#execute方法,让MapperMethod来执行,MapperMethod也是最后真正的执行者。
MapperMethod
MapperMethod才是binding模块真正的执行者,上述的三个步骤也都是在MapperMethod中实现的。
三个步骤:
1、根据sql语句类型和参数选择调用不同的方法
2、找到命名空间和方法名
3、传递参数
属性
MapperMethod内部有两个属性,SqlCommand和MethodSignature对象,它们也都是MapperMethod的内部类。
//mapper接口中方法的命名空间和sql类型 private final SqlCommand command; //mapper接口中的方法签名,包括参数、返回类型等信息 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); }