深入理解mybatis原理(十) Mybatis插件原理简单分析


我们目前在Mybatis中,我们知道Mybatis的Mapper是一个接口,而不是一个实体类。在Java中接口是没有办法运行的。那么它是怎么运行的呢?它是通过动态代理运行。

一、MyBatis是怎么实现这个动态代理的

[java] view plain copy
  1. /** 
  2.  *    Copyright 2009-2015 the original author or authors. 
  3.  * 
  4.  *    Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  *    you may not use this file except in compliance with the License. 
  6.  *    You may obtain a copy of the License at 
  7.  * 
  8.  *       http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  *    Unless required by applicable law or agreed to in writing, software 
  11.  *    distributed under the License is distributed on an "AS IS" BASIS, 
  12.  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  *    See the License for the specific language governing permissions and 
  14.  *    limitations under the License. 
  15.  */  
  16. package org.apache.ibatis.binding;  
  17.   
  18. import java.io.Serializable;  
  19. import java.lang.reflect.InvocationHandler;  
  20. import java.lang.reflect.Method;  
  21. import java.util.Map;  
  22.   
  23. import org.apache.ibatis.reflection.ExceptionUtil;  
  24. import org.apache.ibatis.session.SqlSession;  
  25.   
  26. /** 
  27.  * @author Clinton Begin 
  28.  * @author Eduardo Macarron 
  29.  */  
  30. public class MapperProxy<T> implements InvocationHandler, Serializable {  
  31.   
  32.   private static final long serialVersionUID = -6424540398559729838L;  
  33.   private final SqlSession sqlSession;  
  34.   private final Class<T> mapperInterface;  
  35.   private final Map<Method, MapperMethod> methodCache;  
  36.   
  37.   public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {  
  38.     this.sqlSession = sqlSession;  
  39.     this.mapperInterface = mapperInterface;  
  40.     this.methodCache = methodCache;  
  41.   }  
  42.   
  43.   @Override  
  44.   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  45.     if (Object.class.equals(method.getDeclaringClass())) {  
  46.       try {  
  47.         return method.invoke(this, args);  
  48.       } catch (Throwable t) {  
  49.         throw ExceptionUtil.unwrapThrowable(t);  
  50.       }  
  51.     }  
  52.     final MapperMethod mapperMethod = cachedMapperMethod(method);  
  53.     return mapperMethod.execute(sqlSession, args);  
  54.   }  
  55.   
  56.   private MapperMethod cachedMapperMethod(Method method) {  
  57.     MapperMethod mapperMethod = methodCache.get(method);  
  58.     if (mapperMethod == null) {  
  59.       mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());  
  60.       methodCache.put(method, mapperMethod);  
  61.     }  
  62.     return mapperMethod;  
  63.   }  
  64. }  
这便是mybaitis处理对象的方法,我们可以看到invoke方法。我们知道一旦mapper是一个代理对象,那么它就会运行到invoke方法里面,invoke首先判断是否一个类,显然这里mapper是一个接口,不是类所以判定失败。那么跟着就会生成MapperMethod对象,它是通过cachedMapperMethod方法对其初始化的,然后执行execute方法,把sqlSession和当前运行的参数传递进去。

于是让我们看看这个execute方法的源码:

[java] view plain copy
  1. package org.apache.ibatis.binding;  
  2.   
  3.   
  4. public class MapperMethod {  
  5.   
  6.   private final SqlCommand command;  
  7.   private final MethodSignature method;  
  8.   
  9.   public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {  
  10.     this.command = new SqlCommand(config, mapperInterface, method);  
  11.     this.method = new MethodSignature(config, method);  
  12.   }  
  13.   
  14.   public Object execute(SqlSession sqlSession, Object[] args) {  
  15.     Object result;  
  16.     if (SqlCommandType.INSERT == command.getType()) {  
  17.       Object param = method.convertArgsToSqlCommandParam(args);  
  18.       result = rowCountResult(sqlSession.insert(command.getName(), param));  
  19.     } else if (SqlCommandType.UPDATE == command.getType()) {  
  20.       Object param = method.convertArgsToSqlCommandParam(args);  
  21.       result = rowCountResult(sqlSession.update(command.getName(), param));  
  22.     } else if (SqlCommandType.DELETE == command.getType()) {  
  23.       Object param = method.convertArgsToSqlCommandParam(args);  
  24.       result = rowCountResult(sqlSession.delete(command.getName(), param));  
  25.     } else if (SqlCommandType.SELECT == command.getType()) {  
  26.       if (method.returnsVoid() && method.hasResultHandler()) {  
  27.         executeWithResultHandler(sqlSession, args);  
  28.         result = null;  
  29.       } else if (method.returnsMany()) {  
  30.         <span style="color:#FF0000;">result = executeForMany(sqlSession, args);//我们主要看看这个方法</span>  
  31.       } else if (method.returnsMap()) {  
  32.         result = executeForMap(sqlSession, args);  
  33.       } else {  
  34.         Object param = method.convertArgsToSqlCommandParam(args);  
  35.         result = sqlSession.selectOne(command.getName(), param);  
  36.       }  
  37.     } else if (SqlCommandType.FLUSH == command.getType()) {  
  38.         result = sqlSession.flushStatements();  
  39.     } else {  
  40.       throw new BindingException("Unknown execution method for: " + command.getName());  
  41.     }  
  42.     if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {  
  43.       throw new BindingException("Mapper method '" + command.getName()   
  44.           + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");  
  45.     }  
  46.     return result;  
  47.   }  
  48.  ........  
  49.   //方法还是很多,我们不需要全看就看一个很常用的查询返回多条记录的  
  50.   private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {  
  51.     List<E> result;  
  52.     Object param = method.convertArgsToSqlCommandParam(args);  
  53.     if (method.hasRowBounds()) {  
  54.       RowBounds rowBounds = method.extractRowBounds(args);  
  55.       result = sqlSession.<E>selectList(command.getName(), param, rowBounds);  
  56.     } else {  
  57.       <span style="color:#FF0000;">result = sqlSession.<E>selectList(command.getName(), param);</span>  
  58.     }  
  59.     // issue #510 Collections & arrays support  
  60.     if (!method.getReturnType().isAssignableFrom(result.getClass())) {  
  61.       if (method.getReturnType().isArray()) {  
  62.         return convertToArray(result);  
  63.       } else {  
  64.         return convertToDeclaredCollection(sqlSession.getConfiguration(), result);  
  65.       }  
  66.     }  
  67.     return result;  
  68.   }  
  69.   .......  
  70.  }  
好这里我们看到,MapperMethod 类采用命令模式运行,然后根据上下文跳转可能跳转到许多方法中取,我们不需要全部明白,我们可以看到里面的executeForMany方法,再看看它的实现,实际上它最后就是通过sqlSession对象去运行对象的SQL而已。

至此,相信大家已经了解了Mybatis为什么只用mappper接口便能够运行sql,因为mapperd的xml文件的命名空间对应的便是这个接口全路径,那么它根据全路径和方法名,便能够绑定起来,通过动态代理技术,让这个接口跑起来。

二、SqlSession下面的四大对象之一-----执行器(executor)

首先我先解释一下标题 四大对象是指:executor, statementHandler,parameterHandler,resultSetHandler对象。(为了方便下面的文章说道四大对象就专指它们)

它们都是sqlSession的底层类实现,也是插件能够拦截的四大对象。所以这里已经触及了Mybatis的底层,动态代理,反射随时可以看到,了解他们的协作,是插件编写的基础之一,所以这是十分的重要。

Executor在sqlSession中的应用

一个mapper被执行是通过动态代理来完成的,然后进入到了sqlSession的方法中去。这个并不难理解,但是sqlSession内部是怎么运行的呢?答案四大对象的协作。在SqlSession它还是一个接口,mybatis内部是通过DefaultSqlSession这个实现类为我们提供服务的,它比较长,但是我们不需要全部看到,我们只看到很常用的selectList方法便可以了。

[java] view plain copy
  1. package org.apache.ibatis.session.defaults;  
  2. public class DefaultSqlSession implements SqlSession {  
  3.   
  4.   private Configuration configuration;  
  5.   private Executor executor;  
  6.   
  7.   private boolean autoCommit;  
  8.   private boolean dirty;  
  9.   
  10. .......  
  11. @Override  
  12.   public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {  
  13.     try {  
  14.       MappedStatement ms = configuration.getMappedStatement(statement);  
  15.       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);  
  16.     } catch (Exception e) {  
  17.       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);  
  18.     } finally {  
  19.       ErrorContext.instance().reset();  
  20.     }  
  21.   }  
  22. ......  
  23. }  
