mybatis-binding 绑定包解析

概述

为了实现直接调用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对代理模式的运用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值