深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)

        集中 MyBatis 框架的设计和核心代码的实现上,一些无关细节将会适当的忽略。
        MyBatis 的运行分为两部分,一部分是读取配置文件缓存到 Configuration对象,用以创建 SqlSessionFactory,第二部分是 SQLSession 的执行过程,相对而言,SqlSessionFactory 创建比较容易,而 SqlSession 的执行过程远远不是那么简单。

构建SqlSessionFactory 过程

        SqlSessionFactory 是 MyBatis 的核心类之一,其最重要的功能就是提供创建 MyBatis 核心接口 SqlSession,所以我们需要创建 SqlSessionFactory,为此我们需要提供配置文件和相关的参数,而 MyBatis 是一个复杂的系统,采用构造模式去创建 SqlSessionFactory,我们可以通过 SqlSessionFactoryBuilder 去构建,构建分为两部分。
        第一步,通过 org.apache.ibatis.builder.xml.XMLConfigBuilder 解析配置 XML文件,读取配置参数,并将读取的数据存入这个 org.apache.ibatis.session.Configuration 类中,注意,MyBatis 几乎所有的配置都是存储在这里。
        第二步,使用 Configuration 对象去创建 SqlSessionFactory,MyBatis 中的 SqlSessionFactory 是一个接口,而不是实现类,为此 MyBatis 提供了一个默认的 SqlSessionFactory 实现类,我们一般都会使用它org.apache.ibatis.session.defaults.DefaultSqlSessionFactory,注意,在大部分情况下我们都没有必要去自己创新 SqlSessionFactory 实现类。
        这种创建方式是一种 Builder模式,对于复杂的对象而言,直接使用构造方法构建有困难,这会导致大量的逻辑在构建方法中,由于对象的复杂性,在构建的时候,我们更加希望一步步有秩序的来构建它,从而降低其复杂性,这个时候使用一个参数类总领全局,例如,configuration 类,然后分步构建,例如:DefaultSqlSessionFactory 类,就可以构建一个复杂对象,例如,SqlSessionFactory,这种方式值得我们在工作中学习中使用。

构建Configuration

        在 SqlSessionFactory 构建中,Configuration 是最重要的,它的作用如下。

  • 读取配置文件,包括基础配置的 xml 文件和映射器 XML 文件。
  • 初始化基础配置,比如 MyBatis 的别名,一些重要的类对象,例如,插件,映射器,ObjectFactory和typeHandler对象。
  • 提供单例,为后续创建SesesionFactory服务提供配置参数
  • 执行一些重要对象方法,初始化配置信息。

        显然Configuration不是一个简单的类,MyBatis的配置信息都会来自于此,几乎所有的配置都会在这里找到踪影,全部都会被读入这里并保存为一个单例,Configuration是通过XMLConfigBuilder去构建的,首先,MyBatis会读出所有的XML配置信息,然后将这些信息保存到Configuration中类的单例中,它会做如下初始化。

  • properties全局参数
  • settings设置
  • typeAliases别名
  • typeHandler类型处理器
  • ObjectFactory对象
  • pllugin插件
  • environment环境
  • DatabaseIdProvider数据库标识
  • Mapper映射器

        在上一篇博客中己经对映射器做了详细的解析,这里再来回顾一下。

映射器的内部组成

        一般而言,一个映射器是由3个部分组成。

  • MappedStatement,它保存映射器的一个节点,(select|insert|delete|update),包括许多我们配置的SQL,SQL的id,缓存信息,resultMap,parameterType,resultType,languageDriver等重要配置内容。
  • SqlSource,它是提供BoundSql对象的地方,它是MappedStatement的一个属性。
  • BoundSql,它是建立BoundSql和参数的地方,它有3个属性,SQL,parameterObject,parameterMappings,稍后,我们讨论它们。
            这些都是映射器的重要内容,也是MyBatis的核心内容,在插件的应用中常常会用到它们,映射器的解析过程在上一篇博客中己经做了详细的说明,先来看看映射器的内部组成。
    深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第1张图片

        MappedStatement对象涉及到的东西很多,我们一般不去修改他,因为容易产生不必要的错误,SqlSource是一个接口,它的主要作用是根据参数和其他的规则组装SQL,这些东西很复杂,有兴趣的话,可以去看看我上一篇博客,对于参数和SQL而言,主要的规则都反映在BoudSql类对象上,在插件中往往需要拿到它进而可以拿到当前运行的SQL和参数及参数规则,做出适当的修改,来满足我们的需求。
        BoundSql会提供3个主要的属性:parameterMappings,parameterObject和Sql。

  • 其中parameterObject为参数本身,我们可以传递简单的对象,POJO,Map或者@Param注解的参数,由于它在插件中相当常用我们有必要讨论一下它的规则 。
  • 如果我们传递的是POJO或者Map,那么这个parameterObject就是你传入的POJO或者Map不变
  • 当然我们也可以传递多个参数,如果没有@Param注解,那么MyBatis就会把parameterObject变成一个Map对象,其键值的关系是按顺序来规划的,类似于这样的形式{“1”:p1,“2”:p2,“3”:p3…,“param1”:p1,“param2”:p2,“param3”:p3…},所以在编写的时候我们都可以使用#{param1}或者#{1}去引用第一个参数。
  • 如果我们使用@Param注解,那么MyBatis就会把parameterObject也会变成一个Map对象,类似于没有@Param注解,只是把其数字的键值对应转换为了@Param注解的键值,比如我们注解@Param(“key1”) String p1,@Param(“key2”) int p2,@Param(“key3”) User p3,那么parameterObject对象就是一个Map,它的键值包含:{“key1”:p1,“key2”:p2,“key3”:p3,“pram1”:p1,“param2”:p2,“param3”:p3}
  • parameterMappings,它是一个List,每一个元素都是ParameterMapping对象,这个对象会描述我们的参数,参数包括属性,名称,表达式,javaType,jdbcType,typeHandler,等重要信息,我们一般不需要去改变它,通过它可以实现参数和SQL的结合,以便PreparedStatement能够通过它找到parameterObject对象的属性并设置参数,使得程序准确运行。
  • sql属性就是我们书写在映射器里面的一条SQL 在大多数时候无需修改它,只有在插件的情况下,我们可以根据需要进行改写,改写SQL是一件危险的事情,请务必慎重行事。
构建SqlSessionFactory

        有了Configuration对象构建SqlSessionFactory就很简单了,我们只要写简单的代码,就可以完成
        sqlSEssionFactory = new SqlSessionFactoryBuilder().build(reader);
        MyBatis会根据Configuration的配置读取所有的配置信息,构建SqlSessionFactory。

SqlSession的运行过程

        SqlSession的运行过程是重点和难点,也是整个MyBatis难以理解的部分,SqlSession是一个接口,使用它并不复杂,我们构建SqlSessionFactory就可以轻易拿到SqlSesion了,SqlSession给出了查询 ,插入,更新,删除的方法,在旧版的MyBatis或iBatis中常常使用这些接口方法,而在新版的MyBatis中我们建义使用Mapper,所以它就是MyBatis最常用也是最重要的接口之一。
        但是,SqlSession的内部可没有那么容易,因为它的内部实现相当复杂。

映射器动态代理

        Mapper的映射是通过动态代理来实现的。我们获取getMapper()类来创建代理。

SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
MapperRegistry.java
public  T getMapper(Class type, SqlSession sqlSession) {
    final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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);
    }
}

        调用MapperProxyFactory创建JDK动态代理。

MapperProxyFactory.java
public class MapperProxyFactory {

  private final Class mapperInterface;
  private Map methodCache = new ConcurrentHashMap();

  public MapperProxyFactory(Class mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class getMapperInterface() {
    return mapperInterface;
  }

  public Map getMethodCache() {
    return methodCache;
  }


  public T newInstance(SqlSession sqlSession) {
    final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
}

        使用Proxy创建JDK动态代理,在理解这一块代码块之前,我们来看看代理的使用。

JDK动态代理

        JDK动态代理,是由JDK的java.lang.reflect.*包提供支持的,在测试之前,我们要完成下面的几个步骤。

  • 编写服务类和接口,这个是真正的服务提供者,在JDK代理中是必需的。
  • 编写代理类,提供绑定和代理方法。

        JDK动态代理最大的特点就是需要提供一个接口,而在MyBatis中正好有这样一个接口,因此,MyBatis是使用JDK动态代理。下面来看看JDK动态代理的实现。

  1. 创建接口
public interface HelloService {
    public void sayHello(String name);
}
  1. 创建接口实现类
public class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello(String name) {
        System.out.println("hello " + name);
    }
}
  1. 创建代理类

