mybatis入门基础(十一)-----分页查询原理详解

本文的内容将作为前面我们叙述的内容的内部原理篇的解释,希望各位读者能够仔细理解本篇的内容,下一篇,我们将详细的介绍如何实现一个生产环境下的分页查询,好了,马上开始我们的正文部分吧。【在此,先对开源世界的大神表示感谢】

准备工作:Mybatis 3.3.1版本源码。【其他版本,请读者自行对比即可】

--------------------------------------------------------------------------------------------------------------------------------------------------------

1.首先,请大家回想下面的代码:

[java]  view plain  copy
  1. public class SqlSessionFactoryUtil {  
  2.     private static SqlSessionFactory sqlSessionFactory;  
  3.     public static SqlSessionFactory getSqlSessionFactory(){  
  4.         if(sqlSessionFactory==null){  
  5.             InputStream inputStream=null;  
  6.             try{  
  7.                 inputStream=Resources.getResourceAsStream("mybatis-config.xml");  
  8.                 sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);  
  9.             }catch(Exception e){  
  10.                 e.printStackTrace();  
  11.             }  
  12.         }  
  13.         return sqlSessionFactory;  
  14.     }  
  15.     public static SqlSession openSession(){  
  16.         return getSqlSessionFactory().openSession();  
  17.     }  
  18. }  

2.Mybatis的整体架构图,如下:【摘自百科】


3.Mybatis执行流程图,如下:【摘自其他博文,详情见末尾】


4.上图中各个组件的概念解释:

SqlSessionFactoryBuilder:每一个Mybatis的应用程序的入口是SqlSessionFactoryBuilder,它的作用是通过XML配置文件创建Configuration对象(当然也可以在程序中自行创建),然后通过build方法创建SqlSessionFactory对象。没有必要每次访问Mybatis就创建一次SqlSessionFactoryBuilder,通常的做法是创建一个全局的对象就可以了。正如我们在前文中演示的那样,专门有一个Util负责管理SqlSwssionFactoryBuilder。【下面的方法请参照上文程序对比学习】


SqlSessionFactory:SqlSessionFactory对象由SqlSessionFactoryBuilder创建。它的主要功能是创建SqlSession对象,和SqlSessionFactoryBuilder对象一样,没有必要每次访问Mybatis就创建一次SqlSessionFactory,通常的做法是创建一个全局的对象就可以了。SqlSessionFactory对象一个必要的属性是Configuration对象,它是保存Mybatis全局配置的一个配置对象,通常由SqlSessionFactoryBuilder从XML配置文件创建。

【SqlSessionFactory为接口】


【DefaultSqlSessionFactory为默认实现】


SqlSession:SqlSession对象的主要功能是完成一次数据库的访问和结果的映射,它类似于数据库的session概念,由于不是线程安全的,所以SqlSession对象的作用域需限制在方法内。SqlSession的默认实现类是DefaultSqlSession,它有两个必须配置的属性:Configuration和Executor。【截图中省略部分内容】


Configuration和Executor。Configuration即是我们的mybatis-config.xml文件,具体的配置请参考前文《Mybatis最入门---Mapper文件配置详解(上)》等内容。SqlSession对数据库的操作都是通过Executor来完成的,Executor的具体功能在下文描述。

到目前为止,我们看到的都是mybatis的流程,我们的应用程序在什么地方插入到这个流程中并获得我们想要的结果呢?就是SqlSession这里。SqlSession有一个重要的方法getMapper,顾名思义,这个方式是用来获取Mapper对象的。什么是Mapper对象?根据Mybatis的官方手册,应用程序除了要初始并启动Mybatis之外,还需要定义一些接口,接口里定义访问数据库的方法,存放接口的包路径下需要放置同名的XML配置文件。SqlSession的getMapper方法是联系应用程序和Mybatis纽带,应用程序访问getMapper时,Mybatis会根据传入的接口类型和对应的XML配置文件生成一个代理对象,这个代理对象就叫Mapper对象。应用程序获得Mapper对象后,就应该通过这个Mapper对象来访问Mybatis的SqlSession对象,这样就达到里插入到Mybatis流程的目的。示例代码如下:

[java]  view plain  copy
  1. UserInfoDao userInfo = sqlSession.getMapper(UserInfoDao.class);  

Executor:Executor对象在创建Configuration对象的时候创建,并且缓存在Configuration对象里。Executor对象的主要功能是调用StatementHandler访问数据库,并将查询结果存入缓存中(如果配置了缓存的话)。

