mybatis执行过程

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编程步骤

  1. 创建SqlSessionFactory
  2. 通过SqlSessionFactory创建SqlSession
  3. 通过SqlSession执行数据库操作
  4. 调用session.commit() 提交事务
  5. 调用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的执行流程

几个很重要的类

  1. Configuration是一个很关键的类,mybatis中所有配置信息都是基本存在于此。
  2. sqlSession类是一个非常重要的类,是mybatis运行最核心的Java接口,通过这个接口与数据库打交道,执行命令、获取Mapper类,管理事务,获取到sqlSesion对象后,就说明已经与数据库真正简历连接关系,接下来就是实际的交互,发送sql了
  3. 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

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值