前面的章节主要讲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>T getMapper(Class<T> 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>T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
ClassLoaderclassLoader = mapperInterface.getClassLoader();
Class<?>[] interfaces = new Class[]{mapperInterface};
MapperProxyproxy = new MapperProxy(sqlSession);
return (T) Proxy.newProxyInstance(classLoader,interfaces, proxy);
}

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

MapperProxy的invoke

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

    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,另一类是普通Executor。Executor的创建前面已经介绍了,下面介绍下他们的功能:

CacheExecutor

CacheExecutor有一个重要属性delegate,它保存的是某类普通的Executor,值在构照时传入。执行数据库update操作时,它直接调用delegate的update方法,执行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

普通Executor有3类,他们都继承于BaseExecutor,BatchExecutor专门用于执行批量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。可选的StatementHandler有SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler三种。选用哪种在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通过调用ParameterHandler的setParameters完成参数的设置,ParameterHandler随着StatementHandler的创建而创建,默认的实现是DefaultParameterHandler:

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

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

参数设置

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

    publicvoidsetParameters(PreparedStatement ps) throwsSQLException {
ErrorContext.instance().activity("settingparameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> 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<String> 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使用的是结果参数的属性类型。因此我们在定义作为结果的对象的属性时一定要考虑与数据库字段类型的兼容性。

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

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

    上篇<深入浅出Mybatis系列(七)---mapper映射文件配置之insert.update.delete>介绍了insert.update.delete的用法,本篇将介绍select ...

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

    上篇文章<深入浅出Mybatis系列(一)---Mybatis入门>, 写了一个Demo简单体现了一下Mybatis的流程.本次,将简单介绍一下Mybatis的配置文件: 上次例子中,我们 ...

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

    上篇<深入浅出Mybatis系列(七)---mapper映射文件配置之insert.update.delete>介绍了insert.update.delete的用法,本篇将介绍select ...

  4. 深入浅出mybatis之启动详解

    深入浅出mybatis之启动详解 MyBatis功能丰富,但使用起来非常简单明了,今天我们来追踪一下它的启动过程. 目录 如何启动MyBatis 如何使用MyBatis MyBatis启动过程 如何启 ...

  5. 2017.2.9 深入浅出MyBatis技术原理与实践-第八章 MyBatis-Spring(二)-----配置文件详解

    深入浅出MyBatis技术原理与实践-第八章 MyBatis-Spring(二) ------配置文件详解 8.2 MyBatis-Spring应用 8.2.1 概述 本文主要讲述通过注解配置MyBa ...

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

    上篇文章<深入浅出Mybatis系列(一)---Mybatis入门>, 写了一个Demo简单体现了一下Mybatis的流程.本次,将简单介绍一下Mybatis的配置文件: 上次例子中,我们 ...

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

    注:本文转载自南轲梦 注:博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 上篇<深入浅出Mybatis系列(七)---mapper映射文件配置之inse ...

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

    注:本文转载自南轲梦 注:博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 上篇文章<深入浅出Mybatis系列(一)---Mybatis入门>, ...

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

    上篇文章<深入浅出Mybatis系列(二)---Mybatis入门>写了一个Demo简单体现了一下Mybatis的流程.本次,将简单介绍一下Mybatis的配置文件: 上次例子中,我们以  ...

  10. 深入浅出Mybatis系列(九)---强大的动态SQL

    上篇文章<深入浅出Mybatis系列(八)---mapper映射文件配置之select.resultMap>简单介绍了mybatis的查询,至此,CRUD都已讲完.本文将介绍mybatis ...

随机推荐

  1. js 正则之检测素数

    原文:js 正则之检测素数 相信很多人应该看过这篇文章,我第一次看到的时候是11年的样子,那时候学vbs的时候看过这个问题.原文<检查素数的正则表达式>,在文章里已经解释了他是怎么判断的, ...

  2. Android利用CountDownTimer类实现倒计时功能

    public class MainActivity extends Activity { private MyCount mc; private TextView tv; @Override publ ...

  3. Backup and Recovery Strategies1

    2.1.Data Recovery Strategy Determines Backup Strategy 在设计备份策略.如若数据恢复需求和数据恢复战略启动.每种类型的数据恢复需要你采取相应的备份类 ...

  4. view components介绍

    view components介绍 在ASP.NET MVC 6中,view components (VCs) 功能类似于虚拟视图,但是功能更加强大. VCs兼顾了视图和控制器的优点,你可以把VCs ...

  5. PHP 调用asp.net Web Services服务问题总结

    原文:PHP 调用asp.net Web Services服务问题总结 PHP是弱类型语言,转换非常不方便. < ?php //soap 客户端 $client=new SoapClient(' ...

  6. ORACLE经常使用的命令

    一个.ORACLE启动和关机 1.在独立环境中 要启用或禁用ORACLE该系统必须切换到ORACLE用户,例如以下 su-oracle a.启动ORACLE系统 oracle>svrmgrl S ...

  7. 使用OpenWrt的SDK

    原文:http://wiki.openwrt.org/doc/howto/obtain.firmware.sdk 为什么要使用SDK: Reasons for using the SDK are: C ...

  8. Solr多核心及分词器(IK)配置

    Solr多核心及分词器(IK)配置   多核心的概念 多核心说白了就是多索引库.也可以理解为多个"数据库表" 说一下使用multicore的真实场景,比若说,产品搜索和会员信息搜索 ...

  9. robin 今日南

    我很高兴,在学校体育馆看到李彦宏博士. 这是第一个真正的一次在媒体上看到,只能看到人才足够多的人,现实,我觉得非常好. 我不是一个真正罗宾的粉丝.百度是不是很热衷于这家公司.,但现在我仍然兴奋,我会被 ...

  10. mediawiki在windows下的安装

    mediawiki在windows下的安装 对于刚接触wiki的朋友们来说,配置一个服务器环境,安装并运行mediawiki是一件很麻烦的事情,在这里,我尽量用通俗易懂的语言,介绍mw(mediawi ...