StatementHandler:是真正访问数据库的地方,并调用ResultSetHandler处理查询结果。

ResultSetHandler:处理查询结果。

-------------------------------------------------------------------------------------------------------------------------------------

SqlSession详解:

1.现在我们的程序已经执行到了

[java]  view plain  copy
  1. public static SqlSession openSession(){  
  2.         return getSqlSessionFactory().openSession();  
  3.     }  
其对应的实现如下:【中间省略部分过程,读者可以自行跟踪】

[java]  view plain  copy
  1. public class DefaultSqlSessionFactory implements SqlSessionFactory {  
  2.   //.....  
  3.   @Override  
  4.   public SqlSession openSession() {  
  5.     return openSessionFromDataSource(configuration.getDefaultExecutorType(), nullfalse);  
  6.   }  
  7.   //.....  
  8. }  
openSessionFromDataSource(,,,)详细内容如下:

[java]  view plain  copy
  1. private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {  
  2.     Transaction tx = null;  
  3.     try {  
  4.       final Environment environment = configuration.getEnvironment();  
  5.       final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);  
  6.       tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);  
  7.       final Executor executor = configuration.newExecutor(tx, execType);  
  8.       return new DefaultSqlSession(configuration, executor, autoCommit);  
  9.     } catch (Exception e) {  
  10.       closeTransaction(tx); // may have fetched a connection so lets call close()  
  11.       throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);  
  12.     } finally {  
  13.       ErrorContext.instance().reset();  
  14.     }  
  15.   }  
上面的过程概括为:

1)       从配置中获取Environment

2)       Environment中取得TransactionFactory

3)       Environment中取得DataSource

4)       创建事务对象Transaction

5)       创建Executor对象(该对象非常重要,事实上sqlsession的所有操作都是通过它完成的);

6)       创建sqlsession对象。

Executor的创建:Executor与Sqlsession的关系就像市长与书记,Sqlsession只是个门面,真正干事的是Executor,Sqlsession对数据库的操作都是通过Executor来完成的。与Sqlsession一样,Executor也是动态创建的:

此时,程序执行到了上文中:

[java]  view plain  copy
  1. final Executor executor = configuration.newExecutor(tx, execType);  
其对应的具体实现为:
[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.  }  

可以看出,如果不开启cache的话,创建的Executor只是3中基础类型之一,BatchExecutor专门用于执行批量sql操作,ReuseExecutor会重用statement执行sql操作,SimpleExecutor只是简单执行sql没有什么特别的。【按照我们前文的设置,这里创建的就是SimpleExecutor】开启cache的话(默认是开启的并且没有任何理由去关闭它),就会创建CachingExecutor,它以前面创建的Executor作为唯一参数。CachingExecutor在查询数据库前先查找缓存,若没找到的话调用delegate(就是构造时传入的Executor对象)从数据库查询,并将查询结果存入缓存中。

Executor对象是可以被插件拦截的,如果定义了针对Executor类型的插件,最终生成的Executor对象是被各个插件插入后的代理对象。

至此,SqlSession对象已经成功创建。接下来就该执行getMapper(..)

-------------------------------------------------------------------------------------------------------------------------------------

1.我们首先来看看getMapper的实现,如下:【默认执行下面的方法】

[java]  view plain  copy
  1. public class DefaultSqlSession implements SqlSession {  
  2.   //...  
  3. @Override  
  4.   public <T> T getMapper(Class<T> type) {  
  5.     return configuration.<T>getMapper(type, this);  
  6.   }  
  7.   //...  
  8. }  
2.接着向下跟踪,实际执行的如下的方法:

[java]  view plain  copy
  1. public class Configuration {  
  2. //...  
  3. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {  
  4.     return mapperRegistry.getMapper(type, sqlSession);  
  5.   }  
  6. //..  
  7. }  
3.接着向下跟踪,执行如下方法:

[java]  view plain  copy
  1. public class MapperRegistry {  
  2. //....  
  3.  @SuppressWarnings("unchecked")  
  4.   public <T> T getMapper(Class<T> type, SqlSession sqlSession) {  
  5.     final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);  
  6.     if (mapperProxyFactory == null) {  
  7.       throw new BindingException("Type " + type + " is not known to the MapperRegistry.");  
  8.     }  
  9.     try {  
  10.       return mapperProxyFactory.newInstance(sqlSession);  
  11.     } catch (Exception e) {  
  12.       throw new BindingException("Error getting mapper instance. Cause: " + e, e);  
  13.     }  
  14.   }  
  15. //...  
  16. }  
