深入浅出MyBatis-Sqlsession

前面的章节主要讲mybatis如何解析配置文件,这些都是一次性的过程。从本章开始讲解动态的过程,它们跟应用程序对mybatis的调用密切相关。本章先从sqlsession开始。

创建

正如其名,Sqlsession对应着一次数据库会话。由于数据库回话不是永久的,因此Sqlsession的生命周期也不应该是永久的,相反,在你每次访问数据库时都需要创建它(当然并不是说在Sqlsession里只能执行一次sql,你可以执行多次,当一旦关闭了Sqlsession就需要重新创建它)。创建Sqlsession的地方只有一个,那就是SqlsessionFactory的openSession方法:

[java] view plaincopy

  1. public SqlSessionopenSession() {
  2. returnopenSessionFromDataSource(configuration.getDefaultExecutorType(),null, false);
  3. }

我们可以看到实际创建SqlSession的地方是openSessionFromDataSource,如下:

[java] view plaincopy

  1. private SqlSessionopenSessionFromDataSource(ExecutorType execType, TransactionIsolationLevellevel, boolean autoCommit) {
  2. Connectionconnection = null;
  3. try {
  4. finalEnvironment environment = configuration.getEnvironment();
  5. final DataSourcedataSource = getDataSourceFromEnvironment(environment);
  6. TransactionFactory transactionFactory =getTransactionFactoryFromEnvironment(environment);
  7. connection = dataSource.getConnection();
  8. if (level != null) {
  9. connection.setTransactionIsolation(level.getLevel());
  10. }
  11. connection = wrapConnection(connection);
  12. Transaction tx = transactionFactory.newTransaction(connection,autoCommit);
  13. Executorexecutor = configuration.newExecutor(tx, execType);
  14. returnnewDefaultSqlSession(configuration, executor, autoCommit);
  15. } catch (Exceptione) {
  16. closeConnection(connection);
  17. throwExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  18. } finally {
  19. ErrorContext.instance().reset();
  20. }
  21. }

可以看出,创建sqlsession经过了以下几个主要步骤:

1)       从配置中获取Environment;

2)       从Environment中取得DataSource;

3)       从Environment中取得TransactionFactory;

4)       从DataSource里获取数据库连接对象Connection;

5)       在取得的数据库连接上创建事务对象Transaction;

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

7)       创建sqlsession对象。

Executor的创建

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