        现在我们写一个代理类,提供真实对象的绑定一代理方法,代理类要求实现InvocationHandler接口的代理方法,当一个对象被绑定后,执行其方法的时候就进入到代理方法里了。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class HelloServiceProxy implements InvocationHandler {

    //真实服务对象
    private Object target;
    
    //绑定委托的对象返回一个代理
    public Object bind(Object target){
        this.target = target;
        //取得代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);

    }

    /**
     * 通过代理对象调用方法首先进入这个方法
     * @param proxy   代理对象
     * @param method   被调用的方法
     * @param args   方法参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("#############我是JDK动态代理################");
        Object result = null;
        //反射方法前调用
        System.out.println("我准备说Hello");
        //执行方法,相当于调用HelloServiceImpl的sayHello方法
        result = method.invoke(target,args);
        //反射方法后调用
        System.out.println("我说过Hello了");
        return result;
    }
}

        上面这段代码让JDK产生一个代理对象,这个代理对象有三个参数,第一个参数是target.getClass().getClassLoader()是类加载器,第二个参数是target.getClass().getInterfaces()是接口(代理对象挂在哪个接口下),第三个参数是this,代表当前HelloServiceProxy类,也就是说HelloServiceProxy代理方法作为对象的代理执行者。
        一旦绑定后,在进入代理对应方法调用的时候会到HelloServiceProxy的代理方法上,代理方法有三个参数,第一个proxy是代理对象,第二个是当前调用的那个方法,第三个是方法的参数,比方说,现在HelloServiceImpl对象(obj)用bind方法绑定后,返回其占位,我们再调用proxy.sayHello(“张三”),那么就会进入到HelloServiceProxy的invoke()方法,而invoke参数中第一个便是代理对象proxy,方法便是sayHello,参数张三。
        我们己经用HelloServiceProxy类的属性target保存了真实服务对象,那么我们可以通过反射技术调用真实对象的方法。

  1. 测试
public class Test150 {
    public static void main(String[] args) {
        HelloServiceProxy helloServiceProxy = new HelloServiceProxy();
        HelloService helloService =(HelloService) helloServiceProxy.bind(new HelloServiceImpl());
        helloService.sayHello("张三");
    }
}

  1. 打印结果
    深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第2张图片
CGLIB动态代理

        JDK提供的动态代理存在一个缺陷,就是你必须提供接口才可以使用,为了克服这个缺陷,我们可以使用开源框架-CGLIB,它是一种流行的动态代理 。
        让我们看看如何使用CGLIB动态代理,HelloService.java和HelloServiceImpl.java这两个类,我们就不动了,我们增加CGLIB类,实现MethodInterceptor代理方法如下:

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class HelloServiceCglib implements MethodInterceptor {

    private Object target;

    //创建代理对象
    public Object getInstance(Object target) {

        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        //回调方法
        enhancer.setCallback(this);
        //创建代理对象
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("#########这是CGLIB代理#############");
        //反射方法前调用
        System.out.println("我准备说hello");
        Object returnObj = methodProxy.invokeSuper(obj, args);
        //反射方法后调用
        System.out.println("我己经说Hello了");
        return returnObj;
    }
}

测试:

public static void main(String[] args) {
    HelloServiceCglib cglibProxy = new HelloServiceCglib();
    HelloService helloService = (HelloService) cglibProxy.getInstance(new HelloServiceImpl());
    helloService.sayHello("张三");
}

结果:
在这里插入图片描述
        这样便能实现CGLIB动态代理了,在MyBatis中,通常在延迟加载的时候才会用于CGLIB动态代理,有了这个基础,我们来分析下面代码。

public class MapperProxy implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class mapperInterface;
  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 {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
      	//如果方法的声明类为Object类,直接反射调用
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      //通过接口和方法创建MapperMethod
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
}

        通过上面JDK动态代理和CGLIB动态代理的分析,我们终于知道上面这块代码的意思了,当我们通过sqlSession.getMapper()时,获取到的是一个代理对象,代理对象中设置了类为MapperProxy,回调方法为invoke()方法,因此,我们通过sqlSesion获取到的Mapper,调用Mapper中任意方法将会调用MapperProxy中的invoke()方法。

MapperMethod.java
public MapperMethod(Class mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, method);
}

        在进行SQL方法执行之前,我们来看看SqlCommand.class和MethodSignature.class 的实现。

SqlCommand.java
public static class SqlCommand {

    private final String name;
    private final SqlCommandType type;

    public SqlCommand(Configuration configuration, Class mapperInterface, Method method) throws BindingException {
    	//接口名+方法名=Configuration中存储Statement的key
        String statementName = mapperInterface.getName() + "." + method.getName();
        MappedStatement ms = null;
        if (configuration.hasStatement(statementName)) {
        	//Configuration中mappedStatements存在statementName
            ms = configuration.getMappedStatement(statementName);
        } else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { // issue #35
        	//如果mapperInterface和方法声明类不相等,则方法可能是父类继承过来的
        	//如selectOne()方法是 UserMapper extends BaseMapper 继承而来,
        	//则使用BaseMaper.selectOne再到Configuration的mappedStatements中查找
            String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
            if (configuration.hasStatement(parentStatementName)) {
                ms = configuration.getMappedStatement(parentStatementName);
            }
        }
        //如果MappedStatement为空,抛出异常
        if (ms == null) {
            throw new BindingException("Invalid bound statement (not found): " + statementName);
        }
        name = ms.getId();
        //初始化当前的SQL类型为UNKNOWN, INSERT, UPDATE, DELETE, SELECT中的一种,如果为UNKNOWN,则抛出异常
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
            throw new BindingException("Unknown execution method for: " + name);
        }
    }
    public String getName() {
        return name;
    }

    public SqlCommandType getType() {
        return type;
    }
}
MethodSignature.java
public static class MethodSignature {
    private final boolean returnsMany;
    private final boolean returnsMap;
    private final boolean returnsVoid;
    private final Class returnType;
    private final String mapKey;
    private final Integer resultHandlerIndex;
    private final Integer rowBoundsIndex;
    private final SortedMap params;
    private final boolean hasNamedParameters;

    public MethodSignature(Configuration configuration, Method method) throws BindingException {
    	//获取方法的返回值类型
        this.returnType = method.getReturnType();
        //如果方法的返回值类型为void,则returnsVoid为true
        this.returnsVoid = void.class.equals(this.returnType);
        //方法返回值类型是集体或者是数组
        this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
        //如果返回值类型是Map.class的子类,并且方法配置了MapKey注解,获取MapKey注解的value属性
        this.mapKey = getMapKey(method);
        //mapKey不为空,returnsMap值为true
        this.returnsMap = (this.mapKey != null);
        //方法参数是否配置了@Param注解
        this.hasNamedParameters = hasNamedParams(method);
        //获取RowBounds在方法参数的位置,如果方法中有多个RowBounds,将抛出异常,如果没有配置RowBounds,返回默认值null
        this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
        //获取ResultHandler类及子类在方法参数中的位置
        this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
        //从getParams()方法的实现可以看出,先过滤掉RowBounds,ResultHandler类型的方法参数,
        //然后再构建SortedMap集合,key为方法参数所在参数位置索引,值为@Param的value
        //或key为方法参数所在参数位置索引
        //这句话什么意思呢?举个例子吧,如下方法
        //void updateRealName(@Param("id") long id, @Param("realName") String realName,int a );
        //将会构建一个这样的Map对象
        // {"0":"id","1","realName","2","2"},如下图所示
        this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
    }

深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第3张图片

    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 SortedMap getParams(Method method, boolean hasNamedParameters) {
        final SortedMap params = new TreeMap();
        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;
    }

}

        经过上面的分析,我们己经知道了command属性和method属性的由来。下面来分析execute()方法。

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //如果SQL是INSERT类型
    if (SqlCommandType.INSERT == command.getType()) {
    	//将方法参数转化为SQL命令所需要的参数
        Object param = method.convertArgsToSqlCommandParam(args);
        //执行插入并统计影响行数
        result = rowCountResult(sqlSession.insert(command.getName(), param));
    //如果SQL是UPDATE类型
    } else if (SqlCommandType.UPDATE == command.getType()) {
        Object param = method.convertArgsToSqlCommandParam(args);
        //执行update并统计影响行数
        result = rowCountResult(sqlSession.update(command.getName(), param));
    //如果SQL是DELETE类型
    } else if (SqlCommandType.DELETE == command.getType()) {
        Object param = method.convertArgsToSqlCommandParam(args);
        //执行delete并统计影响行数
        result = rowCountResult(sqlSession.delete(command.getName(), param));
    //如果SQL是SELECT类型
    } else if (SqlCommandType.SELECT == command.getType()) {
    	//如果方法返回值为void类型,并且方法参数中ResultHandler类
        if (method.returnsVoid() && method.hasResultHandler()) {
            executeWithResultHandler(sqlSession, args);
            result = null;
        //如果方法返回值是Collection及子类型,或者返回值为Array类型
        } else if (method.returnsMany()) {
            result = executeForMany(sqlSession, args);
        //如果方法返回值为Map类及子类型,并且Mapper接口方法中配置了@MapKey注解
        } else if (method.returnsMap()) {
            result = executeForMap(sqlSession, args);
        } else {
            Object param = method.convertArgsToSqlCommandParam(args);
            //只查询一条记录
            result = sqlSession.selectOne(command.getName(), param);
        }
    } else {
    	//如果SQL类型是Unknown类型,抛出异常
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
    	//如果执行sql返回值为null,方法的返回值非void类型,并且是int,double等基本数据类型,抛出异常
        throw new BindingException("Mapper method '" + command.getName()
                + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
}
MapperMethod.java
public Object convertArgsToSqlCommandParam(Object[] args) {
    final int paramCount = params.size();
    if (args == null || paramCount == 0) {
    	//如果方法参数为空或方法参数个数为0
        return null;
    } else if (!hasNamedParameters && paramCount == 1) {
    	//如果方法参数只有一个,方法参数没有配置@Param注解,直接取args[i]个参数返回
    	//为什么是第i个,而不是第0个呢?因为方法参数类型可能是RowBounds或ResultHandler类型,
    	//这些类型是不会添加到params里
        return args[params.keySet().iterator().next()];
    } else {
        final Map param = new ParamMap();
        int i = 0;
        for (Map.Entry entry : params.entrySet()) {
        	//entry的key是1,2,3....,entry的值可能是id或username这些具体的属性名,也可能是"0","1","2"...
        	//如果有多个参数,配置了@Param,则取Param的value,如果方法参数没有配置@Param注解,则entry的value就是
        	//参数所在方法参数的位置
            param.put(entry.getValue(), args[entry.getKey()]);
            final String genericParamName = "param" + String.valueOf(i + 1);
            //增加pram1,pram2...
            if (!param.containsKey(genericParamName)) {
                param.put(genericParamName, args[entry.getKey()]);
            }
            i++;
        }
        return param;
    }
}

方法参数转换

  1. 如果方法参数为null,或方法参数个数为0 ,直接返回null。
  2. 如果方法参数个数为1个,方法参数没有配置@Param注解,则取方法参数位置处的参数值返回。如getUser(RowBounds rowBounds,ResultHandler resultHandler,Long id ),则取args[2]位置的值返回,如果是getUser(Long id ),则取args[0]返回。
  3. 当然我们也可以传递多个参数,如果没有@Param注解,那么MyBatis就会把parameterObject变成一个Map对象,其键值的关系是按顺序来规划的,类似于这样的形式{“1”:p1,“2”:p2,“3”:p3…,“param1”:p1,“param2”:p2,“param3”:p3…},所以在编写的时候我们都可以使用#{param1}或者#{1}去引用第一个参数。
  4. 如果我们使用@Param注解,那么MyBatis就会把parameterObject也会变成一个Map对象,类似于没有@Param注解,只是把其数字的键值对应转换为了@Param注解的键值,比如我们注解@Param(“key1”) String p1,@Param(“key2”) int p2,@Param(“key3”) User p3,那么parameterObject对象就是一个Map,它的键值包含:{“key1”:p1,“key2”:p2,“key3”:p3,“pram1”:p1,“param2”:p2,“param3”:p3}

方法参数封装己经清楚了,下面我们来对insert执行流程分析。
        众所究知,MyBatis底层是用jdbc来实现的,而jdbc所用到的方法中,用到最多的也是查询和更新方法,对于插入和删除操作实际对于jdbc而言也是更新操作。我们就挑选比较复杂的更新操作insert和查询来对MyBatis源码讲解,查询和插入操作,有一部分功能是类似的的,那就是statement sql请求参数封装部分,不同的部分是在返回结果的处理上。话不多说,直接来分析源码吧。

insert

DefaultSqlSession.java
public int insert(String statement, Object parameter) {
    return update(statement, parameter);
}
public int update(String statement, Object parameter) {
    try {
        dirty = true;
        //从configuration中获取当前statement对应的MappedStatement
        //statement的值如:com.spring_101_200.test_141_150.test_141_mybatis_usegeneratedkeys_keyproperty.UserMapper.insertUser
        MappedStatement ms = configuration.getMappedStatement(statement);
        //wrapCollection()方法主要是对于List集合和array类型用map包裹一下,如果parameter是List,则Object被替换为{"list",object}
        return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

        在执行更新之前,我们来看看executor这个重要的属性,executor是从哪里生成的呢?从下图中发现竟然是创建SqlSession时来创建executor的。我们来看看源码
深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第4张图片

DefaultSqlSessionFactory.java
public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
DefaultSqlSessionFactory.java
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        final Environment environment = configuration.getEnvironment();
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        //配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新
        final Executor executor = configuration.newExecutor(tx, execType);
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx); close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}
Configuration.java
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
        executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
        executor = new ReuseExecutor(this, transaction);
    } else {
        executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
        executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

        执行器(Executor)起到了至关重要的作用,它是一个真正的执行Java和数据库交互的东西,在MyBatis中存在三种执行器,那三个执行器之间的区别是什么呢?

  • SimpleExecutor是最简单的执行器,根据对应的sql直接执行即可,不会做一些额外的操作;
  • BatchExecutor执行器,顾名思义,通过批量操作来优化性能。通常需要注意的是批量更新操作,由于内部有缓存的实现,使用完成后记得调用flushStatements来清除缓存。
  • ReuseExecutor 可重用的执行器,重用的对象是Statement,也就是说该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能。内部的实现是通过一个HashMap来维护Statement对象的。由于当前Map只在该session中有效,所以使用完成后记得调用flushStatements来清除Map。

        CachingExecutor执行器不是真正意义的执行器,只是对上述三个执行器做了一个包装而已,目的是使用缓存,使不使用缓存执行器包装,主要通过全局配置cacheEnabled来控制,这里我们使用默认的执行器来研究

BaseExecutor.java
public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) throw new ExecutorException("Executor was closed.");
    //清理本地缓存
    clearLocalCache();
    //真正执行更新
    return doUpdate(ms, parameter);
}

        不是说只有三个有用的执行器嘛,怎么又出现了BaseExecutor执行器呢?看下图应该理解了BaseExecutor只是SimpleExecutor,BatchExecutor,ReuseExecutor的抽象而已。
深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第5张图片

SimpleExecutor.java
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
        stmt = prepareStatement(handler, ms.getStatementLog());
        return handler.update(stmt);
    } finally {
    	//close Statement
        closeStatement(stmt);
    }
}
Configuration.java
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}

        很显然创建真实的对象是一个RountingStatementHandler对象,它实现了接口StatementHandler,和Executor一样,用代理对象做一层层封装。
        RountingStatementHandler不是我们真实的服务对象,它是通过适配器模式找到对应的StatementHandler来执行,MyBatis中StatementHandler和Executor一样分为三种,SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler

RoutingStatementHandler.java
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
	//全局配置statementType来控制
    switch (ms.getStatementType()) {
        case STATEMENT:
            delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case PREPARED:
            delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case CALLABLE:
            delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        default:
            throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
}

        MappedStatement.getStatementType()的值选择 SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler。 CallableStatementHandler是处理存储过程的,而SimpleStatementHandler、PreparedStatementHandler有什么区别呢?区别是SimpleStatementHandler处理sql中不含有参数的,即sql中不含有参数,而PreparedStatementHandler则处理sql中有?的需要预编译的sql,那我们从源码的角度下来看看三个类的具体区别吧。经过分析,三个Handler其他的方法内容大同小异,最重要的区别在于parameterize()方法,下面我们来看看三个Handler对于这个方法的实现。

SimpleStatementHandler.java
public void parameterize(Statement statement) throws SQLException {
    // N/A
}
PreparedStatementHandler.java
public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
}
CallableStatementHandler.java
public void parameterize(Statement statement) throws SQLException {
    registerOutputParameters((CallableStatement) statement);
    parameterHandler.setParameters((CallableStatement) statement);
}

private void registerOutputParameters(CallableStatement cs) throws SQLException {
    List parameterMappings = boundSql.getParameterMappings();
    for (int i = 0, n = parameterMappings.size(); i < n; i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        //对于参数类型是OUT,INOUT参数处理
        if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) {
            if (null == parameterMapping.getJdbcType()) {
                throw new ExecutorException("The JDBC Type must be specified for output parameter.  Parameter: " + parameterMapping.getProperty());
            } else {
                if (parameterMapping.getNumericScale() != null && (parameterMapping.getJdbcType() == JdbcType.NUMERIC || parameterMapping.getJdbcType() == JdbcType.DECIMAL)) {
                    cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE, parameterMapping.getNumericScale());
                } else {
                    if (parameterMapping.getJdbcTypeName() == null) {
                        cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE);
                    } else {
                        cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE, parameterMapping.getJdbcTypeName());
                    }
                }
            }
        }
    }
}

        从三个方法的功能上来看,我们知道为什么MyBatis默认是使用PreparedStatementHandler作为默认处理器了,SimpleStatementHandler不支持动态SQL,因此一般不用,而CallableStatementHandler是支持存储过程的,一般我们在项目中不使用存储过程,因此默认使用PreparedStatementHandler处理器,而在本篇博客中,我们就使用PreparedStatementHandler来研究吧。

PreparedStatementHandler.java
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
BaseStatementHandler.java
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
    	//主要对select标签内部处理
        generateKeys(parameterObject);
        
        boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
protected void generateKeys(Object parameter) {
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    ErrorContext.instance().store();
    //对SQL执行前,做相关参数处理
    keyGenerator.processBefore(executor, mappedStatement, null, parameter);
    ErrorContext.instance().recall();
}

        对于KeyGenerator接口中有两个方法。processBefore()和processAfter() 代码如下:

public interface KeyGenerator {

  void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

  void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

}

        这两个方法分别在SQL执行前和SQL执行后对相应参数做处理,在MyBatis中KeyGenerator在三个实现类NoKeyGenerator,Jdbc3KeyGenerator,SelectKeyGenerator而这三个类中,对于processBefore()方法,只有keyGenerator是SelectKeyGenerator时才做处理,什么时候keyGenerator会变成SelectKeyGenerator呢?在之前的博客中,我们分析过parseSelectKeyNode()这个方法,在这个方法中,对于标签中配置了的标签或配置了@SelectKey注解的Mapper进行解析,并将结果保存到configuration的keyGenerators中。

configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));

        而在XMLStatementBuilder的parseStatementNode()中,对于每一个MappedStatement的添加方法中,

KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
//如果当前节点有 
    	//	select * from lz_user where username =#{_parameter.username} 
		//
    	//这个时候,取出username的propertyType为String类型
        propertyType = metaParameters.getGetterType(property);
    } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
    	//基本数据类型处理,如
    	//parameterType="com.spring_101_200.test_141_150.test_147_mybatis_constructorargs.User"
            resultType="com.spring_101_200.test_141_150.test_147_mybatis_constructorargs.User"/>
        //对于这种自定义类型情况,因为MyBatis没有提供typeHandler,因此,只能通过反射来获取参数类型了
        MetaClass metaClass = MetaClass.forClass(parameterType);
        if (metaClass.hasGetter(property)) {
            propertyType = metaClass.getGetterType(property);
        } else {
            propertyType = Object.class;
        }
    } else {
    	//如果都不符合条件,默认是Object类型了
        propertyType = Object.class;
    }
    ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
    Class javaType = propertyType;
    String typeHandlerAlias = null;
    //下面主要是将parseParameterMapping参数设置到ParameterMapping中
    for (Map.Entry entry : propertiesMap.entrySet()) {
        String name = entry.getKey();
        String value = entry.getValue();
        if ("javaType".equals(name)) {
            javaType = resolveClass(value);
            builder.javaType(javaType);
        } else if ("jdbcType".equals(name)) {
            builder.jdbcType(resolveJdbcType(value));
        } else if ("mode".equals(name)) {
            builder.mode(resolveParameterMode(value));
        } else if ("numericScale".equals(name)) {
            builder.numericScale(Integer.valueOf(value));
        } else if ("resultMap".equals(name)) {
            builder.resultMapId(value);
        } else if ("typeHandler".equals(name)) {
            typeHandlerAlias = value;
        } else if ("jdbcTypeName".equals(name)) {
            builder.jdbcTypeName(value);
        } else if ("property".equals(name)) {
            // Do Nothing
        } else if ("expression".equals(name)) {
        	//如果sql表达式是#{(xxx)}将抛出异常
            throw new BuilderException("Expression based parameters are not supported yet");
        } else {
            throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + parameterProperties);
        }
    }
    if (typeHandlerAlias != null) {
        builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
    }
    return builder.build();
}

        通过上述buildParameterMapping()方法分析,我们知道了ParameterMapping的propertyType的构建过程。到这里己经将sql解析完,

SimpleExecutor.java
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //获取Connection
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection);
    //为statement设置sql参数
    //也就是为select * from lz_user where id = ? ,为?设置值
    handler.parameterize(stmt);
    return stmt;
}
RoutingStatementHandler.java
public Statement prepare(Connection connection) throws SQLException {
    return delegate.prepare(connection);
}
BaseStatementHandler.java
public Statement prepare(Connection connection) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
        statement = instantiateStatement(connection);
        //设置执行statement的queryTimeout
        setStatementTimeout(statement);
        //设置statement的fetchSize
        setFetchSize(statement);
        return statement;
    } catch (SQLException e) {
        closeStatement(statement);
        throw e;
    } catch (Exception e) {
        closeStatement(statement);
        throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
}

protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
    	//如果是插入,并且需要返回主键
        String[] keyColumnNames = mappedStatement.getKeyColumns();
        if (keyColumnNames == null) {
        	//如果没有标签,如果有标签,但没有设置keyColumn属性
            return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
        } else {
        	//如果设置了标签,并且设置了keyColumn属性
            return connection.prepareStatement(sql, keyColumnNames);
        }
    } else if (mappedStatement.getResultSetType() != null) {
        return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
        return connection.prepareStatement(sql);
    }
}

        这个方法非常重要,如果想在插入数据后返回主键,创建PrepareStatement的方式必需注意。

RoutingStatementHandler.java
public void parameterize(Statement statement) throws SQLException {
    delegate.parameterize(statement);
}
PreparedStatementHandler.java
public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
}
DefaultParameterHandler.java
public void setParameters(PreparedStatement ps) throws SQLException {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
    	//遍历ParameterMapping
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            //如果不是存储过程
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                Object value;
                //获取属性名
                String propertyName = parameterMapping.getProperty();
                if (boundSql.hasAdditionalParameter(propertyName)) { 
                	//_parameter中获取属性值
                    value = boundSql.getAdditionalParameter(propertyName);
                } else if (parameterObject == null) {
                    value = null;
                } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                	//如果有类型处理器,则直接赋值,因为在类型处理器中会处理parameterObject值
                    value = parameterObject;
                } else {
                	//如果parameterObject是自定义类型,则一般通过反射调用属性的get方法来获取属性值
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    value = metaObject.getValue(propertyName);
                }
                TypeHandler typeHandler = parameterMapping.getTypeHandler();
                JdbcType jdbcType = parameterMapping.getJdbcType();
                if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();
                //setParameter()最终调用的是ps的setString()或setLong(),setDouble() ,setBigDecimal()... 方法,
                //我们选其中一个TypeHandler看一下,下面是StringTypeHandler内部实现。
                //public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
    			//	throws SQLException {
  				//	ps.setString(i, parameter);
				//}
				//setParameter()本质上就是调用ps的setXXX(i,value)方法,和jdbc执行sql一样了 
                typeHandler.setParameter(ps, i + 1, value, jdbcType);
            }
        }
    }
}

        数据准备好以后,下面来看方法的执行。

RoutingStatementHandler.java
public int update(Statement statement) throws SQLException {
    return delegate.update(statement);
}
PreparedStatementHandler.java
public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    //执行SQL
    ps.execute();
    //获取更新条数
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    //获取主键生成器
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    //sql执行后处理,如果是insert的话,主要是处理主键映射
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
}
Jdbc3KeyGenerator.java
public class Jdbc3KeyGenerator implements KeyGenerator {
    public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
        // do nothing
    }

    public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
        List parameters = new ArrayList();
        parameters.add(parameter);
        processBatch(ms, stmt, parameters);
    }

    public void processBatch(MappedStatement ms, Statement stmt, List parameters) {
        ResultSet rs = null;
        try {
            rs = stmt.getGeneratedKeys();
            final Configuration configuration = ms.getConfiguration();
            final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            //获取keyProperty="id" />
            //获取keyProperty值,可以以逗号隔开,如keyProperty="id,username,password" 
            final String[] keyProperties = ms.getKeyProperties();
            final ResultSetMetaData rsmd = rs.getMetaData();
            TypeHandler[] typeHandlers = null;
            if (keyProperties != null && rsmd.getColumnCount() >= keyProperties.length) {
                for (Object parameter : parameters) {
                	//如果rs.next()没有值,则返回
                    if (!rs.next()) break; 
                    final MetaObject metaParam = configuration.newMetaObject(parameter);
                    //根据keyProperties获取所有属性的类型处理器
                    //如username为String类型,id为Long类型,将获得类型处理器StringTypeHandler,LongTypeHandler
                    if (typeHandlers == null) typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties);
                    populateKeys(rs, metaParam, keyProperties, typeHandlers);
                }
            }
        } catch (Exception e) {
            throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
        } finally {
            if (rs != null) {
                try {
                	//close ResultSet 
                    rs.close();
                } catch (Exception e) {
                    // ignore
                }
            }
        }
    }

    private TypeHandler[] getTypeHandlers(TypeHandlerRegistry typeHandlerRegistry, MetaObject metaParam, String[] keyProperties) {
        TypeHandler[] typeHandlers = new TypeHandler[keyProperties.length];
        for (int i = 0; i < keyProperties.length; i++) {
        	//Sql的parameterType的对象User中是否有id属性的set方法
            if (metaParam.hasSetter(keyProperties[i])) {
            	//如果有setId()方法,则获取方法参数类型
                Class keyPropertyType = metaParam.getSetterType(keyProperties[i]);
                //方法参数类型获取类型转换器,如id是Long类型,则从系统自定义默认转换器中获取Long类型转换器LongTypeHandler
                TypeHandler th = typeHandlerRegistry.getTypeHandler(keyPropertyType);
                typeHandlers[i] = th;
            }
        }
        return typeHandlers;
    }
    private void populateKeys(ResultSet rs, MetaObject metaParam, String[] keyProperties, TypeHandler[] typeHandlers) throws SQLException {
        for (int i = 0; i < keyProperties.length; i++) {
            TypeHandler th = typeHandlers[i];
            if (th != null) {
            	//从rs中获取返回值,如:rs.getLong(1)
                Object value = th.getResult(rs, i + 1);
                //通过反射调用User对象的setId()方法为主键id赋值
                metaParam.setValue(keyProperties[i], value);
            }
        }
    }
}

        代码分析到这里,我相信大家对MyBatis insert 执行有了深入的了解了,从整个上来看,代码还是比较清晰,虽然当中有各种executor选择,handler选择,动态sql处理,sql参数封装,返回值主键封装,有些可能比较绕,整个过程还是不复杂的,上面插入操作,下面我用jdbc的方式来实现,更加有助于大家对MyBatis插入操作的理解。

@Test
public void jdbcInsert() {
    Connection conn = null;
    PreparedStatement pstemt = null;
    try {
        //注册加载jdbc驱动
        Class.forName("com.mysql.jdbc.Driver");
        //打开连接
        conn = DriverManager.getConnection("jdbc:mysql://172.16.157.238:3306/pple_test?characterEncoding=utf-8", "ldd_biz", "Hello1234");
        //创建执行对象
        String sql = " INSERT INTO lz_user (username, password, real_name, manager_id )" +
                " VALUES " +
                " ( ?, ?, ?, ?)";
        pstemt = conn.prepareStatement(sql, com.mysql.jdbc.Statement.RETURN_GENERATED_KEYS);
        pstemt.setString(1, "18389328");
        pstemt.setString(2, "123456");
        pstemt.setString(3, "张三");
        pstemt.setString(4, "1");
        //执行sql语句
        int num = pstemt.executeUpdate();
        System.out.println(num);
		//在MyBatis中如果配置了useGeneratedKeys="true" keyProperty="id"  />
		//useGeneratedKeys和keyProperty属性,获取主键值反射封装到parameterType的User对象中
        ResultSet rs = pstemt.getGeneratedKeys();
        while (rs.next()){
        	//打印主键id
            System.out.println("============" + rs.getLong(1));
        }
        conn.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

        对于MyBatis操作update ,delete比insert少了主键的处理,这里就不做过多分析,但是值得一提的是update操作中标签的使用,使用如下:


    update
    lz_user
    prefix="set" suffixOverrides="," >
        is_delete = #{isDelete},
        gmt_create = #{gmtCreate},
        username = #{username},
        password = #{password},
        real_name = #{realName},
        manager_id = #{managerId},
        sex = #{sex},
        sexStr != null">sex_str = #{sexStr}
    
    ,gmt_modified = now()
    where id = #{id}

        上面例子中,如果没有配置suffixOverrides=",",同时sexStr为null,那么拼接出来的sql后缀中有一个【,】而【,gmt_modified = now()】的前缀也有逗号,那就导致了sql中可能会出现相邻的两个逗号。如果上述例子中没有配置prefix=“set”,那么set 应该写在里呢?写在is_delete = #{isDelete} 前面,那么如果isDelete为null,则更新语句中没有set了肯定报错,如果同时写在set is_delete = #{isDelete} 和set username = #{username}前面,假如username和isDelete都不为空,update sql中将会出现两个set,执行还是会报错,真是猪八戒照镜子,里外不是人,所幸的是Mybatis在trim标签中给我们提供了下面4个属性prefixOverrides,prefix,suffixOverrides,suffix,那这4个属性有什么作用呢?

  • prefixOverrides:只要sql字符串以prefixOverrides为前缀,替换成""(空)
  • suffixOverrides:只要sql字符串以suffixOverrides为后缀,替换成""(空)
  • prefix:直接在sql字符串前面拼上prefix
  • suffix:直接在sql字符串后面拼上suffix

        理解是这样理解,那我们来看看源码的实现吧。

public class TrimSqlNode implements SqlNode {

    private SqlNode contents;
    private String prefix;
    private String suffix;
    private List prefixesToOverride;//prefixOverrides以|隔开得到的集合
    private List suffixesToOverride;//suffixOverrides以|隔开得到的集合
    private Configuration configuration;

    public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {
    	//将suffixOverrides或prefixOverrides以|隔开
        this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));
    }

    protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List prefixesToOverride, String suffix, List suffixesToOverride) {
        this.contents = contents;
        this.prefix = prefix; //将标签中prefix属性到该值中
        this.prefixesToOverride = prefixesToOverride;//将标签中prefixOverrides属性到该值中
        this.suffix = suffix;//将标签中suffix属性到该值中
        this.suffixesToOverride = suffixesToOverride;//将标签中suffixOverrides属性到该值中
        this.configuration = configuration;
    }

    public boolean apply(DynamicContext context) {
        org.apache.ibatis.scripting.xmltags.TrimSqlNode.FilteredDynamicContext filteredDynamicContext = new org.apache.ibatis.scripting.xmltags.TrimSqlNode.FilteredDynamicContext(context);
        boolean result = contents.apply(filteredDynamicContext);
        filteredDynamicContext.applyAll();
        return result;
    }


    private class FilteredDynamicContext extends DynamicContext {
        private DynamicContext delegate;
        private boolean prefixApplied;
        private boolean suffixApplied;
        private StringBuilder sqlBuffer;

        public FilteredDynamicContext(DynamicContext delegate) {
            super(configuration, null);
            this.delegate = delegate;
            this.prefixApplied = false;
            this.suffixApplied = false;
            this.sqlBuffer = new StringBuilder();
        }

        public void applyAll() {
            sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
            String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
            if (trimmedUppercaseSql.length() > 0) {
            	//处理前缀
                applyPrefix(sqlBuffer, trimmedUppercaseSql);
                //处理后缀
                applySuffix(sqlBuffer, trimmedUppercaseSql);
            }
            delegate.appendSql(sqlBuffer.toString());
        }

        private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
        	//prefixApplied默认值为false
            if (!prefixApplied) {
                prefixApplied = true;
                if (prefixesToOverride != null) {
                    for (String toRemove : prefixesToOverride) {
                    	//如果sql以prefixesToOverride集合中的元素开头,直接删除
                        if (trimmedUppercaseSql.startsWith(toRemove)) {
                            sql.delete(0, toRemove.trim().length());
                            break;
                        }
                    }
                }
                //直接在sql开头插入前缀 prefix + 空格
                if (prefix != null) {
                    sql.insert(0, " ");
                    sql.insert(0, prefix);
                }
            }
        }

        private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
        	//suffixApplied的默认值为false
            if (!suffixApplied) {
                suffixApplied = true;
                if (suffixesToOverride != null) {
                    for (String toRemove : suffixesToOverride) {
                    	//如果sql以suffixesToOverride集合中的元素结束,直接删除
                        if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) {
                            int start = sql.length() - toRemove.trim().length();
                            int end = sql.length();
                            sql.delete(start, end);
                            break;
                        }
                    }
                }
                //直接拼上后缀 空格 + suffix 
                if (suffix != null) {
                    sql.append(" ");
                    sql.append(suffix);
                }
            }
        }
    }
}

        通过TrimSqlNode节点分析,我们己经理解了prefixOverrides,prefix,suffixOverrides,suffix 4个属性的实现原理,现在再来理解下面的Mapper.xml,相信大家十分清楚了。


    update
    lz_user
    prefix="set" suffixOverrides="," >
        is_delete = #{isDelete},
        gmt_create = #{gmtCreate},
        username = #{username},
        password = #{password},
        real_name = #{realName},
        manager_id = #{managerId},
        sex = #{sex},
        sexStr != null">sex_str = #{sexStr}
    
    ,gmt_modified = now()
    where id = #{id}

        无论if条件生成的sql字符串有没有【,】结束,都会删除掉【,】,再在gmt_modified = now()前面拼接一个【,】sql就不会报错了,因为是update语句,因此,无论if条件生成什么样的sql字符串,都会在前面插入set 字符串。这样就能保证无论什么情况,动态sql都不会报错了。当然SetSqlNode和WhereSqlNode都是继承TrimSqlNode,其实现也是非常的简单,代码如下。

public class SetSqlNode extends TrimSqlNode {
  private static List suffixList = Arrays.asList(",");
  public SetSqlNode(Configuration configuration,SqlNode contents) {
    super(configuration, contents, "SET", null, null, suffixList);
  }
}

        从上面代码来看标签处理只是设置了prefix为SET,suffixOverrides为,也就是说上面trim标签(prefix=“set” suffixOverrides="," />)可以替换成&set />标签。


    update
    lz_user
    
        is_delete = #{isDelete},
        gmt_create = #{gmtCreate},
        username = #{username},
        password = #{password},
        real_name = #{realName},
        manager_id = #{managerId},
        sex = #{sex},
        sex_str = #{sexStr}
    
    ,gmt_modified = now()
    where id = #{id}

        上面插入了一个小插曲,对 标签分析,相信此时此刻大家对insert和update及delete操作有了深入的理解,下面来分析select操作,因为select和insert操作相比,前面传入参数处理相同,不同在于返回结果集的处理,下面来分析select返回结果集的处理吧。

select

        在了解select部分源码时,我们先来看一个例子。后面大部分源码解析都是围绕着这个例子来讲解析。

  1. 准备数据库表lz_user
    CREATE TABLE lz_user (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            is_delete tinyint(2) DEFAULT ‘0’,
            gmt_create datetime DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’,
            gmt_modified datetime DEFAULT CURRENT_TIMESTAMP,
            username varchar(32) DEFAULT NULL COMMENT ‘用户名’,
            password varchar(64) DEFAULT NULL COMMENT ‘密码’,
            real_name varchar(64) DEFAULT NULL,
            manager_id int(11) DEFAULT NULL COMMENT ‘管理员id’,
            sex int(11) DEFAULT ‘1’ COMMENT ‘性别’,
            sex_str varchar(32) DEFAULT NULL,
    PRIMARY KEY (id)
    ) ENGINE=InnoDB AUTO_INCREMENT=501 DEFAULT CHARSET=utf8mb4 COMMENT=‘公益口罩’;
    数据库表中的数据如下:
    深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第11张图片

  2. 准备UserMapper.xml


  1. 准备UserMapper.xml
User getUser(Long id);

4.测试

@Test
public void testGetUser() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = userMapper.getUser(456l);
    System.out.println(JSON.toJSONString(user));
}

        关于如何创建SqlSession,如何写mybatis-config.xml配置文件,这里将不再赘述,有兴趣的同学可以去github上下载我的代码,https://github.com/quyixiao/spring_tiny/tree/master/src/main/java/com/spring_101_200/test_121_130/test_125_mybatis_properties ,下载下来去测试。

MapperMethod.java
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.");
    }
    //将方法的参数转化为一个对象或者Map
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
    	//如果方法参数中有RowBounds,获取并返回
        RowBounds rowBounds = method.extractRowBounds(args);
        sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
    } else {
        sqlSession.select(command.getName(), param, method.extractResultHandler(args));
    }
}
DefaultSqlSession.java
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
        MappedStatement ms = configuration.getMappedStatement(statement);
        executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

        调用sqlSession的select方法。并传入了用户自定义的ResultHandler,对于SqlSession中的select方法,有一个特点,就是都没有返回值,同时都传入了ResultHandler,好像特定为处理ResultHandler而写的方法。

public interface SqlSession extends Closeable {

  void select(String statement, Object parameter, ResultHandler handler);

  void select(String statement, ResultHandler handler);

  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
}

        对于ResultHandler的使用,我们可能没什么感觉,下面来看一个例子吧。

例3.1

  1. 创建MyDefaultResultSetHandler继承ResultHandler
public class MyDefaultResultSetHandler implements ResultHandler {

    private Map> resultMap = new HashMap<>();

    @Override
    public void handleResult(ResultContext context) {
        Object object = context.getResultObject();
        if (object instanceof User) {
            User user = (User)object;
            List userList = resultMap.get(user.getRealName());
            if(userList == null){
                userList = new ArrayList<>();
            }
            userList.add(user);
            resultMap.put(user.getRealName(),userList);
        }
    }

    public Map> getResultMap() {
        return resultMap;
    }
}

        MyDefaultResultSetHandler不做过多的事情,直接打印回调回来的ResultContext对象中的resultObject,并将相同realName的用户存储到一个List集合中。

  1. 新建UserMapper
void getUserByResultHandler(@Param("id") long id, ResultHandler resultHandler);

注意: getUserByResultHandler()方法返回值必需是void类型,不然ResultHandler不起作用。因为上述源码就是如下写法。

if (method.returnsVoid() && method.hasResultHandler()) {
    executeWithResultHandler(sqlSession, args);
    result = null;
}
  1. 修改UserMapper.xml

        虽然UserMapper中返回值为void类型,但是在Mapper.xml中resultType不能为空。

  1. 测试
@Test
public void test5() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    MyDefaultResultSetHandler myDefaultResultSetHandler = new MyDefaultResultSetHandler();
    userMapper.getUserByResultHandler(456l,myDefaultResultSetHandler);
    System.out.println(JSON.toJSONString(myDefaultResultSetHandler.getResultMap()));
}
  1. 执行结果
    在这里插入图片描述

        从执行结果来看,每得到一个Object对象,都会调用一次MyDefaultResultSetHandler的handleResult()方法。MyDefaultResultSetHandler中将用户真实名字相同的用户存储到了resultMap中。这个例子没有什么实际意义,但是给我们提供了ResultSetHandler的使用。

MapperMethod.java
private  Object executeForMany(SqlSession sqlSession, Object[] args) {
    List result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
        RowBounds rowBounds = method.extractRowBounds(args);
        result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
        result = sqlSession.selectList(command.getName(), param);
    }
    //Collections和arrays类型支持
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
    	//如果Mapper方法返回值是数组类型,则将sql返回的List集合转化为数组类型
        if (method.getReturnType().isArray()) {
            return convertToArray(result);
        } else {
        	//只要是Collection类型及子类型,创建该类型实例,并调用其addAll()方法,将List集合加入到实例中,并返回
            return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
        }
    }
    //Mapper方法返回值类型本身是List类型,直接返回
    return result;
}
MapperMethod.java
private  Map executeForMap(SqlSession sqlSession, Object[] args) {
    Map result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
        RowBounds rowBounds = method.extractRowBounds(args);
        result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
    } else {
        result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
    }
    return result;
}
DefaultSqlSession.java
public  Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
    final List list = selectList(statement, parameter, rowBounds);
    final DefaultMapResultHandler mapResultHandler = new DefaultMapResultHandler(mapKey,
            configuration.getObjectFactory(), configuration.getObjectWrapperFactory());
    final DefaultResultContext context = new DefaultResultContext();
    for (Object o : list) {
        context.nextResultObject(o);
        mapResultHandler.handleResult(context);
    }
    Map selectedMap = mapResultHandler.getMappedResults();
    return selectedMap;
}
DefaultMapResultHandler.java
public void handleResult(ResultContext context) {
    final V value = (V) context.getResultObject();
    final MetaObject mo = MetaObject.forObject(value, objectFactory, objectWrapperFactory);
    //获取@MapKey注解的value值,并调用该属性值的get()方法,获取实体中的该属性值
    final K key = (K) mo.getValue(mapKey);
    //以此属性值作为key存储该实体
    mappedResults.put(key, value);
}

        关于上面这一段源码可能有些同学还是不太理解,那我们来举一个例子吧。

  1. 新建UserMapper
@MapKey("username")
Map getUserByMap(@Param("id") long id);
  1. 在UserMapper.xml中创建getUserByMap

  1. 测试
@Test
public void test4() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    Map user = userMapper.getUserByMap(456l);
    System.out.println(JSON.toJSONString(user));
}

在这里插入图片描述

        从结果中我们可以看到,以username为key,User对象为值,封装了一个Map返回。虽然实现简单,但是对方法是有要求的,必需Mapper接口方法返回值为Map类及子类型,同时Mapper接口方法中配置了@MapKey注解,并且设置了value的值。

DefaultSqlSession.java
public  T selectOne(String statement, Object parameter) {
    List list = this.selectList(statement, parameter);
    if (list.size() == 1) {
        return list.get(0);
    } else if (list.size() > 1) {
        throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
        return null;
    }
}

        对于selectOne的实现就更加简单了,直接取selectList()中的第0个元素就可以了。
        无论是executeForMany(),executeForMap()还是selectOne()方法,最终都调用了selectList()方法,只是对selectList()返回的List对象做不同的处理而己,executeForMany()方法根据Mapper接口方法返回值的不同而做不同的处理,如果返回值是Collection类型及子类型,用DefaultObjectFactory生成其(Collection类型及子类型)实例,再调用addAll()方法,将List集合加入到实例中,并返回,如果返回值是数组类型,则将List集合转化为数组并返回。executeForMap() 方法,将实体的某个属性值作为key,实体本身为value,封装Map返回。selectOne()方法就更加简单了,直接取List集合中的第0个元素返回就可以了。因此,对于Select操作,最主要的处理是在selectList()方法上,下面,我们来看看selectList()方法的实现吧。

DefaultSqlSession.java
public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        MappedStatement ms = configuration.getMappedStatement(statement);
        //ResultHandler NO_RESULT_HANDLER == null
        List result = executor.query(ms, wrapCollection(parameter), rowBounds,Executor.NO_RESULT_HANDLER );
        return result;
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

        代码分析到这里,我们终于知道了executeWithResultHandler()方法和executeForMany(),executeForMap(),selectOne()方法的区别,executeWithResultHandler()最终也是调用了executor的query()方法,但是对结果集不做处理,也不返回,但是传入了自定义的ResultHandler,而executeForMany(),executeForMap(),selectOne()最终调用了selectList()方法,selectList()方法传入的ResultHandler为null,并返回结果集。

  1. executeWithResultHandler()方法
  • ResultHandler为自定义类型
  • 返回值为null
  1. executeForMany(),executeForMap(),selectOne()方法
  • ResultHandler为null
  • 返回值为List
BaseExecutor.java
public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    //MyBatis 对于其 Key 的生成采取规则为:[mappedStementId + offset + limit + SQL + queryParams + environment]生成一个哈希码
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
BaseExecutor.java
public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) throw new ExecutorException("Executor was closed.");
    //如果在select标签中配置了flushCache="true",查询之前刷新本地缓存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        clearLocalCache();
    }
    List list;
    try {
    	//加一,这样递归调用到上面的时候就不会再清局部缓存了
        queryStack++;
        //resultHandler为null,并且同一个sqlSession中有两次相同的查询(包括sql查询参数,分页参数,sql 等),则取本地缓存返回
        list = resultHandler == null ? (List) localCache.getObject(key) : null;
        if (list != null) {
        	//如果查到localCache缓存,处理localOutputParameterCache
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        } else {
        	//从数据库中查询 
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    } finally {
    	//清空堆栈
        queryStack--;
    }
    
    if (queryStack == 0) {
    	//延迟加载队列中所有元素
        for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        //清空延迟加载队列
        deferredLoads.clear(); // issue #601
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        	//如果是statement,清本地缓存
            clearLocalCache(); // issue #482
        }
    }
    return list;
}
//处理存储过程的out参数
private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
    if (ms.getStatementType() == StatementType.CALLABLE) {
        final Object cachedParameter = localOutputParameterCache.getObject(key);
        if (cachedParameter != null && parameter != null) {
            final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
            final MetaObject metaParameter = configuration.newMetaObject(parameter);
            for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
                if (parameterMapping.getMode() != ParameterMode.IN) {
                    final String parameterName = parameterMapping.getProperty();
                    final Object cachedValue = metaCachedParameter.getValue(parameterName);
                    metaParameter.setValue(parameterName, cachedValue);
                }
            }
        }
    }
}
BaseExecutor.java
private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List list;
    //向缓存中放入占位符
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
    	//查询数据库
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
    	//清除占位符
        localCache.removeObject(key);
    }
    //结果集加入缓存
    localCache.putObject(key, list);
    //如果是存储过程,OUT参数也加入缓存
    if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}
SimpleExecutor.java
public  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        Configuration configuration = ms.getConfiguration();
        //创建statement
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        //准备statement,
        //为statement设置sql参数
    	//也就是为select * from lz_user where id = ? ,为?设置值,等处理
        stmt = prepareStatement(handler, ms.getStatementLog());
        return handler.query(stmt, resultHandler);
    } finally {
    	//关闭statement
        closeStatement(stmt);
    }
}
PreparedStatementHandler.java
public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  //执行sql
  ps.execute();
  //结果集处理
  return resultSetHandler. handleResultSets(ps);
}

        在处理结果集之前,我们来看看resultSetHandler是怎样创建的。

BaseStatementHandler.java
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
        generateKeys(parameterObject);
        boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}

        在创建StatementHandler时创建resultSetHandler,而resultSetHandler是调用configuration的newResultSetHandler()方法创建。下面我们来看看newResultSetHandler()方法实现。

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
                                            ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    //sql执行前调用拦截器链
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
}

        从newResultSetHandler()方法中,我们得知,resultSetHandler默认是DefaultResultSetHandler。下面我们来看看DefaultResultSetHandler的handleResultSets()方法处理。

DefaultResultSetHandler.java
public List handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List multipleResults = new ArrayList();

    int resultSetCount = 0;
    //将数据库中的元数据转化为java能识别的数据结构,封装于ResultSetWrapper对象中,结构如图3.0
    ResultSetWrapper rsw = getFirstResultSet(stmt);
	//获取到Mapper.xml中所配置的resultMap或resultType,如图3.1所示
    List resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    //如果rsw不为空,并且没有配置resultType或resultMap将抛出异常
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
        ResultMap resultMap = resultMaps.get(resultSetCount);
        //对结果集处理
        handleResultSet(rsw, resultMap, multipleResults, null);
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResulSets();
    if (resultSets != null) {
        while (rsw != null && resultSetCount < resultSets.length) {
            ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
            if (parentMapping != null) {
                String nestedResultMapId = parentMapping.getNestedResultMapId();
                ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
                handleResultSet(rsw, resultMap, null, parentMapping);
            }
            rsw = getNextResultSet(stmt);
            cleanUpAfterHandlingResultSet();
            resultSetCount++;
        }
    }
    return collapseSingleResultList(multipleResults);
}

        从下图中,我们己经了解ResultSetWrapper了数据及结构,为我们后面的数据封装奠定基础。

图3.0
深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第12张图片

深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第13张图片

图3.1
深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第14张图片

DefaultResultSetHandler.java
private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
    ResultSet rs = stmt.getResultSet();
    while (rs == null) {
    	//如果驱动程序没有将结果集作为第一个结果返回,则继续获取第一个结果集(HSQLDB 2.1)
        if (stmt.getMoreResults()) {
            rs = stmt.getResultSet();
        } else {
            if (stmt.getUpdateCount() == -1) {
                // no more results. Must be no resultset
                break;
            }
        }
    }
    //如果结果集不为空,创建ResultSetWrapper
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
}
ResultSetWrapper.java
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
    super();
    //初始化类型处理器
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    //初始化结果集
    this.resultSet = rs;
    final ResultSetMetaData metaData = rs.getMetaData();
    //初始化数据库表中的列数
    final int columnCount = metaData.getColumnCount();
    for (int i = 1; i <= columnCount; i++) {
        columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
        jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
        classNames.add(metaData.getColumnClassName(i));
    }
}

        从rs中获取ResultSetMetaData,而ResultSetMetaData的值如下图所示,使用ResultSetMetaData结果集元数据,遍历每一个field,将数据库中的每一列的列名,jdbc类型,Java类型名都封装到ResultSetWrapper实体中。
深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第15张图片

DefaultResultSetHandler.java
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
        if (parentMapping != null) {
            handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
        } else {
        	//如果用户没有自定义resultHandler传入,则使用默认的resultHandler
            if (resultHandler == null) {
                DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
                //处理数据库查询返回的每一行数据,并将结果保存到DefaultResultHandler的list集合中
                handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
                //将DefaultResultHandler的list集合数据添加到结果multipleResults中
                multipleResults.add(defaultResultHandler.getResultList());
            } else {
            	//对于用户传入了resultHandler的处理,使用如:例3.1,传入了我们自定义的MyDefaultResultSetHandler
                handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
            }
        }
    } finally {
    	//关闭ResultSet
        closeResultSet(rsw.getResultSet()); // issue #228 (close resultsets)
    }
}


DefaultResultSetHandler.java
private void  handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
	//如果有子查询,做相应的较验
    if (resultMap.hasNestedResultMaps()) {
    	//safeRowBoundsEnabled较验
        ensureNoRowBounds();
        //safeResultHandlerEnabled较验
        checkResultHandler();
        //复杂嵌套查询处理
        handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
    	handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
}

        在MyBatis中判断当前查询是否包含子查询 ,如下必需在resultMap中有association,collection或case,并且在association或collection标签中不能有select属性。源码如下:

private String processNestedResultMappings(XNode context, List resultMappings) throws Exception {
    if ("association".equals(context.getName())
            || "collection".equals(context.getName())
            || "case".equals(context.getName())) {
        if (context.getStringAttribute("select") == null) {
            ResultMap resultMap = resultMapElement(context, resultMappings);
            return resultMap.getId();
        }
    }
    return null;
}

        可能对上面的讲述还是有点困惑,我们来举个例子,下面的resultMap是子查询的。因为有标签,但是标签中没有select属性。


    
    <collection property="billList" ofType="Bill" >
        
        
        
        
    collection>

        下面的例子不是子查询 ,因为即使有<association/>标签,但是<association/>标签中有select属性,因此被判定为不是子查询。


    
    
    
    
    
    
        
            <association property="user" javaType="User" select="findUserById" column="user_id"/>
        
    



private void ensureNoRowBounds() {
	//如果sql中有子查询 ,并且全局配置文件中配置了safeRowBoundsEnabled=true,同时传入了rowBounds分页参数,将抛出
	//Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. Use safeRowBoundsEnabled=false setting to bypass this check.
	//异常
    if (configuration.isSafeRowBoundsEnabled() && rowBounds != null && (rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT || rowBounds.getOffset() > RowBounds.NO_ROW_OFFSET)) {
        throw new ExecutorException("Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. "
                + "Use safeRowBoundsEnabled=false setting to bypass this check.");
    }
}

        说了这么多,对于safeRowBoundsEnabled的使用,我们还是来举个例子吧。

例3.3

  1. 在mybatis-config.xml中添加全局配置

    
    

  1. 添加UserMapper
public interface UserMapper {
    UserBillInfo selectUserBill(@Param("id") Long id, @Param("rowBounds") RowBounds rowBounds);
    
    List selectUserBills(@Param("id") long id, @Param("rowBounds") RowBounds rowBounds);
}
  1. 添加UserMapper.xml



    
        
        <collection property="billList" ofType="Bill" >
            
            
            
            
        collection>

    

    

    
    

        我相信大家对selectUserBill方法和selectUserBills方法感到好奇,两个方法在Mapper.xml的写法是一样的嘛。为什么写重复的呢?来看测试结果

4.测试1

@Test
public void test3() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    UserBillInfo userBillInfo = userMapper.selectUserBill(456l,new RowBounds(0,5));
    System.out.println(JSON.toJSONString(userBillInfo));
}

深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第16张图片

  1. 测试2
@Test
public void test4() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List userBillInfos = userMapper.selectUserBills(456l,new RowBounds(0,5));
    System.out.println(JSON.toJSONString(userBillInfos));
}

深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第17张图片
        上面两个测试中,selectUserBill()和selectUserBills()两个方法的唯一区别就是Mapper.java中的返回值不同,一个是返回UserBillInfo,一个是返回List,正因为返回值的不同,才使得执行结果产生差异,因为selectUserBill()方法返回UserBillInfo对象,在execute()方法中,调用了selectOne()方法,而selectOne()调用selectList()方法时,传入的RowBounds.DEFAULT【selectList(statement, parameter, RowBounds.DEFAULT) 】,而RowBounds.DEFAULT的offset和limit分别是0和Integer.MAX_VALUE,因此if (configuration.isSafeRowBoundsEnabled() && rowBounds != null && (rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT || rowBounds.getOffset() > RowBounds.NO_ROW_OFFSET)) 条件不满足,因此不会抛出异常,但是selectUserBills()方法最终调用的是execute()方法中,调用了executeForMany()方法,而executeForMany()方法中,调用selectList()方法时,传入的是用户自定义的rowBounds【selectList(command.getName(), param, method.extractRowBounds(args))】,因此if (configuration.isSafeRowBoundsEnabled() && rowBounds != null && (rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT || rowBounds.getOffset() > RowBounds.NO_ROW_OFFSET)) 条件满足,将抛出

### Error querying database.  Cause: org.apache.ibatis.executor.ExecutorException: Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. Use safeRowBoundsEnabled=false setting to bypass this check.
### The error may exist in spring_101_200/config_131_140/spring136_mybatis_saferowboundsenabled/UserMapper.xml
### The error may involve com.spring_101_200.test_131_140.test_136_mybatis_saferowboundsenabled.UserMapper.selectUserBills
### The error occurred while handling results
### SQL: select * from lz_user lu  left join lz_user_bill lub on lu.id =lub.user_id where lu.id =456
### Cause: org.apache.ibatis.executor.ExecutorException: Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. Use safeRowBoundsEnabled=false setting to bypass this check.

    at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:26)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:111)
    at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:117)
    at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:63)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:52)
    at com.sun.proxy.$Proxy4.selectUserBills(Unknown Source)
    at com.spring_101_200.test_131_140.test_136_mybatis_saferowboundsenabled.Test136.test4(Test136.java:53)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	...

异常,我相信对saferowboundsenabled的使用有了详细的了解了,那大家对safeResultHandlerEnabled的理解应该也不难了,这里感兴趣的小伙伴可以去对safeResultHandlerEnabled的配置如何使用去研究一下。这里就不再赘述了。

protected void checkResultHandler() {
	//如果sql中有子查询 ,并且全局配置文件中配置了safeResultHandlerEnabled=true,同时传入了自定义结果处理器resultHandler,将抛出异常
    if (resultHandler != null && configuration.isSafeResultHandlerEnabled() && !mappedStatement.isResultOrdered()) {
        throw new ExecutorException("Mapped Statements with nested result mappings cannot be safely used with a custom ResultHandler. "
                + "Use safeResultHandlerEnabled=false setting to bypass this check "
                + "or ensure your statement returns ordered data and set resultOrdered=true on it.");
    }
}
DefaultResultSetHandler.java
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
        throws SQLException {
    DefaultResultContext resultContext = new DefaultResultContext();
    //如果rowBounds的offset大于零,则跳过offset之前的结果集
    skipRows(rsw.getResultSet(), rowBounds);
    //如果resultCount小于rowBounds中的limit,rowBounds的limit默认是Integer.MAX_VALUE
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
    	//如果有鉴别器,则获取鉴别器的resultMap,如果没有鉴别器,discriminatedResultMap默认是传入的resultMap
        ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
        //处理每一行元数据,得到结果对象
        Object rowValue = getRowValue(rsw, discriminatedResultMap);
        //存储结果对象,如果Mapper方法参数中有RsultTypeHandler,则回调其handleResult方法
        storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
}
DefaultResultSetHandler.java
public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) throws SQLException {
    Set pastDiscriminators = new HashSet();
    //如果resultMap下有鉴别器
    Discriminator discriminator = resultMap.getDiscriminator();
    while (discriminator != null) {
    	//如:
    	//获取鉴别器中column的值
        final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
        //获取case所对应的MapId,
        //如:com.spring_101_200.test_141_150.test_146_mybatis_discriminator.UserMapper.mapper_resultMap[ordersUserLazyLoading]_discriminator_case[1]
        final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
        //如果configuration中注册了相应的resultMap
        if (configuration.hasResultMap(discriminatedMapId)) {
            resultMap = configuration.getResultMap(discriminatedMapId);
            //保存当前discriminator为最后的鉴别器
            Discriminator lastDiscriminator = discriminator;
            //当前鉴别器的resultMap中是否还有鉴别器,如果有,则找到最里层的鉴别器,如果没有返回当前鉴别器的resultMap
            discriminator = resultMap.getDiscriminator();
            //为了解决重复引用和循环依赖问题
            if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
                break;
            }
        } else {
            break;
        }
    }
    return resultMap;
}

        关于鉴别器的获取这一块,这样分析,可能大家还不是很理解resolveDiscriminatedResultMap()这个方法,下面来看一个例子

例3.5

  1. 准备POJO
@Data
public class Bill {
    private Long id;
    private String type;
    private Long userId;
    private BigDecimal amount;
    private User user;
}

@Data
public class UserBill {
    private Long id;
    private Integer isDelete;
    private String type;
    private Long userId;
    private BigDecimal amount;
    private User user;

}
@Data
public class UserBillInfo {
    private Long id;
    private List billList;
    private User user;
}
  1. 准备UserMapper
public interface UserMapper {
    UserBillInfo selectUserBill(Long id );
}
  1. 准备UserMapper.xml



    
        
        
        
            
            
                
            
        
    

    
        
        
            
            
            
            
        
    

    
        
            
            
        
    


    

        上述代码的主要意思是,如果用户没有被删除了,并且性别1的用户,打印出用户信息,如果用户被删除了,打印出用户的账单信息。

  1. 测试
@Test
public void test1() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    UserBillInfo user = userMapper.selectUserBill(456l);
    System.out.println(JSON.toJSONString(user));
}

  1. 测试:将用户id=456的用户is_delete置为0,并且sex置为1
    深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第18张图片

结果输出:
在这里插入图片描述

  1. 测试:将用户id=456的用户is_delete置为1

深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第19张图片

测试结果:
深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第20张图片

        鉴别器如例3.5使用,相信此时再来理解resolveDiscriminatedResultMap()方法,大家就没有那样迷惑了,while第一次循环获取userBillMapChoose内的鉴别器,第二次循环获取userResult内的鉴别器,返回id为userResult的resultMap。

DefaultResultSetHandler.java
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    //创建结果对象
    Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null);
    if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
    	//获取resultType或resultMap的元对象
        final MetaObject metaObject = configuration.newMetaObject(resultObject);
        //resultMap中是否配置了constructor标签或@ConstructorArgs注解
        boolean foundValues = resultMap.getConstructorResultMappings().size() > 0;
        //如果在resultMap中配置了autoMapping="true"或全局配置autoMappingBehavior配置了FULL或PARTIAL,则对属性自动映射
        //autoMappingBehavior:指定 MyBatis是否以及如何自动映射指定的列到字段或属性,NONE 表示取消自动映射,
        //PARTIAL只会自动映射没有定义嵌套结果集映射的结果集 FULL会自动映射任意复杂的结果集(包括嵌套和其他情况)
        if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {
            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
        }
        
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
        //只要对象中有懒加载属性,将返回resultObject对象
        foundValues = lazyLoader.size() > 0 || foundValues;
        //如果没有任何值被设置到resultObject中,将返回null
        resultObject = foundValues ? resultObject : null;
        return resultObject;
    }
    return resultObject;
}
DefaultResultSetHandler.java
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    final List> constructorArgTypes = new ArrayList>();
    final List constructorArgs = new ArrayList();
    //创建返回结果对象
    final Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
    //如果结果对象有对应的类型处理器,调用类型处理器的getNullableResult()方法返回值返回
    if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
        final List propertyMappings = resultMap.getPropertyResultMappings();
        for (ResultMapping propertyMapping : propertyMappings) {
        	//如果当前属性需要查询其他数据,并且配置了延迟加载
        	//如:
        	//该属性的getXXX()方法的调用将使用CGLIB代理
        	//因为对象属性的getXXX()方法是对象本身的方法,不是通过实现接口而来的,因此,在这里只能使用CGLIB代理
            if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { // issue gcode #109 && issue #149
            	//创建代理对象
                return configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
            }
        }
    }
    return resultObject;
}

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List> constructorArgTypes, List constructorArgs, String columnPrefix)
        throws SQLException {
    final Class resultType = resultMap.getType();
    final List constructorMappings = resultMap.getConstructorResultMappings();
    //resultType是否有类型处理器
    if (typeHandlerRegistry.hasTypeHandler(resultType)) {
        return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
    } else if (constructorMappings.size() > 0) {
    	//对Mapper接口中配置了 
    	//@ConstructorArgs({
        //	@Arg(column = "id", javaType = Long.class),
        //	@Arg(column = "is_delete", javaType = Integer.class)
		//})
		//或在Mapper.xml中配置了
		//
	    //	
        //		 column="id" javaType="long">
        //		 column="is_delete" javaType="int">
    	//	
    	//	
    	//	
    	//	...
		//将会走下面代码 
        return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
    } else {
        return objectFactory.create(resultType);
    }
}

private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    final Class resultType = resultMap.getType();
    final String columnName;
    //
    //	
    //	
    //	
    //	
    //	...
	//
	//如果是上述配置,取resultMap第0个ResultMapping,也就是id作为columnName值
    if (resultMap.getResultMappings().size() > 0) {
        final List resultMappingList = resultMap.getResultMappings();
        final ResultMapping mapping = resultMappingList.get(0);
        columnName = prependPrefix(mapping.getColumn(), columnPrefix);
    } else {
    	//取表中的第0列作为columnName值
        columnName = rsw.getColumnNames().get(0);
    }
    final TypeHandler typeHandler = rsw.getTypeHandler(resultType, columnName);
    return typeHandler.getResult(rsw.getResultSet(), columnName);
}
public T getResult(ResultSet rs, String columnName) throws SQLException {
    T result = getNullableResult(rs, columnName);
    if (rs.wasNull()) {
        return null;
    } else {
        return result;
    }
}
//调用实现类的getNullableResult()方法
public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;

        对于createPrimitiveResultObject()方法的处理,可能还是有人比较迷惑,来看下面例子吧

例3.6

  1. 注册实体的typeHandler,在mybatis-config.xml添加typeHandlers标签

  
    


  1. 准备UserMapper
public interface UserMapper {
   User getUser(Long id);
}

  1. 准备UserMapper.xml

  1. 准备UserTypeHandler
public class UserTypeHandler extends BaseTypeHandler {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, User parameter, JdbcType jdbcType) throws SQLException {
        System.out.println("=================setNonNullParameter==============");

    }

    @Override
    public User getNullableResult(ResultSet rs, String columnName) throws SQLException {
        System.out.println("==================getNullableResult======111=======");
        String username =rs.getString("username");
        System.out.println("username:" + username);
        String password = rs.getString("password");
        System.out.println("password:" + password);
        User user =  new User();
        user.setUsername(username);
        user.setPassword(password);
        return user;
    }

    @Override
    public User getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        System.out.println("================getNullableResult=======22222========");
        return null;
    }

    @Override
    public User getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        System.out.println("================getNullableResult========33333=======");
        return null;
    }
}

        UserTypeHandler 不做过多处理,直接获取用户名和密码,封装成User对象返回。

  1. 测试
@Test
public void testGetUser() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = userMapper.getUser(456l);
    System.out.println(JSON.toJSONString(user));

}
  1. 结果输出
    深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第21张图片

        从结果中我们可以看到,打印了用户名和密码,并且覆盖了MyBatis的默认返回值。为什么呢?因为根据rsw.getTypeHandler(resultType, columnName)方法将获取到UserTypeHandler处理器,再调用UserTypeHandler处理器的getNullableResult()方法,而MyBatis只对没有TypeHandler对象进行封装。通过这个例子,我相信大家对createPrimitiveResultObject()方法有了深刻的理解。
        我们来看另一种情况,就是配置了@ConstructorArg注解或resultMap中使用了标签的情况,createParameterizedResultObject()方法就是对此种情况解析。

private Object createParameterizedResultObject(ResultSetWrapper rsw, Class resultType, List constructorMappings, List> constructorArgTypes,
                                               List constructorArgs, String columnPrefix) throws SQLException {
    boolean foundValues = false;
    for (ResultMapping constructorMapping : constructorMappings) {
    	//获取arg或idArg中的javaType属性
        final Class parameterType = constructorMapping.getJavaType();
        //获取arg或idArg中的column属性
        final String column = constructorMapping.getColumn();
        final Object value;
        //如果arg中配置了select属性
        if (constructorMapping.getNestedQueryId() != null) {
            value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
        //如果arg中配置了resultMap属性
        } else if (constructorMapping.getNestedResultMapId() != null) {
            final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId());
            value = getRowValue(rsw, resultMap);
        } else {
        	//普通属性处理,没有配置select和resultMap的情况
            final TypeHandler typeHandler = constructorMapping.getTypeHandler();
            value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
        }
        constructorArgTypes.add(parameterType);
        constructorArgs.add(value);
        foundValues = value != null || foundValues;
    }
    //封装构建函数参数,通过对象工厂实例化对象
    return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}

        对于上述方法,根据注释,可能有很多同学还是不理解,下面我们来看一个例子。

  1. 准备数据库表
    lz_user表
    CREATE TABLE lz_user (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            is_delete tinyint(2) DEFAULT ‘0’,
            gmt_create datetime DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’,
            gmt_modified datetime DEFAULT CURRENT_TIMESTAMP,
            username varchar(32) DEFAULT NULL COMMENT ‘用户名’,
            password varchar(64) DEFAULT NULL COMMENT ‘密码’,
            real_name varchar(64) DEFAULT NULL,
            manager_id int(11) DEFAULT NULL COMMENT ‘管理员id’,
            sex int(11) DEFAULT ‘1’ COMMENT ‘性别’,
            sex_str varchar(32) DEFAULT NULL,
            PRIMARY KEY (id)
    ) ENGINE=InnoDB AUTO_INCREMENT=501 DEFAULT CHARSET=utf8mb4 COMMENT=‘公益口罩’;
    深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第22张图片

lz_user_bill表
CREATE TABLE lz_user_bill (
        id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
        is_delete tinyint(2) DEFAULT ‘0’,
        gmt_create datetime DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’,
        gmt_modified datetime DEFAULT CURRENT_TIMESTAMP,
        type varchar(32) DEFAULT ‘-’ COMMENT ‘收支类型’,
        user_id int(11) DEFAULT NULL COMMENT ‘用户id’,
        manager_id int(11) DEFAULT NULL COMMENT ‘管理员id’,
        amount decimal(12,2) DEFAULT NULL,
        remark text COMMENT ‘备注’,
        bill_type varchar(256) DEFAULT NULL COMMENT ‘账单类型’,
        pay_type varchar(255) DEFAULT NULL COMMENT ‘支付方式’,
        status int(11) DEFAULT ‘0’ COMMENT ‘-1表示作费,0表示提交,1表示已经报销’,
        self_look int(11) DEFAULT ‘0’ COMMENT ‘0表示公开,1表示仅仅自己可见’,
        PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=62 DEFAULT CHARSET=utf8mb4 COMMENT=‘公益口罩’;
深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第23张图片
用户表和用户账单表是一对多的关系,在lz_user_bill表中有一个user_id相关联

  1. 准备POJO
@Data
public class User {
    private Long id;
    private Integer isDelete;
    private Date gmtCreate;
    private Date gmtModified;
    private String username;
    private String password;
    private String realName;
    private Long managerId;
    private Long userId;
    public User(Long id, Integer isDelete) {
        this.id = id;
        this.isDelete = isDelete;
    }
}


@Data
public class UserBill implements java.io.Serializable {
    private Long id;
    private Integer isDelete;
    private Date gmtCreate;
    private Date gmtModified;
    private String type;
    private Long userId;
    private Long managerId;
    private BigDecimal amount;
    private String remark;
    private String billType;
    private String payType;
    private Integer status;
    private Integer selfLook;
    private User user ;
    public UserBill(User user ){
        this.user = user;
    }
}


@Data
public class DateInfo {
    //创建时间
    private Date gmtCreate;
    private Date gmtModified;
}



@Data
public class UserBillInfo {
    private Long id;
    private Integer isDelete;
    private DateInfo dateInfo;
    public UserBillInfo(DateInfo dateInfo) {
        this.dateInfo = dateInfo;
    }
    //收支类型
    private String type;
    //用户id
    private Long userId;
    //管理员id
    private Long managerId;
    private BigDecimal amount;
    //备注
    private String remark;
    //账单类型
    private String billType;
    //支付方式
    private String payType;
    //-1表示作费,0表示提交,1表示已经报销
    private Integer status;
    //0表示公开,1表示仅仅自己可见
    private Integer selfLook;
}
  1. 准备mybatis-config.xml



    

    
        
        
        
        
		
    

    
        
    

    
        
            
            
                
                
                
                
            
        
    

    
        
    

为了更加体现测试效果,我们将autoMappingBehavior设置为NONE

  1. 准备SqlSessionFactory
public class MyBatisUtil {

    private final static SqlSessionFactory sqlSEssionFactory;

    static {
        String resource = "spring_101_200/config_141_150/spring147_mybatis_constructorargs/mybatis-config.xml";
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        sqlSEssionFactory = new SqlSessionFactoryBuilder().build(reader);

    }

    public static SqlSessionFactory getSqlSEssionFactory(){
        return sqlSEssionFactory;
    }
}
  1. 准备UserMapper
public interface UserMapper {
	//----------------------test1----------------------
    @ConstructorArgs({
            @Arg(column = "id", javaType = Long.class),
            @Arg(column = "is_delete", javaType = Integer.class)
    })
    @Select("select * from lz_user where id = #{id}")
    User getUser(Long id);

    User getUserById(long id);

    UserBill getUserBillById(@Param("id") Long id );
    
    UserBillInfo getUserBillResultMapById(Long id);
}

  1. 准备UserMapper.xml


	
	
    
        
            
            
        
        
    

    
    
	
    
        
            
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    

    

	
    
        
        
    

    
        
            
        
        
        
        
        
        
        
        
        
        
        
        
    

    


  1. 测试4.1
static SqlSessionFactory sqlSessionFactory = null;

static {
    sqlSessionFactory = MyBatisUtil.getSqlSEssionFactory();
}

@Test
public void test1() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = userMapper.getUser(456l);
    System.out.println(JSON.toJSONString(user));
}

        在全局配置中,我们使用autoMappingBehavior为NONE,这样主要是为了体现使用了自定义构造函数来构建对象,而不是使用默认的构造函数来创建的。
        test1()中,我们使用了注解的方式来使用constructor属性,但是值得注意的是只有@Arg注解,没有IdArg注解,同时只能是Mapper.java中只能使用@Select 或 @Update等来写sql,不能既使用@ConstructorArgs注解,又使用Mapper.xml中写sql的方式来实现查询。
        测试结果使用了User对象中自定义构建函数来创建对象。
在这里插入图片描述

  1. 测试4.2
@Test
public void test2() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User userB = userMapper.getUserById(456l);
    System.out.println(JSON.toJSONString(userB));
}

        在resultMap内使用了constructor标签,constructor标签内有idArg和arg标签可以使用,测试效果和使用注解的方式实现一样。
深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第24张图片

  1. 测试4.3
@Test
public void test3() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    UserBill userB = userMapper.getUserBillById(60l);
    System.out.println(JSON.toJSONString(userB));
}

        在UserBill实体中有一个构建函数,需要传入了User对象。在id为UserBillBaseResultMap的resultMap中,arg标签中有一个select属性,属性映射了id为getUserBillById的查询方法,同时设置了column属性为user_id,从下面测试中可以看出 ,MyBatis先查询出结果集,然后从结果集中取出userId作为参数调用getUserBillById的查询方法查询出User对象,再将User对象传入UserBill的构造方法,构造出UserBill对象,从而将ResultSet中结果集封装到UserBill对象中并返回。

深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第25张图片

  1. 测试4.4
@Test
public void test4() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    UserBillInfo userB = userMapper.getUserBillResultMapById(60l);
    System.out.println(JSON.toJSONString(userB));
}

        test4()相对于test3()就更加简单了,只是将结果集中的数据封装到arg中的resultMap中而已。
深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第26张图片

private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix) throws SQLException {
	//获取查询id
	//com.spring_101_200.test_141_150.test_147_mybatis_constructorargs.UserMapper.getUserById
    final String nestedQueryId = constructorMapping.getNestedQueryId();
    //从configuration获取到查询的statement
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
    //获取arg中column的java类型
    final Class nestedQueryParameterType = nestedQuery.getParameterMap().getType();
    //获取arg标签column属性对应的值
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping, nestedQueryParameterType, columnPrefix);
    Object value = null;
    //如果arg标签column属性对应的值不为空
    if (nestedQueryParameterObject != null) {
        final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
        final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
        final Class targetType = constructorMapping.getJavaType();
        //封装查询需要的参数
        final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
        //查询并返回结果对象
        value = resultLoader.loadResult();
    }
    return value;
}

public Object loadResult() throws SQLException {
	//最终还是调用了SqlSession的selectList方法
    List list = selectList();
    resultObject = resultExtractor.extractObjectFromList(list, targetType);
    return resultObject;
}

private  List selectList() throws SQLException {
    Executor localExecutor = executor;
    if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) {
        localExecutor = newExecutor();
    }
    try {
    	//从数据库中查询
        return localExecutor. query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, cacheKey, boundSql);
    } finally {
        if (localExecutor != executor) {
        	//如果执行器不相等,关闭执行器
            localExecutor.close(false);
        }
    }
}

        相信通过上述源码分析,大家对arg标签中配置select属性使用有了深刻的理解。对于arg中配置了resultMap的情况,只是递归调用了getRowValue()方法而已,在getRowValue()方法中,将结果集封装到resultMap中并返回,再作为构建函数参数创建实例,而普通参数的处理,就是直接从结果集中取出属性值作为构造函数参数创建实例。

CglibProxyFactory.java
public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) {
  return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
}

EnhancedResultObjectProxyImpl.java
private static class EnhancedResultObjectProxyImpl implements MethodInterceptor {

    private Class type;
    private ResultLoaderMap lazyLoader;
    private boolean aggressive;
    private Set lazyLoadTriggerMethods;
    private ObjectFactory objectFactory;
    private List> constructorArgTypes;
    private List constructorArgs;

    private EnhancedResultObjectProxyImpl(Class type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) {
        this.type = type;
        this.lazyLoader = lazyLoader;
        //获取全局配置的aggressiveLazyLoading设置
        //aggressiveLazyLoading:将积极加载改为消极加载及按需加载,当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载,默认值 true,,,
        //如果aggressiveLazyLoading=true,只要触发到对象任何的方法,就会立即加载所有属性的加载
        this.aggressive = configuration.isAggressiveLazyLoading();
        //获取全局配置lazyLoadTriggerMethods的值
        //lazyLoadTriggerMethods:当逻辑触发lazyLoadTriggerMethods 对应的方法(equals,clone,hashCode,toString)则执行延迟加载
        this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods();
        this.objectFactory = objectFactory;
        this.constructorArgTypes = constructorArgTypes;
        this.constructorArgs = constructorArgs;
    }

    public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) {
        final Class type = target.getClass();
        //EnhancedResultObjectProxyImpl作为回调函数
        EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
        //创建CBLIB代理
        Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
        //将目标对象的属性复制到代理对象中
        PropertyCopier.copyBeanProperties(type, target, enhanced);
        return enhanced;
    }
}

CglibProxyFactory.java
private static Object crateProxy(Class type, Callback callback, List> constructorArgTypes, List constructorArgs) {
    Enhancer enhancer = new Enhancer();
    enhancer.setCallback(callback);
    enhancer.setSuperclass(type);
    try {
        type.getDeclaredMethod(WRITE_REPLACE_METHOD);
        // ObjectOutputStream will call writeReplace of objects returned by writeReplace
        log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this");
    } catch (NoSuchMethodException e) {
        enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class});
    } catch (SecurityException e) {
        // nothing to do here
    }
    Object enhanced = null;
    //如果是无参的构造函数创建代理
    if (constructorArgTypes.isEmpty()) {
        enhanced = enhancer.create();
    } else {
    	//有参构造函数创建代理
        Class[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);
        Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);
        enhanced = enhancer.create(typesArray, valuesArray);
    }
    return enhanced;
}

        对返回结果中每一个属性设置值。

DefaultResultSetHandler.java
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
	//遍历数据库表中在resultMap中没有配置的列,如lz_user表中有10个字段
	//id,is_delete,gmt_create,gmt_modified,username,password,real_name,manager_id,sex,sex_str
	//
	//    
	//    
	//    
	//    
	//    
	//    
	//    
	//    
	//
	//在上述中sex,sex_str,没有在resultMap中配置,因此unmappedColumnNames为sex,sex_str
	//如果查询返回值为resultType,那么unmappedColumnNames为数据库中所有的列
    final List unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    for (String columnName : unmappedColumnNames) {
        String propertyName = columnName;
        //如果需要前缀处理,使用具体例子请看 例5.1
        if (columnPrefix != null && columnPrefix.length() > 0) {
        	//如果列名称以columnPrefix开头,删除前缀,否则跳过该列赋值
            if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
                propertyName = columnName.substring(columnPrefix.length());
            } else {
                continue;
            }
        }
        //返回对象中是否有propertyName属性
        final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
        //如果有属性并且还提供了set方法
        if (property != null && metaObject.hasSetter(property)) {
        	//获取返回实例属性类型
            final Class propertyType = metaObject.getSetterType(property);
            //获取属性类型处理器
            if (typeHandlerRegistry.hasTypeHandler(propertyType)) {
                final TypeHandler typeHandler = rsw.getTypeHandler(propertyType, columnName);
                //利用类型处理器从元数据中获取属性值,在类型处理器中通过rs.getBigDecimal(columnName),
                //rs.getLong(columnName)等方法获取属性值
                //系统定义了许多默认的类型处理器
                //public TypeHandlerRegistry() {
    			//	register(Boolean.class, new BooleanTypeHandler());
    			//	register(boolean.class, new BooleanTypeHandler());
    			//	register(JdbcType.BOOLEAN, new BooleanTypeHandler());
				//  register(JdbcType.BIT, new BooleanTypeHandler());
				//...
				//当然,我们也可以自定义类型处理器,下面来看看例5.2,自定义类型处理器就在这一行代码起作用的
                final Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
                //如果属性值不为空或全局配置中配置了callSettersOnNulls为true
                if (value != null || configuration.isCallSettersOnNulls()) { // issue #377, call setter on nulls
                	//属性值不为空或属性类型不是int,double等基本数据类型
                    if (value != null || !propertyType.isPrimitive()) {
                    	//反射调用对象属性setXXX()方法,为对象属性赋值
                        metaObject.setValue(property, value);
                    }
                    foundValues = true;
                }
            }
        }
    }
    return foundValues;
}

        这个方法非常的重要,我们先来看一个简单的例子。

  1. 构建UserMapper
User getUser(Long id);
  1. 构建UserMapper.xml


3.测试

@Test
public void testGetUser() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = userMapper.getUser(456l);
    System.out.println(JSON.toJSONString(user));

}

深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第27张图片

例5.1

  1. 准备POJO
@Data
public class UserInfo {
    private Long id;
    private String realName;
    private List billList;

}

@Data
public class UserBill {
    private Long id;
    private String type;
    private Long userId;
    private BigDecimal amount;
    private String name;
}

从下面的数据中,我们可以看到,查询结果集中是没有name列的,但是在最终的返回结果中,name被赋值了。这就归功于applyAutomaticMappings()方法的前缀处理了。

  1. 准备UserMapper
UserInfo findUserById(Long id );
  1. 准备UserMapper.xml


columnPrefix="user" ofType="com.spring_101_200.test_121_130.test_125_mybatis_properties.UserBill"  >
        
        
        
        
    



        在源码中寻寻觅觅,最终在mybatis-3-mapper.dtd文档中找到了columnPrefix的使用,因此本例中使用用户和账单之间一对多关系,查找用户信息和账单信息。

深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第28张图片

  1. 为了测试columnPrefix的使用,mybatis-config.xml中autoMappingBehavior属性必需设置为FULL

    
    
    
    
    
    



  1. 数据准备

深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第29张图片

  1. 测试
@Test
public void test8() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    UserInfo userB = userMapper.findUserById(456l);
    System.out.println(JSON.toJSONString(userB));
}
  1. 执行结果

深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第30张图片

  1. 结果分析
            查询select * from lz_user lu left join lz_user_bill lub on lu.id =lub.user_id where lu.id =456这条SQL得到的视图中,并没有name列,但是最终得到的结果中UserBill的name属性被赋值为lz_user表的username属性,这是为什么呢?我们再来看applyAutomaticMappings()方法就能理解了,在applyAutomaticMappings()方法中columnPrefix的值为user,遍历视图中的每一列,截除前缀user,得到属性名,看UserBill中是否有相同属性名的属性,视图中刚好有一个列username去除前缀user后,propertyName为name,而UserBill刚好有name属性和setName方法,此时通过反射将username的值设置到UserBill的name属性中。

例5.2

  1. 创建POJO
@Data
public class User {
    private Long id;
    private Integer isDelete;
    private Date gmtCreate;
    private Date gmtModified;
    private PhoneNumber username;
    private String password;
    private String realName;
    private Long managerId;

}

@Data
public class PhoneNumber {
    private String phone ;

    public PhoneNumber() {

    }
    public PhoneNumber(String phone) {
        this.phone = phone;
    }
}

  1. 创建TypeHandler
public class PhoneTypeHandler extends BaseTypeHandler {
	
	//使用列名进行封装
	@Override
	public PhoneNumber getNullableResult(ResultSet rs, String columnName)
			throws SQLException {
		return new PhoneNumber(rs.getString(columnName));
	}
	
	//使用列的下标进行封装
	@Override
	public PhoneNumber getNullableResult(ResultSet rs, int i)
			throws SQLException {
		return new PhoneNumber(rs.getString(i));
	}
	
	//CallableStatement遇到PhoneNumber,如何设置参数
	@Override
	public PhoneNumber getNullableResult(CallableStatement cs, int i)
			throws SQLException {
		return null;
	}
	//PreparedStatement遇到PhoneNumber,如何设置参数
	@Override
	public void setNonNullParameter(PreparedStatement ps, int i,
                                    PhoneNumber phoneNumber, JdbcType type) throws SQLException {
		ps.setString(i, phoneNumber.toString());
	}
}
  1. 注册类型处理器,在mybatis-config.xml中配置typeHandler

    

  1. 准备UserMapper

User getUser(Long id);

  1. 准备UserMapper.xml

  1. 测试
@Test
public void testGetUser() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = userMapper.getUser(456l);
    System.out.println(JSON.toJSONString(user));
}

深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第31张图片

  1. 结果分析
            先看applyAutomaticMappings()方法中的这两行代码。
final Class propertyType = metaObject.getSetterType(property);
TypeHandler typeHandler = rsw.getTypeHandler(propertyType, columnName);

        先根据username从元对象中获取setXXX()方法参数类型,再根据类型(PhoneNumber)获取类型处理器(PhoneTypeHandler),最终调用类型处理器PhoneTypeHandler的getNullableResult(ResultSet rs, String columnName)方法返回PhoneNumber对象,再通过反射封装username属性值为PhoneNumber对象。

DefaultResultSetHandler.java
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
        throws SQLException {
    //值和applyAutomaticMappings()方法中的unmappedColumnNames相反
    final List mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    //遍历所有的属性映射
    final List propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
        final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
        if (propertyMapping.isCompositeResult()
                || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
                || propertyMapping.getResultSet() != null) {
            //获取属性映射的值
            Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
            final String property = propertyMapping.getProperty(); // issue #541 make property optional
            if (value != NO_VALUE && property != null && (value != null || configuration.isCallSettersOnNulls())) { // issue #377, call setter on nulls
                if (value != null || !metaObject.getSetterType(property).isPrimitive()) {
                    metaObject.setValue(property, value);
                }
                foundValues = true;
            }
        }
    }
    return foundValues;
}

######DefaultResultSetHandler.java

private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
        throws SQLException {
    //如果propertyMapping是 标签,并里面有select属性
    if (propertyMapping.getNestedQueryId() != null) {
    	//直接查询数据库或将当前属性加入到CGLIB代理中
        return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
    } else if (propertyMapping.getResultSet() != null) {
        addPendingChildRelation(rs, metaResultObject, propertyMapping);
        return NO_VALUE;
    } else if (propertyMapping.getNestedResultMapId() != null) {
        return NO_VALUE;
    } else {
        final TypeHandler typeHandler = propertyMapping.getTypeHandler();
        final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
        return typeHandler.getResult(rs, column);
    }
}
DefaultResultSetHandler.java
private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
        throws SQLException {
   	//获取命名查询id,如:
   	//com.spring_101_200.test_131_140.test_138_mybatis_lazyloadtriggermethods.UserMapper.selectUserBill
    final String nestedQueryId = propertyMapping.getNestedQueryId();
    //获取属性名,如
    //property的属性值billList
    final String property = propertyMapping.getProperty();
    //获取查询的statement,如
    //selectUserBill对应的MappedStatement
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
    //获取查询参数类型,如
    //column值id的java数据类型
    final Class nestedQueryParameterType = nestedQuery.getParameterMap().getType();
    //获取column对应属性的值,如
    //获取id的值作为新的statement查询参数
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
    Object value = NO_VALUE;
    if (nestedQueryParameterObject != null) {
        final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
        final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
        final Class targetType = propertyMapping.getJavaType();
        //如果有本地缓存(localCache),则从本地缓存localCache中获取
        if (executor.isCached(nestedQuery, key)) {
            executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
        } else {
            final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
            //如果billList需要延迟加载
            if (propertyMapping.isLazy()) {
                lazyLoader.addLoader(property, metaResultObject, resultLoader);
            } else {
            	 //如果billList不需要延迟加载,直接查找数据库
                value = resultLoader.loadResult();
            }
        }
    }
    return value;
}

深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第32张图片

public void addLoader(String property, MetaObject metaResultObject, ResultLoader resultLoader) {
	String upperFirst = getUppercaseFirstProperty(property);
	if (!upperFirst.equalsIgnoreCase(property) && loaderMap.containsKey(upperFirst)) {
		//resultMap中不允许两个一模一样的属性延迟加载
	    throw new ExecutorException("Nested lazy loaded result property '" + property +
	            "' for query id '" + resultLoader.mappedStatement.getId() +
	            " already exists in the result map. The leftmost property of all lazy loaded properties must be unique within a result map.");
	}
	
	loaderMap.put(upperFirst, new LoadPair(property, metaResultObject, resultLoader));
}
LoadPair.java
private LoadPair(final String property, MetaObject metaResultObject, ResultLoader resultLoader) {
  this.property = property;
  this.metaResultObject = metaResultObject;
  this.resultLoader = resultLoader;

  / *仅在原始对象可以序列化时才保存所需信息。 * /
  if (metaResultObject != null && metaResultObject.getOriginalObject() instanceof Serializable) {
    final Object mappedStatementParameter = resultLoader.parameterObject;

    
    /* @todo May the parameter be null? */
    if (mappedStatementParameter instanceof Serializable) {
      this.mappedStatement = resultLoader.mappedStatement.getId();
      this.mappedParameter = (Serializable) mappedStatementParameter;

      this.configurationFactory = resultLoader.configuration.getConfigurationFactory();
    } else {
      this.getLogger().debug("Property [" + this.property + "] of ["
              + metaResultObject.getOriginalObject().getClass() + "] cannot be loaded "
              + "after deserialization. Make sure it's loaded before serializing "
              + "forenamed object.");
    }
  }
}

