为了实现直接调用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是接口,用户无需自己实现,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。
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其实是没有改变的,所以可以缓存起来。
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的,不适合做缓存。
注册逻辑简单粗暴
根据type获取MapperProxyFactory,再根据SqlSession创建代理对象。easy。
binding包类不多,逻辑不复杂,但还是蛮重要的。解析binding包主要给我的收获还是 mybatis对代理模式的运用。