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