您的位置:首页 > 移动开发

【转载】MyBatis Mapper 接口如何通过JDK动态代理来包装SqlSession 源码分析

2017-08-30 10:06 621 查看
我们以往使用ibatis或者mybatis 都是以这种方式调用XML当中定义的CRUD标签来执行SQL 比如这样

[xml] view
plain copy

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.mybatis.example.BlogMapper">

<select id="selectBlog" resultType="Blog">

select * from Blog where id = #{id}

</select>

</mapper>

[java] view
plain copy

SqlSession session = sqlSessionFactory.openSession();

try {

Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);

} finally {

session.close();

}

这种方式有很明显的缺点就是通过字符串去调用标签定义的SQL,第一容易出错,第二是当XML当中的id修改过以后你不知道在程序当中有多少个地方使用了这个ID,需要手工查找并一一修改。在Mybatis这个版本中做了一些改进,支持这种方式调用。

定义一个接口 方法名,参数需要与XML定义保持一致。

[java] view
plain copy

org.mybatis.example.BlogMapper.selectBlog

public interface BlogMapper {

Blog selectBlog(int id);

}

然后这么调用,这样以来当我们修改了XML的ID以后,只需要修改接口中的方法就可以了,编译器会在其他使用该接口的地方报错,很容易进行修改。当然好处还不只这些,还可以通过与spring进行无缝集成,动态注入
等等。后面会一一讲到。

[java] view
plain copy

SqlSession session = sqlSessionFactory.openSession();

try {

BlogMapper mapper = session.getMapper(BlogMapper.class);

Blog blog = mapper.selectBlog(101);

} finally {

session.close();

}

本文的重点不是去讲解如何使用MyBatis(关于如何使用Mybatis可以参考官方API http://mybatis.github.io/mybatis-3/zh/getting-started.html),而是讲解MyBatis是如何通过接口对SqlSession进行动态封装的。
在上面的例子当中呢,BlogMapper是一个接口 它并没有实现类,为什么接口可以直接使用呢?

那是因为MyBbatis使用了JDK动态代理机制动态生成了代理类,那么代理类又是如何多SqlSession进行封装的呢?

带着这些疑问,让我们通过分析源代码的方式来解释这些问题。

Mybatis关于包装Mapper的代码都在org.apache.ibatis.binding 这个包下面。

其中有4个类。



上面的4个类封装了Mapper接口动态生成代理类的全部细节

MapperRegistry 类是注册Mapper接口与获取代理类实例的工具类

[java] view
plain copy

package org.apache.ibatis.binding;

import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;

import org.apache.ibatis.io.ResolverUtil;

import org.apache.ibatis.session.Configuration;

import org.apache.ibatis.session.SqlSession;

import java.util.Collection;

import java.util.Collections;

import java.util.HashMap;

import java.util.Map;

import java.util.Set;

//这个类通过名字就可以看出 是用来注册Mapper接口与获取生成代理类实例的工具类

public class MapperRegistry {

//全局配置文件对象

private Configuration config;

//一个HashMap Key是mapper的类型对象, Value是MapperProxyFactory对象

//这个MapperProxyFactory是创建Mapper代理对象的工厂 我们一会在分析

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

public MapperRegistry(Configuration config) {

this.config = config;

}

//获取生成的代理对象

@SuppressWarnings("unchecked")

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {

//通过Mapper的接口类型 去Map当中查找 如果为空就抛异常

final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);

if (mapperProxyFactory == null)

throw new BindingException("Type " + type + " is not known to the MapperRegistry.");

try {

//否则创建一个当前接口的代理对象 并且传入sqlSession

return mapperProxyFactory.newInstance(sqlSession);

} catch (Exception e) {

throw new BindingException("Error getting mapper instance. Cause: " + e, e);

}

}

public <T> boolean hasMapper(Class<T> type) {

return knownMappers.containsKey(type);

}

//注册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 {

knownMappers.put(type, new MapperProxyFactory<T>(type));

// It's important that the type is added before the parser is run

// otherwise the binding may automatically be attempted by the

// mapper parser. If the type is already known, it won't try.

MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);

parser.parse();

loadCompleted = true;

} finally {

if (!loadCompleted) {

knownMappers.remove(type);

}

}

}

}

public Collection<Class<?>> getMappers() {

return Collections.unmodifiableCollection(knownMappers.keySet());

}

ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();

resolverUtil.find(new ResolverUtil.IsA(superType), packageName);

Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();

for (Class<?> mapperClass : mapperSet) {

addMapper(mapperClass);

}

}

//通过包名扫描下面所有接口

