集中 MyBatis 框架的设计和核心代码的实现上,一些无关细节将会适当的忽略。
MyBatis 的运行分为两部分,一部分是读取配置文件缓存到 Configuration对象,用以创建 SqlSessionFactory,第二部分是 SQLSession 的执行过程,相对而言,SqlSessionFactory 创建比较容易,而 SqlSession 的执行过程远远不是那么简单。
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,这种方式值得我们在工作中学习中使用。
在 SqlSessionFactory 构建中,Configuration 是最重要的,它的作用如下。
显然Configuration不是一个简单的类,MyBatis的配置信息都会来自于此,几乎所有的配置都会在这里找到踪影,全部都会被读入这里并保存为一个单例,Configuration是通过XMLConfigBuilder去构建的,首先,MyBatis会读出所有的XML配置信息,然后将这些信息保存到Configuration中类的单例中,它会做如下初始化。
在上一篇博客中己经对映射器做了详细的解析,这里再来回顾一下。
一般而言,一个映射器是由3个部分组成。
MappedStatement对象涉及到的东西很多,我们一般不去修改他,因为容易产生不必要的错误,SqlSource是一个接口,它的主要作用是根据参数和其他的规则组装SQL,这些东西很复杂,有兴趣的话,可以去看看我上一篇博客,对于参数和SQL而言,主要的规则都反映在BoudSql类对象上,在插件中往往需要拿到它进而可以拿到当前运行的SQL和参数及参数规则,做出适当的修改,来满足我们的需求。
BoundSql会提供3个主要的属性:parameterMappings,parameterObject和Sql。
有了Configuration对象构建SqlSessionFactory就很简单了,我们只要写简单的代码,就可以完成
sqlSEssionFactory = new SqlSessionFactoryBuilder().build(reader);
MyBatis会根据Configuration的配置读取所有的配置信息,构建SqlSessionFactory。
SqlSession的运行过程是重点和难点,也是整个MyBatis难以理解的部分,SqlSession是一个接口,使用它并不复杂,我们构建SqlSessionFactory就可以轻易拿到SqlSesion了,SqlSession给出了查询 ,插入,更新,删除的方法,在旧版的MyBatis或iBatis中常常使用这些接口方法,而在新版的MyBatis中我们建义使用Mapper,所以它就是MyBatis最常用也是最重要的接口之一。
但是,SqlSession的内部可没有那么容易,因为它的内部实现相当复杂。
Mapper的映射是通过动态代理来实现的。我们获取getMapper()类来创建代理。
SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
publicT 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动态代理。
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的java.lang.reflect.*包提供支持的,在测试之前,我们要完成下面的几个步骤。
JDK动态代理最大的特点就是需要提供一个接口,而在MyBatis中正好有这样一个接口,因此,MyBatis是使用JDK动态代理。下面来看看JDK动态代理的实现。
public interface HelloService { public void sayHello(String name); }
public class HelloServiceImpl implements HelloService { @Override public void sayHello(String name) { System.out.println("hello " + name); } }
现在我们写一个代理类,提供真实对象的绑定一代理方法,代理类要求实现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保存了真实服务对象,那么我们可以通过反射技术调用真实对象的方法。
public class Test150 { public static void main(String[] args) { HelloServiceProxy helloServiceProxy = new HelloServiceProxy(); HelloService helloService =(HelloService) helloServiceProxy.bind(new HelloServiceImpl()); helloService.sayHello("张三"); } }
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 MapperProxyimplements 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()方法。
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 的实现。
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; } }
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 SortedMapparams; 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)); }
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 SortedMapgetParams(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; }
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 Mapparam = new ParamMap
方法参数转换
方法参数封装己经清楚了,下面我们来对insert执行流程分析。
众所究知,MyBatis底层是用jdbc来实现的,而jdbc所用到的方法中,用到最多的也是查询和更新方法,对于插入和删除操作实际对于jdbc而言也是更新操作。我们就挑选比较复杂的更新操作insert和查询来对MyBatis源码讲解,查询和插入操作,有一部分功能是类似的的,那就是statement sql请求参数封装部分,不同的部分是在返回结果的处理上。话不多说,直接来分析源码吧。
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的。我们来看看源码
public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }
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(); } }
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中存在三种执行器,那三个执行器之间的区别是什么呢?
CachingExecutor执行器不是真正意义的执行器,只是对上述三个执行器做了一个包装而已,目的是使用缓存,使不使用缓存执行器包装,主要通过全局配置cacheEnabled来控制,这里我们使用默认的执行器来研究
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的抽象而已。
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); } }
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
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对于这个方法的实现。
public void parameterize(Statement statement) throws SQLException { // N/A }
public void parameterize(Statement statement) throws SQLException { parameterHandler.setParameters((PreparedStatement) statement); }
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来研究吧。
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql); }
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()这个方法,在这个方法中,对于标签中配置了
configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
而在XMLStatementBuilder的parseStatementNode()中,对于每一个MappedStatement的添加方法中,
KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); //如果当前节点有 节点或对应的Mapper中有@SelectKey标签 if (configuration.hasKeyGenerator(keyStatementId)) { //从configuration中的keyGenerators取出 keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); } //设置到MappedStatement中 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
我们说了这么多,那
select if(max(id) is null ,1 ,max(id) + 2 ) as newId from lz_user insert into lz_user(id, username, password, real_name, manager_id, is_delete, gmt_create, gmt_modified )values(#{ id}, #{ username}, #{ password}, #{ realName}, #{ managerId}, 0, now(), now() )
从这段代码来看,主要目的就是为了在sql主键每次递增2,那源码中是如何实现的呢?我们来看SelectKeyGenerator的processBefore()方法。
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) { if (executeBefore) { processGeneratedKeys(executor, ms, parameter); } } private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) { try { if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) { //获取的keyProperty属性,如果是多个属性可以以逗号隔开 String[] keyProperties = keyStatement.getKeyProperties(); final Configuration configuration = ms.getConfiguration(); //将对象转换为MetaObject,比如将User对象转换成MetaObject对象 final MetaObject metaParam = configuration.newMetaObject(parameter); if (keyProperties != null) { Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE); //执行查询获取List集体 List
我相信对这段源码的分析,大家对外层
public BoundSql getBoundSql(Object parameterObject) { //获取绑定的sql,并将参数对象与sql语句的#{}一一对应 BoundSql boundSql = sqlSource.getBoundSql(parameterObject); ListparameterMappings = boundSql.getParameterMappings(); if (parameterMappings == null || parameterMappings.size() <= 0) { //如果parameterMappings为空,重构BoundSql boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject); } //hasNestedResultMaps: 是否含有嵌套的结果映射, 如果某个映射关系中有 resultMap, 没有 resultSet , 则为true for (ParameterMapping pm : boundSql.getParameterMappings()) { String rmId = pm.getResultMapId(); if (rmId != null) { //如果配置了#{username,resultMap=xxx},并且configuration中有相应的xxx的resultMap,说明是嵌套结果映射 ResultMap rm = configuration.getResultMap(rmId); if (rm != null) { hasNestedResultMaps |= rm.hasNestedResultMaps(); } } } return boundSql; }
public BoundSql getBoundSql(Object parameterObject) { //parameterObject为动态代理传入进来的方法参数 //封装_parameter,_databaseId参数 DynamicContext context = new DynamicContext(configuration, parameterObject); //去除Mapper.xml动态标签,如if ,trim ,set ,where等,拼接成sql rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); //解析sql,主要将#{}转化为?,同时构建ParameterMapping SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); for (Map.Entryentry : context.getBindings().entrySet()) { boundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); } return boundSql; }
public boolean apply(DynamicContext context) { for (SqlNode sqlNode : contents) { sqlNode.apply(context); } return true; }
先来看看contents内容
再来看看Mapper.xml文件中配置的sql
insert into lz_user( username, password, real_name, manager_id, is_delete, gmt_create, gmt_modified )values(#{ username}, #{ password}, #{ realName}, #{ managerId}, 0, now(), now() )
终于理解了
public boolean apply(DynamicContext context) { if (evaluator.evaluateBoolean(test, context.getBindings())) { contents.apply(context); return true; } return false; } public boolean evaluateBoolean(String expression, Object parameterObject) { Object value = OgnlCache.getValue(expression, parameterObject); if (value instanceof Boolean) return (Boolean) value; if (value instanceof Number) return !new BigDecimal(String.valueOf(value)).equals(BigDecimal.ZERO); return value != null; }
IfSqlNode的apply()方法而言,用Ognl表达式来判断
@Test public void test(){ User user = new User(); user.setUsername("zhangsan"); Object value = OgnlCache.getValue("username !=null ", user); System.out.println(value); }
public boolean apply(DynamicContext context) { context.appendSql(text); return true; } public void appendSql(String sql) { sqlBuilder.append(sql); sqlBuilder.append(" "); }
对于StaticTextSqlNode结点的apply()方法,只是将sql以空格拼接起来就可以了。
通过这两个节点简单的分析,我相信大家理解了apply()方法的用途了,其实就是将动态sql标签(如if ,choose ,where,when,case,trim ,set 等)去除,将动态sql标签内的内容拼接成静态SQL。
public SqlSource parse(String originalSql, Class parameterType, MapadditionalParameters) { ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters); //设置GenericTokenParser中的 //openToken为#{ //closeToken为} //handler为ParameterMappingTokenHandler GenericTokenParser parser = new GenericTokenParser("#{", "}", handler); String sql = parser.parse(originalSql); return new StaticSqlSource(configuration, sql, handler.getParameterMappings()); }
public class GenericTokenParser { private final String openToken;//#{ private final String closeToken;//} private final TokenHandler handler;//ParameterMappingTokenHandler public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) { this.openToken = openToken; this.closeToken = closeToken; this.handler = handler; } public String parse(String text) { StringBuilder builder = new StringBuilder(); if (text != null && text.length() > 0) { char[] src = text.toCharArray(); int offset = 0; //判断当前sql中是否有#{ int start = text.indexOf(openToken, offset); while (start > -1) { //判断#{前是不是\\,如果是,则直接拼接sql if (start > 0 && src[start - 1] == '\\') { builder.append(src, offset, start - offset - 1).append(openToken); offset = start + openToken.length(); } else { //如果#{前没有 \\,则判断有没有结束符 } int end = text.indexOf(closeToken, start); if (end == -1) { //如果没有,则直接拼接为sql builder.append(src, offset, src.length - offset); offset = src.length; } else { builder.append(src, offset, start - offset); offset = start + openToken.length(); String content = new String(src, offset, end - offset); //如果#{前没有\\,同时又有} ,取出#{xxx}内的xxx 用ParameterMappingTokenHandler处理器构建ParameterMapping builder.append(handler.handleToken(content)); offset = end + closeToken.length(); } } start = text.indexOf(openToken, offset); } if (offset < src.length) { builder.append(src, offset, src.length - offset); } } return builder.toString(); } }
对于上述parse方法,MyBatis分三种情况处理。
下面,我们来看handleToken()方法处理。
public String handleToken(String content) { parameterMappings.add(buildParameterMapping(content)); return "?"; }
在这里,我们可能看到,SQL 【select * from lz_user where id = #{xxx} and username = #{YYY}】中的#{xxx}和#{YYY}最终都会被替换成 ? ,而buildParameterMapping()方法主要是构建ParameterMapping。
private ParameterMapping buildParameterMapping(String content) { //在parseParameterMapping()方法调用前,我们先来看看#{}配置如何使用,在正常的情况下, //我们一般在#{}里配置的是:#{id},#{username} 或#{password,jdbcType=VARCHAR} //但源码比我们想得更多,如 //#{(a?1:2),userName:VARCHAR,javaType=java.util.Long,numericScale=2,resultMap=xxx,typeHandler=MyTypeHandler,jdbcTypeName=long} //这样配置,我们将得到如下的propertiesMap //{"userName:VARCHAR,javaType":"java.util.Long","expression":"a?1:2","numericScale":"2","typeHandler":"MyTypeHandler","jdbcTypeName":"long","resultMap":"xxx"} //内部是如何生成Map的,有兴趣同学可以去研究一下生成Map的算法 MappropertiesMap = parseParameterMapping(content); String property = propertiesMap.get("property"); Class propertyType; if (metaParameters.hasGetter(property)) { //这种情况的使用场景 // //这个时候,取出username的propertyType为String类型 propertyType = metaParameters.getGetterType(property); } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) { //基本数据类型处理,如 // resultType="User"/> //typeHandlerRegistry先会到TypeHandlerRegistry的Map >> TYPE_HANDLER_MAP 属性中查的,而TYPE_HANDLER_MAP属性中注册了很多基本类型的处理器 //public TypeHandlerRegistry() { // register(Boolean.class, new BooleanTypeHandler()); // register(boolean.class, new BooleanTypeHandler()); // register(JdbcType.BOOLEAN, new BooleanTypeHandler()); // register(JdbcType.BIT, new BooleanTypeHandler()); // register(byte.class, new ByteTypeHandler()); // register(JdbcType.TINYINT, new ByteTypeHandler()); // ... //} //因此,如果是基本类型,直接赋值 propertyType = parameterType; } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) { //如果像这样配置#{id,jdbcType=CURSOR},则设置propertyType为ResultSet类型 propertyType = java.sql.ResultSet.class; } else if (property != null) { // 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解析完,
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; }
public Statement prepare(Connection connection) throws SQLException { return delegate.prepare(connection); }
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的方式必需注意。
public void parameterize(Statement statement) throws SQLException { delegate.parameterize(statement); }
public void parameterize(Statement statement) throws SQLException { parameterHandler.setParameters((PreparedStatement) statement); }
public void setParameters(PreparedStatement ps) throws SQLException { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); ListparameterMappings = 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); } } } }
数据准备好以后,下面来看方法的执行。
public int update(Statement statement) throws SQLException { return delegate.update(statement); }
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; }
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
代码分析到这里,我相信大家对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="," > ,gmt_modified = now() where id = #{id}is_delete = #{isDelete}, gmt_create = #{gmtCreate}, username = #{username}, password = #{password}, real_name = #{realName}, manager_id = #{managerId}, sex = #{sex}, sexStr != null">sex_str = #{sexStr}
上面例子中,如果没有配置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个属性有什么作用呢?
理解是这样理解,那我们来看看源码的实现吧。
public class TrimSqlNode implements SqlNode { private SqlNode contents; private String prefix; private String suffix; private ListprefixesToOverride;//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="," > ,gmt_modified = now() where id = #{id}is_delete = #{isDelete}, gmt_create = #{gmtCreate}, username = #{username}, password = #{password}, real_name = #{realName}, manager_id = #{managerId}, sex = #{sex}, sexStr != null">sex_str = #{sexStr}
无论if条件生成的sql字符串有没有【,】结束,都会删除掉【,】,再在gmt_modified = now()前面拼接一个【,】sql就不会报错了,因为是update语句,因此,无论if条件生成什么样的sql字符串,都会在前面插入set 字符串。这样就能保证无论什么情况,动态sql都不会报错了。当然SetSqlNode和WhereSqlNode都是继承TrimSqlNode,其实现也是非常的简单,代码如下。
public class SetSqlNode extends TrimSqlNode { private static ListsuffixList = Arrays.asList(","); public SetSqlNode(Configuration configuration,SqlNode contents) { super(configuration, contents, "SET", null, null, suffixList); } }
从上面代码来看
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}
上面插入了一个小插曲,对
在了解select部分源码时,我们先来看一个例子。后面大部分源码解析都是围绕着这个例子来讲解析。
准备数据库表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=‘公益口罩’;
数据库表中的数据如下:
准备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 ,下载下来去测试。
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)); } }
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
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集合中。
void getUserByResultHandler(@Param("id") long id, ResultHandler resultHandler);
注意
: getUserByResultHandler()方法返回值必需是void类型,不然ResultHandler不起作用。因为上述源码就是如下写法。
if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; }
虽然UserMapper中返回值为void类型,但是在Mapper.xml中resultType不能为空。
@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())); }
从执行结果来看,每得到一个Object对象,都会调用一次MyDefaultResultSetHandler的handleResult()方法。MyDefaultResultSetHandler中将用户真实名字相同的用户存储到了resultMap中。这个例子没有什么实际意义,但是给我们提供了ResultSetHandler的使用。
privateObject 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; }
privateMap 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; }
publicMap 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; }
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); }
关于上面这一段源码可能有些同学还是不太理解,那我们来举一个例子吧。
@MapKey("username") Map getUserByMap(@Param("id") long id);
@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的值。
publicT 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()方法的实现吧。
publicList 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,并返回结果集。
publicList 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); }
publicList 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); } } } } }
privateList 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; }
publicList 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); } }
publicList query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; //执行sql ps.execute(); //结果集处理 return resultSetHandler. handleResultSets(ps); }
在处理结果集之前,我们来看看resultSetHandler是怎样创建的。
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()方法处理。
public ListhandleResultSets(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了数据及结构,为我们后面的数据封装奠定基础。
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; }
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实体中。
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, ListmultipleResults, 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) } }
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, ListresultMappings) 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是子查询的。因为有
<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
public interface UserMapper { UserBillInfo selectUserBill(@Param("id") Long id, @Param("rowBounds") RowBounds rowBounds); ListselectUserBills(@Param("id") long id, @Param("rowBounds") RowBounds rowBounds); }
<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)); }
@Test public void test4() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); ListuserBillInfos = userMapper.selectUserBills(456l,new RowBounds(0,5)); System.out.println(JSON.toJSONString(userBillInfos)); }
上面两个测试中,selectUserBill()和selectUserBills()两个方法的唯一区别就是Mapper.java中的返回值不同,一个是返回UserBillInfo,一个是返回List
### 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."); } }
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()); } }
public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) throws SQLException { SetpastDiscriminators = 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
@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; }
public interface UserMapper { UserBillInfo selectUserBill(Long id ); }
上述代码的主要意思是,如果用户没有被删除了,并且性别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)); }
鉴别器如例3.5使用,相信此时再来理解resolveDiscriminatedResultMap()方法,大家就没有那样迷惑了,while第一次循环获取userBillMapChoose内的鉴别器,第二次循环获取userResult内的鉴别器,返回id为userResult的resultMap。
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; }
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中配置了 // // 将会走下面代码 return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix); } else { return objectFactory.create(resultType); } }// column="id" javaType="long"> //column="is_delete" javaType="int"> // //// // ... //
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
public interface UserMapper { User getUser(Long id); }
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对象返回。
@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的默认返回值。为什么呢?因为根据rsw.getTypeHandler(resultType, columnName)方法将获取到UserTypeHandler处理器,再调用UserTypeHandler处理器的getNullableResult()方法,而MyBatis只对没有TypeHandler对象进行封装。通过这个例子,我相信大家对createPrimitiveResultObject()方法有了深刻的理解。
我们来看另一种情况,就是配置了@ConstructorArg注解或resultMap中使用了
private Object createParameterizedResultObject(ResultSetWrapper rsw, Class resultType, ListconstructorMappings, 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; }
对于上述方法,根据注释,可能有很多同学还是不理解,下面我们来看一个例子。
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,id
)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=‘公益口罩’;
用户表和用户账单表是一对多的关系,在lz_user_bill表中有一个user_id相关联
@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; }
为了更加体现测试效果,我们将autoMappingBehavior设置为NONE
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; } }
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); }
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对象中自定义构建函数来创建对象。
@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标签可以使用,测试效果和使用注解的方式实现一样。
@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对象中并返回。
@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中而已。
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方法 Listlist = 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中并返回,再作为构建函数参数创建实例,而普通参数的处理,就是直接从结果集中取出属性值作为构造函数参数创建实例。
public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) { return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); }
private static class EnhancedResultObjectProxyImpl implements MethodInterceptor { private Class type; private ResultLoaderMap lazyLoader; private boolean aggressive; private SetlazyLoadTriggerMethods; 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; } }
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; }
对返回结果中每一个属性设置值。
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; }
这个方法非常的重要,我们先来看一个简单的例子。
User getUser(Long id);
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)); }
例5.1
@Data public class UserInfo { private Long id; private String realName; private ListbillList; } @Data public class UserBill { private Long id; private String type; private Long userId; private BigDecimal amount; private String name; }
从下面的数据中,我们可以看到,查询结果集中是没有name列的,但是在最终的返回结果中,name被赋值了。这就归功于applyAutomaticMappings()方法的前缀处理了。
UserInfo findUserById(Long id );
columnPrefix="user" ofType="com.spring_101_200.test_121_130.test_125_mybatis_properties.UserBill" >
在源码中寻寻觅觅,最终在mybatis-3-mapper.dtd文档中找到了columnPrefix的使用,因此本例中使用用户和账单之间一对多关系,查找用户信息和账单信息。
FULL
,@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)); }
例5.2
@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; } }
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()); } }
User getUser(Long id);
@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)); }
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对象。
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { //值和applyAutomaticMappings()方法中的unmappedColumnNames相反 final ListmappedColumnNames = 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); } }
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; }
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)); }
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."); } } }
代码运行于这里,我们只发现了程序将billList存储到loaderMap,其他的就什么都没有做了,难道这就实现了延迟加载吗?我们先来看一下延迟加载的示例,再接着分析延迟加载的实现
@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; }
public interface UserMapper { User findUserById(Long id ); }
注意
:如果需要使用延迟加载,lazyLoadingEnabled必需为true,如果aggressiveLazyLoading=true,表示调用对象的任何方法,都会触发延迟加载,为了达到测试效果,将aggressiveLazyLoading置为false,当对象触发lazyLoadTriggerMethods中配置的方法时,将触发延迟加载,在本例中想测试对象调用equals()方法不触发延迟加载,而调用hashCode()方法触发延迟加载,
@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作为方法拦截器,下面来看看该方法的实现。
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); } }
public void loadAll() throws SQLException { final SetmethodNameSet = 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); }
public class DefaultResultHandler implements ResultHandler { private final Listlist; 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
users
(id
int(11) DEFAULT NULL,name
varchar(20) DEFAULT NULL,group_id
int(11) DEFAULT NULL,rol_id
int(11) DEFAULT NULLINSERT 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);
@Data public class User { private Integer id; private String name; private Listgroups; private List roles; }
public interface UserMapper { ListgetUser(@Param("id") Long id); }
@Test public void testGetUser() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); Listuser = userMapper.getUser(456l); Iterator iterator1 = user.iterator(); System.out.println("===========开始打印==============="); while (iterator1.hasNext()) { User next = iterator1.next(); System.out.println("=====打印User===="+next); } }
为什么会出现这种情况呢?
在Mybatis源码之美:3.5.4.唯一标标识符–id元素一文中,我们提供了一个因为错误配置id元素导致数据丢失的问题.
在这篇文章中,我们指出:id元素可以通过临时缓存对象来提高在嵌套结果映射中处理数据的性能.
这是一种典型的用空间换时间的解决方案,通过临时缓存,mybatis提高了对数据的解析速度,但是势必会消耗更多的内存.
当我们执行大数量的操作时,可能就会导致OOM(OutOfMemoryError )的发生.
为此,mybatis为我们提供了一个resultOrdered属性.
resultOrdered属性是一个标志性的属性,用户可以通过配置该属性的值为true来告知mybatis当前select语句的查询结果针对于元素的配置是有序的,即,多个相同属性是分组且连续的.
比如:
key | value |
---|---|
A | 数据2 |
A | 数据3 |
A | 数据4 |
A | 数据5 |
B | 数据6 |
B | 数据7 |
B | 数据8 |
B | 数据9 |
B | 数据10 |
这样mybatis在解析数据时,根据元素的配置最多只会缓存一行数据,降低了内存的使用量,因此降低了OOM的发生几率.
针对上面的数据记录来讲,mybatis在解析数据1时,会将数据1对应的记录缓存起来,之后的的数据2,数据3以及数据4因为具有相同的key值,因此都能命中缓存,避免了重复解析数据.
但是在处理数据5时,因为key值为B,无法命中缓存,数据1的缓存将会被移除,数据5对应的记录被放入缓存中.
在这种处理方式下,因为相同Key值的数据分组在一起,因此效率高的同时还降低了内存的使用.
但是,针对数据记录:
key | value |
---|---|
A | 数据1 |
B | 数据2 |
A | 数据3 |
B | 数据4 |
A | 数据5 |
B | 数据6 |
A | 数据7 |
B | 数据8 |
A | 数据9 |
具有相同key值的数据并没有分组在一起,而是交叉出现,因此虽然mybatis在解析数据1时,会将数据1对应的记录缓存起来,但是因为数据2的key值为B,不能命中缓存,因此数据1的缓存将会被移除,数据2对应的记录进入缓存.
依次类推,每一条数据记录都不能命中缓存,都需要重新解析.针对这种场景,虽然降低了内存使用量,但是大大降低了数据解析的效率.
在例6.1中,resultOrdered为true和false时,出现不同的测试结果的原因,正是MyBatis使用缓存导致的,那我们来看源代码。
private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { final DefaultResultContext resultContext = new DefaultResultContext(); skipRows(rsw.getResultSet(), rowBounds); Object rowValue = null; while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) { final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null); Object partialObject = nestedResultObjects.get(rowKey); //如果标签中配置了resultOrdered为true if (mappedStatement.isResultOrdered()) { // issue #577 && #542 if (partialObject == null && rowValue != null) { //每次遇到nestedResultObjects缓存中不存在的值时,存储到的返回结果集中,并且清理nestedResultObjects缓存 //导致例6.1中两次测试结果不一致的原因,就是在这一行代码 nestedResultObjects.clear(); storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, rowKey, null, partialObject); } else { //如果标签中配置了resultOrdered为false,或者不配置resultOrdered属性 rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, rowKey, null, partialObject); if (partialObject == null) { //每次遇到nestedResultObjects缓存中不存在的值时,存储到的返回结果集中 storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } } } if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) { storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } }
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, CacheKey absoluteKey, String columnPrefix, Object partialObject) throws SQLException { final String resultMapId = resultMap.getId(); Object resultObject = partialObject; //如果nestedResultObjects缓存中存在该值,使用缓存中的值作为结果对象 if (resultObject != null) { final MetaObject metaObject = configuration.newMetaObject(resultObject); //在applyNestedResultMappings()方法中需要用到resultObject对象,因此先put putAncestor(absoluteKey, resultObject, resultMapId, columnPrefix); //嵌套resultMap处理 applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false); //resultObject用完之后移除 ancestorObjects.remove(absoluteKey); } else { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); //缓存中不存在,直接创建结果对象 resultObject = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); //如果没有类型处理器,并且结果对象不为空 if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(resultObject); boolean foundValues = resultMap.getConstructorResultMappings().size() > 0; //如果全局配置中autoMappingBehavior为FULL或标签中配置了autoMapping=true if (shouldApplyAutomaticMappings(resultMap, AutoMappingBehavior.FULL.equals(configuration.getAutoMappingBehavior()))) { foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; } foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; //在applyNestedResultMappings()方法中需要用到resultObject对象,因此先put putAncestor(absoluteKey, resultObject, resultMapId, columnPrefix); //嵌套resultMap处理 foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues; //resultObject用完之后移除 ancestorObjects.remove(absoluteKey); foundValues = lazyLoader.size() > 0 || foundValues; resultObject = foundValues ? resultObject : null; } //如果有类型处理器,并且结果对象不为空,将值保存到nestedResultObjects缓存中 if (combinedKey != CacheKey.NULL_CACHE_KEY) nestedResultObjects.put(combinedKey, resultObject); } return resultObject; }
private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) { boolean foundValues = false; //遍历所有的嵌套resultMaping for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) { //凡是有nestedResultMapId都不为空 final String nestedResultMapId = resultMapping.getNestedResultMapId(); if (nestedResultMapId != null && resultMapping.getResultSet() == null) { try { //将parentPrefix和resultMapping中的columnPrefix属性拼接并转化为大写 final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping); //获取嵌套的resultMap final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix); CacheKey rowKey = null; Object ancestorObject = null; if (ancestorColumnPrefix.containsKey(nestedResultMapId)) { //如果在putAncestor()方法中存储了columnPrefix rowKey = createRowKey(nestedResultMap, rsw, ancestorColumnPrefix.get(nestedResultMapId)); ancestorObject = ancestorObjects.get(rowKey); } if (ancestorObject != null) { if (newObject) metaObject.setValue(resultMapping.getProperty(), ancestorObject); } else { rowKey = createRowKey(nestedResultMap, rsw, columnPrefix); //创建组合索引 //具体值如:902157268:927332410:com.spring_101_200.test_141_150.test_149_mybatis_resultordered.UserMapper.mapper_resultMap[BaseResultMap]_collection[groups]:group_id:1:1627739720:-833330954:com.spring_101_200.test_141_150.test_149_mybatis_resultordered.UserMapper.BaseResultMap:id:1 final CacheKey combinedKey = combineKeys(rowKey, parentRowKey); Object rowValue = nestedResultObjects.get(combinedKey); boolean knownValue = (rowValue != null); //如果metaObject对象中,该属性集合存在,则返回,如果不存在,则创建集合对象,并设置到metaObject属性中 final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); //如果在 notNullColumn="id,username" />配置了notNullColumn属性 //并且结果集中,该属性不为空 if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw.getResultSet())) { //获取该行值,并存储到nestedResultObjects中 rowValue = getRowValue(rsw, nestedResultMap, combinedKey, rowKey, columnPrefix, rowValue); //如果rowValue不为空,并且缓存nestedResultObjects中也不存在 if (rowValue != null && !knownValue) { if (collectionProperty != null) { //加入到metaObject对象属性集中 final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty); targetMetaObject.add(rowValue); } else { metaObject.setValue(resultMapping.getProperty(), rowValue); } foundValues = true; } } } } catch (SQLException e) { throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'. Cause: " + e, e); } } } return foundValues; }
从上述源码分析来看,无论resultOrdered值是true还是false,都使用了nestedResultObjects缓存,不同的是,如果resultOrdered为true时,当遍历结果集到User4时,将缓存中的User1,User2,User3(id相同都为1)封装的值User【User(id=1, name=User1, groups=[1, 2], roles=[1, 2])】结果对象添加到返回结果集合中,清除nestedResultObjects缓存中的数据,创建User4的结果对象【User(id=2, name=User4, groups=[2], roles=[2])】,当结果集再次next()到User5时,因为id为1的User结果对象己经从缓存nestedResultObjects中清除了,所以,缓存中没有User5对应的结果对象,此时将清除缓存nestedResultObjects中的所有数据,并将User4【User(id=2, name=User4, groups=[2], roles=[2])】对应的结果对象保存到结果集合中,结果集再次next()到User6时,发现缓存中并没有id为2的User结果对象,创建新的结果对象【User(id=2, name=User6, groups=[1], roles=[1])】,将缓存nestedResultObjects中的数据清除掉。结果集再次next()到User7时,nestedResultObjects中有id为2的User对象,合并User7中的数据到id为2的User结果对象中,此时id为2的User对象变成了User(id=2, name=User6, groups=[1], roles=[1,2])】,依次类推,结果就不难理解了。
理解了resultOrdered为true时,再来理解resultOrdered为false的情况就简单多了,因为一次结果集遍历nestedResultObjects不会被清理,因此,当遍历到User5时,因为缓存nestedResultObjects中存在id=1的User结果对象,因此将User5数据合并到id=1的用户对象中,此时id为1的结果对象为User(id=1, name=User1, groups=[1, 2], roles=[1, 2, 3]),其他的依次类推。
对MyBatis源码基本流程分析完了,下面,我们来总结一下执行流程吧。
1)getMapper(): 默认调用DefaultSqlSession方法 1)getMapper():调用configuration的方法 1)getMapper():调用MapperRegistry方法 1)newInstance(sqlSession):获取Mapper代理工厂,创建JDK动态代理 1)newInstance(new MapperProxy(...)):创建JDK动态代理 1)Proxy.newProxyInstance():do create proxy 1)invoke():JDK代理回调方法,在MapperProxy中。 1)execute(sqlSession, args):执行SQL操作 1)convertArgsToSqlCommandParam():将方法参数转化为ParamMap或Object 2)insert(): 插入操作(MapperMethod) 1)update():DefaultSqlSession 1)update():SimpleExecutor,ReuseExecutor,BatchExecutor根据configuration中的defaultExecutorType配置选择执行器 1)clearLocalCache():清理本地缓存 2)doUpdate():在BaseExecutor类中 1)newStatementHandler(): 创建StatementHandler 1)new RoutingStatementHandler():创建路由StatementHandler 1)new SimpleStatementHandler():statementType为STATEMENT,没有参数的sql处理 2)new PreparedStatementHandler():statementType为PREPARED,有参数的sql处理 3)new CallableStatementHandler():statementType为CALLABLE,存储过程处理 2)pluginAll():加载所有插件 2)prepareStatement():创建PrepareStatement 1)getConnection():获取数据库连接 2)handler.prepare():获取statement 1)instantiateStatement():初始化statement 1)connection.prepareStatement():根据不同的参数初始化statement 2)setStatementTimeout():设置statement的queryTimeout 3)setFetchSize():设置statement的fetchSize参数 3)handler.parameterize():为statement设置sql参数 1)parameterHandler.setParameters():为statement设置参数 1)ps.setXXX(i,xxx);设置statement参数 3)handler.update(stmt):执行更新操作 1)execute():执行更新 1)processAfter():如果是插入操作,并且配置了 useGeneratedKeys="true" keyProperty="id",为实体设置主键 1)getGeneratedKeys():获取主键 2)populateKeys():设置主键,在主键设置上,只对Jdbc3KeyGenerator作分析,其他情况,自己去研究博客或源码吧 4)closeStatement():关闭statement 3)update() : 更新操作 1)和insert()大同小异,这里略过 4)delete():删除操作 1)和insert()大同小异,这里略过 5)executeWithResultHandler():当方法参数中传入resultHandler处理 1)和executeForMany()大同小异,这里略过 6)executeForMany():查询多条数据 1)convertArgsToSqlCommandParam():将方法参数转化为ParamMap或Object 1)selectList():查询sql,返回List1)query():查询sql 1)getBoundSql():将#{xxx}转化为?,获取statement能识别的sql 2)createCacheKey():创建本地缓存key 3)clearLocalCache():如果Mapper.xml中配置了flushCacheRequired,则清除本地缓存 4)localCache.getObject(key):从本地缓存中获取 5)handleLocallyCachedOutputParameters():处理存储过程参数 6)queryFromDatabase():从数据库中查询 1)doQuery():真正的查询数据 1)newStatementHandler(): 创建StatementHandler 1)new RoutingStatementHandler():创建路由StatementHandler 1)new SimpleStatementHandler():statementType为STATEMENT,没有参数的sql处理 2)new PreparedStatementHandler():statementType为PREPARED,有参数的sql处理 3)new CallableStatementHandler():statementType为CALLABLE,存储过程处理 2)pluginAll():加载所有插件 2)prepareStatement():创建PrepareStatement 1)getConnection():获取数据库连接 2)handler.prepare():获取statement 1)instantiateStatement():初始化statement 1)connection.prepareStatement():根据不同的参数初始化statement 2)setStatementTimeout():设置statement的queryTimeout 3)setFetchSize():设置statement的fetchSize参数 3)handler.parameterize():为statement设置sql参数 1)parameterHandler.setParameters():为statement设置参数 1)ps.setXXX(i,xxx);设置statement参数 3)handler.query(stmt, resultHandler):处理查询 1)execute():执行查询 2)handleResultSets():处理结果集 1)getFirstResultSet():获取返回的第一个结果集 2)handleResultSet():真正处理结果集 1)handleRowValues():处理每一行结果集 1)hasNestedResultMaps():如果有嵌套子查询 1)ensureNoRowBounds():safeRowBoundsEnabled较验 2)checkResultHandler():safeResultHandlerEnabled较验 3)handleRowValuesForNestedResultMap():处理嵌套结果集 1)skipRows():跳过RowBound的offset的行 2)isResultOrdered():如果中配置了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的使用,以及源码理解有了质的改变,如果有,那来看下面的这个例子吧。
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,id
)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=‘公益口罩’;
UserBill selectBillInfo(@Param("billId") Long billId, @Param("userId") Long userId );
@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; }
创建存储过程
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 $$
测试
@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存储过程的源码,相信认真分析的你,有一定收获。