Mybatis中SQL语句执行过程详解

前面的十来篇文章我们对Mybatis中的配置和使用已经进行了比较详细的说明,想了解的朋友可以查看一下我专栏中的其他文章。

但是你对整个SQL语句操作的流程了解吗?如果你还不是很了解,那么可以继续往下看,如果你已经了解了,那么可以跳过啦大笑(因为一大推的源码估计要看的你头晕啊!!!)


所有语句的执行都是通过SqlSession对象来操作的,SqlSession是由SqlSessionFactory类生成的。

首先根据配置文件来创建一个SqlSessionFactory,然后调用openSession来获取一个SqlSession。我们从时序图来看看可能会更加清晰:

(1)生成SqlSessionFactory对象(默认实现是DefaultSqlSessionFactory)的过程

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">public SqlSessionFactory build(Reader reader, String environment, Properties properties) {  
  2.     try {  
  3.       XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);  
  4.       return build(parser.parse());  
  5.     } catch (Exception e) {  
  6.       throw ExceptionFactory.wrapException("Error building SqlSession.", e);  
  7.     } finally {  
  8.       ErrorContext.instance().reset();  
  9.       try {  
  10.         reader.close();  
  11.       } catch (IOException e) {  
  12.         // Intentionally ignore. Prefer previous error.  
  13.       }  
  14.     }  
  15.   }</span>  

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">// Mybatis 通过SqlSessionFactory获取SqlSession, 然后才能通过SqlSession与数据库进行交互  
  2.     private static SqlSessionFactory getSessionFactory() {  
  3.         SqlSessionFactory sessionFactory = null;  
  4.         String resource = "configuration.xml";  
  5.         try {  
  6.             sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));  
  7.         } catch (IOException e) {  
  8.             e.printStackTrace();  
  9.         }  
  10.         return sessionFactory;  
  11.     }</span>  

(2)获取SqlSession对象

通过调用DefaultSqlSessionFactory的openSession()方法来获取SqlSession对象。

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">@Override  
  2.   public SqlSession openSession() {  
  3.     return openSessionFromDataSource(configuration.getDefaultExecutorType(), nullfalse);  
  4.   }</span>  

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">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.   }</span>  

通过代码可以看出,最终返回的是一个DefaultSqlSession实例对象。接下来就是根据这个DefaultSqlSession来获取对应的Mapper对象。


(3)获取MapperProxy对象

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">UserDao userMapper = sqlSession.getMapper(UserDao.class);  </span>  

如上所示,通过SqlSession对象调用getMapper()方法来获取相应的Dao接口实现。我们通过代码跟踪一直往下看:

DefaultSqlSession类中:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">@Override  
  2.   public <T> T getMapper(Class<T> type) {  
  3.     return configuration.<T>getMapper(type, this);  
  4.   }</span>  

Configuration类中:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">public <T> T getMapper(Class<T> type, SqlSession sqlSession) {  
  2.     return mapperRegistry.getMapper(type, sqlSession);  
  3.   }</span>  

MapperRegistry类中:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;"@SuppressWarnings("unchecked")  
  2.   public <T> T getMapper(Class<T> type, SqlSession sqlSession) {  
  3.     final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);  
  4.     if (mapperProxyFactory == null) {  
  5.       throw new BindingException("Type " + type + " is not known to the MapperRegistry.");  
  6.     }  
  7.     try {  
  8.       return mapperProxyFactory.newInstance(sqlSession);  
  9.     } catch (Exception e) {  
  10.       throw new BindingException("Error getting mapper instance. Cause: " + e, e);  
  11.     }  
  12.   }</span>  

在MapperRegistry类中维护着一个Map,这个Map中存储着每个Mapper类型和其对应的代理对象工厂类,如下定义所示:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();</span>  

