一、一个简略的 Demo
开始之前先创立一张表,搭建一个简略的工程。
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`email` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`sex` int(255) DEFAULT NULL,
`schoolName` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', '易哥', 'yeecode@sample.com', '18', '0', 'Sunny School');
INSERT INTO `user` VALUES ('2', '莉莉', 'lili@sample.com', '15', '1', 'Garden School');
INSERT INTO `user` VALUES ('3', '杰克', 'jack@sample.com', '25', '0', 'Sunny School');
INSERT INTO `user` VALUES ('4', '张大壮', 'zdazhaung@sample.com', '16', '0', 'Garden School');
INSERT INTO `user` VALUES ('5', '王小壮', 'wxiaozhuang@sample.com', '27', '0', 'Sunny School');
INSERT INTO `user` VALUES ('6', '露西', 'lucy@sample.com', '14', '1', 'Garden School');
INSERT INTO `user` VALUES ('7', '李二壮', 'lerzhuang@sample.com', '9', '0', 'Sunny School');
1、实体类
package com.gitee.mycode.mybatisdemo.entity;
import lombok.Data;
/**
* @program: mybatisdemo
* @description: 用户实体
* @author: Mr.Hu
* @create: 2022-05-31 21:19
*/
@Data
public class User {
private Integer id;
private String name;
private String email;
private Integer age;
private Integer sex;
private String schoolName;
}
2、配置文件
<?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>
<typeAliases>
<package name="com.gitee.mycode.mybatisdemo.entity"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/demo?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
3、映射接口文件
package com.gitee.mycode.mybatisdemo.mapper;
import com.gitee.mycode.mybatisdemo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @program: mybatisdemo
* @description:
* @author: Mr.Hu
* @create: 2022-05-31 21:22
*/
@Mapper
public interface UserMapper {List<User> queryUserBySchoolName( User user);
}
4、映射文件
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.gitee.mycode.mybatisdemo.mapper.UserMapper">
<select id="queryUserBySchoolName" resultType="com.gitee.mycode.mybatisdemo.entity.User">
select * from user
<if test="schoolName!=null">
where schoolName=#{schoolName}
</if>
</select>
</mapper>
5、外围逻辑
package com.gitee.mycode.mybatisdemo;
import com.gitee.mycode.mybatisdemo.entity.User;
import com.gitee.mycode.mybatisdemo.mapper.UserMapper;
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;
@SpringBootApplication
public class MybatisdemoApplication {public static void main(String[] args) {
// 第一阶段:mybatis 初始化
String resource = "mybatis-config.xml";
// 失去配置文件输出流
InputStream inputStream = null;
try {inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 第二阶段:数据读写阶段
try (SqlSession session = sqlSessionFactory.openSession()) {UserMapper userMapper = session.getMapper(UserMapper.class);
User userParam = new User();
userParam.setSchoolName("Sunny School");
List<User> users = userMapper.queryUserBySchoolName(userParam);
for (User user : users) {System.out.println("userName:"+user.getName()+";email:"+user.getEmail());
}
}
}
}
最终我的项目构造和运行后果如下
<img src=”https://pic.imgdb.cn/item/629a294609475431292aa084.png” style=”zoom:80%” />
二、初始化阶段追踪
从下面 demo 的外围逻辑能够看出整个运行过程分为两个阶段,初始化阶段和数据读取阶段。初始化阶段的最外围代码为:
inputStream = Resources.getResourceAsStream(resource);
和SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
1、获取 InputStream
通过 Resources.getResourceAsStream
追踪会发现 ClassLoaderWrapper 类有这样一段代码
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;
}
getResourcecAsSream 办法会调用传入的每一个类加载器的 getResourceAsStream 办法来尝试获取配置文件的输出流。在尝试过程中如果获取失败的话,会在传入的地址前加上“/”再获取一次。只有尝试胜利,则表明胜利加载了指定的资源,会将所取得的输出流返回。
2、构建 SqlSessionFactory 对象
通过对SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
build 办法的追踪能够发现
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
// 创立 XMLConfigBuilder 对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 通过 XMLConfigBuilder 对象生成出一个 Congfiguration 对象之后,传入 build 办法,构建 SqlSessionFactory
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;
}
2.1 构建 Configuration 对象
进入 parser.parse()办法能够看到上面这段代码
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 {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);
}
}
有没有感觉代码里的这些字符串很眼生?没错,就是咱们 demo 中配置文件里的那些标签名。可想而知,parse 办法作用就是解析配置文件并返回 Configuration 实例。因而 Configuration 类中保留了配置文件的所有设置音讯,也保留了映射文件的信息。可见,Configuration 类是一个十分重要的类。
2.2、构建 SqlSessionFactory 对象
执行完 parse 办法之后,接下来就是将返回的 Configuration 对象传入 build 办法,源码如下:
public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);
}
至此,SqlSessionFactory 对象创立胜利!
三、数据读写阶段追踪
接着,咱们来到外围逻辑的第二阶段。
// 第二阶段:数据读写阶段
try (SqlSession session = sqlSessionFactory.openSession()) {UserMapper userMapper = session.getMapper(UserMapper.class);
User userParam = new User();
userParam.setSchoolName("Sunny School");
List<User> users = userMapper.queryUserBySchoolName(userParam);
for (User user : users) {System.out.println("userName:"+user.getName()+";email:"+user.getEmail());
}
}
1、取得 SqlSession
咱们追踪 sqlSessionFactory.openSession()
能够在 DefaultSqlFactory 的 openSessionFromDataSource 办法找到上面这段代码,这是生成 SqlSession 的外围源码
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);
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 对象中存储的设置音讯被用来创立各种对象。包含事务工厂 TransactionFactory、执行器 Executor
以及默认的 DefaultSqlSession。进入 DefaultSqlSession 后能够看到大量的增删改查、提交、回滚等办法。从 DefaultSqlSession 返回之后,SqlSession session = sqlSessionFactory.openSession()
这段代码执行结束。
2、映射接口文件与映射文件的绑定
下一步就是执行主办法中的 UserMapper userMapper = session.getMapper(UserMapper.class);
这段代码,跟进 getMappe 办法会发现 MapperRegistry 类中的 getMapper 办法如下
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {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);
}
}
}
上述代码中,getMapper 办法通过映射接口信息从所有曾经解析的映射文件找到对应的映射文件,而后依据该映射文件组建并返回接口的一个实现对象。
3、映射接口的代理
那么 mapperProxyFactory.newInstance(sqlSession)
返回的对象到底是什么呢?持续追踪代码
protected T newInstance(MapperProxy<T> mapperProxy) {return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
可见这里返回的是一个动静代理对象,因而咱们找到 MapperProxy 类的 invoke 办法并在其中打上断点。
所以主办法 List<User> users = userMapper.queryUserBySchoolName(userParam)
会执行上图的代码。接着会触发 MapperMethod 对象的 execute 办法,源码如下:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 下一步入口
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);
} else {Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for:" + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException("Mapper method'" + command.getName()
+ "attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
该办法中 mybatis 依据不同的数据库操作进行了响应的办法解决。以后 demo 是进行数据库的查问操作,会触发 result = executeForMany(sqlSession, args);
语句,executeForMany 办法源码如下:
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {RowBounds rowBounds = method.extractRowBounds(args);
// 下一步入口
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {if (method.getReturnType().isArray()) {return convertToArray(result);
} else {return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
在该办法中 mybatis 开始通过 SqlSession 对象的 selectList 办法开展后续的查问工作。跟到这里,mybatis 曾经实现了为映射接口注入实现的过程。于是,对映射接口中的形象办法调用转变成了数据查问操作。
4、SQL 语句的查找
进入result = sqlSession.selectList(command.getName(), param, rowBounds)
,源码如下:
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {MappedStatement ms = configuration.getMappedStatement(statement);
// 下一步入口
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database. Cause:" + e, e);
} finally {ErrorContext.instance().reset();}
}
configuration.getMappedStatement(statement)
语句将要执行的 MappedStatement 对象从 Configuration 对象存储的映射文件信息中找了进去
每个 MappedStatement 对象对应了咱们设置的一个数据库操作节点,它次要定义了数据库操作语句、输出 / 输入参数等信息。
5、查问后果缓存
点进 executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER)
能够发现 query 办法为形象办法,该办法有 BaseExecutor 和 CachingExecutor 两种实现形式,在 query 形象办法上打上断点后从新运行我的项目,会发现断点主动跳进了 CachingExecutor 类,上面是 CachingExecutor 类的 query 办法的代码
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 下一步入口
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
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) {flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 下一步入口
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
依据以上代码能够看出,mybatis 会查看以后操作是否命中缓存。如果是,则从缓存中获取数据后果;否则,便通过 delegate 调用 query 办法。
BoundSql 是通过层层转化后去除 if、where 等标签的 SQL 语句,而 CacheKey 是本次查问操作计算出来的缓存键。
6、数据库查问
再次进入 delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql)
发现它同样是一个形象办法,再次采纳下面的操作,在该办法上打一断点后,发现这次是进入了 BaseExecutor 类,上面来看看 BaseExecutor 类的 query 办法
@Override
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 (closed) {throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 下一步入口
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {queryStack--;}
if (queryStack == 0) {for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();}
}
return list;
}
这个办法逻辑有些简单,重点看 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql)
这行外围代码,持续跟进去查看:
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 下一步入口
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);
}
return list;
}
通过代码能够看出,mybatis 先在缓存中放入一个占位符,而后调用 doQuery 办法执行理论查问操作。最初又把缓存中的占位符替换成真正的查问后果。
doQuery 是 BaseExcutor 的形象办法,在该形象办法上打上断点后发现,跳到 SimpleExecutor 类的 doQuery 办法
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
// 下一步入口
return handler.query(stmt, resultHandler);
} finally {closeStatement(stmt);
}
}
上述办法生成了一个 java.sql.Statement 类的对象 stmt,Statement 类能够执行动态 SQL 语句并返回后果。
程序先获取到一个 StatementHandler 对象之后,而后再将查问操作交给 StatementHandler 对象执行。
StatementHandler 对象是一个语句处理器类,其中封装了很多语句操作方法,暂不细说。
接着走到 handler.query(stmt, resultHandler)
这一行,最初通过屡次跳转,程序执行到了 PreparedStatementHandler 类的 query 办法
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
这里 ps.execute()
真正执行了 SQL 语句,而后把执行后果交给 ResultHandler 解决。下图是 debug 模式下最初查问进去的数据库信息
数据库查问后果在 PreparedStatement 对象中藏的比拟深,为 h >statement>results。数据库指端信息在 columnDefinition 变量中,数据记录信息在 rowData 变量中
7、处理结果集
查问到的后果并没有间接返回,而是交给了 ResultHandler 解决,持续跟进handler.query(stmt, resultHandler)
,其源码如下:
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {while (rsw != null && resultSetCount < resultSets.length) {ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
在上述办法中查问进去的后果被遍历后放入 multipleResults 汇合并返回,该汇合中存储的就是这次查问冀望的后果 LIst<User>,至于 mybatis 是如何将数据库输入的记录转化为对象列表的具体过程比拟长,这里就不开展了。次要的就是一下三个办法。
- DefaultResultSetHandler.createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix):该办法创立了输入后果对象,也就是 demo 中的 User 对象
- DefaultResultSetHandler.applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix):在主动属性映射性能开启的状况下,该办法将数据记录的赋给输入后果对象
- DefaultResultSetHandler.applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix):该办法依照用户的映射设置,给输入后果对象的属性赋值
四、总结
在整个数据库操作阶段,mybatis 实现的工作能够分为以下几步:
- 依据配置文件地位,获取他的输出流
- 从配置文件跟节点开始,逐层解析配置文件,也包含相干的映射文件。解析过程中一直将解析后果放入 Configuration 对象
- 以配置好的 Configuration 对象为参数,获取一个 SqlSessionFactory 对象
- 建设连贯数据库的 SqlSession
- 查找以后映射接口中形象办法对应的数据库操作节点,依据节点生成接口的实现
- 接口的实现拦挡映射接口中形象办法的调用,并将其转化为数据库查问操作
- 对数据库节点中的操作语句进行屡次解决,最终失去规范的 SQL 语句
- 尝试从缓存中查找后果,如果找到则返回;如果找不到则持续从数据库中查找
- 从数据库查问后果
-
处理结果集
- 建设输入对象
- 依据输入后果对输入对象的属性赋值
- 在缓存中记录查问后果
- 返回查问后果