我们可以看到它是通过executor去执行方法来完成查询的。

初认Executor

那么我们对executor就很感兴趣,于是我们看看executor是怎么样的,首先在MyBATIS中有三种executor:

SimpleExecutor -- SIMPLE 就是普通的执行器。

ReuseExecutor -执行器会重用预处理语句(prepared statements)

BatchExecutor --它是批量执行器

这些就是mybatis的三种执行器。你可以通过配置文件的settings里面的元素defaultExecutorType,配置它,默认是采用SimpleExecutor如果你在spring运用它,那么你可以这么配置它:

[html] view plain copy
  1. <bean id="sqlSessionTemplateBatch" class="org.mybatis.spring.SqlSessionTemplate">       
  2. <constructor-arg index="0" ref="sqlSessionFactory" />    
  3. <!--更新采用批量的executor -->    
  4. <constructor-arg index="1" value="BATCH"/>    
  5. </bean>    
这样,它便是一个批量的执行器。mybatis的三个executor都有一个共同的父类——BaseExecutor。

Executor初始化

首先我们先了解一下mybatis是怎么样生成executor的。我们看到生成Executor的地方(org.apache.ibatis.session.Configuration):

[java] view plain copy
  1. public Executor newExecutor(Transaction transaction, ExecutorType executorType) {  
  2.     executorType = executorType == null ? defaultExecutorType : executorType;  
  3.     executorType = executorType == null ? ExecutorType.SIMPLE : executorType;  
  4.     Executor executor;  
  5.     if (ExecutorType.BATCH == executorType) {  
  6.       executor = new BatchExecutor(this, transaction);  
  7.     } else if (ExecutorType.REUSE == executorType) {  
  8.       executor = new ReuseExecutor(this, transaction);  
  9.     } else {  
  10.       executor = new SimpleExecutor(this, transaction);  
  11.     }  
  12.     if (cacheEnabled) {  
  13.       executor = new CachingExecutor(executor);  
  14.     }  
  15.     executor = (Executor) interceptorChain.pluginAll(executor);  
  16.     return executor;  
  17.   }  
这里大部分都很好理解,但是有个地方就好不好理解,它就是:
[java] view plain copy
  1. executor = (Executor) interceptorChain.pluginAll(executor);  
这是一段非常重要的代码,它是采用责任链模式,来产生代理对象。我们需要再深入理解它,打开它具体的pluginAll方法:
[java] view plain copy
  1. public Object pluginAll(Object target) {  
  2.     for (Interceptor interceptor : interceptors) {  
  3.       target = interceptor.plugin(target);  
  4.     }  
  5.     return target;  
  6.   }  
我们这里先介绍一下这段代码:

Interceptor它是mybatis拦截器必须要实现的接口,换句话说,这个遍历就是遍历mybatis的拦截器。

然后调用plugin方法,这个方法是为了生成代理对象(占位)的。于是可以想象我们的插件的代理对象将会是一层层的嵌套,所以当中任何一个插件(Interceptor)都有机会拦截这个真是的服务对象(executor),则便是责任链模式,我们完全可以提供插件(Interceptor),进入到代理对象的invoke方法里面,来改变executor的行为和方法。