public void addMappers(String packageName) {

addMappers(packageName, Object.class);

}

}

MapperRegistry

[java] view
plain copy

package org.apache.ibatis.binding;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;

import org.apache.ibatis.session.SqlSession;

//这个类负责创建具体Mapper接口代理对象的工厂类

public class MapperProxyFactory<T> {

//具体Mapper接口的Class对象

private final Class<T> mapperInterface;

//该接口下面方法的缓存 key是方法对象 value是对接口中方法对象的封装

private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

//构造参数没啥好说的

public MapperProxyFactory(Class<T> mapperInterface) {

this.mapperInterface = mapperInterface;

}

public Class<T> getMapperInterface() {

return mapperInterface;

}

public Map<Method, MapperMethod> getMethodCache() {

return methodCache;

}

@SuppressWarnings("unchecked")

protected T newInstance(MapperProxy<T> mapperProxy) {

//创建了一个代理类并返回

//关于Proxy的API 可以查看java官方的API

return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

}

//在这里传入sqlSession 创建一个Mapper接口的代理类

public T newInstance(SqlSession sqlSession) {

//在这里创建了MapperProxy对象 这个类实现了JDK的动态代理接口 InvocationHandler

final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);

//调用上面的方法 返回一个接口的代理类

return newInstance(mapperProxy);

}

}

MapperProxyFactory

[java] view
plain copy

package org.apache.ibatis.binding;

import java.io.Serializable;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.util.Map;

import org.apache.ibatis.session.SqlSession;

//实现了JDK动态代理的接口 InvocationHandler

//在invoke方法中实现了代理方法调用的细节

public class MapperProxy<T> implements InvocationHandler, Serializable {

private static final long serialVersionUID = -6424540398559729838L;

//SqlSession

private final SqlSession sqlSession;

//接口的类型对象

private final Class<T> mapperInterface;

//接口中方法的缓存 有MapperProxyFactory传递过来的。

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;

}

//接口代理对象所有的方法调用 都会调用该方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

//判断是不是基础方法 比如toString() hashCode()等,这些方法直接调用不需要处理

&nbs
1fff7
p;if (Object.class.equals(method.getDeclaringClass())) {

return method.invoke(this, args);

}

//这里进行缓存

final MapperMethod mapperMethod = cachedMapperMethod(method);

//调用mapperMethod.execute 核心的地方就在这个方法里,这个方法对才是真正对SqlSession进行的包装调用

return mapperMethod.execute(sqlSession, args);

}

//缓存处理

private MapperMethod cachedMapperMethod(Method method) {

MapperMethod mapperMethod = methodCache.get(method);

if (mapperMethod == null) {

mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());

methodCache.put(method, mapperMethod);

}

return mapperMethod;

}

}

MapperProxy

[java] view
plain copy

package org.apache.ibatis.binding;

import org.apache.ibatis.annotations.MapKey;

import org.apache.ibatis.annotations.Param;

import org.apache.ibatis.mapping.MappedStatement;

import org.apache.ibatis.mapping.SqlCommandType;

import org.apache.ibatis.reflection.MetaObject;

import org.apache.ibatis.session.Configuration;

import org.apache.ibatis.session.ResultHandler;

import org.apache.ibatis.session.RowBounds;

import org.apache.ibatis.session.SqlSession;

import java.lang.reflect.Array;

import java.lang.reflect.Method;

import java.util.*;

//这个类是整个代理机制的核心类,对Sqlsession当中的操作进行了封装

public class MapperMethod {

//一个内部封 封装了SQL标签的类型 insert update delete select

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, method);

}

//这个方法是对SqlSession的包装调用

public Object execute(SqlSession sqlSession, Object[] args) {

//定义返回结果

Object result;

//如果是INSERT操作

if (SqlCommandType.INSERT == command.getType()) {

//处理参数

Object param = method.convertArgsToSqlCommandParam(args);

//调用sqlSession的insert方法

result = rowCountResult(sqlSession.insert(command.getName(), param));

//如果是UPDATE操作 同上

} else if (SqlCommandType.UPDATE == command.getType()) {

Object param = method.convertArgsToSqlCommandParam(args);

result = rowCountResult(sqlSession.update(command.getName(), param));

//如果是DELETE操作 同上

} else if (SqlCommandType.DELETE == command.getType()) {

Object param = method.convertArgsToSqlCommandParam(args);

result = rowCountResult(sqlSession.delete(command.getName(), param));

//如果是SELECT操作 那么情况会多一些 但是也都和sqlSession的查询方法一一对应

} else if (SqlCommandType.SELECT == command.getType()) {

//如果返回void 并且参数有resultHandler

//则调用 void select(String statement, Object parameter, ResultHandler handler);方法

if (method.returnsVoid() && method.hasResultHandler()) {

executeWithResultHandler(sqlSession, args);

result = null;

//如果返回多行结果这调用 <E> List<E> selectList(String statement, Object parameter);

//executeForMany这个方法调用的

} else if (method.returnsMany()) {

result = executeForMany(sqlSession, args);

//如果返回类型是MAP 则调用executeForMap方法

} else if (method.returnsMap()) {

result = executeForMap(sqlSession, args);

} else {

//否则就是查询单个对象

Object param = method.convertArgsToSqlCommandParam(args);

result = sqlSession.selectOne(command.getName(), param);

}

} else {

//如果全都不匹配 说明mapper中定义的方法不对

throw new BindingException("Unknown execution method for: " + command.getName());

}

//如果返回值为空 并且方法返回值类型是基础类型 并且不是VOID 则抛出异常

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;

}