我们进一步看看mapperProxyFactory.newInstance(sqlSession);背后发生了什么事情:

[java]  view plain  copy
  1. public class MapperProxyFactory<T> {  
  2.   //...  
  3. @SuppressWarnings("unchecked")  
  4.   protected T newInstance(MapperProxy<T> mapperProxy) {  
  5.     return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);  
  6.   }  
  7.   
  8.   public T newInstance(SqlSession sqlSession) {  
  9.     final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);  
  10.     return newInstance(mapperProxy);  
  11.   }  
  12. //...  
  13. }  
可以看到,mapper是一个代理对象,它实现的接口就是传入的type,这就是为什么mapper对象可以通过接口直接访问。同时还可以看到,创建mapper代理对象时传入了sqlsession对象,这样就把sqlsession也关联起来了。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

现在,准备工作已经基本结束,程序流即将执行到具体的接口中的CRUD方法,如:

[java]  view plain  copy
  1. int re = userInfo.insertUserInfo(ui);  
我们接着向下跟踪,看到即将执行MapperProxy的invoke();

我们知道对被代理对象的方法的访问都会落实到代理者的invoke上来,MapperProxyinvoke如下:

[java]  view plain  copy
  1. public class MapperProxy<T> implements InvocationHandler, Serializable {  
  2. //....  
  3.   @Override  
  4.   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  5.     if (Object.class.equals(method.getDeclaringClass())) {  
  6.       try {  
  7.         return method.invoke(this, args);  
  8.       } catch (Throwable t) {  
  9.         throw ExceptionUtil.unwrapThrowable(t);  
  10.       }  
  11.     }  
  12.     final MapperMethod mapperMethod = cachedMapperMethod(method);  
  13.     return mapperMethod.execute(sqlSession, args);  
  14.   }  
  15.   
  16.   private MapperMethod cachedMapperMethod(Method method) {  
  17.     MapperMethod mapperMethod = methodCache.get(method);  
  18.     if (mapperMethod == null) {  
  19.       mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());  
  20.       methodCache.put(method, mapperMethod);  
  21.     }  
  22.     return mapperMethod;  
  23.   }  
  24. //....  
  25. }  
可以看到invoke把执行权转交给了MapperMethod,我们来看看MapperMethod里又是怎么运作的:

[java]  view plain  copy
  1. public class MapperMethod {   
  2. //...  
  3. public Object execute(SqlSession sqlSession, Object[] args) {  
  4.     Object result;  
  5.     if (SqlCommandType.INSERT == command.getType()) {  
  6.       Object param = method.convertArgsToSqlCommandParam(args);  
  7.       result = rowCountResult(sqlSession.insert(command.getName(), param));  
  8.     } else if (SqlCommandType.UPDATE == command.getType()) {  
  9.       Object param = method.convertArgsToSqlCommandParam(args);  
  10.       result = rowCountResult(sqlSession.update(command.getName(), param));  
  11.     } else if (SqlCommandType.DELETE == command.getType()) {  
  12.       Object param = method.convertArgsToSqlCommandParam(args);  
  13.       result = rowCountResult(sqlSession.delete(command.getName(), param));  
  14.     } else if (SqlCommandType.SELECT == command.getType()) {  
  15.       if (method.returnsVoid() && method.hasResultHandler()) {  
  16.         executeWithResultHandler(sqlSession, args);  
  17.         result = null;  
  18.       } else if (method.returnsMany()) {  
  19.         result = executeForMany(sqlSession, args);  
  20.       } else if (method.returnsMap()) {  
  21.         result = executeForMap(sqlSession, args);  
  22.       } else {  
  23.         Object param = method.convertArgsToSqlCommandParam(args);  
  24.         result = sqlSession.selectOne(command.getName(), param);  
  25.       }  
  26.     } else if (SqlCommandType.FLUSH == command.getType()) {  
  27.         result = sqlSession.flushStatements();  
  28.     } else {  
  29.       throw new BindingException("Unknown execution method for: " + command.getName());  
  30.     }  
  31.     if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {  
  32.       throw new BindingException("Mapper method '" + command.getName()   
  33.           + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");  
  34.     }  
  35.     return result;  
  36.   }  
  37. //...  
  38. }  

可以看到,MapperMethod就像是一个分发者,他根据参数和返回值类型选择不同的sqlsession方法来执行。这样mapper对象与sqlsession就真正的关联起来了。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Executor

前面提到过,sqlsession只是一个门面,真正发挥作用的是executor,对sqlsession方法的访问最终都会落到executor的相应方法上去。
这里我们以insert语句为例,跟踪其执行过程,如下:
[java]  view plain  copy
  1. int re = userInfo.insertUserInfo(ui);  
执行到invoke----->execute(SqlSession sqlSession, Object[] args)中的 if (SqlCommandType.INSERT == command.getType())---->sqlSession.insert(command.getName(), param)---->DefaultSqlSession中的insert(String statement, Object parameter)---->update(String statement, Object parameter)---->return executor.update(ms, wrapCollection(parameter));
上面最后一句的executor声明为借口,其实现分为两个大类,如下图:

如上图,在Mybatis3.3.1版本中,BaseExcutor分为4类,BatchExecutor用于执行批量sql操作,ClosedExecutor用于关闭Executor,ReuseExecutor用于重用statement执行sql操作,SimpleExecutor用于简单的执行Sql语句,并不做特别处理。
首先,我们来看看CachingExecutor的执行细节,如下:

【接下来,由于查询语句的执行流程包含插入语句的执行过程,借此以查询语句为例,说明后续程序执行流程。】

[java]  view plain  copy
  1. public class CachingExecutor implements Executor {  
  2.   private Executor delegate;   
  3. @Override  
  4.   public int update(MappedStatement ms, Object parameterObject) throws SQLException {  
  5.     flushCacheIfRequired(ms);  
  6.     return delegate.update(ms, parameterObject);  
  7.   }  
  8.   
  9.   @Override  
  10.   public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {  
  11.     BoundSql boundSql = ms.getBoundSql(parameterObject);  
  12.     CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);  
  13.     return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);  
  14.   }  
  15.   
  16.   @Override  
  17.   public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)  
  18.       throws SQLException {  
  19.     Cache cache = ms.getCache();  
  20.     if (cache != null) {  
  21.       flushCacheIfRequired(ms);  
  22.       if (ms.isUseCache() && resultHandler == null) {  
  23.         ensureNoOutParams(ms, parameterObject, boundSql);  
  24.         @SuppressWarnings("unchecked")  
  25.         List<E> list = (List<E>) tcm.getObject(cache, key);  
  26.         if (list == null) {  
  27.           list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);  
  28.           tcm.putObject(cache, key, list); // issue #578 and #116  
  29.         }  
  30.         return list;  
  31.       }  
  32.     }  
  33.     return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);  
  34.   }  
  35.   
  36. }  
从上面的执行细节,我们发现,实际的插入语句还是执行的BaseExecutor中的update,而查询功能,是先从cache中获取,取不到时再执行查询方法,并将结果保存到cache中。
现在,我们看看SimpleExecutor内部执行细节,如下:【接着上文执行流程】
[java]  view plain  copy
  1. @Override  
  2.  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {  
  3.    Statement stmt = null;  
  4.    try {  
  5.      Configuration configuration = ms.getConfiguration();  
  6.      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, nullnull);  
  7.      stmt = prepareStatement(handler, ms.getStatementLog());  
  8.      return handler.update(stmt);  
  9.    } finally {  
  10.      closeStatement(stmt);  
  11.    }  
  12.  }  
从上面的内容,我们可以看到,归根结底执行update的地方是StatementHandler。当executor将控制权交给StatementHandler之后,剩下的事就是StatementHandler内部的执行细节了。
我们先看看StatementHandler是如何创建的,如下:【上面的程序执行到:configuration.newStatementHandler(....)】
[java]  view plain  copy
  1. public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {  
  2.    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);  
  3.    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);  
  4.    return statementHandler;  
  5.  }  
在这里,我们发现每次创建的StatementHandler都是RoutingStatementHandler
[java]  view plain  copy
  1. public class RoutingStatementHandler implements StatementHandler {  
  2.   
  3.   private final StatementHandler delegate;  
  4.   
  5.   public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {  
  6.   
  7.     switch (ms.getStatementType()) {  
  8.       case STATEMENT:  
  9.         delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);  
  10.         break;  
  11.       case PREPARED:  
  12.         delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);  
  13.         break;  
  14.       case CALLABLE:  
  15.         delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);  
  16.         break;  
  17.       default:  
  18.         throw new ExecutorException("Unknown statement type: " + ms.getStatementType());  
  19.     }  
  20.   
  21.   }  
  22. //...  
  23. }  