[java] view plaincopy

  1. public ExecutornewExecutor(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. } elseif(ExecutorType.REUSE == executorType) {
  8. executor = new ReuseExecutor(this,transaction);
  9. } else {
  10. executor = newSimpleExecutor(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没有什么特别的。开启cache的话(默认是开启的并且没有任何理由去关闭它),就会创建CachingExecutor,它以前面创建的Executor作为唯一参数。CachingExecutor在查询数据库前先查找缓存,若没找到的话调用delegate(就是构造时传入的Executor对象)从数据库查询,并将查询结果存入缓存中。

Executor对象是可以被插件拦截的,如果定义了针对Executor类型的插件,最终生成的Executor对象是被各个插件插入后的代理对象(关于插件会有后续章节专门介绍,敬请期待)。

Mapper

Mybatis官方手册建议通过mapper对象访问mybatis,因为使用mapper看起来更优雅,就像下面这样:

[java] view plaincopy

  1. session = sqlSessionFactory.openSession();
  2. UserDao userDao= session.getMapper(UserDao.class);
  3. UserDto user =new UserDto();
  4. user.setUsername("iMbatis");
  5. user.setPassword("iMbatis");
  6. userDao.insertUser(user);

那么这个mapper到底是什么呢,它是如何创建的呢,它又是怎么与sqlsession等关联起来的呢?下面为你一一解答。

创建

表面上看mapper是在sqlsession里创建的,但实际创建它的地方是MapperRegistry:

[java] view plaincopy

  1. public <T>T getMapper(Class<T> type, SqlSession sqlSession) {
  2. if (!knownMappers.contains(type))
  3. thrownewBindingException("Type " + type + " isnot known to the MapperRegistry.");
  4. try {
  5. returnMapperProxy.newMapperProxy(type, sqlSession);
  6. } catch (Exceptione) {
  7. thrownewBindingException("Error getting mapper instance. Cause: " + e, e);
  8. }
  9. }

可以看到,mapper是一个代理对象,它实现的接口就是传入的type,这就是为什么mapper对象可以通过接口直接访问。同时还可以看到,创建mapper代理对象时传入了sqlsession对象,这样就把sqlsession也关联起来了。我们进一步看看MapperProxy.newMapperProxy(type,sqlSession);背后发生了什么事情:

[java] view plaincopy

  1. publicstatic <T>T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
  2. ClassLoaderclassLoader = mapperInterface.getClassLoader();
  3. Class<?>[] interfaces = new Class[]{mapperInterface};
  4. MapperProxyproxy = new MapperProxy(sqlSession);
  5. return (T) Proxy.newProxyInstance(classLoader,interfaces, proxy);
  6. }

看起来没什么特别的,和其他代理类的创建一样,我们重点关注一下MapperProxy的invoke方法

MapperProxy的invoke

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

[java] view plaincopy

  1. public Objectinvoke(Object proxy, Method method, Object[] args) throws Throwable{
  2. if (method.getDeclaringClass()== Object.class) {
  3. return method.invoke(this, args);
  4. }
  5. finalClass<?> declaringInterface = findDeclaringInterface(proxy, method);
  6. finalMapperMethod mapperMethod = newMapperMethod(declaringInterface, method, sqlSession);
  7. final Objectresult = mapperMethod.execute(args);
  8. if (result ==null && method.getReturnType().isPrimitive()&& !method.getReturnType().equals(Void.TYPE)) {
  9. thrownewBindingException("Mapper method ‘" + method.getName() + "‘(" + method.getDeclaringClass()
  10. + ") attempted toreturn null from a method with a primitive return type ("
  11. + method.getReturnType() + ").");
  12. }
  13. return result;
  14. }

可以看到invoke把执行权转交给了MapperMethod,我们来看看MapperMethod里又是怎么运作的:

[java] view plaincopy

  1. public Objectexecute(Object[] args) {
  2. Objectresult = null;
  3. if(SqlCommandType.INSERT == type) {
  4. Objectparam = getParam(args);
  5. result= sqlSession.insert(commandName, param);
  6. } elseif(SqlCommandType.UPDATE == type) {
  7. Object param = getParam(args);
  8. result= sqlSession.update(commandName, param);
  9. } elseif(SqlCommandType.DELETE == type) {
  10. Objectparam = getParam(args);
  11. result= sqlSession.delete(commandName, param);
  12. } elseif(SqlCommandType.SELECT == type) {
  13. if (returnsVoid &&resultHandlerIndex != null) {
  14. executeWithResultHandler(args);
  15. } elseif (returnsList) {
  16. result = executeForList(args);
  17. } elseif (returnsMap) {
  18. result = executeForMap(args);
  19. } else {
  20. Object param = getParam(args);
  21. result = sqlSession.selectOne(commandName, param);
  22. }
  23. } else {
  24. thrownewBindingException("Unknown execution method for: " + commandName);
  25. }
  26. return result;
  27. }

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

Executor

前面提到过,sqlsession只是一个门面,真正发挥作用的是executor,对sqlsession方法的访问最终都会落到executor的相应方法上去。Executor分成两大类,一类是CacheExecutor,另一类是普通Executor。Executor的创建前面已经介绍了,下面介绍下他们的功能:

CacheExecutor

CacheExecutor有一个重要属性delegate,它保存的是某类普通的Executor,值在构照时传入。执行数据库update操作时,它直接调用delegate的update方法,执行query方法时先尝试从cache中取值,取不到再调用delegate的查询方法,并将查询结果存入cache中。代码如下:

[java] view plaincopy

  1. public Listquery(MappedStatement ms, Object parameterObject, RowBounds rowBounds,ResultHandler resultHandler) throws SQLException {
  2. if (ms != null) {
  3. Cachecache = ms.getCache();
  4. if (cache != null) {
  5. flushCacheIfRequired(ms);
  6. cache.getReadWriteLock().readLock().lock();
  7. try {
  8. if (ms.isUseCache() && resultHandler ==null) {
  9. CacheKey key = createCacheKey(ms, parameterObject, rowBounds);
  10. final List cachedList = (List)cache.getObject(key);
  11. if (cachedList != null) {
  12. returncachedList;
  13. } else {
  14. List list = delegate.query(ms,parameterObject, rowBounds, resultHandler);
  15. tcm.putObject(cache,key, list);
  16. return list;
  17. }
  18. } else {
  19. returndelegate.query(ms,parameterObject, rowBounds, resultHandler);
  20. }
  21. } finally {
  22. cache.getReadWriteLock().readLock().unlock();
  23. }
  24. }
  25. }
  26. returndelegate.query(ms,parameterObject, rowBounds, resultHandler);
  27. }

普通Executor

普通Executor有3类,他们都继承于BaseExecutor,BatchExecutor专门用于执行批量sql操作,ReuseExecutor会重用statement执行sql操作,SimpleExecutor只是简单执行sql没有什么特别的。下面以SimpleExecutor为例:

[java] view plaincopy

  1. public ListdoQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,ResultHandler resultHandler) throws SQLException {
  2. Statementstmt = null;
  3. try {
  4. Configuration configuration = ms.getConfiguration();
  5. StatementHandler handler = configuration.newStatementHandler(this, ms,parameter, rowBounds,resultHandler);
  6. stmt =prepareStatement(handler);
  7. returnhandler.query(stmt, resultHandler);
  8. } finally {
  9. closeStatement(stmt);
  10. }
  11. }