private Object rowCountResult(int rowCount) {

final Object result;

if (method.returnsVoid()) {

result = null;

} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {

result = rowCount;

} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {

result = (long) rowCount;

} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {

result = (rowCount > 0);

} else {

throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());

}

return result;

}

private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {

MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());

if (void.class.equals(ms.getResultMaps().get(0).getType())) {

throw new BindingException("method " + command.getName()

+ " needs either a @ResultMap annotation, a @ResultType annotation,"

+ " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");

}

Object param = method.convertArgsToSqlCommandParam(args);

if (method.hasRowBounds()) {

RowBounds rowBounds = method.extractRowBounds(args);

sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));

} else {

sqlSession.select(command.getName(), param, method.extractResultHandler(args));

}

}

//返回多行结果 调用sqlSession.selectList方法

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {

List<E> result;

Object param = method.convertArgsToSqlCommandParam(args);

//如果参数含有rowBounds则调用分页的查询

if (method.hasRowBounds()) {

RowBounds rowBounds = method.extractRowBounds(args);

result = sqlSession.<E>selectList(command.getName(), param, rowBounds);

} else {

//没有分页则调用普通查询

result = sqlSession.<E>selectList(command.getName(), param);

}

// issue #510 Collections & arrays support

if (!method.getReturnType().isAssignableFrom(result.getClass())) {

if (method.getReturnType().isArray()) {

return convertToArray(result);

} else {

return convertToDeclaredCollection(sqlSession.getConfiguration(), result);

}

}

return result;

}

private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {

Object collection = config.getObjectFactory().create(method.getReturnType());

MetaObject metaObject = config.newMetaObject(collection);

metaObject.addAll(list);

return collection;

}

@SuppressWarnings("unchecked")

private <E> E[] convertToArray(List<E> list) {

E[] array = (E[]) Array.newInstance(method.getReturnType().getComponentType(), list.size());

array = list.toArray(array);

return array;

}

private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {

Map<K, V> result;

Object param = method.convertArgsToSqlCommandParam(args);

if (method.hasRowBounds()) {

RowBounds rowBounds = method.extractRowBounds(args);

result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);

} else {

result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());

}

return result;

}

public static class ParamMap<V> extends HashMap<String, V> {

private static final long serialVersionUID = -2212268410512043556L;

@Override

public V get(Object key) {

if (!super.containsKey(key)) {

throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());

}

return super.get(key);

}

}

[java] view
plain copy

//一个内部类 封装了具体执行的动作

public static class SqlCommand {

//xml标签的id

private final String name;

//insert update delete select的具体类型

private final SqlCommandType type;

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) throws BindingException {

//拿到全名 比如 org.mybatis.example.BlogMapper.selectBlog
4000

String statementName = mapperInterface.getName() + "." + method.getName();

MappedStatement ms = null;

//获取MappedStatement对象 这个对象封装了XML当中一个标签的所有信息 比如下面

//<select id="selectBlog" resultType="Blog">

//select * from Blog where id = #{id}

//</select>

if (configuration.hasStatement(statementName)) {

ms = configuration.getMappedStatement(statementName);

} else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { // 这里是一个BUG

String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();

if (configuration.hasStatement(parentStatementName)) {

ms = configuration.getMappedStatement(parentStatementName);

}

}

//为空抛出异常

if (ms == null) {

throw new BindingException("Invalid bound statement (not found): " + statementName);

}

name = ms.getId();

type = ms.getSqlCommandType();

//判断SQL标签类型 未知就抛异常

if (type == SqlCommandType.UNKNOWN) {

throw new BindingException("Unknown execution method for: " + name);

}

}