深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第33张图片
        代码运行于这里,我们只发现了程序将billList存储到loaderMap,其他的就什么都没有做了,难道这就实现了延迟加载吗?我们先来看一下延迟加载的示例,再接着分析延迟加载的实现

  1. 创建POJO
@Data
public class User {
    private Long id;
    private String realName;
    private List billList;

}

@Data
public class UserBill {
    private Long id;
    private String type;
    private Long userId;
    private BigDecimal amount;
    private User user;

}

  1. 创建UserMapper
public interface UserMapper {
    User findUserById(Long id );
}
  1. 创建UserMapper.xml



    
        
        
        
    

    

    
    
    


  1. 创建mybatis-config.xml



    

    
        
        
        
        
        
        
        
        
       	
    

    
      
    

    
        
            
        
    

    
        
            
            
                
                
                
                
            
        
    

    
        
    

        注意:如果需要使用延迟加载,lazyLoadingEnabled必需为true,如果aggressiveLazyLoading=true,表示调用对象的任何方法,都会触发延迟加载,为了达到测试效果,将aggressiveLazyLoading置为false,当对象触发lazyLoadTriggerMethods中配置的方法时,将触发延迟加载,在本例中想测试对象调用equals()方法不触发延迟加载,而调用hashCode()方法触发延迟加载,

  1. 测试