可以看到它只是一个分发者,他一个属性delegate用于指定用哪种具体的StatementHandler。可选的StatementHandlerSimpleStatementHandlerPreparedStatementHandlerCallableStatementHandler三种。选用哪种在mapper配置文件的每个statement里指定,默认的是PreparedStatementHandler。同时还要注意到StatementHandler是可以被拦截器拦截的,和Executor一样,被拦截器拦截后的对像是一个代理对象。由于mybatis没有实现数据库的物理分页,众多物理分页的实现都是在这个地方使用拦截器实现的。
StatementHandler创建后需要执行一些初始操作,比如statement的开启和参数设置、对于PreparedStatement还需要执行参数的设置操作等,如下:
[java]  view plain  copy
  1. public abstract class BaseStatementHandler implements StatementHandler {   
  2. //....  
  3. protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {  
  4.     this.configuration = mappedStatement.getConfiguration();  
  5.     this.executor = executor;  
  6.     this.mappedStatement = mappedStatement;  
  7.     this.rowBounds = rowBounds;  
  8.   
  9.     this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();  
  10.     this.objectFactory = configuration.getObjectFactory();  
  11.   
  12.     if (boundSql == null) { // issue #435, get the key before calculating the statement  
  13.       generateKeys(parameterObject);  
  14.       boundSql = mappedStatement.getBoundSql(parameterObject);  
  15.     }  
  16.   
  17.     this.boundSql = boundSql;  
  18.   
  19.     this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);  
  20.     this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);  
  21.   }  
  22. //...  
  23. }  

[java]  view plain  copy
  1. public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {  
  2.   ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);  
  3.   parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);  
  4.   return parameterHandler;  
  5. }  

[java]  view plain  copy
  1. public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,  
  2.     ResultHandler resultHandler, BoundSql boundSql) {  
  3.   ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);  
  4.   resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);  
  5.   return resultSetHandler;  
  6. }  
【其他细节内容省略,有兴趣的读者可以继续跟踪】
现在程序已经执行到了:stmt = prepareStatement(handler, ms.getStatementLog());继续F5:执行流到下面的内容:【对于PreparedStatement还需要执行参数的设置操作等。
[java]  view plain  copy
  1. private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {  
  2.   Statement stmt;  
  3.   Connection connection = getConnection(statementLog);  
  4.   stmt = handler.prepare(connection);  
  5.   handler.parameterize(stmt);  
  6.   return stmt;  
  7. }  

statement的开启和参数设置没什么特别之处,handler.parameterize倒是可以看看其内部细节。handler.parameterize通过调用ParameterHandlersetParameters完成参数的设置,ParameterHandler随着StatementHandler的创建而创建,默认的实现是DefaultParameterHandler:如下:

[java]  view plain  copy
  1. public class RoutingStatementHandler implements StatementHandler {  
  2.  private final StatementHandler delegate;  
 @Override public void parameterize(Statement statement) throws SQLException { delegate.parameterize(statement); }//...} 
 
 
 
[java]  view plain  copy
  1. public class PreparedStatementHandler extends BaseStatementHandler {  
  2. //...  
  3. @Override  
  4.   public void parameterize(Statement statement) throws SQLException {  
  5.     parameterHandler.setParameters((PreparedStatement) statement);  
  6.   }  
  7. //...  
  8. }  
我们再来看看setParameters(PreparedStatement ps) 内部的执行细节吧,如下:
[java]  view plain  copy
  1. public class DefaultParameterHandler implements ParameterHandler {  
  2.   
  3.   private final TypeHandlerRegistry typeHandlerRegistry;  
  4.   
  5.   private final MappedStatement mappedStatement;  
  6.   private final Object parameterObject;  
  7.   private BoundSql boundSql;  
  8.   private Configuration configuration;  
  9. //...  
  10. @Override  
  11.   public void setParameters(PreparedStatement ps) {  
  12.     ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());  
  13.     List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();  
  14.     if (parameterMappings != null) {  
  15.       for (int i = 0; i < parameterMappings.size(); i++) {  
  16.         ParameterMapping parameterMapping = parameterMappings.get(i);  
  17.         if (parameterMapping.getMode() != ParameterMode.OUT) {  
  18.           Object value;  
  19.           String propertyName = parameterMapping.getProperty();  
  20.           if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params  
  21.             value = boundSql.getAdditionalParameter(propertyName);  
  22.           } else if (parameterObject == null) {  
  23.             value = null;  
  24.           } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {  
  25.             value = parameterObject;  
  26.           } else {  
  27.             MetaObject metaObject = configuration.newMetaObject(parameterObject);  
  28.             value = metaObject.getValue(propertyName);  
  29.           }  
  30.           TypeHandler typeHandler = parameterMapping.getTypeHandler();  
  31.           JdbcType jdbcType = parameterMapping.getJdbcType();  
  32.           if (value == null && jdbcType == null) {  
  33.             jdbcType = configuration.getJdbcTypeForNull();  
  34.           }  
  35.           try {  
  36.             typeHandler.setParameter(ps, i + 1, value, jdbcType);  
  37.           } catch (TypeException e) {  
  38.             throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);  
  39.           } catch (SQLException e) {  
  40.             throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);  
  41.           }  
  42.         }  
  43.       }  
  44.     }  
  45.   }  
  46. //...  
  47. }  

