我们目前在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. 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. null;  
  29. else if (method.returnsMany()) {  
  30. "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. "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. null ? defaultExecutorType : executorType;  
  3. null ? ExecutorType.SIMPLE : executorType;  
  4.     Executor executor;  
  5. if (ExecutorType.BATCH == executorType) {  
  6. new BatchExecutor(this, transaction);  
  7. else if (ExecutorType.REUSE == executorType) {  
  8. new ReuseExecutor(this, transaction);  
  9. else {  
  10. new SimpleExecutor(this, transaction);  
  11.     }  
  12. if (cacheEnabled) {  
  13. 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. 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. 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. "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. 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. 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的掌握尤其重要。