可以看出,Executor本质上也是个甩手掌柜,具体的事情原来是StatementHandler来完成的。

StatementHandler

当Executor将指挥棒交给StatementHandler后,接下来的工作就是StatementHandler的事了。我们先看看StatementHandler是如何创建的。

创建

[java] view plaincopy

  1. publicStatementHandler newStatementHandler(Executor executor, MappedStatementmappedStatement,
  2. ObjectparameterObject, RowBounds rowBounds, ResultHandler resultHandler) {
  3. StatementHandler statementHandler = newRoutingStatementHandler(executor, mappedStatement,parameterObject,rowBounds, resultHandler);
  4. statementHandler= (StatementHandler) interceptorChain.pluginAll(statementHandler);
  5. returnstatementHandler;
  6. }

可以看到每次创建的StatementHandler都是RoutingStatementHandler,它只是一个分发者,他一个属性delegate用于指定用哪种具体的StatementHandler。可选的StatementHandler有SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler三种。选用哪种在mapper配置文件的每个statement里指定,默认的是PreparedStatementHandler。同时还要注意到StatementHandler是可以被拦截器拦截的,和Executor一样,被拦截器拦截后的对像是一个代理对象。由于mybatis没有实现数据库的物理分页,众多物理分页的实现都是在这个地方使用拦截器实现的,本文作者也实现了一个分页拦截器,在后续的章节会分享给大家,敬请期待。

初始化

StatementHandler创建后需要执行一些初始操作,比如statement的开启和参数设置、对于PreparedStatement还需要执行参数的设置操作等。代码如下:

[java] view plaincopy

  1. private StatementprepareStatement(StatementHandler handler) throwsSQLException {
  2. Statementstmt;
  3. Connectionconnection = transaction.getConnection();
  4. stmt =handler.prepare(connection);
  5. handler.parameterize(stmt);
  6. return stmt;
  7. }

statement的开启和参数设置没什么特别的地方,handler.parameterize倒是可以看看是怎么回事。handler.parameterize通过调用ParameterHandler的setParameters完成参数的设置,ParameterHandler随着StatementHandler的创建而创建,默认的实现是DefaultParameterHandler:

[java] view plaincopy

  1. publicParameterHandler newParameterHandler(MappedStatement mappedStatement, ObjectparameterObject, BoundSql boundSql) {
  2. ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement,parameterObject,boundSql);
  3. parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
  4. returnparameterHandler;
  5. }

同Executor和StatementHandler一样,ParameterHandler也是可以被拦截的。

参数设置

DefaultParameterHandler里设置参数的代码如下:

[java] view plaincopy

  1. publicvoidsetParameters(PreparedStatement ps) throwsSQLException {
  2. ErrorContext.instance().activity("settingparameters").object(mappedStatement.getParameterMap().getId());
  3. List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  4. if(parameterMappings != null) {
  5. MetaObject metaObject = parameterObject == null ? null :configuration.newMetaObject(parameterObject);
  6. for (int i = 0; i< parameterMappings.size(); i++) {
  7. ParameterMapping parameterMapping = parameterMappings.get(i);
  8. if(parameterMapping.getMode() != ParameterMode.OUT) {
  9. Object value;
  10. String propertyName = parameterMapping.getProperty();
  11. PropertyTokenizer prop = newPropertyTokenizer(propertyName);
  12. if (parameterObject == null) {
  13. value = null;
  14. } elseif (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())){
  15. value = parameterObject;
  16. } elseif (boundSql.hasAdditionalParameter(propertyName)){
  17. value = boundSql.getAdditionalParameter(propertyName);
  18. } elseif(propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX)
  19. && boundSql.hasAdditionalParameter(prop.getName())){
  20. value = boundSql.getAdditionalParameter(prop.getName());
  21. if (value != null) {
  22. value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));
  23. }
  24. } else {
  25. value = metaObject == null ? null :metaObject.getValue(propertyName);
  26. }
  27. TypeHandler typeHandler = parameterMapping.getTypeHandler();
  28. if (typeHandler == null) {
  29. thrownew ExecutorException("Therewas no TypeHandler found for parameter " + propertyName  + " of statement " + mappedStatement.getId());
  30. }
  31. typeHandler.setParameter(ps, i + 1, value,parameterMapping.getJdbcType());
  32. }
  33. }
  34. }
  35. }