这里面最重要的一句其实就是最后一句代码,它的作用是用合适的TypeHandler完成参数的设置。那么什么是合适的TypeHandler呢,它又是如何决断出来的呢?BaseStatementHandler的构造方法里有这么一句:
[java]  view plain  copy
  1. if (boundSql == null) { // issue #435, get the key before calculating the statement  
  2.       generateKeys(parameterObject);  
  3.       boundSql = mappedStatement.getBoundSql(parameterObject);  
  4.     }  

它触发了sql 的解析,在解析sql的过程中,TypeHandler也被决断出来了,决断的原则就是根据参数的类型和参数对应的JDBC类型决定使用哪个TypeHandler。比如:参数类型是String的话就用StringTypeHandler,参数类型是整数的话就用IntegerTypeHandler等

参数设置完毕后,执行数据库操作(update或query)。如果是query最后还有个查询结果的处理过程。

-------------------------------------------------------------------------------------------------------------------------------------
查询结果的处理过程
【程序执行到:configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);】
[java]  view plain  copy
  1. public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,  
  2.       ResultHandler resultHandler, BoundSql boundSql) {  
  3.     ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);  
  4.     resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);  
  5.     return resultSetHandler;  
  6.   }  
【注意:此时执行的是查询语句】
继续向下跟踪,可以看出ResultSetHandler也是可以被拦截的,可以编写自己的拦截器改变ResultSetHandler的默认行为。
[java]  view plain  copy
  1. public class DefaultResultSetHandler implements ResultSetHandler {  
  2. //...  
  3. private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {  
  4.     final ResultLoaderMap lazyLoader = new ResultLoaderMap();  
  5.     Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null);  
  6.     if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {  
  7.       final MetaObject metaObject = configuration.newMetaObject(resultObject);  
  8.       boolean foundValues = !resultMap.getConstructorResultMappings().isEmpty();  
  9.       if (shouldApplyAutomaticMappings(resultMap, false)) {  
  10.         foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;  
  11.       }  
  12.       foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;  
  13.       foundValues = lazyLoader.size() > 0 || foundValues;  
  14.       resultObject = foundValues ? resultObject : null;  
  15.       return resultObject;  
  16.     }  
  17.     return resultObject;  
  18.   }  
  19. //...  
  20. }  

[java]  view plain  copy
  1. private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {  
  2.    List<UnMappedColumAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);  
  3.    boolean foundValues = false;  
  4.    if (autoMapping.size() > 0) {  
  5.      for (UnMappedColumAutoMapping mapping : autoMapping) {  
  6.        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);  
  7.        // issue #377, call setter on nulls  
  8.        if (value != null || configuration.isCallSettersOnNulls()) {  
  9.          if (value != null || !mapping.primitive) {  
  10.            metaObject.setValue(mapping.property, value);  
  11.          }  
  12.          foundValues = true;  
  13.        }  
  14.      }  
  15.    }  
  16.    return foundValues;  
  17.  }  

[java]  view plain  copy
  1. private List<UnMappedColumAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {  
  2.    final String mapKey = resultMap.getId() + ":" + columnPrefix;  
  3.    List<UnMappedColumAutoMapping> autoMapping = autoMappingsCache.get(mapKey);  
  4.    if (autoMapping == null) {  
  5.      autoMapping = new ArrayList<UnMappedColumAutoMapping>();  
  6.      final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);  
  7.      for (String columnName : unmappedColumnNames) {  
  8.        String propertyName = columnName;  
  9.        if (columnPrefix != null && !columnPrefix.isEmpty()) {  
  10.          // When columnPrefix is specified,  
  11.          // ignore columns without the prefix.  
  12.          if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {  
  13.            propertyName = columnName.substring(columnPrefix.length());  
  14.          } else {  
  15.            continue;  
  16.          }  
  17.        }  
  18.        final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());  
  19.        if (property != null && metaObject.hasSetter(property)) {  
  20.          final Class<?> propertyType = metaObject.getSetterType(property);  
  21.          if (typeHandlerRegistry.hasTypeHandler(propertyType)) {  
  22.            final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);  
  23.            autoMapping.add(new UnMappedColumAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));  
  24.          }  
  25.        }  
  26.      }  
  27.      autoMappingsCache.put(mapKey, autoMapping);  
  28.    }  
  29.    return autoMapping;  
  30.  }  