@Test
public void findUserBillLazyLoading() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = userMapper.findUserById(456l);
    System.out.println("-------------equals方法执行---------");
    System.out.println("equals result  : " + user.equals(new User()));
    System.out.println("-------------hashCode方法执行---------");
    System.out.println("hashCode: " + user.hashCode());
}

        为了达到测试效果,使用DataScopeInterceptor拦截器,每调用一次数据库查询,将打印一条sql,结果如下:
在这里插入图片描述
        理解了这个例子,我们再来看源码,在前面crateProxy()方法中,我们传入了EnhancedResultObjectProxyImpl作为方法拦截器,下面来看看该方法的实现。

EnhancedResultObjectProxyImpl.java
public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    final String methodName = method.getName();
    try {
        synchronized (lazyLoader) {
        	//如果方法名是
            if (WRITE_REPLACE_METHOD.equals(methodName)) {
                Object original = null;
                if (constructorArgTypes.isEmpty()) {
                    original = objectFactory.create(type);
                } else {
                    original = objectFactory.create(type, constructorArgTypes, constructorArgs);
                }
                PropertyCopier.copyBeanProperties(type, enhanced, original);
                if (lazyLoader.size() > 0) {
                    return new CglibSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
                } else {
                    return original;
                }
            } else {
            	//如果loaderMap尺寸大于0并且方法名不是finalize
                if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
                	//如果aggressiveLazyLoading=true或方法名是lazyLoadTriggerMethods配置的方法
                    if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
                    	//加载所有的延迟加载属性
                        lazyLoader.loadAll();
                    //如果aggressiveLazyLoading=false并且lazyLoadTriggerMethods中并没有配置该属性的getXXX,
                    //setXXX或isXXX方法,但是在程序中又调用了getXXX(),setXXX()或isXXX()方法
                    //此时则触发XXX属性加载
                    } else if (PropertyNamer.isProperty(methodName)) {
                        final String property = PropertyNamer.methodToProperty(methodName);
                        //如果该属性还没有被加载
                        if (lazyLoader.hasLoader(property)) {
                        	//单独触发该属性的加载
                            lazyLoader. load(property);
                        }
                    }
                }
            }
        }
        return methodProxy.invokeSuper(enhanced, args);
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}
ResultLoaderMap.java
public void loadAll() throws SQLException {
    final Set methodNameSet = loaderMap.keySet();
    String[] methodNames = methodNameSet.toArray(new String[methodNameSet.size()]);
    //延迟加载所有未被加载的属性
    for (String methodName : methodNames) {
        load(methodName);
    }
}
public boolean load(String property) throws SQLException {
	//加载前从map中移除,一个属性只会被加载一次,加载好以后,不会重复加载
    LoadPair pair = loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
    if (pair != null) {
        pair.load();
        return true;
    }
    return false;
}

