zoukankan      html  css  js  c++  java
  • MyBatist庖丁解牛(二)

    站在巨人的肩膀上

    https://blog.csdn.net/xiaokang123456kao/article/details/76228684 

    一、概述

    我们知道,Mybatis实现增删改查需要进行XML的配置,其基本的配置如下:

    <?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>  

    上面的配置表示我们在命名空间org.mybatis.example.BlogMapper下定义一个ID为selectBlog的查询操作,

    其操作结果集为Blog,对应的语句为select * from Blog where id = #{id}

    那么在实际使用时,我们可以采用如下形式:

    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定义保持一致。

    1、新建一个com.kang.mapper的包,定义map接口,接口名任意,这里是UserMapper。

    package com.kang.mapper;  
    
    
    import java.util.List;  
    import com.kang.pojo.User;  
    public interface UserMapper {  
        //根据用户id查询用户信息  
        public User findUserById(int id) throws Exception;  
        //查询用户列表  
        public List<User> findUserByUsername(String username) throws Exception;  
        //添加用户信息  
        public void insertUser(User user)throws Exception;   
    }  
    • 2、配置xml文件 UserMapper.xml
    <?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="com.kang.mapper.UserMapper">  
    <!-- 注意这里的 namespace必须对应着map接口的全类名-->  
        <select id="findUserById" parameterType="int" resultType="user">  
            select * from user where id = #{id}  
        </select>  
    
        <select id="findUserByUsername" parameterType="java.lang.String"  
            resultType="user">  
            select * from user where username like '%${value}%'  
        </select>  
    
        <insert id="insertUser" parameterType="user">  
            <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">  
                select LAST_INSERT_ID()  
            </selectKey>  
            insert into user(username,birthday,sex,address)  
            values(#{username},#{birthday},#{sex},#{address})  
        </insert>  
    </mapper>  

    注意到配置文件中的id属性值和map接口中的方法名是一一对应的。

    • 3、在SqlMapConfig.xml中加入映射文件
    <!-- 加载 映射文件 -->  
        <mappers>  
            <mapper resource="map/UserMapper.xml" />  
        </mappers>
    • 4、调用方法
            //获取session  
            SqlSession session = sqlSessionFactory.openSession();  
            //获取mapper接口的代理对象  
            UserMapper userMapper = session.getMapper(UserMapper.class);  
            //调用代理对象方法  
            User user = userMapper.findUserById(27);  
            System.out.println(user);  
            //关闭session  
            session.close();  
            System.out.println("---------执行完毕-----------");  

    这样以来当我们修改了XML的ID以后,只需要修改接口中的方法就可以了,编译器会在其他使用该接口的地方报错,很容易进行修改。当然好处还不只这些,还可以通过与spring进行无缝集成,动态注入 等等。
    关于Mybatis的Mapp接口使用方法请参考这篇博文Mybatis之Mapper动态代理
    在上面的例子当中呢,UserMapper是一个接口 它并没有实现类,为什么接口可以直接使用呢? 那是因为MyBbatis使用了JDK动态代理机制动态生成了代理类,那么代理类又是如何多SqlSession进行封装的呢?带着这些疑问,让我们通过分析源代码的方式来解释这些问题。

    二、源码解析

    Mybatis关于包装Mapper的代码都在org.apache.ibatis.binding 这个包下面。其中有4个类: 
    MapperRegistry 类是注册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<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);
      }
    
    }

    类的getMapper方法里面最后会去调用MapperProxyFactory类的newInstance方法。

    从上面的源码可以看出来,在调用getMapper方法前会初始化MapperProxyFactory,它是创建Mapper代理对象的工厂,其源码如下:

    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);
      }
    }

    上述代码中的关键代码是

    final MapperProxy<T> mapperProxy = new MapperProxy<T>(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<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 {
       // method.getDeclareingClass()返回申请此方法的对象,任何对象都是集成Object的,所以会包含Object中的基础方法。
    // 基础方法无需代理实现
    // 判断是不是基础方法 比如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; } }

    从源码的意思:从缓存中获得执行方法对应的MapperMethod类实例。如果MapperMethod类实例不存在的情况,创建加入缓存并返回相关的实例。

    最后调用MapperMethod类的execute方法。
    到这里我们可以看到,getMapper方法就是用来获得相关的数据操作类接口。而事实数据操作类邦定了动态代理。

    所以操据操作类执行方法的时候,会触动每个方法相应的MapperProxy类的invoke方法。所以invoke方法返回的结果就是根据操作类执行方法的结果。

    这样子我们就知道最后的任务交给了MapperMethod类实例。
    那么MapperMethod的详细源码如下:

    &&&&&&&&&&&&&&&&&&最精华精华精华的部分&&&&&&&&&&&&&&&&&&&&&&&&&&&

    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);
        }
      }

    MapperMethod类里面有俩个成员:SqlCommand类和MethodSignature类。从名字上我们大概的能想到一个可能跟SQL语句有关系,一个可能跟要执行的方法有关系。事实也是如此。
    上面代码使用一个内部类SqlCommand来封装底层的增删改查操作,确切来讲这一部分的内容跟XxxMapper的XML配置文件里面的select节点、delete节点等有关。我们都会知道节点上有id属性值。那么MyBatis框架会把每一个节点(如:select节点、delete节点)生成一个MappedStatement类。要找到MappedStatement类就必须通过id来获得。有一个细节要注意:代码用到的id = 当前接口类 + XML文件的节点的ID属性。其源码如下:

    //一个内部类 封装了具体执行的动作
      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
          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;
        }
      }
    }

    看到这里的时候,我们就可以回头去找一找在什么时候增加了MappedStatement类。上面之所以可以执行是建立在XML配置信息都被加载进来了。所以MappedStatement类也一定是在加载配置的时候就进行的。请读者们自行查看一下MapperBuilderAssistant类的addMappedStatement方法——加深理解。SqlCommand类的name成员和type成员我们还是关注一下。name成员就是节点的ID,type成员表示查寻还是更新或是删除。至于MethodSignature类呢。他用于说明方法的一些信息,主要有返回信息。

    
    
    
  • 相关阅读:
    欧拉回路 定理
    UESTC 1087 【二分查找】
    POJ 3159 【朴素的差分约束】
    ZOJ 1232 【灵活运用FLOYD】 【图DP】
    POJ 3013 【需要一点点思维...】【乘法分配率】
    POJ 2502 【思维是朴素的最短路 卡输入和建图】
    POJ 2240 【这题貌似可以直接FLOYD 屌丝用SPFA通过枚举找正权值环 顺便学了下map】
    POJ 1860【求解是否存在权值为正的环 屌丝做的第一道权值需要计算的题 想喊一声SPFA万岁】
    POJ 1797 【一种叫做最大生成树的很有趣的贪心】【也可以用dij的变形思想~】
    js 实现slider封装
  • 原文地址:https://www.cnblogs.com/longxok/p/10873933.html
Copyright © 2011-2022 走看看