深入浅出MyBatis-Sqlsession

深入浅出MyBatis-Sqlsession前面的章节主要讲 mybatis 如何解析配置文件 这些都是一次性的过程 从本章开始讲解动态的过程 它们跟应用程序对 mybatis 的调用密切相关 本章先从 sqlsession 开始 创建正如其名 Sqlsession 对应着一次数据库会话 由于数据库回话不是永久的 因此 Sqlsession 的生命周期也不应该是永久的 相反 在你每次访问数据库时都需要创建它 当然并不是说在 Sqlsession 里只能执

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

创建

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

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

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

 private SqlSessionopenSessionFromDataSource(ExecutorType execType, TransactionIsolationLevellevel, boolean autoCommit) { Connectionconnection = null; try { finalEnvironment environment = configuration.getEnvironment(); final DataSourcedataSource = getDataSourceFromEnvironment(environment); TransactionFactory transactionFactory =getTransactionFactoryFromEnvironment(environment); connection = dataSource.getConnection(); if (level != null) { connection.setTransactionIsolation(level.getLevel()); } connection = wrapConnection(connection); Transaction tx = transactionFactory.newTransaction(connection,autoCommit); Executorexecutor = configuration.newExecutor(tx, execType); returnnewDefaultSqlSession(configuration, executor, autoCommit); } catch (Exceptione) { closeConnection(connection); throwExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }

可以看出,创建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也是动态创建的:

 public ExecutornewExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType :executorType; executorType = executorType == null ?ExecutorType.SIMPLE : executorType; Executor executor; if(ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this,transaction); } elseif(ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this,transaction); } else { executor = newSimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor); } executor =(Executor) interceptorChain.pluginAll(executor); return executor; }

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

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

Mapper

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

 session = sqlSessionFactory.openSession(); UserDao userDao= session.getMapper(UserDao.class); UserDto user =new UserDto(); user.setUsername("iMbatis"); user.setPassword("iMbatis"); userDao.insertUser(user);

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

创建

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

 public 
    
      T getMapper(Class 
     
       type, SqlSession sqlSession) { if (!knownMappers.contains(type)) thrownewBindingException("Type " + type + " isnot known to the MapperRegistry."); try { returnMapperProxy.newMapperProxy(type, sqlSession); } catch (Exceptione) { thrownewBindingException("Error getting mapper instance. Cause: " + e, e); } } 
      
    

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

 publicstatic 
    
      T newMapperProxy(Class 
     
       mapperInterface, SqlSession sqlSession) { ClassLoaderclassLoader = mapperInterface.getClassLoader(); Class 
      [] interfaces = new Class[]{mapperInterface}; MapperProxyproxy = new MapperProxy(sqlSession); return (T) Proxy.newProxyInstance(classLoader,interfaces, proxy); } 
      
    

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

MapperProxy的invoke

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

 public Objectinvoke(Object proxy, Method method, Object[] args) throws Throwable{ if (method.getDeclaringClass()== Object.class) { return method.invoke(this, args); } finalClass 
     declaringInterface = findDeclaringInterface(proxy, method); finalMapperMethod mapperMethod = newMapperMethod(declaringInterface, method, sqlSession); final Objectresult = mapperMethod.execute(args); if (result ==null && method.getReturnType().isPrimitive()&& !method.getReturnType().equals(Void.TYPE)) { thrownewBindingException("Mapper method '" + method.getName() + "'(" + method.getDeclaringClass() + ") attempted toreturn null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; }

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

 public Objectexecute(Object[] args) { Objectresult = null; if(SqlCommandType.INSERT == type) { Objectparam = getParam(args); result= sqlSession.insert(commandName, param); } elseif(SqlCommandType.UPDATE == type) { Object param = getParam(args); result= sqlSession.update(commandName, param); } elseif(SqlCommandType.DELETE == type) { Objectparam = getParam(args); result= sqlSession.delete(commandName, param); } elseif(SqlCommandType.SELECT == type) { if (returnsVoid &&resultHandlerIndex != null) { executeWithResultHandler(args); } elseif (returnsList) { result = executeForList(args); } elseif (returnsMap) { result = executeForMap(args); } else { Object param = getParam(args); result = sqlSession.selectOne(commandName, param); } } else { thrownewBindingException("Unknown execution method for: " + commandName); } return result; }

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

Executor

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

CacheExecutor

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

 public Listquery(MappedStatement ms, Object parameterObject, RowBounds rowBounds,ResultHandler resultHandler) throws SQLException { if (ms != null) { Cachecache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); cache.getReadWriteLock().readLock().lock(); try { if (ms.isUseCache() && resultHandler ==null) { CacheKey key = createCacheKey(ms, parameterObject, rowBounds); final List cachedList = (List)cache.getObject(key); if (cachedList != null) { returncachedList; } else { List list = delegate.query(ms,parameterObject, rowBounds, resultHandler); tcm.putObject(cache,key, list); return list; } } else { returndelegate.query(ms,parameterObject, rowBounds, resultHandler); } } finally { cache.getReadWriteLock().readLock().unlock(); } } } returndelegate.query(ms,parameterObject, rowBounds, resultHandler); }

普通Executor

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

 public ListdoQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,ResultHandler resultHandler) throws SQLException { Statementstmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms,parameter, rowBounds,resultHandler); stmt =prepareStatement(handler); returnhandler.query(stmt, resultHandler); } finally { closeStatement(stmt); } }

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