public void load() throws SQLException {
    if (this.metaResultObject == null) throw new IllegalArgumentException("metaResultObject is null");
    if (this.resultLoader == null) throw new IllegalArgumentException("resultLoader is null");
    this.load(null);
}

public void load(final Object userObject) throws SQLException {
    if (this.metaResultObject == null || this.resultLoader == null) {
        if (this.mappedParameter == null) {
            throw new ExecutorException("Property [" + this.property + "] cannot be loaded because "
                    + "required parameter of mapped statement ["
                    + this.mappedStatement + "] is not serializable.");
        }

        final Configuration config = this.getConfiguration();
        final MappedStatement ms = config.getMappedStatement(this.mappedStatement);
        if (ms == null) {
            throw new ExecutorException("Cannot lazy load property [" + this.property
                    + "] of deserialized object [" + userObject.getClass()
                    + "] because configuration does not contain statement ["
                    + this.mappedStatement + "]");
        }

        this.metaResultObject = config.newMetaObject(userObject);
        this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter,
                metaResultObject.getSetterType(this.property), null, null);
    }

    //为了保证线程安全做的一些处理
    if (this.serializationCheck == null) {
        final ResultLoader old = this.resultLoader;
        this.resultLoader = new ResultLoader(old.configuration, new ClosedExecutor(), old.mappedStatement,
                old.parameterObject, old.targetType, old.cacheKey, old.boundSql);
    }
	//此时才调用selectList方法查询数据库
    this.metaResultObject.setValue(property, this.resultLoader.loadResult());
}

        分析到这里,我相信大家对MyBatis的延迟加载机制己经完全了解了,MyBatis发现对象有属性使用了延迟加载,MyBatis将创建代理对象,逐一赋值每个属性时,如果发现属性使用了延迟加载,将当前属性名和相关属性构建LoadPair存储到loaderMap中。而正在的加载数据分为以下几种情况。

