概述
前面我们已经熟悉了MyBatis,为了更清晰得追踪MyBatis执行过程,我们把项目简洁化,方便聚焦MyBatis核心代码。
另外在阅读源码前,应该已经熟悉官网的文档:
《MyBatis文档》https://mybatis.org/mybatis-3/zh/getting-started.html
一、在SpringBoot中运行MyBatis
- pom.xml文件中的依赖,简化后如下:
org.springframework.boot
spring-boot-starter
mysql
mysql-connector-java
8.0.17
org.mybatis
mybatis
3.5.2
- 创建包:com.zhlab.mybatisdemo.chapter3,并创建实体类SysAdminUser
package com.zhlab.mybatisdemo.chapter3;
/**
* @ClassName SysAdminUser
* @Description //SysAdminUser
* @Author singleZhang
* @Email [email protected]
* @Date 2020/12/29 0029 下午 4:35
**/
public class SysAdminUser {
private Integer adminUserId;
private String userName;
private String email;
private String nickName;
private Integer deptId;
public SysAdminUser() {
}
// ....省略getter setter
}
- 创建接口类SysAdminUserMapper
package com.zhlab.mybatisdemo.chapter3;
import java.util.List;
public interface SysAdminUserMapper {
List getUserList(SysAdminUser user);
}
- 在resources目录下创建文件夹com/zhlab/mybatisdemo/chapter3,并创建映射文件SysAdminUserMapper.xml
- 在resources目录下,创建配置文件mybatis-config.xml文件
- 创建类Chapter3DemoApplication,如下
package com.zhlab.mybatisdemo.chapter3;
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.springframework.boot.autoconfigure.SpringBootApplication;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* @ClassName Chapter3DemoApplication
* @Description //Chapter3DemoApplication
* @Author singleZhang
* @Email [email protected]
* @Date 2020/12/29 0029 下午 4:34
**/
@SpringBootApplication
public class Chapter3DemoApplication {
public static void main (String[] args){
//SpringApplication.run(Chapter3DemoApplication.class,args);
// 第一阶段:MyBatis的初始化阶段
String resource = "mybatis-config.xml";
// 得到配置文件的输入流
InputStream inputStream = null;
try{
inputStream = Resources.getResourceAsStream(resource);
}catch (IOException e){
e.printStackTrace();
}
// 得到SqlSessionFactory
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
// 第二阶段:数据读写阶段
try(SqlSession session = sqlSessionFactory.openSession()){
// 找到接口对应的实现
SysAdminUserMapper userMapper = session.getMapper(SysAdminUserMapper.class);
// 查询参数
SysAdminUser userParam = new SysAdminUser();
userParam.setDeptId(5);
// 调用接口展开数据库操作
List userList = userMapper.getUserList(userParam);
for (SysAdminUser user : userList) {
System.out.println("name : " + user.getNickName() + " ; email : " + user.getEmail());
}
}
}
}
- 运行结果如下
二、分析
从上述过程中可以看出,MyBatis的操作主要分为两大阶段:初始化阶段和数据库操作阶段。
2-1. MyBatis的初始化阶段
MyBatis的初始化在项目启动时进行,这里主要工作内容为:读取并解析配置文件、数据库连接等。
我们要知道每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。初始化阶段介绍创建SqlSessionFactory 实例的过程。
这里关注这几个类:
Resources、ClassLoaderWrapper(MyBatis的io包)
SqlSessionFactoryBuilder、Configuration(MyBatis的session包)
XMLConfigBuilder(MyBatis的builder包)
初始化阶段里的配置文件读取
inputStream = Resources.getResourceAsStream(resource);
这段代码调用了Resource中的 getResourceAsStream方法,getResourceAsStream方法又调用了ClassLoaderWrapper的getResourceAsStream方法:
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
ClassLoader[] var3 = classLoader;
int var4 = classLoader.length;
for(int var5 = 0; var5 < var4; ++var5) {
ClassLoader cl = var3[var5];
if (null != cl) {
InputStream returnValue = cl.getResourceAsStream(resource);
if (null == returnValue) {
returnValue = cl.getResourceAsStream("/" + resource);
}
if (null != returnValue) {
return returnValue;
}
}
}
return null;
}
※Resource类和 ClassLoaderWrapper类是负责读写外部文件。
SqlSessionFactory实例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
可以看出SqlSessionFactory实例是通过SqlSessionFactoryBuilder的build方法来创建的,可以传入xml配置文件内容或者通过Configuration实例对象,源码如下:
// 方法一:通过xml配置文件的InputStream对象 构建SqlSessionFactory
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
// 方法二:通过Configuration 对象 构建SqlSessionFactory
public SqlSessionFactory build(Configuration config) {
//DefaultSqlSessionFactory
return new DefaultSqlSessionFactory(config);
}
我们的demo是采用了方法一,其实最终也是进入方法二里边,继续往下看。这里需要关注的核心代码是:
try {
//步骤一
//生成了一个 XMLConfigBuilder对象parser
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//步骤二
//调用了XMLConfigBuilder对象parser的 parse() 方法,返回值为Configuration对象
//调用了方法二的build(Configuration config) 方法
var5 = this.build(parser.parse());
}
//...当中省略
return var5;
继续跟踪XMLConfigBuilder对象parser的parse() 方法
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
// 进入解析
this.parsed = true;
// "/configuration" 是整个配置文件的根节点
//parseConfiguration具体解析各个节点,并往Configuration对象设值
this.parseConfiguration(this.parser.evalNode("/configuration"));
//返回Configuration对象
return this.configuration;
}
}
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.loadCustomLogImpl(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
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);
}
}
parseConfiguration方法是具体解析配置文件里各个节点的(包括配置节点信息和映射文件信息),里边调用的各类方法都是往Configuration对象configuration里设值。
最终,Configuration对象会进入SqlSessionFactory的 build(Configuration config)方法,得到SqlSessionFactory对象。
所以,归纳一下初始化阶段,MyBatis做了以下几个操作:
- 根据配置文件的位置,获取它的输入流 InputStream
- 解析配置文件及映射文件,放入Configuration 对象
- 以配置好的 Configuration对象为参数,获取一个 SqlSessionFactory对象
2-2. MyBatis的数据操作阶段
从一阶段已经获取了SqlSessionFactory对象,那这个工厂就是会生产SqlSession对象的,数据库操作过程需要这个对象。
获得SqlSession
SqlSession session = sqlSessionFactory.openSession()
通过追踪代码可以看到SqlSession对象由DefaultSqlSessionFactory类的openSessionFromDataSource方法生成,源码如下:
// DefaultSqlSessionFactory类中
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
/**
* Configuration 配置信息
* ExecutorType:执行器的类型。默认类型时simple
* TransactionIsolationLevel:数据库隔离级别。
* autoCommit:是否支持手动事务提交。
*/
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//调用Configuration对象,创建Executor
//因为默认下Configuration里的cacheEnabled=true
//把Executor对象再传入CachingExecutor的构造函数,创建CachingExecutor对象
//具体查看Configuration类中563行newExecutor方法
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
//Configuration类中563行newExecutor方法
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? this.defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Object executor;
//根据类型创建Executor对象:BatchExecutor、ReuseExecutor、SimpleExecutor三个
//它们都继承了BaseExecutor
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
//里边使用了装饰器模式,创建一个CachingExecutor对象
//CachingExecutor为使用二级缓存的SQL执行器
if (this.cacheEnabled) {
executor = new CachingExecutor((Executor)executor);
}
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
}
在以上代码中可以看到,SqlSessionFactory里使用了Configuration对象,创建了Transaction对象、Executor对象等作为参数传入DefaultSqlSession构造函数,最后得到一个DefaultSqlSession对象。
进入DefaultSqlSession类可以看到各类增删改查等数据库操作方法,这里只是一个门面模式,具体交给Executor去执行,SqlSession对象可以供多次数据库读写操作复用不用多次创建。
映射接口文件(Mapper 接口类)与映射文件(Mapper xml文件)
demo中的代码片段如下:
// 找到接口对应的实现
SysAdminUserMapper userMapper = session.getMapper(SysAdminUserMapper.class);
跟踪代码,进入DefaultSqlSession类的getMapper方法,进而继续调用Configuration对象的getMapper方法,继续调用MapperRegistry对象的getMapper方法
// DefaultSqlSession类
public T getMapper(Class type) {
return this.configuration.getMapper(type, this);
}
// Configuration类
public T getMapper(Class type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry类
public T getMapper(Class type, SqlSession sqlSession) {
MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
//通过MapperProxyFactory对象创建Class类型的Mapper接口类实例
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
上面方法中可以看到使用了动态代理模式,使用MapperProxyFactory对象来创建各个传入的Mapper接口类的实例。
MapperProxyFactory类
protected T newInstance(MapperProxy mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
这里返回的就是一个基于反射的动态代理对象,MapperProxy是一个自定义的实现了InvocationHandler接口的类,可以对其invoke方法进行监控,查看各种代理对象的信息比如名称、方法名、参数等等
// MapperProxy类中
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
if (method.isDefault()) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
//获取MapperMethod 对象,传入method
MapperMethod mapperMethod = this.cachedMapperMethod(method);
//执行sql
return mapperMethod.execute(this.sqlSession, args);
}
// 创建MapperMethod对象
private MapperMethod cachedMapperMethod(Method method) {
return (MapperMethod)this.methodCache.computeIfAbsent(method, (k) -> {
return new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
});
}
上面的execute方法,所执行的就是demo里的这段
List userList = userMapper.getUserList(userParam);
继续追踪,进入MapperMethod类,查看execute方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
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);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
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;
}
}
//demo中我们是查询List这里关注executeForMany方法
private Object executeForMany(SqlSession sqlSession, Object[] args) {
Object param = this.method.convertArgsToSqlCommandParam(args);
List result;
if (this.method.hasRowBounds()) {
RowBounds rowBounds = this.method.extractRowBounds(args);
result = sqlSession.selectList(this.command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(this.command.getName(), param);
}
if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
} else {
return result;
}
}
这里最终调用了sqlSession对象的selectList方法。到这里MyBatis实现了Mapper接口的注入,并得到接口中的方法名,并进入数据库操作。
SQL操作
继续上述步骤,进入DefaultSqlSession类的selectList方法
// DefaultSqlSession
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
//获取MappedStatement对象,通过配置信息获取
MappedStatement ms = this.configuration.getMappedStatement(statement);
//执行executor对象里的query方法
//这里的executor是在DefaultSqlSessionFactory中,通过Configuration对象创建的CachingExecutor
//当然根据不同的配置,我们会有不同的Executor,后面具体分析会知道。
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;
}
接下来看CachingExecutor类中具体执行的代码
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
// CacheKey为缓存键,它由createCacheKey方法创建
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//获取缓存,如果MappedStatement 缓存存在,则从缓存中获取数据结果
Cache cache = ms.getCache();
if (cache != null) {
this.flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
this.ensureNoOutParams(ms, boundSql);
List list = (List)this.tcm.getObject(cache, key);
//从缓存获取的结果为空的情况下,则通过delegate对象调用query方法,查询数据,并缓存数据
if (list == null) {
list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
this.tcm.putObject(cache, key, list);
}
return list;
}
}
//不存在MappedStatement 缓存,则通过delegate对象调用query方法,查询数据
return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
this.delegate.query方法在BaseExecutor类中,this.delegate为构建CachingExecutor对象时传入的Executor对象,这里默认为SimpleExecutor,因为它类继承了BaseExecutor类,SimpleExecutor中没有query方法,所以query方法来自BaseExecutor类
//BaseExecutor类118行
public List 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 {
if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
this.clearLocalCache();
}
List list;
try {
++this.queryStack;
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 var8 = this.deferredLoads.iterator();
while(var8.hasNext()) {
BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
deferredLoad.load();
}
this.deferredLoads.clear();
if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
this.clearLocalCache();
}
}
return list;
}
}
//queryFromDatabase
private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 这里是MyBatis的一级缓存,由PerpetualCache类型的成员对象维护,其底层还是一个HashMap。
// 先在缓存中放置一个占位符
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List list;
try {
// 然后调用 doQuery
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
//移除缓存
this.localCache.removeObject(key);
}
//把查询结果存入缓存
this.localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
//缓存参数
this.localOutputParameterCache.putObject(key, parameter);
}
return list;
}
doQuery方法在BaseExecutor中是个抽象方法,在SimpleExecutor类中有具体实现
//SimpleExecutor类44行
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
//通过Configuration对象的newStatementHandler方法
//获取StatementHandler 对象,跟踪源码可以发现为PreparedStatementHandler
//因为MappedStatement的Builder方法对statementType设值为StatementType.PREPARED
//MappedStatement类181行
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
PreparedStatementHandler类中具体执行sql语句
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
//转换为PreparedStatement类型,PreparedStatement类来自java.sql包
PreparedStatement ps = (PreparedStatement)statement;
//具体的execute方法由com.mysql.cj.jdbc包中的PreparedStatementWrapper类实现
ps.execute();
//查询结果交给 ResultHandler对象处理
return this.resultSetHandler.handleResultSets(ps);
}
PreparedStatementWrapper类中的execute方法
public boolean execute() throws SQLException {
try {
if (this.wrappedStmt != null) {
//可以看到查询完成之后的结果放在 PreparedStatement对象中
return ((PreparedStatement)this.wrappedStmt).execute();
} else {
throw SQLError.createSQLException(Messages.getString("Statement.AlreadyClosed"), "S1000", this.exceptionInterceptor);
}
} catch (SQLException var2) {
this.checkAndFireConnectionError(var2);
return false;
}
}
好了,数据库查询操作过程可以归纳为以下几个步骤:
- 在进行数据库查询前,先查询缓存;如果确实需要查询数据库,则数据库查询之后的结果也放入缓存中
- 进入数据库查询的SQL 语句,其执行经过了层层转化,依次经过了 MappedStatement 对象、Statement对象、PreparedStatement对象
- 最终数据库查询得到的结果交给 ResultHandler对象处理
处理结果集
上面步骤中
return this.resultSetHandler.handleResultSets(ps);
这里的handleResultSets方法来自接口ResultSetHandler类中
public interface ResultSetHandler {
List handleResultSets(Statement var1) throws SQLException;
Cursor handleCursorResultSets(Statement var1) throws SQLException;
void handleOutputParameters(CallableStatement var1) throws SQLException;
}
其具体实现由DefaultResultSetHandler类中的handleResultSets方法
public List
这里具体做的就是把之前SQL查询出来的结果被遍历后放入了列表multipleResults 中并返回,即List
这里边的方法调用链路如下
在第二阶段的数据库操作中,MyBatis的工作内容如下
- 建立连接数据库的 SqlSession
- 查找当前映射接口中抽象方法对应的数据库操作节点,根据该节点生成接口的实现
- 接口的实现拦截对映射接口中抽象方法的调用,并将其转化为数据查询操作
- 对数据库操作节点中的数据库操作语句进行多次处理,最终得到标准的 SQL语句
- 尝试从缓存中查找操作结果,如果找到则返回;如果找不到则继续从数据库中查询
- 从数据库中查询结果
- 处理结果集
- 建立输出对象
- 根据输出结果对输出对象的属性赋值
- 在缓存中记录查询结果
- 返回查询结果