mybatis中Mapper接口的动态代理和接口参数源码解析

本章节主要讲解通过Mapper接口传参时,mybatis是如何处理这些参数的

 

EmployeeMapper接口的方法:

Employee getEmpByIdAndLastName(@Param("id")Integer id, @Param("lastName") String lastName);

EmployeeMapper接口对应的sql映射文件EmployeeMapper.xml中的sql:

对应的测试方法:

public SqlSessionFactory sessionFactory = null;

@BeforeEach
public void test()throws Exception{
// 根据全局配置文件(xml)创建一个SqlSessionFactory对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void selectTest(){
    // 获取 SqlSession 的实例 。SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。通过 SqlSession 实例来直接执行已映射的 SQL 语句
    SqlSession sqlSession = null;
    try {
        sqlSession = sessionFactory.openSession();
        // 通过获取接口代理对象来执行sql语句
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        Employee employee = mapper.getEmpByIdAndLastName(2,"BB");
        System.out.println(employee.getLastName()); // AA
    } finally {
        // 资源关闭,放在finally中确保一定会执行
        sqlSession.close();
    }
}

一、获取Mapper接口获取代理对象

EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

sqlSession通过 sqlSession.getMapper(EmployeeMapper.class)方法获取EmployeeMapper接口的代理对象,类的getMapper方法里面最后会去调用MapperProxyFactory类的newInstance方法。从上面的源码可以看出来,在调用getMapper方法前会初始化MapperProxyFactory,它是创建Mapper代理对象的工厂,其源码如下:

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, MapperProxyFactory> knownMappers = new HashMap, MapperProxyFactory>();
  public MapperRegistry(Configuration config) {
    this.config = config;
  }
  //获取生成的代理对象
  @SuppressWarnings("unchecked")
  public  T getMapper(Class type, SqlSession sqlSession) {
    //通过Mapper的接口类型 去Map当中查找 如果为空就抛异常
    final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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  boolean hasMapper(Class type) {
    return knownMappers.containsKey(type);
  }
  //注册Mapper接口
  public  void addMapper(Class 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(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> getMappers() {
    return Collections.unmodifiableCollection(knownMappers.keySet());
  }
    ResolverUtil> resolverUtil = new ResolverUtil>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set>> mapperSet = resolverUtil.getClasses();
    for (Class mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }
  //通过包名扫描下面所有接口
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }

}
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 {
  //具体Mapper接口的Class对象
  private final Class mapperInterface;
  //该接口下面方法的缓存 key是方法对象 value是对接口中方法对象的封装
  private Map methodCache = new ConcurrentHashMap();
  //构造参数没啥好说的
  public MapperProxyFactory(Class mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
  public Class getMapperInterface() {
    return mapperInterface;
  }
  public Map getMethodCache() {
    return methodCache;
  }
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy 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 mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
    //调用上面的方法 返回一个接口的代理类
    return newInstance(mapperProxy);
  }
}

上述代码中的关键代码是

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

在这里创建了MapperProxy对象 这个类实现了JDK的动态代理接口 InvocationHandler,其源码如下:

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 implements InvocationHandler, Serializable {
  private static final long serialVersionUID = -6424540398559729838L;
  //SqlSession
  private final SqlSession sqlSession;
  //接口的类型对象
  private final Class mapperInterface;
  //接口中方法的缓存 有MapperProxyFactory传递过来的。
  private final Map methodCache;
  //构造参数
  public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }
  //接口代理对象所有的方法调用 都会调用该方法
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //判断是不是基础方法 比如toString() hashCode()等,这些方法直接调用不需要处理
    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代理对象执行Sql语句

EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

 

sqlSession.getMapper方法会调用MapperMethod类的execute方法:

//这个类是整个代理机制的核心类,对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, mapperInterface, method);
    }
    //这个方法是对SqlSession的包装调用
    public Object execute(SqlSession sqlSession, Object[] args) {
        // 定义返回结果
        Object result;
        // 判断当前sql操作
        switch (command.getType()) {
            // 如果是Insert操作
            case INSERT: {
                // 处理参数
                // 执行sql
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            // 如果是UPDATE操作
            case UPDATE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            // 如果是DELETE操作
            case DELETE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            // 如果是SELECT操作
            case SELECT:
                //如果返回void 并且参数有resultHandler
                //则调用 void select(String statement, Object parameter, ResultHandler handler);方法
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;

                //如果返回多行结果这调用  List 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 if (method.returnsCursor()) {
                    result = executeForCursor(sqlSession, args);
                } else {
                    Object param = method.convertArgsToSqlCommandParam(args);
                    result = sqlSession.selectOne(command.getName(), param);
                }
                break;
            //如果是FLUSH操作,清空缓存
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            //如果全都不匹配 说明mapper中定义的方法不对
            default:
                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;
    }
}

其中主要获取参数的操作为:

 Object param = method.convertArgsToSqlCommandParam(args);

而获取参数最终会调用ParamNameResolver类的getNameParams方法

