持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情
Mybatis
1、基础架构介绍
- API接口层
- 就是我们接口调用的api,api层接收到调用请求,会到数据处理层完成相应的数据处理;
- 主要是两种调用方式
- 传统基于statementid的api访问模式
- 基于Mapper接口代理方式
- 数据服务处理层
- 主要是负责sql的解析、执行、以及执行结果的映射处理等;总之就是通过它操作数据库;
- 基础支撑层
- 主要是构建一些框架的基础能力,例如连接管理,加载文件,加载缓存,事务管理等
2、流转过程
- 加载配置文件
- 配置文件一般有基于XML和注解方式的,将主配置文件解析成Configuration,将sql配置解析成mappedstatement对象;
- 调用api接口层
- 调用api传入sql的id和入参对象,到数据处理层处理数据
- 数据处理层处理api请求
- 根据sql的id查出相对应的MappedStatement对象;
- 根据传参对象解析TikTokReplyServiceImpl,得到待执行的sql语句和参数;
- 获取连接,执行,得到结果
- 结果映射,拿到最终解析过的结果
- 关闭连接
3、源码分析
先了解一下几个重要组件
- SqlSession:mybatis的顶层接口,表示跟数据库的会话,完成一些增删改查
- Executor:执行器。负责sql语句的执行和缓存查询
- ParameterHandler:将传参转成jdbc statment的参数
- ResultSetHandler:将jdbc返回的set转换成list
- TypeHandler: java类型跟jdbc类型之间的转换
- SqlSource:动态的生成sql,封装到BoundsQL
- BoundSql:存放动态生成的sql语句和参数
1、加载解析配置文件创建SqlSessionFactory
1、通过加载配置文件,构建一个SqlSessionFactory
SqlSessionFactory factory = new
SqlSessionFactoryBuilder().build(xml);
2、将配置文件解析成Configuration,创建一个SqlSessionFactory
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {// 解析配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
3、parser.parse() 解析成Configuration,就是用来保存配置文件的各种信息
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
4、解析XML,就是解析各种标签
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
//解析properties标签,并set到Configration对象中
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
5、新建并返回SqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
2、sql的执行过程
sqlsession
SqlSession是⼀个接⼝,两个实现类:DefaultSqlSession (默认)和 SqlSessionManager,SqlSession是MyBatis中⽤于和数据库交互的顶层类,通常将它与ThreadLocal绑定,⼀个会话使⽤⼀ 个SqlSession,并且在使⽤完毕后需要close;
- 获得sqlsession
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 获取执行器
final Executor executor = configuration.newExecutor(tx, execType);
// 根据执行器获取一个SqlSession
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();
}
}
mybatis的缓存
1、一级缓存
一级缓存属于SqlSession级别的。;
一级缓存是个啥?
public class PerpetualCache implements Cache {
private final String id;
//这就是
private Map<Object, Object> cache = new HashMap<Object, Object>();
实际上就是一个SqlSession的本地Map,每⼀个SqISession都会存放⼀个map对象的引⽤,他也随着sqlsession创建、使用、删除;
-
缓存的删除
-
更新操作删除缓存
-
@Override public int update(MappedStatement ms, Object parameter) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } clearLocalCache(); return doUpdate(ms, parameter); } @Override public void clearLocalCache() { if (!closed) { localCache.clear(); localOutputParameterCache.clear(); } } 以及缓存:private Map<Object, Object> cache = new HashMap<Object, Object>();
-
-
缓存的创建
-
基于Executor执行器来创建以及一级缓存;
-
createCacheKey方法创建缓存并返回;
-
public abstract class BaseExecutor implements Executor { @Override public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { CacheKey cacheKey = new CacheKey(); cacheKey.update(ms.getId()); cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); cacheKey.update(boundSql.getSql()); cacheKey.update(value); } } if (configuration.getEnvironment() != null) { // issue #176 cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey; }
-
-
缓存查询
-
在查询操作的时候,会创建缓存,当缓存存在数据直接返回,不存在则是查询数据库到缓存再返回;
-
@Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
-
2、二级缓存
- ⼆级缓存的原理和⼀级缓存原理类似
- 第⼀次查询,会将数据放⼊缓存中,然后第⼆次查询则会直接去 缓存中取。
- ⼀级缓存是基于sqlSession的,⽽⼆级缓存是基于mapper⽂件的namespace的
- 意思是多个sqlSession可以共享⼀个mapper中的⼆级缓存区域;
- 并且如果两个mapper的namespace 相 同,那么这两个mapper中执⾏sql查询到的数据也将存在相同的⼆级缓存区域 中;
⼆级缓存是基于⼀级缓存之上的,在查询时,⾸先会查询⼆级缓存,若⼆级缓存未命 中,再去查询⼀级缓存,⼀级缓存没有,再查询数据库。 ⼆级缓存和具体的命名空间绑定,⼀个Mapper中有⼀个Cache,相同Mapper中的 MappedStatement共⽤⼀个Cache,⼀级缓存则是和 SqlSession 绑定。