StatementHandler

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

创建

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

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

初始化

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

 private StatementprepareStatement(StatementHandler handler) throwsSQLException { Statementstmt; Connectionconnection = transaction.getConnection(); stmt =handler.prepare(connection); handler.parameterize(stmt); return stmt; }

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

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

ExecutorStatementHandler一样,ParameterHandler也是可以被拦截的。

参数设置

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

 publicvoidsetParameters(PreparedStatement ps) throwsSQLException { ErrorContext.instance().activity("settingparameters").object(mappedStatement.getParameterMap().getId()); List 
        
          parameterMappings = boundSql.getParameterMappings(); if(parameterMappings != null) { MetaObject metaObject = parameterObject == null ? null :configuration.newMetaObject(parameterObject); for (int i = 0; i< parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if(parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); PropertyTokenizer prop = newPropertyTokenizer(propertyName); if (parameterObject == null) { value = null; } elseif (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())){ value = parameterObject; } elseif (boundSql.hasAdditionalParameter(propertyName)){ value = boundSql.getAdditionalParameter(propertyName); } elseif(propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX) && boundSql.hasAdditionalParameter(prop.getName())){ value = boundSql.getAdditionalParameter(prop.getName()); if (value != null) { value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length())); } } else { value = metaObject == null ? null :metaObject.getValue(propertyName); } TypeHandler typeHandler = parameterMapping.getTypeHandler(); if (typeHandler == null) { thrownew ExecutorException("Therewas no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId()); } typeHandler.setParameter(ps, i + 1, value,parameterMapping.getJdbcType()); } } } } 
        

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

this.boundSql= mappedStatement.getBoundSql(parameterObject);

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

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

结果处理

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

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

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

ResultSetHandler内部一条记录一条记录的处理,在处理每条记录的每一列时会调用TypeHandler转换结果,如下: protectedbooleanapplyAutomaticMappings(ResultSet rs, List 
        
          unmappedColumnNames,MetaObject metaObject) throws SQLException { booleanfoundValues = false; for (StringcolumnName : unmappedColumnNames) { final Stringproperty = metaObject.findProperty(columnName); if (property!= null) { final ClasspropertyType =metaObject.getSetterType(property); if (typeHandlerRegistry.hasTypeHandler(propertyType)) { final TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(propertyType); final Object value = typeHandler.getResult(rs,columnName); if (value != null) { metaObject.setValue(property, value); foundValues = true; } } } } returnfoundValues; } 
        

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












版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/199929.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月20日 上午11:53
下一篇 2026年3月20日 上午11:54


相关推荐

  • go get 使用代理

    go get 使用代理众所周知的原因,goget命令在提取一些工程或依赖时(如golang.org域名)被墙掉。通过使用vpn或代理的方法可以解决。买VPN这个就不多说了,买vpn,相当于直接连接。使用代理通过shell环境变量exporthttp_proxy=http://ip:portgogetgolang.org/xxx通过设置git代理这也是go官方指导。gitconfig–globalhttp.

    2022年7月25日
    31
  • java递归算法实现

    java递归算法实现Coding 多了 递归算法是非常常见的 最近我一直在做树形结构的封装 所以更加的离不开递归算法 所以今天就简单说一下这个递归算法 用 java 实现一个非常经典的递归实例 nbsp nbsp nbsp nbsp nbsp 递归算法 其实说白了 就是程序的自身调用 它表现在一段程序中往往会遇到调用自身的那样一种 coding 策略 这样我们就可以利用大道至简的思想 把一个大的复杂的问题层层转换为一个小的和原问题相似的问题来求解的这样一种策

    2026年3月17日
    2
  • 海康ehome协议分析(2):预览请求

    海康ehome协议分析(2):预览请求实时点播 1 信令开始点播 Platfrom gt gt Device Device gt gt Platform 停止点播 Platfrom gt gt Device Device gt gt Platfrom 2 视频流技术交流 1 信令开始点播 Platfrom gt gt Device xmlversion 1 0 encoding GB2312 PPVSPMessage Version 2 5 Version PPVSPMessage

    2025年7月18日
    9
  • html如何只刷新页面指定,js控制页面刷新 JS刷新当前页面的几种方法总结

    html如何只刷新页面指定,js控制页面刷新 JS刷新当前页面的几种方法总结JavaScriptlocation.reload()方法Location对象的reload()方法用于重新加载当前文档(页面),语法如下:location.reload(false|true)说明(实战帮有javascript课程与实训项目哦,可以一试)如果该方法参数为false或者省略参数。JS页面如何实现刷新指定DIV。。。其他DIV不刷新将innerHTML所…

    2022年7月12日
    18
  • 隐马尔科夫模型详解

    隐马尔科夫模型详解转载请注明地址 http blog csdn net xinzhangyanx article details 学习概率的时候 大家一定都学过马尔科夫模型吧 当时就觉得很有意思 后来看了数学之美之隐马模型在自然语言处理中的应用后 看到隐马尔科夫模型竟然能有这么多的应用 并且取得了很好的成果 更觉的不可思议 特地深入学习了一下 这里总结出来 马尔科夫过程

    2026年3月17日
    2
  • Coze插件开发实战:5分钟教你将现有API快速接入扣子商店

    Coze插件开发实战:5分钟教你将现有API快速接入扣子商店

    2026年3月14日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号