if(aggressiveLazyLoading==true){
    //调用对象任何方法都加载所有被延迟加载的属性
}else if(aggressiveLazyLoading==false && lazyLoadTriggerMethods.contains(methodName)){
    //如果方法名配置在lazyLoadTriggerMethods中,加载所有被延迟加载的属性
}else if(aggressiveLazyLoading==false && !lazyLoadTriggerMethods.contains(methodName)){
    if(isXXX==methodName || getXXX==methodName || setXXX == methodName){
    	//如果方法名没有配置在lazyLoadTriggerMethods中,aggressiveLazyLoading也为false
        	//但当前调用的方法名是isXXX()或getXXX()或setXXX,加载所有被延迟加载的属性
            	//其中XXX是被配置为延迟加载的属性
    }
}

        相信经过对上述源码的分析,大家对延迟加载己经很有独到的理解了。

private void storeObject(ResultHandler resultHandler, DefaultResultContext resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
    if (parentMapping != null) {
        linkToParents(rs, parentMapping, rowValue);
    } else {
        callResultHandler(resultHandler, resultContext, rowValue);
    }
}
private void callResultHandler(ResultHandler resultHandler, DefaultResultContext resultContext, Object rowValue) {
	//存储结果对象到DefaultResultContext的resultObject属性中
    resultContext.nextResultObject(rowValue);
    //如果调用了结果处理器处理返回值,如DefaultResultHandler处理
    resultHandler.handleResult(resultContext);
}
DefaultResultHandler.java
public class DefaultResultHandler implements ResultHandler {
    private final List list;
    public DefaultResultHandler() {
        list = new ArrayList();
    }

