Mybatis工作原理(含部分源码)
Posted liycode
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis工作原理(含部分源码)相关的知识,希望对你有一定的参考价值。
MyBatis的初始化
1、读取配置文件,形成InputStream
String resource = "mybatis.xml";
// 加载mybatis的配置文件(它也加载关联的映射文件)
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
2、解析XML配置文件,创建SqlSessionFacotry
sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// SqlSessionFactoryBuilder类
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse()); // 开始进行解析了 :)
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
根据Configuration
对象来创建SqlSession
MyBatis的SQL查询流程
创建SqlSession
sqlSession = sessionFactory.openSession();
User user = sqlSession.selectOne("com.luo.dao.UserDao.getUserById", 1);
// DefaultSqlSession类
public <T> T selectOne(String statement, Object parameter) {
// 使用的selectList
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
// 多于1个结果时抛出异常
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null; // list.size() == 0
}
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 根据mapper.xml文件中的某个SQL语句创建MappedStatement
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();
}
}
执行器在query()
方法中,先查询缓存判断是否命中,命中则直接返回,否则从数据库中查询。
// CachingExecutor类
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 将参数与mapper中的sql合并
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创建缓存的key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// BaseExecutor类,创建缓存对象
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId()); // mapper文件中的id
cacheKey.update(rowBounds.getOffset()); // 分页偏移
cacheKey.update(rowBounds.getLimit()); // 每页的大小
cacheKey.update(boundSql.getSql()); // sql语句
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// 模仿 DefaultParameterHandler 逻辑,记录每个参数
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId()); // 每个SqlSessionFacotry的id
}
return cacheKey;
}
// CachingExecutor类
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, parameterObject, boundSql);
@SuppressWarnings("unchecked")
/**
* PerpetualCache是默认二级缓存实现类
* Map<Object, Object> cache = new HashMap<Object, Object>(); map的key就是CacheKey key
* CacheKey中有个hashcode = multiplier * hashcode + 每个update(Object object)object的hashCode()
* update()方法会向updateList添加元素
* CacheKey重写的equals()方法中先判断hashcode是否相等
* 然后用updateList每个对象的equals()判断
* 这两个条件都满足就说明缓存命中,cache.get(key)也就有值
*/
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 二级缓存中没有数据
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 底层调用queryFromDatabase
tcm.putObject(cache, key, list); // 将结果放入二级缓存
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 底层调用queryFromDatabase
}
// BaseExecutor类,从数据库查询
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;
}
一级缓存和二级缓存
一级缓存和二级缓存的命中判断依据是一样的。
一级缓存是SqlSession级别的缓存,不可关闭。同一个SqlSession对象对象执行2遍相同的SQL查询,第二遍查询直接返回缓存结果。
二级缓存是mapper级别的缓存。不同的SqlSession对象执行两次相同的SQL语句,第二次查询直接返回二级缓存中的结果。MyBatis默认是不开启二级缓存的。
未完,待续...
以上是关于Mybatis工作原理(含部分源码)的主要内容,如果未能解决你的问题,请参考以下文章
Mybatis:MyBatis核心组件介绍原理解析和源码解读
手把手教你阅读mybatis核心源码,掌握底层工作原理与设计思想