这里面最重要的一句其实就是最后一句代码,它的作用是用合适的TypeHandler完成参数的设置。那么什么是合适的TypeHandler呢,它又是如何决断出来的呢?BaseStatementHandler的构造方法里有这么一句:

this.boundSql= mappedStatement.getBoundSql(parameterObject);

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

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

结果处理

结果处理使用ResultSetHandler来完成,默认的ResultSetHandler是FastResultSetHandler,它在创建StatementHandler时一起创建,代码如下:

[java] view plaincopy

  1. publicResultSetHandler newResultSetHandler(Executor executor, MappedStatementmappedStatement,
  2. RowBoundsrowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSqlboundSql) {
  3. ResultSetHandler resultSetHandler =mappedStatement.hasNestedResultMaps() ? newNestedResultSetHandler(executor, mappedStatement, parameterHandler,resultHandler, boundSql, rowBounds): new FastResultSetHandler(executor,mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
  4. resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
  5. returnresultSetHandler;
  6. }

可以看出ResultSetHandler也是可以被拦截的,可以编写自己的拦截器改变ResultSetHandler的默认行为。

[java] view plaincopy

  1. ResultSetHandler内部一条记录一条记录的处理,在处理每条记录的每一列时会调用TypeHandler转换结果,如下:
  2. protectedbooleanapplyAutomaticMappings(ResultSet rs, List<String> unmappedColumnNames,MetaObject metaObject) throws SQLException {
  3. booleanfoundValues = false;
  4. for (StringcolumnName : unmappedColumnNames) {
  5. final Stringproperty = metaObject.findProperty(columnName);
  6. if (property!= null) {
  7. final ClasspropertyType =metaObject.getSetterType(property);
  8. if (typeHandlerRegistry.hasTypeHandler(propertyType)) {
  9. final TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(propertyType);
  10. final Object value = typeHandler.getResult(rs,columnName);
  11. if (value != null) {
  12. metaObject.setValue(property, value);
  13. foundValues = true;
  14. }
  15. }
  16. }
  17. }
  18. returnfoundValues;
  19. }

从代码里可以看到,决断TypeHandler使用的是结果参数的属性类型。因此我们在定义作为结果的对象的属性时一定要考虑与数据库字段类型的兼容性。

时间: 2024-11-05 18:40:02

深入浅出MyBatis-Sqlsession的相关文章

深入浅出Mybatis系列(二)---配置简介(mybatis源码篇)

上篇文章<深入浅出Mybatis系列(一)---Mybatis入门>, 写了一个Demo简单体现了一下Mybatis的流程.本次,将简单介绍一下Mybatis的配置文件: 上次例子中,我们以 SqlSessionFactoryBuilder 去创建 SqlSessionFactory,  那么,我们就先从SqlSessionFactoryBuilder入手, 咱们先看看源码是怎么实现的: SqlSessionFactoryBuilder源码片段: 1 public class SqlSessi

深入浅出Mybatis系列(二)---配置简介(mybatis源码篇)[转]

上篇文章<深入浅出Mybatis系列(一)---Mybatis入门>, 写了一个Demo简单体现了一下Mybatis的流程.本次,将简单介绍一下Mybatis的配置文件: 上次例子中,我们以 SqlSessionFactoryBuilder 去创建 SqlSessionFactory,  那么,我们就先从SqlSessionFactoryBuilder入手, 咱们先看看源码是怎么实现的: SqlSessionFactoryBuilder源码片段: 1 public class SqlSessi

深入浅出Mybatis系列(八)---mapper映射文件配置之select、resultMap good

上篇<深入浅出Mybatis系列(七)---mapper映射文件配置之insert.update.delete>介绍了insert.update.delete的用法,本篇将介绍select.resultMap的用法.select无疑是我们最常用,也是最复杂的,mybatis通过resultMap能帮助我们很好地进行高级映射.下面就开始看看select 以及 resultMap的用法: 先看select的配置吧: <select <!-- 1. id (必须配置) id是命名空间中的

转 深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇)

