mybatis动态代理、一级缓存和二级缓存
之前看到:
1. mapper接口里的方法,是不能重载的,因为使用全限定+方法名的寻找和保存策略,mapper接口的原理是jdk动态代理,为mapper接口生成动态代理对象,代理对象会拦截接口方法,转而执行mapperStatement代理的sql,然后将执行结果返回,接口原理是jdk动态代理,这次终于源码是怎样的流程了,为啥是动态代理。技术越学理解越深。看源码真爽。
全限定名/namespace="com.nowcoder.dao.QuestionDAO"
方法id:selectLatestQuestions
<mapper namespace="com.nowcoder.dao.QuestionDAO">
<sql id="table">question</sql>
<sql id="selectFields">id, title, content, comment_count,created_date,user_id
</sql>
<select id="selectLatestQuestions" resultType="com.nowcoder.model.Question">
SELECT
<include refid="selectFields"/>
FROM
<include refid="table"/>
<if test="userId != 0">
WHERE user_id = #{userId}
</if>
ORDER BY id DESC
LIMIT #{offset},#{limit}
</select>
</mapper>
2. mybatis的一级缓存和二级缓存:
缓存:将数据存放在程序内存中,用于减轻数据查询的压力,提升读取数据的速度,提高性能
mybatis中进行sql查询是通过 org.apache.ibatis.executor.Executor 接口进行的,该接口有两类实现
一级缓存默认实现,二级缓存如何配置?
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- Sets the number of seconds the driver will wait for a response from the database -->
<setting name="defaultStatementTimeout" value="3000"/>
<!-- Enables automatic mapping from classic database column names A_COLUMN to camel case classic Java property names aColumn -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- Allows JDBC support for generated keys. A compatible driver is required.
This setting forces generated keys to be used if set to true,
as some drivers deny compatibility but still work -->
<setting name="useGeneratedKeys" value="true"/>
</settings>
<!-- Continue going here -->
</configuration>
在setting里面开启二级缓存的作用域是针对于全局性的。
如果只是想要单独对于某一个sql的二级缓存查询开启,我们可以在相应的select标签里面设置。https://blog.csdn.net/Danny_idea/article/details/82377924
一级缓存:
BaseExecutor.query
一级缓存是指SqlSession级别的缓存,当在同一个SqlSession中进行相同的sql查询,第二次以后的查询不会从数据库查询而是直接从缓存中获取,以及缓存最后缓存1024条sql,二级缓存的作用范围更大。
mybatis一级缓存,缓存在SqlSession中。一级缓存是默认的,不需要配置,一级缓存默认使用,BaseExecutor.query()中实现,底层默认使用PerpetualCache实现,PerpetualCache / localCache 中没有缓存时,才去执行queryFromDatabase方法,去查询数据库,并将结果缓存到localCache中。perpetualcache采用HashMap存储数据,key为hashcode+sqlId+sql语句,value为查询出来映射生成的Java对象,一级缓存在进行增、删、改操作时,会清除。
原理:
package org.apache.ibatis.cache.impl;
public class PerpetualCache implements Cache {
private String id;
private Map<Object, Object> cache = new HashMap();
public PerpetualCache(String id) {
this.id = id;
}
public String getId() {
return this.id;
}
public int getSize() {
return this.cache.size();
}
public void putObject(Object key, Object value) {
this.cache.put(key, value);
}
public Object getObject(Object key) {
return this.cache.get(key);
}
public Object removeObject(Object key) {
return this.cache.remove(key);
}
public void clear() {
this.cache.clear();
}
public ReadWriteLock getReadWriteLock() {
return null;
}
public boolean equals(Object o) {
if (this.getId() == null) {
throw new CacheException("Cache instances require an ID.");
} else if (this == o) {
return true;
} else if (!(o instanceof Cache)) {
return false;
} else {
Cache otherCache = (Cache)o;
return this.getId().equals(otherCache.getId());
}
}
public int hashCode() {
if (this.getId() == null) {
throw new CacheException("Cache instances require an ID.");
} else {
return this.getId().hashCode();
}
}
}
BaseExecutor
package org.apache.ibatis.executor;
public abstract class BaseExecutor implements Executor {
protected PerpetualCache localCache;
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
//查询栈长度是0,或者需要刷新缓存
if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
this.clearLocalCache();
}
List list;
try {
++this.queryStack;
// 根据key获取对应的Java对象
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
if (list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
--this.queryStack;
}
if (this.queryStack == 0) {
Iterator i$ = this.deferredLoads.iterator();
while(i$.hasNext()) {
BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next();
deferredLoad.load();
}
this.deferredLoads.clear();
if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
this.clearLocalCache();
}
}
return list;
}
}
}
CachingExecutor.query
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
UserMapper有一个二级缓存区域(按namespace分),其它mapper也有自己的二级缓存区域(按namespace分)。每一个namespace的mapper都有一个二级缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。即查询缓存时,作用域是一个mapper的namespace,在同一个namespace中查询sql可以从缓存中获取。启用二级缓存时,采用装饰器模式,当二级缓存没有命中,底层还是通过BaseExecutor实现
开启二级缓存:
mybatis的二级缓存,缓存在Configuration中。
二级缓存是默认关闭的,设置需要在Mapper XML中添加cache配置。原理:
public class CachingExecutor implements Executor {
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
this.flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
this.ensureNoOutParams(ms, parameterObject, boundSql);
List<E> list = (List)this.tcm.getObject(cache, key);
if (list == null) {
list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
this.tcm.putObject(cache, key, list);
}
return list;
}
}
return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
二级缓存实例:
1. 配置文件中开启缓存
<span style="font-size:18px;"><settings>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings> </span>
2. 需要开启二级缓存mapper的cache加入
<span style="font-size:18px;"><cache/></span>
3.
@Test
public void testCache2() throws Exception {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
sqlSession1.close();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
sqlSession2.close();
}
输出结果:
DEBUG [main] - Cache Hit Ratio [com.iot.mybatis.mapper.UserMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 103887628.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@631330c]
DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
User [id=1, username=张三, sex=1, birthday=null, address=null]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@631330c]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@631330c]
DEBUG [main] - Returned connection 103887628 to pool.
DEBUG [main] - Cache Hit Ratio [com.iot.mybatis.mapper.UserMapper]: 0.5
User [id=1, username=张三, sex=1, birthday=null, address=null]
我们可以从打印的信息看出,两个sqlSession,去查询同一条数据,只发起一次select查询语句,第二次直接从Cache中读取。
前面我们说到,Spring和MyBatis整合时, 每次查询之后都要进行关闭sqlSession,关闭之后数据被清空。所以spring整合之后,如果没有事务,一级缓存是没有意义的。那么如果开启二级缓存,关闭sqlsession后,会把该sqlsession一级缓存中的数据添加到namespace的二级缓存中。这样,缓存在sqlsession关闭之后依然存在。
总结:
对于查询多commit少且用户对查询结果实时性要求不高,此时采用mybatis二级缓存技术降低数据库访问量,提高访问速度。
但不能滥用二级缓存,二级缓存也有很多弊端,从MyBatis默认二级缓存是关闭的就可以看出来。
二级缓存是建立在同一个namespace下的,如果对表的操作查询可能有多个namespace,那么得到的数据就是错误的。
举个简单的例子:
订单和订单详情,orderMapper、orderDetailMapper。在查询订单详情时我们需要把订单信息也查询出来,那么这个订单详情的信息被二级缓存在orderDetailMapper的namespace中,这个时候有人要修改订单的基本信息,那就是在orderMapper的namespace下修改,他是不会影响到orderDetailMapper的缓存的,那么你再次查找订单详情时,拿到的是缓存的数据,这个数据其实已经是过时的。
根据以上,想要使用二级缓存时需要想好两个问题:
1)对该表的操作与查询都在同一个namespace下,其他的namespace如果有操作,就会发生数据的脏读。
2)对关联表的查询,关联的所有表的操作都必须在同一个namespace。
翻了好多博文,自己总结的:
https://www.cnblogs.com/yuluoxingkong/p/8205858.html
https://www.cnblogs.com/dongying/p/4142476.html
mybatis编程步骤
- 创建SqlSessionFactory
- 通过SqlSessionFactory创建SqlSession
- 通过SqlSession执行数据库操作
- 调用session.commit() 提交事务
- 调用session.close() 关闭回话
parser:[ˈpɑːzə] 解析器; 分析器; 剖析器; 解析; 语法分析器;
编程实例:
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.InputStream;
public class Main {
@Test
public void test() throws Exception {
String resource = "mybatis-config.xml";
// 1. 读取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 2. 创建 SqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3. 使用工厂创建 SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession()
// 4.使用SqlSession创建Dao接口的代理对象
UserDAO userDAO = sqlSession.getMapper(UserDAO.class);
// 5. 使用代理对象执行方法
User user = userDAO.selectById(1);
// 6.释放资源
sqlSession.close();
in.close();
}
}
走进mybatis源码,分析mybatis的执行流程
几个很重要的类
- Configuration是一个很关键的类,mybatis中所有配置信息都是基本存在于此。
- sqlSession类是一个非常重要的类,是mybatis运行最核心的Java接口,通过这个接口与数据库打交道,执行命令、获取Mapper类,管理事务,获取到sqlSesion对象后,就说明已经与数据库真正简历连接关系,接下来就是实际的交互,发送sql了
- Executor执行器是一个非常重要的接口,它真正是实现了与数据的交互
一. 如何获取sqlSession
1. 首先,SqlSessionFactoryBuilder去读取mybatis的配置文件,然后SqlSessionFactoryBuilder.build() 创建一个SqlSessionFactory。
其中:
进入build方法,可以看到通过XMLConfigBuilder解析inputStream中的配置参数,parser.parse()将从配置数据存入Configuration类中,然后返回DefaultSqlSessionFactory。
package org.apache.ibatis.session;
public class SqlSessionFactoryBuilder {
public SqlSessionFactoryBuilder() {
}
// 执行这个
public SqlSessionFactory build(InputStream inputStream) {
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
/** 一系列的构造方法最终都会调用本方法(配置文件为Reader时会调用本方法,还有一个InputStream方法与此对应)*/
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
// 在创建SqlSessionFactory时,首先解析config配置文件
/* 通过XMLConfigBuilder解析配置文件,解析的配置相关信息都会封装为一个Configuration对象*/
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//这儿创建DefaultSessionFactory对象
var5 = this.build(parser.parse());// parser.parse()
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
我们可以多看一下parser.parse(),parseConfiguration(XNode root) 方法中包含了许多其他方法,每一个方法的作用都是将特定节点的数据存入configuration对象中,
Configuration是一个很关键的类,mybatis中所有配置信息都是基本存在于此。
package org.apache.ibatis.builder;
public abstract class BaseBuilder {
protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
protected final TypeHandlerRegistry typeHandlerRegistry;
}
package org.apache.ibatis.parsing;
public class XNode {
private Node node;
private String name;
private String body;
private Properties attributes;
private Properties variables;
private XPathParser xpathParser;
}
package org.apache.ibatis.builder.xml;
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;
private XPathParser parser;
private String environment;
private ReflectorFactory localReflectorFactory;
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
private void parseConfiguration(XNode root) {
try {
Properties settings = this.settingsAsPropertiess(root.evalNode("settings"));
this.propertiesElement(root.evalNode("properties"));
this.loadCustomVfs(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectionFactoryElement(root.evalNode("reflectionFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));// 重点
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
}
2. 当我们获取到SqlSessionFactory之后,就可以通过SqlSessionFactory去获取SqlSession对象。源码如下:
package org.apache.ibatis.session.defaults;
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
//通过Confuguration对象去获取Mybatis相关配置信息, Environment对象包含了数据源和事务的配置
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//之前说了,从表面上来看,咱们是用sqlSession在执行sql语句, 实际呢,其实是通过excutor执行, excutor是对于Statement的封装
final Executor executor = configuration.newExecutor(tx, execType);
//关键看这儿,创建了一个DefaultSqlSession对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
}
SqlSession咱们也拿到了,咱们可以调用SqlSession中一系列的select..., insert..., update..., delete...方法轻松自如的进行CRUD操作了。 就这样? 那咱配置的映射文件去哪儿了? 别急, 咱们接着往下看:
二. MapperProxy
在mybatis中,通过MapperProxy动态代理咱们的dao, 也就是说, 当咱们执行自己写的dao里面的方法的时候,其实是对应的mapperProxy在代理。那么,咱们就看看怎么获取MapperProxy对象吧:
获取到sqlSession对象后,
UserDAO userDAO = sqlSession.getMapper(UserDAO.class);
通过SqlSession从Configuration中获取
public class DefaultSqlSession implements SqlSession {
// 啥都没做,直接去configuration里找
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
}
SqlSession把包袱甩给了Configuration, 接下来就看看Configuration。
public class Configuration {
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled;
protected boolean useGeneratedKeys;
// 它也不要,你找mapperRegistry去要
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
}
交给MapperRegistry去做,看一下MapperRegistry
public class MapperRegistry {
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//能偷懒的就偷懒,俺把粗活交给MapperProxyFactory去做
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
// 关键在这
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5)
}
}
}
}
我们看看它是如何实现的
package org.apache.ibatis.binding;
public class MapperProxyFactory<T> {
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
//动态代理我们写的dao接口
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
}
上面,咱们拿到了MapperProxy, 每个MapperProxy对应一个dao接口, 那么咱们在使用的时候,MapperProxy是怎么做的呢? 源码奉上
MapperProxy:
MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
// MapperProxy在执行时会触发此方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
} else {
MapperMethod mapperMethod = this.cachedMapperMethod(method);
//主要交给MapperMethod自己去管
return mapperMethod.execute(this.sqlSession, args);
}
}
}
看看都干啥了
public class MapperMethod {
/**
* 看着代码不少,不过其实就是先判断CRUD类型,然后根据类型去选择到底执行sqlSession中的哪个方法,绕了一圈,又转回sqlSession了*/
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
if (SqlCommandType.INSERT == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
} else if (SqlCommandType.UPDATE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
} else if (SqlCommandType.DELETE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
} else if (SqlCommandType.SELECT == this.command.getType()) {
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
} else {
if (SqlCommandType.FLUSH != this.command.getType()) {
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
result = sqlSession.flushStatements();
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
}
既然都是sqlSession的方法,我们就挑一个看看 selectList看看
package org.apache.ibatis.session.defaults;
public class DefaultSqlSession implements SqlSession {
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
//CRUD实际上是交给Excetor去处理, excutor其实也只是穿了个马甲而已,小样,别以为穿个马甲我就不认识你嘞!
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}
}
调用了query,query里调用queryFromDatabase,然后调用 doQuery,我们就看一个doQuery的实现
public class SimpleExecutor extends BaseExecutor {
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
//StatementHandler封装了Statement, 让 StatementHandler 去处理
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
}
接下来,咱们看看StatementHandler 的一个实现类 PreparedStatementHandler(这也是我们最常用的,封装的是PreparedStatement),
看看它使怎么去处理的:到这就很熟悉了
public class PreparedStatementHandler extends BaseStatementHandler {
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//PreparedStatement
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
// 结果交给了ResultSetHandler 去处理
return this.resultSetHandler.handleResultSets(ps);
}
}
下面的待整理
i