mybatis-binding 绑定包解析

    技术2022-07-10  77

    概述

    为了实现直接调用Mapper接口类的方法,便达到调用sql的目标,mybatis-binding包提供了Mapper接口的代理类和其方法的代理类。主要起到连接 Mapper.java 和 Mapper.xml的作用。

    方法映射绑定

    为了连接Mapper接口的方法 和 Mapper.xml的statement,于是就有了类 MapperMethod

    public class MapperMethod { // 代表一条sql命令的属性 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); } }

    MapperMethod只有两个属性,一个是代表sql的,一个是代表java方法的。

    这两个类都是MapperMethod的内部类,mybatis是为了聚合并隔离,所以用了内部类来承载。

    SqlCommand描述和定位sql的基本信息,因为对于Statement的具体信息由MappedStatement承载,这里只保存

    public static class SqlCommand { /** * statementId,其实就是方法的全限定名 */ private final String name; /** * sql的类型 */ private final SqlCommandType type; } public static class MethodSignature { /** * 返回类型是否是集合 */ private final boolean returnsMany; /** * 返回类型是否是map */ private final boolean returnsMap; /** * 返回是否是void */ private final boolean returnsVoid; /** * 返回是否是游标 */ private final boolean returnsCursor; /** * 返回是否是Optional */ private final boolean returnsOptional; /** * 返回类型 */ private final Class<?> returnType; /** * 返回map类型是,指定的key */ private final String mapKey; /** * resultHandler在参数中的索引 * 可能为null,基本不会传这个参数 */ private final Integer resultHandlerIndex; /** * 分页参数在参数中的索引 * 可能为null,一般也很少使用 */ private final Integer rowBoundsIndex; /** * 参数名解析器 */ private final ParamNameResolver paramNameResolver; }

    这两个类的属性是如何得到的比较简单也不是重点,这里不介绍。

    焦点继续放在MapperMethod上。前面讲了MapperMethod的属性,现在看依托属性,MapperMethod提供的能力。

    MapperMethod就提供了一个public方法,即execute执行,方法的执行直接代表了sql的执行,看下实现。

    public Object execute(SqlSession sqlSession, Object[] args) { Object result; //根据sql的类型,路由到sqlSession的具体方法,对结果进行简单处理 switch (command.getType()) { case INSERT: { //处理参数,将参数转换成paramMap Object param = method.convertArgsToSqlCommandParam(args); //结果转换,转成方法返回值的类型 result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { //处理参数,将参数转换成paramMap Object param = method.convertArgsToSqlCommandParam(args); //结果转换,转成方法返回值的类型 result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { //处理参数,将参数转换成paramMap Object param = method.convertArgsToSqlCommandParam(args); //结果转换,转成方法返回值的类型 result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: //查询的方法路由,根据返回值的类型,调用sqlSession不同的方法 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; }

    原来解析好sql、method的属性是为了路由到SqlSession上,虽然情况较多,但还好单个情况的处理并不复杂,只是简单处理下入参、返回值。

    这里传给SqlSession的入参都会被转成ParamMap,key是参数的name,value是参数的值,这里参数的name我们在xml中编写sql中就是根据其引用到参数。

    这里name的规则可以看下org.apache.ibatis.reflection.ParamNameResolver#getNamedParams,这里不多介绍。

    如果单单使用SqlSession操作,那么就没有MapperMethod什么事了,就是因为直接调用SqlSession很麻烦,为了让使用者可以像调用java方法一样调用sql,MapperMethod才应运而生。

    Mapper代理

    Mapper是接口,用户无需自己实现,mybatis使用 MapperProxy伪造其实现类,完成sql调用。

    public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } }

    MapperProxy实现了InvocationHandler,就是通过jdk动态代理的方法 代理Mapper接口。

    保存SqlSession,和该类所有的MapperMethod,拦截方法调用,最终委托给MapperMethod执行。

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //Object的方法直接执行 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } //default方法直接执行 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); } //其余方法需要执行sql,从缓存中获取 MapperMethod final MapperMethod mapperMethod = cachedMapperMethod(method); // 委托给MapperMethod执行 return mapperMethod.execute(sqlSession, args); }

    解析过MapperMethod,MapperProxy so easy。

    MapperProxyFactory

    Mapper代理类工厂,就是为了创建MapperProxy。

    public class MapperProxyFactory<T> { /** * mapper类型 */ private final Class<T> mapperInterface; /** * java方法和 映射方法的缓存 */ private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } /** * 为指定接口创建代理类 */ protected T newInstance(MapperProxy<T> mapperProxy) { //创建代理对象 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } /** * 为指定接口创建代理类 */ public T newInstance(SqlSession sqlSession) { //创建 InvocationHandler final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); //创建代理对象 return newInstance(mapperProxy); } }

    这里可以看到MapperProxy和是根据SqlSession创建的,生命周期与SqlSession相同。

    Map<Method, MapperMethod> methodCache是工厂共享的,其实就是因为MapperProxy的生命周期原因,不同的SqlSession但是Method和MapperMethod其实是没有改变的,所以可以缓存起来。

    Mapper注册器

    MapperRegistry存放mybatis全量Mapper,提供了注册、获取的功能。逻辑也不复杂,看下代码。

    public class MapperRegistry { // mybatis配置,存储而已,和Mapper注册无关 private final Configuration config; /** * key:Mapper接口 * value:MapperProxyFactory */ private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>(); }

    这里Value不是MapperProxy而是MapperProxyFactory的问题在上面解释过了,就是因为MapperProxy的生命周期是跟着SqlSession的,不适合做缓存。

    mapper注册

    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 { // 添加到hashmap中 knownMappers.put(type, new MapperProxyFactory<>(type)); //解析mapper接口上的注解,包括(Cache、Select、Insert等) MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }

    注册逻辑简单粗暴

    获取Mapper

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

    根据type获取MapperProxyFactory,再根据SqlSession创建代理对象。easy。

    总结

    binding包类不多,逻辑不复杂,但还是蛮重要的。解析binding包主要给我的收获还是 mybatis对代理模式的运用。

    Processed: 0.021, SQL: 9