    @SuppressWarnings("unchecked")
    public DefaultResultHandler(ObjectFactory objectFactory) {
        list = objectFactory.create(List.class);
    }
	//将resultObject从DefaultResultContext取出存储到List集合中
    public void handleResult(ResultContext context) {
        list.add(context.getResultObject());
    }

    public List getResultList() {
        return list;
    }
}

        可能大家莫名其妙,为什么从DefaultResultContext取出又加入到List集合中,再来看handleResultSet()方法,这个方法中将DefaultResultHandler的list加入到multipleResults中,并在上一个 handleResultSets()将multipleResults封装为List返回。

        我们己经对简单查询分析完了,下面来看看复杂嵌套查询MyBatis是如何处理。在对复杂表达式理解之前,我们先来看一个示例。

例6.1

  1. 创建表
    CREATE TABLE users (
            id int(11) DEFAULT NULL,
            name varchar(20) DEFAULT NULL,
            group_id int(11) DEFAULT NULL,
            rol_id int(11) DEFAULT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO users (id, name, group_id, rol_id)
VALUES
        (1, ‘User1’, 1, 1),
        (1, ‘User2’, 1, 2),
        (1, ‘User3’, 2, 1),
        (2, ‘User4’, 2, 2),
        (1, ‘User5’, 2, 3),
        (2, ‘User6’, 1, 1),
        (2, ‘User7’, 1, 2),
        (2, ‘User8’, 1, 3),
        (3, ‘User9’, 1, 1),
        (3, ‘User10’, 2, 1),
        (3, ‘User11’, 3, 1),
        (4, ‘User12’, 1, 1),
        (4, ‘User13’, 1, 2),
        (4, ‘User14’, 2, 1),
        (4, ‘User15’, 2, 4);

深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第34张图片

  1. 准备POJO
@Data
public class User {
    private Integer id;
    private String name;
    private List groups;
    private List roles;
}
  1. 创建UserMapper
public interface UserMapper {
    List getUser(@Param("id") Long id);
}
  1. 准备UserMapper.xml



    
        
        
        
            
        
        
            
        
    


    



  1. 在全局mybatis-config.xml配置文件添加logImpl

    
    
    
    
    

  1. 测试
@Test
public void testGetUser() throws Exception {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List user = userMapper.getUser(456l);
    Iterator iterator1 = user.iterator();
    System.out.println("===========开始打印===============");
    while (iterator1.hasNext()) {
        User next = iterator1.next();
        System.out.println("=====打印User===="+next);
    }
}
  1. 测试结果:
    深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第35张图片

  2. 设置中配置了resultOrdered为true 1)clear():满足条件清理nestedResultObjects 2)storeObject():存储结果对象 3)getRowValue():获取每行结果对象 1)resultObject !=null :如果缓存中有数据 1)applyNestedResultMappings():将新一行的数据和缓存中的数据合并 2)createResultObject():创建结果对象 3)applyAutomaticMappings():如果autoMappingBehavior为FULL或者autoMapping为true,自动映射实体属性。 4)applyPropertyMappings():映射resultMap中配置的属性。 5)applyNestedResultMappings():将新一行的数据和缓存中的数据合并 2)handleRowValuesForSimpleResultMap():处理简单查询 1)skipRows():跳过rowBound中offset行 2)getRowValue():处理其中一行数据 1)createResultObject():创建结果对象 2)applyAutomaticMappings():autoMappingBehavior为PARTIAL或FULL或,autoMapping为true的情况处理,也就是对resultMap中没有配置,但是实体对象中有的属性,或者直接对resultType对象属性进行映射,映射一般是通过反射调用set方法,为对象属性赋值 3)applyPropertyMappings():resultMap中配置了的属性进行映射 3)storeObject():存储结果对象集合 3)getNextResultSet():多结果集时处理 4)cleanUpAfterHandlingResultSet():清理处理结果集过程中产生的缓存,如 nestedResultObjects,ancestorColumnPrefix 4)closeStatement():关闭statement 1)localCache.putObject(key, list):将查询结果加入到本地缓存 2)convertToArray():将返回List转化为数组 3)convertToDeclaredCollection():将返回List转化为Collection或其子类 7)executeForMap():查询返回结果是Map 1)和executeForMany()大同小异,这里略过 8)selectOne():只查询一条数据 1)和executeForMany()大同小异,这里略过

            虽然我不知道我的文采怎样,或者我的表达怎样,但是至少,我对MyBatis源码有了深刻的理解了,不知道读者你有没有对MyBatis的使用,以及源码理解有了质的改变,如果有,那来看下面的这个例子吧。

    1. 创建表
      CREATE TABLE lz_user (
              id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
              is_delete tinyint(2) DEFAULT ‘0’,
              gmt_create datetime DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’,
              gmt_modified datetime DEFAULT CURRENT_TIMESTAMP,
              username varchar(32) DEFAULT NULL COMMENT ‘用户名’,
              password varchar(64) DEFAULT NULL COMMENT ‘密码’,
              real_name varchar(64) DEFAULT NULL,
              manager_id int(11) DEFAULT NULL COMMENT ‘管理员id’,
              sex int(11) DEFAULT ‘1’ COMMENT ‘性别’,
              sex_str varchar(32) DEFAULT NULL,
              PRIMARY KEY (id)
      ) ENGINE=InnoDB AUTO_INCREMENT=501 DEFAULT CHARSET=utf8mb4 COMMENT=‘公益口罩’;
      深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第37张图片

    CREATE TABLE lz_user_bill (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            is_delete tinyint(2) DEFAULT ‘0’,
            gmt_create datetime DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’,
            gmt_modified datetime DEFAULT CURRENT_TIMESTAMP,
            type varchar(32) DEFAULT ‘-’ COMMENT ‘收支类型’,
            user_id bigint(20) unsigned DEFAULT NULL COMMENT ‘用户id’,
            manager_id int(11) DEFAULT NULL COMMENT ‘管理员id’,
            amount decimal(12,2) DEFAULT NULL,
            remark text COMMENT ‘备注’,
            bill_type varchar(256) DEFAULT NULL COMMENT ‘账单类型’,
            pay_type varchar(255) DEFAULT NULL COMMENT ‘支付方式’,
            status int(11) DEFAULT ‘0’ COMMENT ‘-1表示作费,0表示提交,1表示已经报销’,
            self_look int(11) DEFAULT ‘0’ COMMENT ‘0表示公开,1表示仅仅自己可见’,
            PRIMARY KEY (id),
            KEY f_user_id (user_id),
            CONSTRAINT f_user_id FOREIGN KEY (user_id) REFERENCES lz_user (id) ON DELETE SET NULL ON UPDATE SET NULL
    ) ENGINE=InnoDB AUTO_INCREMENT=63 DEFAULT CHARSET=utf8mb4 COMMENT=‘公益口罩’;

    深入浅出MyBatis技术原理与实战-学习-源码解析-MyBatis 运行原理(四)_第38张图片

    1. 准备UserMapper
    UserBill selectBillInfo(@Param("billId") Long billId, @Param("userId") Long userId );
    
    1. 准备UserMapper.xml
    
        
        
        
        
        
        
            
            
            
            
            
            
            
        
    
    
    
    
    1. 准备POJO
    @Data
    public class UserBill {
        private Long id;
        private String type;
        private Long userId;
        private BigDecimal amount;
        private String name;
        private User user ;
    }
    
    @Data
    public class User {
        private Long id;
        private Integer isDelete;
        private Date gmtCreate;
        private Date gmtModified;
        private String username;
        private String password;
        private String realName;
        private Long managerId;
        private Integer sex;
        private String sexStr;
    }
    
    1. 创建存储过程
      DELIMITER $$
      CREATE PROCEDURE getBlogsAndAuthors(IN bill_id INT, IN user_id INT)

      BEGIN

      SELECT * FROM lz_user_bill WHERE ID = bill_id;
      SELECT * FROM lz_user WHERE ID = user_id;
      END $$

    2. 测试

    @Test
    public void test12() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        UserBill userB = userMapper.selectBillInfo(60l,456l);
        System.out.println(JSON.toJSONString(userB));
    }
    

            如果对MyBatis源码有一定理解的小伙伴,请来分析一下,MyBatis存储过程的源码,相信认真分析的你,有一定收获。

    你可能感兴趣的:(MyBatis,源码)