从代码里可以看到,TypeHandler使用的是结果参数的属性类型。因此我们在定义作为结果的对象的属性时一定要考虑与数据库字段类型的兼容性。
-------------------------------------------------------------------------------------------------------------------------------------
学习了Mybatis整个Sql语句的执行细节之后,现在,我们开始看看插件的实现原理。
Mybatis采用责任链模式,【关于设计模式的内容,后续我们有专门章节给大家讲述,现在请各位看官先自行的学习下吧】通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。

代理链的生成

Mybatis支持对ExecutorStatementHandlerPameterHandlerResultSetHandler进行拦截,也就是说会对这4种对象进行代理。下面以Executor为例。Mybatis在创建Executor对象时会执行下面一行代码:

[java]  view plain  copy
  1. executor =(Executor) interceptorChain.pluginAll(executor);    
InterceptorChain里保存了所有的拦截器,它在mybatis初始化的时候创建。上面这句代码的含义是调用拦截器链里的每个拦截器依次对executor进行plugin,代码如下:
[java]  view plain  copy
  1. public class InterceptorChain {  
  2.   
  3.   private final List<Interceptor> interceptors = new ArrayList<Interceptor>();  
  4.   
  5.   public Object pluginAll(Object target) {  
  6.     for (Interceptor interceptor : interceptors) {  
  7.       target = interceptor.plugin(target);  
  8.     }  
  9.     return target;  
  10.   }  
  11. //...  
  12. }  
下面以一个简单的例子来看看这个plugin方法里到底发生了什么。

[java]  view plain  copy
  1. @Intercepts({@Signature(type = Executor.class, method ="update", args = {MappedStatement.class, Object.class})})    
  2. public class ExamplePlugin implements Interceptor {    
  3.     @Override    
  4.     public Object intercept(Invocation invocation) throws Throwable {    
  5.         return invocation.proceed();    
  6.     }    
  7.     
  8.     @Override    
  9.     public Object plugin(Object target) {    
  10.         return Plugin.wrap(target, this);    
  11.     }    
  12.     
  13.     @Override    
  14.     public void setProperties(Properties properties) {    
  15.     }    
  16. }   

每一个拦截器都必须实现上面的三个方法,其中:

1)       Object intercept(Invocation invocation)是实现拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器拦截目标方法。

2)       Object plugin(Object target) 就是用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target,this) 来完成的,把目标target和拦截器this传给了包装函数。

3)       setProperties(Properties properties)用于设置额外的参数,参数配置在拦截器的Properties节点里。

注解里描述的是指定拦截方法的签名  [type,method,args] (即对哪种对象的哪种方法进行拦截),它在拦截前用于决断。

Plugin.wrap方法

从前面可以看出,每个拦截器的plugin方法是通过调用Plugin.wrap方法来实现的。代码如下:

[java]  view plain  copy
  1. public staticObject wrap(Object target, Interceptor interceptor) {    
  2.    //从拦截器的注解中获取拦截的类名和方法信息    
  3.    Map<Class<?>, Set<Method>> signatureMap =getSignatureMap(interceptor);    
  4.    Class<?> type = target.getClass();    
  5.    //解析被拦截对象的所有接口(注意是接口)    
  6.    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);    
  7.    if(interfaces.length > 0) {    
  8.         //生成代理对象, Plugin对象为该代理对象的InvocationHandler  (InvocationHandler属于java代理的一个重要概念,不熟悉的请参考相关概念)    
  9.         return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target,interceptor,signatureMap));    
  10.     }    
  11.     returntarget;    
  12. }    

这个Plugin类有三个属性:

   private Object target;//被代理的目标类

   private Interceptor interceptor;//对应的拦截器

   private Map<Class<?>, Set<Method>> signatureMap;//拦截器拦截的方法缓存

我们再次结合(Executor)interceptorChain.pluginAll(executor)这个语句来看,这个语句内部对