但是我要在这里强调,当你使用插件的时候,你将改变mybatis的executor内容实现,你必须慎重的使用它。
如果要我们自己编写动态的代理,那么工作量可不小,好在mybatis为我们提供了Plugin.Java类来完成我们所需要的功能。
[java] view plain copy
  1. public class Plugin implements InvocationHandler {  
  2.   
  3.   private Object target;  
  4.   private Interceptor interceptor;  
  5.   private Map<Class<?>, Set<Method>> signatureMap;  
  6.   
  7.   private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {  
  8.     this.target = target;  
  9.     this.interceptor = interceptor;  
  10.     this.signatureMap = signatureMap;  
  11.   }  
  12.   
  13.   public static Object wrap(Object target, Interceptor interceptor) {  
  14.     Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);  
  15.     Class<?> type = target.getClass();  
  16.     Class<?>[] interfaces = getAllInterfaces(type, signatureMap);  
  17.     if (interfaces.length > 0) {  
  18.       return Proxy.newProxyInstance(  
  19.           type.getClassLoader(),  
  20.           interfaces,  
  21.           new Plugin(target, interceptor, signatureMap));  
  22.     }  
  23.     return target;  
  24.   }  
  25.     
  26.   @Override  
  27.   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  28.     try {  
  29.       Set<Method> methods = signatureMap.get(method.getDeclaringClass());  
  30.       if (methods != null && methods.contains(method)) {  
  31.         return interceptor.intercept(new Invocation(target, method, args));  
  32.       }  
  33.       return method.invoke(target, args);  
  34.     } catch (Exception e) {  
  35.       throw ExceptionUtil.unwrapThrowable(e);  
  36.     }  
  37.   }  
  38.  ......  
  39. }  
这里有一个wrap方法:它会为我们生成代理对象。一旦我们的插件和它绑定,那么我们可以想到就会进入invoke方法里面。

invoke方法:很简单,它运行首先通过class和method的过滤,看看是否需要拦截这个方法,如果被拦截,那么它就运行interceptor的intercept方法。所以当我们配置了签名,就能够拦截我们的方法。

我们先讨论那么多,我们知道后面讲插件的时候我们还会提及它,这里我们知道它会根据插件的个数生成一层层的代理对象就可以了。

executor的执行

executor的执行是依赖于Statement对象来操作的,让我们以SimpleExecutor的doQuery方法为例子:

[java] view plain copy
  1. public class SimpleExecutor extends BaseExecutor {  
  2. ......  
  3.     
  4.   @Override  
  5.   public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {  
  6.     Statement stmt = null;  
  7.     try {  
  8.       Configuration configuration = ms.getConfiguration();  
  9.       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);  
  10.       stmt = prepareStatement(handler, ms.getStatementLog());  
  11.       return handler.<E>query(stmt, resultHandler);  
  12.     } finally {  
  13.       closeStatement(stmt);  
  14.     }  
  15.   }  
  16.   
  17.   ......  
  18.   private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {  
  19.     Statement stmt;  
  20.     Connection connection = getConnection(statementLog);  
  21.     stmt = handler.prepare(connection);  
  22.     handler.parameterize(stmt);  
  23.     return stmt;  
  24.   }  
  25.   
  26. }  
很显然这里调度的是一个查询方法

首先它先生成StatementHandler对象。

通过prepareStatement方法调用prepare方法初始化参数。

然后使用parameterize方法设置参数到运行环境。

然后便通过handler.<E>query(stmt, resultHandler);方法来完成结果组装。

于是我们的焦点就集中在了StatementHandler对象上。

三、SqlSession下面的四大对象之一-----statementHandler

讲到statementHandler,毫无疑问它是我们四大对象最重要的一个,它的任务就是和数据库对话。在它这里会使用parameterHandler和ResultSetHandler对象为我们绑定SQL参数和组装最后的结果返回。

首先我们先来看看statementHandler接口的定义:

[java] view plain copy
  1. public interface StatementHandler {  
  2.   
  3.   Statement prepare(Connection connection)  
  4.       throws SQLException;  
  5.   
  6.   void parameterize(Statement statement)  
  7.       throws SQLException;  
  8.   
  9.   void batch(Statement statement)  
  10.       throws SQLException;  
  11.   
  12.   int update(Statement statement)  
  13.       throws SQLException;  
  14.   
  15.   <E> List<E> query(Statement statement, ResultHandler resultHandler)  
  16.       throws SQLException;  
  17.   
  18.   BoundSql getBoundSql();  
  19.   
  20.   ParameterHandler getParameterHandler();  
  21.   
  22. }  