深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇) 最近太忙了,一直没时间继续更新博客,今天忙里偷闲继续我的Mybatis学习之旅.在前九篇中,介绍了mybatis的配置以及使用, 那么本篇将走进mybatis的源码,分析mybatis 的执行流程, 好啦,鄙人不喜欢口水话,还是直接上干活吧: 1. SqlSessionFactory 与 SqlSession. 通过前面的章节对于mybatis 的介绍及使用,大家都能体会到SqlSession的重要性了吧, 没错,从表面上来看,

深入浅出Mybatis系列(八)---mapper映射文件配置之select、resultMap

上篇<深入浅出Mybatis系列(七)---mapper映射文件配置之insert.update.delete>介绍了insert.update.delete的用法,本篇将介绍select.resultMap的用法.select无疑是我们最常用,也是最复杂的,mybatis通过resultMap能帮助我们很好地进行高级映射.下面就开始看看select 以及 resultMap的用法: 先看select的配置吧: <select <!-- 1. id (必须配置) id是命名空间中的

深入浅出mybatis之启动详解

深入浅出mybatis之启动详解 MyBatis功能丰富,但使用起来非常简单明了,今天我们来追踪一下它的启动过程. 目录 如何启动MyBatis 如何使用MyBatis MyBatis启动过程 如何启动MyBatis 我们知道,SqlSessionFactory是MyBatis中最为核心的组件,每个基于MyBatis的应用都是以一个SqlSessionFactory实例为中心的.SqlSessionFactory的实例可以通过SqlSessionFactoryBuilder获得,而SqlSess

深入浅出MyBatis:JDBC和MyBatis介绍

最近在休陪产假,时间比较零碎,准备看2本书充实下,一本是「深入浅出MyBatis:技术原理与实践」,一本是「RabbitMQ实战:高效部署分布式消息队列」,为了加深记忆和理解,会进行整理.扩展和记录. 看书的目标不是把所有的细节都记住,而是从整体上了解一个技术能做什么,包含的特性.基本模块,实现原理和常见使用场景. 本篇分享MyBatis书籍的第一篇,首先回忆下JDBC的相关概念,了解Java提供的访问数据库最基本的方式,然后介绍下MyBatis的基本特性和核心组件,最后说下书的整体结构,了解后

深入浅出MyBatis:「映射器」全了解

本篇文章是「深入浅出MyBatis:技术原理与实践」书籍的总结笔记. 上一篇总结了MyBatis的配置,详细说明了各个配置项,其中提到了映射器,它是MyBatis最强大的工具,也是使用最多的工具. 通过映射器,可以很容易的进行数据的增删改查操作,我们抽象下进行这些操作的关键点:传递查询参数.组装各种场景下的查询条件.关联查询.将查询结果映射为Java Bean对象或集合等.另外,可以通过延迟加载.缓存提高数据查询的性能. 本篇就按照这个思路进行总结,首先列举下映射器的主要元素,每个元素提供的配置

深入浅出MyBatis:MyBatis插件及开发过程

本篇文章是「深入浅出MyBatis:技术原理与实践」书籍的总结笔记. 上一篇介绍了 MyBatis解析和运行原理 ,包括SqlSessionFactory的构建和SqlSession的执行过程,其中,SqlSession包含四大对象,可以在四大对象调度的时候插入自定义的代码,以满足特殊的需求,这便是MyBatis提供的插件技术. 有些特殊场景,需要使用插件统一处理,比如:在进行多租户开发时,数据要按租户隔离,可以在sql语句后面统一添加租户编号筛选条件. 本篇就来介绍下插件,通过本篇的介绍,你会

深入浅出MyBatis:MyBatis与Spring集成及实用场景

本系列是「深入浅出MyBatis:技术原理与实践」书籍的总结笔记. 本篇是「深入浅出MyBatis」系列的最后一篇,主要介绍与Spring的集成,以及工作中的一些实用场景. 介绍之前,先整体总结下该系列的内容和写作思路. MyBatis是一个框架,封装了数据库相关的操作,给我们开发人员带来了极大地便利,相对于Hibernate,有很大的灵活性和扩展性,在高并发高性能应用中,这点很重要. 首先介绍了JDBC的规范,了解我们最原始最熟悉的操作数据库的方式,MyBatis就是在此基础上进行封装和抽象.