executor执行了多次plugin,第一次plugin后通过Plugin.wrap方法生成了第一个代理类,姑且就叫executorProxy1,这个代理类的target属性是该executor对象。第二次plugin后通过Plugin.wrap方法生成了第二个代理类,姑且叫executorProxy2,这个代理类的target属性是executorProxy1...这样通过每个代理类的target属性就构成了一个代理链(从最后一个executorProxyN往前查找,通过target属性可以找到最原始的executor类)。

代理链上的拦截

代理链生成后,对原始目标的方法调用都转移到代理者的invoke方法上来了。Plugin作为InvocationHandler的实现类,它的invoke方法是怎么样的呢?

[java]  view plain  copy
  1. @Override  
  2.  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  3.    try {  
  4.      Set<Method> methods = signatureMap.get(method.getDeclaringClass());  
  5.      if (methods != null && methods.contains(method)) {  
  6.        //调用代理类所属拦截器的intercept方法,  
  7.        return interceptor.intercept(new Invocation(target, method, args));  
  8.      }  
  9.      return method.invoke(target, args);  
  10.    } catch (Exception e) {  
  11.      throw ExceptionUtil.unwrapThrowable(e);  
  12.    }  
  13.  }  

invoke里,如果方法签名和拦截中的签名一致,就调用拦截器的拦截方法。我们看到传递给拦截器的是一个Invocation对象,这个对象是什么样子的,他的功能又是什么呢?
[java]  view plain  copy
  1. package org.apache.ibatis.plugin;  
  2.   
  3. import java.lang.reflect.InvocationTargetException;  
  4. import java.lang.reflect.Method;  
  5.   
  6. /** 
  7.  * @author Clinton Begin 
  8.  */  
  9. public class Invocation {  
  10.   
  11.   private Object target;  
  12.   private Method method;  
  13.   private Object[] args;  
  14.   
  15.   public Invocation(Object target, Method method, Object[] args) {  
  16.     this.target = target;  
  17.     this.method = method;  
  18.     this.args = args;  
  19.   }  
  20.   
  21.   public Object getTarget() {  
  22.     return target;  
  23.   }  
  24.   
  25.   public Method getMethod() {  
  26.     return method;  
  27.   }  
  28.   
  29.   public Object[] getArgs() {  
  30.     return args;  
  31.   }  
  32.   
  33.   public Object proceed() throws InvocationTargetException, IllegalAccessException {  
  34.     return method.invoke(target, args);  
  35.   }  
  36.   
  37. }  
可以看到,Invocation类保存了代理对象的目标类,执行的目标类方法以及传递给它的参数。

在每个拦截器的intercept方法内,最后一个语句一定是return invocation.proceed()(不这么做的话拦截器链就断了,你的mybatis基本上就不能正常工作了)。invocation.proceed()只是简单的调用了下target的对应方法,如果target还是个代理,就又回到了上面的Plugin.invoke方法了。这样就形成了拦截器的调用链推进。

[java]  view plain  copy
  1. public Object intercept(Invocation invocation) throws Throwable {    
  2.     
  3.      //完成代理类本身的逻辑    
  4.      ...    
  5.      //通过invocation.proceed()方法完成调用链的推进    
  6.      return invocation.proceed();    
  7.   }     

我们假设在MyBatis配置了一个插件,在运行时会发生什么?

1)       所有可能被拦截的处理类都会生成一个代理

2)       处理类代理在执行对应方法时,判断要不要执行插件中的拦截方法

3)       执行插接中的拦截方法后,推进目标的执行

如果有N个插件,就有N个代理,每个代理都要执行上面的逻辑。这里面的层层代理要多次生成动态代理,是比较影响性能的。虽然能指定插件拦截的位置,但这个是在执行方法时动态判断,初始化的时候就是简单的把插件包装到了所有可以拦截的地方。

因此,在编写插件时需注意以下几个原则:

1)       不编写不必要的插件;

2)       实现plugin方法时判断一下目标类型,是本插件要拦截的对象才执行Plugin.wrap方法,否者直接返回目标本省,这样可以减少目标被代理的次数。

本文,我们就先介绍到这里,下一篇文章,我们将详细的介绍其原理及实现。

-------------------------------------------------------------------------------------------------------------------------------------

至此,Mybatis最入门---分页查询(内部原理篇)


备注:

本文内容请读者仔细理解,最好手动的跟踪代码的执行过程


参考资料:

百度百科

特别感谢:http://blog.csdn.net/hupanfeng/article/details/9247379

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值