这里有几个重要的方法,prepare,parameterize和query,update,他们的作用是不一样的。

在MyBatis实现了statementHandler的有四个类:

RoutingStatementHandler,这是一个封装类,它不提供具体的实现,只是根据Executor的类型,创建不同的类型StatementHandler。

SimpleStatementHandler,这个类对应于JDBC的Statement对象,用于没有预编译参数的SQL的运行。

PreparedStatementHandler 这个用于预编译参数SQL的运行。

CallableStatementHandler 它将实存储过程的调度。

在MyBatis中,Configuration对象会采用new RoutingStatementHandler()来生成StatementHandler对象,换句话说我们真正使用的是RoutingStatementHandler对象,然后它会根据Executor的类型去创建对应具体的statementHandler对象(SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler)。

然后利用具体statementHandler的方法完成所需要的功能。那么这个具体的statementHandler是保存在RoutingStatementHandler对象的delegate属性的,所以当我们拦截statementHandler的时候就要常常访问它了。

首先prepare方法是用来编译SQL的,让我们看看它的源码实现。这里我们看到了BaseStatementHandler对prepare方法的实现.

[java] view plain copy
  1. @Override  
  2.  public Statement prepare(Connection connection) throws SQLException {  
  3.    ErrorContext.instance().sql(boundSql.getSql());  
  4.    Statement statement = null;  
  5.    try {  
  6.      statement = instantiateStatement(connection);  
  7.      setStatementTimeout(statement);  
  8.      setFetchSize(statement);  
  9.      return statement;  
  10.    } catch (SQLException e) {  
  11.      closeStatement(statement);  
  12.      throw e;  
  13.    } catch (Exception e) {  
  14.      closeStatement(statement);  
  15.      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);  
  16.    }  
  17.  }  
  18.   
  19.  protected abstract Statement instantiateStatement(Connection connection) throws SQLException;  
显然我们通过源码更加关注抽象方法instantiateStatement是做了什么事情。它依旧是一个抽象方法,那么它就有其实现类。那就是之前说的那几个具体的StatementHandler对象,让我们看看PreparedStatementHandler:
[java] view plain copy
  1. @Override  
  2.   protected Statement instantiateStatement(Connection connection) throws SQLException {  
  3.     String sql = boundSql.getSql();  
  4.     if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {  
  5.       String[] keyColumnNames = mappedStatement.getKeyColumns();  
  6.       if (keyColumnNames == null) {  
  7.         return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);  
  8.       } else {  
  9.         return connection.prepareStatement(sql, keyColumnNames);  
  10.       }  
  11.     } else if (mappedStatement.getResultSetType() != null) {  
  12.       return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);  
  13.     } else {  
  14.       return connection.prepareStatement(sql);  
  15.     }  
  16.   }  
好这个方法非常简单,我们可以看到它主要是根据上下文来预编译SQL,这是我们还没有设置参数。设置参数的任务是交由,statement接口的parameterize方法来实现的。

上面我们在prepare方法里面预编译了SQL。那么我们这个时候希望设置参数。在Statement中我们是使用parameterize方法进行设置参数的。让我们看看PreparedStatementHandler中的parameterize方法:

[java] view plain copy
  1. @Override  
  2.   public void parameterize(Statement statement) throws SQLException {  
  3.     parameterHandler.setParameters((PreparedStatement) statement);  
  4.   }  

我们用了prepare方法预编译了SQL,用了parameterize方法设置参数,那么我们接下来肯定是想执行SQL,而SQL无非是两种:

一种是进行查询——query,另外就是更新——update。

这些方法都很简单,让我们看看PreparedStatementHandler的实现:

[java] view plain copy
  1. @Override  
  2.   public int update(Statement statement) throws SQLException {  
  3.     PreparedStatement ps = (PreparedStatement) statement;  
  4.     ps.execute();  
  5.     int rows = ps.getUpdateCount();  
  6.     Object parameterObject = boundSql.getParameterObject();  
  7.     KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();  
  8.     keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);  
  9.     return rows;  
  10.   }  
  11.   
  12. ......  
  13. @Override  
  14.   public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  
  15.     PreparedStatement ps = (PreparedStatement) statement;  
  16.     ps.execute();  
  17.     return resultSetHandler.<E> handleResultSets(ps);  
  18.   }  
我们可以看到如果是进行update的,它将会执行生成主键的操作(插入数据要自动生成主键的时候),然后就返回影响行数。

如果是进行query的就更加简单了,它就是执行SQL语句,然后讲结果使用resultHandler的handleResultSets去完成我们的结果组装。至于resultsetHandler的内部实现还是很复杂的,值得期待哦。这里我们暂且不讲等待下一章吧。

StatementHandler是MyBatis四大对象里面最重要的对象,它的方法是十分重要的,也是我们插件的基础。

当我们需要改变sql的时候,显然我们要在预编译SQL(prepare方法前加入修改的逻辑)。

当我们需要修改参数的时候我们可以在调用parameterize方法前修改逻辑。或者使用ParameterHandler来改造设置参数。

我们需要控制组装结果集的时候,也可以在query方法前后加入逻辑,或者使用ResultHandler来改造组装结果。

懂的这些方法,才能理解我需要拦截什么对象,如何处理插件,这是MyBatis的核心内容。

四、SqlSession下面的四大对象之一-----parmeterHandler和resultsetHandler

我们知道ParameterHandler是用来设置参数规则的。当StatementHandler使用prepare()方法后,接下来就是使用它来设置参数,让我们看看它的定义:

[java] view plain copy
  1. package org.apache.ibatis.executor.parameter;  
  2.   
  3. import java.sql.PreparedStatement;  
  4. import java.sql.SQLException;  
  5.   
  6. /** 
  7.  * A parameter handler sets the parameters of the {@code PreparedStatement} 
  8.  * 
  9.  * @author Clinton Begin 
  10.  */  
  11. public interface ParameterHandler {  
  12.   
  13.   Object getParameterObject();  
  14.   
  15.   void setParameters(PreparedStatement ps)  
  16.       throws SQLException;  
  17.   
  18. }  
十分简单getParameterObject()是获取参数的,而setParameters()是设置参数的,相当于对一条sql所有的参数都执行ps.setXXX(value);

让我们看看它的实现类:DefaultParameterHandler