在mybatis初始化的过程中就根据配置文件<mappers>元素的配置,将相关的映射文件给加载到了内存,同时保存到了这个knownMappers中。这里,在调用getMapper()的时候,就会从这个knownMappers中寻找该Dao接口,如果没有找到,就直接抛出异常,说明没有在配置文件中配置说明,如果获取到了,那么就拿出其对应的代理对象工厂类出来,并从工厂类中通过newInstance()方法来获取一个代理对象。


MapperProxyFactory类中:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">@SuppressWarnings("unchecked")  
  2.   protected T newInstance(MapperProxy<T> mapperProxy) {  
  3.     return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);  
  4.   }  
  5.   
  6.   public T newInstance(SqlSession sqlSession) {  
  7.     final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);  
  8.     return newInstance(mapperProxy);  
  9.   }</span>  

从代码可以看出来,其实我们调用sqlSession.getMapper(UserDao.class)方法的时候,返回的是一个和UserDao接口对应的MapperProxy代理对象。如下定义所示,MapperProxy类是一个实现了InvocationHandler的代理类:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">public class MapperProxy<T> implements InvocationHandler, Serializable</span>  


上面的代码,如果整理成时序图,如下所示:

拿到了Dao接口的代理对象后,我们应该就可以进行具体的增删改查了,我们继续往下看。


(4)Executor对象

当拿到了UserDao对象(其实是MapperProxy代理对象)后,我们调用Dao接口中定义的方法,如下所示:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">User user = userMapper.findUserById(10);  </span>  

这时候便调用了MapperProxy对象的invoke方法了;

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">@Override  
  2.   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  3.     if (Object.class.equals(method.getDeclaringClass())) {  
  4.       try {  
  5.         return method.invoke(this, args);  
  6.       } catch (Throwable t) {  
  7.         throw ExceptionUtil.unwrapThrowable(t);  
  8.       }  
  9.     }  
  10.     final MapperMethod mapperMethod = cachedMapperMethod(method);  
  11.     return mapperMethod.execute(sqlSession, args);  
  12.   }</span>  

MapperMethod类中:

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

从上面这个方法实现上可以看出,已经根据执行方法(CRUD)进行了不同的处理,我们简单看一个方法executeForMany,代码如下所示:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {  
  2.     List<E> result;  
  3.     Object param = method.convertArgsToSqlCommandParam(args);  
  4.     if (method.hasRowBounds()) {  
  5.       RowBounds rowBounds = method.extractRowBounds(args);  
  6.       result = sqlSession.<E>selectList(command.getName(), param, rowBounds);  
  7.     } else {  
  8.       result = sqlSession.<E>selectList(command.getName(), param);  
  9.     }  
  10.     // issue #510 Collections & arrays support  
  11.     if (!method.getReturnType().isAssignableFrom(result.getClass())) {  
  12.       if (method.getReturnType().isArray()) {  
  13.         return convertToArray(result);  
  14.       } else {  
  15.         return convertToDeclaredCollection(sqlSession.getConfiguration(), result);  
  16.       }  
  17.     }  
  18.     return result;  
  19.   }</span>  

通过观察这些代码,发现最终的实现都是通过sqlSession对象来进行操作的。我们继续往里看,看看selectList方法:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">@Override  
  2.   public <E> List<E> selectList(String statement) {  
  3.     return this.selectList(statement, null);  
  4.   }  
  5.   
  6.   @Override  
  7.   public <E> List<E> selectList(String statement, Object parameter) {  
  8.     return this.selectList(statement, parameter, RowBounds.DEFAULT);  
  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.   }</span>  

可以看到,内部是把查询操作委托给了一个Executor对象(即executor.query()),Executor是一个接口,mybatis为其实现了一个抽象基类BaseExecutor,我们跟踪上面的代码中的query方法继续往里看:


BaseExecutor类中:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">@Override  
  2.   public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {  
  3.     BoundSql boundSql = ms.getBoundSql(parameter);  
  4.     CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);  
  5.     return query(ms, parameter, rowBounds, resultHandler, key, boundSql);  
  6.  }  
  7.   
  8.   @SuppressWarnings("unchecked")  
  9.   @Override  
  10.   public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {  
  11.     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());  
  12.     if (closed) {  
  13.       throw new ExecutorException("Executor was closed.");  
  14.     }  
  15.     if (queryStack == 0 && ms.isFlushCacheRequired()) {  
  16.       clearLocalCache();  
  17.     }  
  18.     List<E> list;  
  19.     try {  
  20.       queryStack++;  
  21.       list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;  
  22.       if (list != null) {  
  23.         handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);  
  24.       } else {  
  25.         list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);  
  26.       }  
  27.     } finally {  
  28.       queryStack--;  
  29.     }  
  30.     if (queryStack == 0) {  
  31.       for (DeferredLoad deferredLoad : deferredLoads) {  
  32.         deferredLoad.load();  
  33.       }  
  34.       // issue #601  
  35.       deferredLoads.clear();  
  36.       if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {  
  37.         // issue #482  
  38.         clearLocalCache();  
  39.       }  
  40.     }  
  41.     return list;  
  42.   }</span>  

在上面的方法中,我们看到当list==null的时候会调用queryFromDatabase()方法,这个方法如下:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {  
  2.     List<E> list;  
  3.     localCache.putObject(key, EXECUTION_PLACEHOLDER);  
  4.     try {  
  5.       list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);  
  6.     } finally {  
  7.       localCache.removeObject(key);  
  8.     }  
  9.     localCache.putObject(key, list);  
  10.     if (ms.getStatementType() == StatementType.CALLABLE) {  
  11.       localOutputParameterCache.putObject(key, parameter);  
  12.     }  
  13.     return list;  
  14.   }</span>  

然后会调用doQuery()方法,BaseExecutor中的doQuery方法定义成了抽象方法,由具体的继承类进行个性化的实现。这里,我们拿mybatis中默认使用的SimpleExecutor来看看:

SimpleExecutor类中:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;"@Override  
  2.   public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {  
  3.     Statement stmt = null;  
  4.     try {  
  5.       Configuration configuration = ms.getConfiguration();  
  6.       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);  
  7.       stmt = prepareStatement(handler, ms.getStatementLog());  
  8.       return handler.<E>query(stmt, resultHandler);  
  9.     } finally {  
  10.       closeStatement(stmt);  
  11.     }  
  12.   }</span>  

从这个方法可以看到,首先根据调用Configuration类的newStatementHandler方法来获取一个sql操作对象:

Configuration类中:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">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.   }</span>  

RoutingStatementHandler类中:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;">public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {  
  2.   
  3.     switch (ms.getStatementType()) {  
  4.       case STATEMENT:  
  5.         delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);  
  6.         break;  
  7.       case PREPARED:  
  8.         delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);  
  9.         break;  
  10.       case CALLABLE:  
  11.         delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);  
  12.         break;  
  13.       default:  
  14.         throw new ExecutorException("Unknown statement type: " + ms.getStatementType());  
  15.     }  
  16.   
  17.   }</span>  

可以看到,这里根据配置来创建Statement、PreparedStatement或者CallableStatement三者之中的一个。然后调用相应的方法,如query()方法。以SimpleStatementHandler为例,我们看看具体的sql操作:

[java]  view plain  copy
  1. <span style="font-family:'KaiTi_GB2312';font-size:18px;"@Override  
  2.   public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  
  3.     String sql = boundSql.getSql();  
  4.     statement.execute(sql);  
  5.     return resultSetHandler.<E>handleResultSets(statement);  
  6.   }</span>  

看到这里,我们终于看到了黎明的曙光,因为这里已经看到Jdbc中的数据库操作代码了,即statement.execute(sql)。在查询完之后使用resultSetHandler来进行查询结果集的处理。

上面的代码的整体流程图大概如下所示:

至此,一次完整的sql解析和处理过程便讲解完毕了,感兴趣的可以自己对着源码看看。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值