class ParamNameResolver {

    private static final String GENERIC_NAME_PREFIX = "param";
    private static final String PARAMETER_CLASS = "java.lang.reflect.Parameter";
    private static Method GET_NAME = null;
    private static Method GET_PARAMS = null;

    static {
        try {
            Class paramClass = Resources.classForName(PARAMETER_CLASS);
            GET_NAME = paramClass.getMethod("getName");
            GET_PARAMS = Method.class.getMethod("getParameters");
        } catch (Exception e) {
            // ignore
        }
    }

    // 存放Param注解的参数名
    private final SortedMap names;

    private boolean hasParamAnnotation;

    public ParamNameResolver(Configuration config, Method method) {
        final Class[] paramTypes = method.getParameterTypes();
        final Annotation[][] paramAnnotations = method.getParameterAnnotations();
        final SortedMap map = new TreeMap();
        int paramCount = paramAnnotations.length;
        
        // 把Param注解定义的参数放入names中
        for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
            if (isSpecialParameter(paramTypes[paramIndex])) {
                // skip special parameters
                continue;
            }
            String name = null;
            for (Annotation annotation : paramAnnotations[paramIndex]) {
                if (annotation instanceof Param) {
                    hasParamAnnotation = true;
                    name = ((Param) annotation).value();
                    break;
                }
            }
            if (name == null) {
                // 如果全局配置中没有配置useActualParamName=true,允许使用方法签名中的名称作为语句参数名称的话
                if (config.isUseActualParamName()) {
                    name = getActualParamName(method, paramIndex);
                }
                if (name == null) {
                    // use the parameter index as the name ("0", "1", ...)
                    // gcode issue #71
                    name = String.valueOf(map.size());
                }
            }
            map.put(paramIndex, name);
        }
        names = Collections.unmodifiableSortedMap(map);
    }

    private String getActualParamName(Method method, int paramIndex) {
        if (GET_PARAMS == null) {
            return null;
        }
        try {
            Object[] params = (Object[]) GET_PARAMS.invoke(method);
            return (String) GET_NAME.invoke(params[paramIndex]);
        } catch (Exception e) {
            throw new ReflectionException("Error occurred when invoking Method#getParameters().", e);
        }
    }

    private static boolean isSpecialParameter(Class clazz) {
        return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
    }

    public String[] getNames() {
        return names.values().toArray(new String[0]);
    }

    public Object getNamedParams(Object[] args) {
        // 获取当前参数的个数
        final int paramCount = names.size();
        // 如果为null或者0个直接返回null
        if (args == null || paramCount == 0) {
            return null;

        // 如果只有一个参数,并且没有Param注解,直接返回该参数
        } else if (!hasParamAnnotation && paramCount == 1) {
            return args[names.firstKey()];

        // 多个参数或者有Param注解
        } else {
            final Map param = new ParamMap();
            int i = 0;
            // 遍历names集合{0=id, 1=lastName}
            for (Map.Entry entry : names.entrySet()) {
                // 把names集合中的val作为param的key  {id=2, lastName=BB}
                param.put(entry.getValue(), args[entry.getKey()]);
                // 除了按照注解Param的方式,额外使用param作为key  {param1=2, param2=BB}
                final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
                if (!names.containsValue(genericParamName)) {
                    param.put(genericParamName, args[entry.getKey()]);
                }
                i++;
            }
            return param;
        }
    }
}

 

总结:

单个参数:mybatis不会做特殊处理,
    #{参数名/任意名}:取出参数值。
    
多个参数:mybatis会做特殊处理。
    多个参数会被封装成 一个map,
        key:param1...paramN,或者参数的索引也可以
        value:传入的参数值
    #{}就是从map中获取指定的key的值;
    
    异常:
    org.apache.ibatis.binding.BindingException: 
    Parameter 'id' not found. 
    Available parameters are [1, 0, param1, param2]
    操作:
        方法:public Employee getEmpByIdAndLastName(Integer id,String lastName);
        取值:#{id},#{lastName}

【命名参数】:明确指定封装参数时map的key;@Param("id")
    多个参数会被封装成 一个map,
        key:使用@Param注解指定的值
        value:参数值
    #{指定的key}取出对应的参数值


POJO:
如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo;
    #{属性名}:取出传入的pojo的属性值    

Map:
如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map
    #{key}:取出map中对应的值

TO:
如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)数据传输对象

 

 

public Employee getEmp(@Param("id")Integer id,String lastName);

    取值:id==>#{id/param1}   lastName==>#{param2}

public Employee getEmp(Integer id,@Param("e")Employee emp);

    取值:id==>#{param1}    lastName===>#{param2.lastName/e.lastName}

特别注意:如果是Collection(List、Set)类型或者是数组, 也会特殊处理。也是把传入的list或者数组封装在map中。
            key:Collection(collection),如果是List还可以使用这个key(list)、数组(array)

public Employee getEmpById(List ids);

    取值:取出第一个id的值:   #{list[0]}

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(mybatis)