public String getName() {

return name;

}

public SqlCommandType getType() {

return type;

}

}

//内部类 封装了接口当中方法的 参数类型 返回值类型 等信息

public static class MethodSignature {

//是否返回多调结果

private final boolean returnsMany;

//返回值是否是MAP

private final boolean returnsMap;

//返回值是否是VOID

private final boolean returnsVoid;

//返回值类型

private final Class<?> returnType;

//mapKey

private final String mapKey;

//resultHandler类型参数的位置

private final Integer resultHandlerIndex;

//rowBound类型参数的位置

private final Integer rowBoundsIndex;

//用来存放参数信息

private final SortedMap<Integer, String> params;

//是否存在命名参数

private final boolean hasNamedParameters;

//在这里对上面的属性进行初始化 就不一一详细说明了 具体实现细节可以看下面的代码。

public MethodSignature(Configuration configuration, Method method) throws BindingException {

this.returnType = method.getReturnType();

this.returnsVoid = void.class.equals(this.returnType);

this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());

this.mapKey = getMapKey(method);

this.returnsMap = (this.mapKey != null);

this.hasNamedParameters = hasNamedParams(method);

this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);

this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);

this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));

}

public Object convertArgsToSqlCommandParam(Object[] args) {

final int paramCount = params.size();

if (args == null || paramCount == 0) {

return null;

} else if (!hasNamedParameters && paramCount == 1) {

return args[params.keySet().iterator().next()];

} else {

final Map<String, Object> param = new ParamMap<Object>();

int i = 0;

for (Map.Entry<Integer, String> entry : params.entrySet()) {

param.put(entry.getValue(), args[entry.getKey()]);

// issue #71, add param names as param1, param2...but ensure backward compatibility

final String genericParamName = "param" + String.valueOf(i + 1);

if (!param.containsKey(genericParamName)) {

param.put(genericParamName, args[entry.getKey()]);

}

i++;

}

return param;

}

}

public boolean hasRowBounds() {

return (rowBoundsIndex != null);

}

public RowBounds extractRowBounds(Object[] args) {

return (hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null);

}

public boolean hasResultHandler() {

return (resultHandlerIndex != null);

}

public ResultHandler extractResultHandler(Object[] args) {

return (hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null);

}

public String getMapKey() {

return mapKey;

}

public Class<?> getReturnType() {

return returnType;

}

public boolean returnsMany() {

return returnsMany;

}

public boolean returnsMap() {

return returnsMap;

}

public boolean returnsVoid() {

return returnsVoid;

}

private Integer getUniqueParamIndex(Method method, Class<?> paramType) {

Integer index = null;

final Class<?>[] argTypes = method.getParameterTypes();

for (int i = 0; i < argTypes.length; i++) {

if (paramType.isAssignableFrom(argTypes[i])) {

if (index == null) {

index = i;

} else {

throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");

}

}

}

return index;

}

private String getMapKey(Method method) {

String mapKey = null;

if (Map.class.isAssignableFrom(method.getReturnType())) {

final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);

if (mapKeyAnnotation != null) {

mapKey = mapKeyAnnotation.value();

}

}

return mapKey;

}

private SortedMap<Integer, String> getParams(Method method, boolean hasNamedParameters) {

final SortedMap<Integer, String> params = new TreeMap<Integer, String>();

final Class<?>[] argTypes = method.getParameterTypes();

for (int i = 0; i < argTypes.length; i++) {

if (!RowBounds.class.isAssignableFrom(argTypes[i]) && !ResultHandler.class.isAssignableFrom(argTypes[i])) {

String paramName = String.valueOf(params.size());

if (hasNamedParameters) {

paramName = getParamNameFromAnnotation(method, i, paramName);

}

params.put(i, paramName);

}

}

return params;

}

private String getParamNameFromAnnotation(Method method, int i, String paramName) {

final Object[] paramAnnos = method.getParameterAnnotations()[i];

for (Object paramAnno : paramAnnos) {

if (paramAnno instanceof Param) {

paramName = ((Param) paramAnno).value();

}

}

return paramName;

}

private boolean hasNamedParams(Method method) {

boolean hasNamedParams = false;

final Object[][] paramAnnos = method.getParameterAnnotations();

for (Object[] paramAnno : paramAnnos) {

for (Object aParamAnno : paramAnno) {

if (aParamAnno instanceof Param) {

hasNamedParams = true;

break;

}

}

}

return hasNamedParams;

}

}

}

MapperMethod

通过上面的分析就很容易弄清楚Mybatis是如何利用JDK动态代理的机制生成代理类来对各种Mapper接口进行封装的了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: