简介
使用MyBatis进行数据库操作时,需要先调用SqlSession接口的getMapper方法为Mapper接口生成实现类。然后就可以通过Mapper接口进行数据库操作。
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
为Mapper接口创建代理对象
SqlSession是通过JDK动态代理的方式为接口生成代理对象的。在调用接口方法时,相关调用会被代理逻辑拦截。在代理逻辑中可根据方法名及接口获取到当前方法对应的SQL以及其他一些信息,拿到这些信息即可进行数据库操作。
// DefaultSqlSession
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
//..省略中间调用...
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 从 knownMappers 中获取与 type 对应的 MapperProxyFactory
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);
}
}
knownMappers 中的 MapperProxyFactory 对象是在解析配置文件中的 <mappers> 标签所创建的。在获取到 MapperProxyFactory 对象后,即可调用方法 newInstance() 为Mapper接口生成代理对象。
//MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
// 创建 MapperProxy 对象,MapperProxy 实现了 InvocationHandler接口
// 代理逻辑封装在此类中
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
//调用重载方法创建代理对象
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// 通过JDK动态代理创建代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
执行代理逻辑
MapperProxy 类实现了 InvocationHandler 接口,通过动态代理拦截了 Mapper 接口方法去执行代理逻辑。
// MapperProxy
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 {
// 创建MapperMethodInvoker接口的对象,用于执行真正的代理逻辑
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
创建 MapperMethodInvoker
MapperMethodInvoker 是个接口,里面只有1个 invoke() 方法,用于执行不同的代理逻辑。该接口有2个实现类,分别是 DefaultMethodInvoker 和 PlainMethodInvoker。实例化过程在 cachedInvoker() 方法中。
// MapperProxy
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
MapperMethodInvoker invoker = methodCache.get(method);
if (invoker != null) {
return invoker;
}
return methodCache.computeIfAbsent(method, m -> {
// 支持JDK 1.8特性,在接口中可以实现default方法。
if (m.isDefault()) {
try {
// JDK1.8和JDK1.9 执行default方法不同的实现逻辑
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
// 执行定义在Mapper接口中方法的代理逻辑
} else {
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
定义Mapper接口中的方法的代理逻辑是在 PlainMethodInvoker 类,它的 invoke() 方法实际是将职责委托给MapperMethod类的的 execute() 方法。所以这里就能确定,将Mapper接口中的方法和XML映射文件所关联起来,就是 MapperMehtod 类的职责。
创建MapperMethod
在创建 PlainMethodInvoker 类的时候,会实例化一个 MapperMethod。
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
// 创建SqlCommand对象,包含一些SQL信息
this.command = new SqlCommand(config, mapperInterface, method);
// 创建MethodSignature对象,顾名思义,包含一些方法签名的信息
this.method = new MethodSignature(config, mapperInterface, method);
}
创建SqlCommand
SqlCommand 对象保存了与该方法所关联的SQL语句以及它的类型。
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
// 解析MappedStatement
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
// 判断当前方法是否有对应的MappedStatement
if (ms == null) {
//检测当前方法是否有@Flush注解
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
// 很常见的异常,没查询到与方法关联的SQL语句
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
}
创建MethodSignature
MethodSignature 即方法签名,该类保存了一些和目标方法相关的信息。比如方法的返回类型、参数列表等。
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
// 通过反射解析方法的返回类型
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
// 检测返回值类型是否是void、集合或数组、Cursor、Optional 等
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.returnsOptional = Optional.class.equals(this.returnType);
// 解析 @MapKey 注解,并获取注解的内容
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
// 获取 RowBounds 参数在参数列表中的位置,如果参数列表中包含多个 RowBounds 参数,则方法会抛出异常
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
// 获取ResultHandler参数在参数列表中的位置
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
// 解析参数列表
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
解析方法参数
ParamNameResolver 用于解析方法参数,可以得到参数下标与参数名的映射关系。这些映射关系存储在 names 成员变量中。
// ParamNameResolver
public ParamNameResolver(Configuration config, Method method) {
this.useActualParamName = config.isUseActualParamName();
// 获取参数类型列表
final Class<?>[] paramTypes = method.getParameterTypes();
// 获取参数注解
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
// 检测当前的参数类型是否为 RowBounds 或 ResultHandler
if (isSpecialParameter(paramTypes[paramIndex])) {
continue;
}
String name = null;
//for循环遍历是否含有@Param注解,如果有则使用@Param注解的值
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
// 如果不包含@Param注解
if (name == null) {
// 检测是否设置了 useActualParamName 全局配置
if (useActualParamName) {
// 获取指定索引位置的方法的参数名称
// 这个方法需要jdk 1.8的支持
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// 使用map.size()返回值作为参数名称。
// 使用map.size()而不使用String.valueOf(paramIndex)是因为参数列表如果包含Row Bounds 或 ResultHandler,这2个参数会被忽略掉,导致名称不连续。
name = String.valueOf(map.size());
}
}
//paramIndex 到 name的映射
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
执行 execute 方法
调用 MapperMethod#execute() 执行sql,最终的执行操作还是由 SqlSession 对象完成。
// MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根据 SQL 类型执行相应的数据库操作
switch (command.getType()) {
case INSERT: {
// 转换传入的方法的参数
Object param = method.convertArgsToSqlCommandParam(args);
// 执行insert操作,rowCountResult 方法用于处理返回值
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
// 转换传入的方法的参数
Object param = method.convertArgsToSqlCommandParam(args);
// 执行更新操作, rowCountResult 方法用于处理返回值
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
// 转换传入的方法的参数
Object param = method.convertArgsToSqlCommandParam(args);
// 执行删除操作, rowCountResult 方法用于处理返回值
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
// 根据目标方法的返回类型进行相应的查询操作
if (method.returnsVoid() && method.hasResultHandler()) {
// 如果方法返回值为void,但参数列表中包含ResultHandler,表明使用者想通过ResultHandler的方式获取查询结果,而非通过返回值获取。
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 执行查询操作,并返回多个结果
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
// 执行查询操作,并将结果封装到Map中返回
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
// 执行查询操作,并返回一个Cursor对象
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());
}
// 如果目标方法的返回类型为基本类型且返回值为null,抛出异常
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参数
通过 MapperMethod#convertArgsToSqlCommandParam() 方法将方法参数转换为SQL执行所需要的参数。实际上 convertArgsToSqlCommandParam() 方法是将职责委托给ParamNameResolver#getNamedParams() 方法
// MapperMethod
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
// ParamNameResolver
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
// 如果方法参数列表无@Param注解,且仅有一个非RowBounds和ResultHandler的参数,则返回该参数的值。names={0 : "name"}
Object value = args[names.firstKey()];
// 如果参数值是集合或者数组,则以参数名为key,参数值为value,存储在Map中并返回。
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
// 添加<参数名、参数值>键值对到param 中
param.put(entry.getValue(), args[entry.getKey()]);
// 创建通用参数名称:param1,param2,param3......
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// 判断 names 中是否包含 genericParamName。比如用@Param("param1")显示配置参数名
if (!names.containsValue(genericParamName)) {
// 添加 <param*,value> 到 param中
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}