[java] view plain copy
  1. /** 
  2.  *    Copyright 2009-2015 the original author or authors. 
  3.  * 
  4.  *    Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  *    you may not use this file except in compliance with the License. 
  6.  *    You may obtain a copy of the License at 
  7.  * 
  8.  *       http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  *    Unless required by applicable law or agreed to in writing, software 
  11.  *    distributed under the License is distributed on an "AS IS" BASIS, 
  12.  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  *    See the License for the specific language governing permissions and 
  14.  *    limitations under the License. 
  15.  */  
  16. package org.apache.ibatis.scripting.defaults;  
  17.   
  18. import java.sql.PreparedStatement;  
  19. import java.sql.SQLException;  
  20. import java.util.List;  
  21.   
  22. import org.apache.ibatis.executor.ErrorContext;  
  23. import org.apache.ibatis.executor.parameter.ParameterHandler;  
  24. import org.apache.ibatis.mapping.BoundSql;  
  25. import org.apache.ibatis.mapping.MappedStatement;  
  26. import org.apache.ibatis.mapping.ParameterMapping;  
  27. import org.apache.ibatis.mapping.ParameterMode;  
  28. import org.apache.ibatis.reflection.MetaObject;  
  29. import org.apache.ibatis.session.Configuration;  
  30. import org.apache.ibatis.type.JdbcType;  
  31. import org.apache.ibatis.type.TypeException;  
  32. import org.apache.ibatis.type.TypeHandler;  
  33. import org.apache.ibatis.type.TypeHandlerRegistry;  
  34.   
  35. /** 
  36.  * @author Clinton Begin 
  37.  * @author Eduardo Macarron 
  38.  */  
  39. public class DefaultParameterHandler implements ParameterHandler {  
  40.   
  41.   private final TypeHandlerRegistry typeHandlerRegistry;  
  42.   
  43.   private final MappedStatement mappedStatement;  
  44.   private final Object parameterObject;  
  45.   private BoundSql boundSql;  
  46.   private Configuration configuration;  
  47.   
  48.   public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {  
  49.     this.mappedStatement = mappedStatement;  
  50.     this.configuration = mappedStatement.getConfiguration();  
  51.     this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();  
  52.     this.parameterObject = parameterObject;  
  53.     this.boundSql = boundSql;  
  54.   }  
  55.   
  56.   @Override  
  57.   public Object getParameterObject() {  
  58.     return parameterObject;  
  59.   }  
  60.   
  61.   @Override  
  62.   public void setParameters(PreparedStatement ps) {  
  63.     ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());  
  64.     List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();  
  65.     if (parameterMappings != null) {  
  66.       for (int i = 0; i < parameterMappings.size(); i++) {  
  67.         ParameterMapping parameterMapping = parameterMappings.get(i);  
  68.         if (parameterMapping.getMode() != ParameterMode.OUT) {  
  69.           Object value;  
  70.           String propertyName = parameterMapping.getProperty();  
  71.           if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params  
  72.             value = boundSql.getAdditionalParameter(propertyName);  
  73.           } else if (parameterObject == null) {  
  74.             value = null;  
  75.           } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {  
  76.             value = parameterObject;  
  77.           } else {  
  78.             MetaObject metaObject = configuration.newMetaObject(parameterObject);  
  79.             value = metaObject.getValue(propertyName);  
  80.           }  
  81.           TypeHandler typeHandler = parameterMapping.getTypeHandler();  
  82.           JdbcType jdbcType = parameterMapping.getJdbcType();  
  83.           if (value == null && jdbcType == null) {  
  84.             jdbcType = configuration.getJdbcTypeForNull();  
  85.           }  
  86.           try {  
  87.             typeHandler.setParameter(ps, i + 1, value, jdbcType);  
  88.           } catch (TypeException e) {  
  89.             throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);  
  90.           } catch (SQLException e) {  
  91.             throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);  
  92.           }  
  93.         }  
  94.       }  
  95.     }  
  96.   }  
  97.   
  98. }  
这里重点是setParameters(),首先它读取了ParameterObject参数对象,然后用typeHandler对参数进行设置,而typeHandler里面需要对jdbcType和javaType进行处理,然后就设置参数了。也很好理解。所以当我们使用TypeHandler的时候完全可以控制如何设置SQL参数。
ResultSetHandler要比Parameter复杂得多,对其原理要讲清楚真的很困难,但是我们一般在插件里面使用不多。所以我就不分析太详细了,大致讲讲它的大概原理。让我们看看ResultSetHandler的接口定义:

[java] view plain copy
  1. package org.apache.ibatis.session;  
  2.   
  3. /** 
  4.  * @author Clinton Begin 
  5.  */  
  6. public interface ResultHandler<T> {  
  7.   
  8.   void handleResult(ResultContext<? extends T> resultContext);  
  9.   
  10. }  
接口就只有一个方法,但是内部真的包罗万象,要读懂源码真的很不容易,而且在插件中使用得不算太多,所以我就不带大家读了。大概的道理是,首先要考虑是否延迟加载,如果是使用延迟加载就只记录其主要的关联信息,否则就用TypeHandler读取出resultSet的返回的记录,然后通过ObjectFactory组装结果集。

因为掌握好四大对象的基本方法,了解他们的作用是我们插件编写的根本,没有掌握它们你是没有办法准确的去编写你的插件的。

尤其是对StatmentHanlder的掌握尤其重要。

智能推荐

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
© 2014-2019 ITdaan.com 粤ICP备14056181号  

赞助商广告