架构设计
我们可以把Mybatis的功能架构分为三层:
Mybatis和数据库的交互有两种方式:
- 使用传统的Mybatis提供API
- 使用Mapper代理的方式
- 数据处理层:负责具体的sql查找、sql解析、sql执行和执行结果映射处理等。他主要的目的是根据调用的请求完成一次数据库操作。
- 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来最为基础组件。为上层的数据处理层提供最基础的支撑。
Mybatis主要构件
构件 | 描述 |
sqlSession | |
Executor | |
StatementHandler | 封装了JDBC Statement操作,负责对JDBC statement的操作,如设置参数、将Statement结果集转换为List集合 |
ParameterHandler | 负责对用户传递的参数转换为JDBC Statement所需要的参数 |
ResultSetHandler | 负责将JDBC返回的ResultSet结果集对象转换为List类型的集合 |
TypeHandler | 负责java数据类型和jdbc数据类型之间的映射和转换 |
MappedStatement | MappedStatement维护了一条<select、 update 、 delete 、insert >节点的封装 |
sqlSource | |
Boundsql |
总体流程:
- 加载配置并初始化
配置来源于两个地方,一个是配置文件(conf.xml,mapper*.xml),一个是java代码中的注解,将配置文件内容封装到Configuration,将sql的配置信息加载成为一个mappedstatement对象,存储在内存中。
2. 接收调用请求
触发条件:调用Mybatis提供的API
传入参数:为sql的ID和传入的参数
将请求传递给下层的请求处理层进行处理
- 处理操作请求
- 根据sql的ID查找对应的MappedStatement对象
- 根据传入参数对象解析,得到最终要执行的sql和执行传入参数
- 获取数据库连接,将最终sql语句和参数给到数据库执行,并得到执行结果
- 根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理结果
- 释放连接资源
- 返回处理结果
Mybatis缓存
Mybatis有一级缓存和二级缓存。Mybatis收到查询请求后首先会查询二级缓存,若二级缓存未命中,再去查询一级缓存,一级缓存没有,再查询数据库。
一级缓存
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++;
//从localCache缓存里查数据,没有就去查数据库
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;
}
这个localCache是BaseExecutor里面的一个属性
public abstract class BaseExecutor implements Executor {
protected PerpetualCache localCache;
PerpetualCache类
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getobject(Object key) {
return cache.get(key);
}
二级缓存
启用二级缓存步骤:
- 开启cacheEnabled(默认打开)
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
- 需要在二级缓存的Mapper配置文件中加入
<cache></cache>
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")
List<E> list = (List<E>) tcm.getobject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundsql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundsql);
}
注意Cache cache = ms.getCache();,这个cache是从MappedStatement中获取到的,由于MappedStatement存在全局配置中,可以多个CachingExecutor获取到,这样就会出现线程安全问题。除此之外,若不加以控制,多个事务共用一个缓存实例,会导致脏读的存在。
那么mybatis是怎么解决脏读的呢?借用了上面的tcm这个变量,也就是TransactionalCacheManager类来解决的。
TransactionalCacheManager类维护了Cache,TransactionalCache的关系,真正的数据还是交由TransactionalCache处理的。
结构如图:
public class TransactionalCacheManager {
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
public void clear(Cache cache) {
getTransactionalCache(cache).clear();
}
public Object getobject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getobject(key);
}
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
private TransactionalCache getTransactionalCache(Cache cache) {
TransactionalCache txCache = transactionalCaches.get(cache);
if (txCache == null) {
txCache = new TransactionalCache(cache);
transactionalCaches.put(cache, txCache);
}
return txCache;
}
}
接下来看一下TransactionalCache的代码
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
// 真正的缓存对象
private final Cache delegate;
private boolean clearOnCommit;
//在事务被提交前,所有从数据库中查询的结果将缓存在此集合中
private final Map<Object, Object> entriesToAddOnCommit;
//在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此集合中
private final Set<Object> entriesMissedInCache;
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<Object, Object>();
this.entriesMissedInCache = new HashSet<Object>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public Object getobject(Object key) {
// issue #116
//获取缓存的时候从delegate里获取的
Object object = delegate.getobject(key);
if (object == null) {
//缓存未命中,将key存入entriesMissedInCache.
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
} else {
return object;
}
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
@Override
public void putObject(Object key, Object object) {
//put的时候只是将数据库的数据放入到了entriesToAddOnCommit
entriesToAddOnCommit.put(key, object);
}
@Override
public Object removeObject(Object key) {
return null;
}
@Override
public void clear() {
clearOnCommit = true;
entriesToAddOnCommit.clear();
}
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
//刷新未缓存的结果到delegate中去
flushPendingEntries();
reset();
}
public void rollback() {
unlockMissedEntries();
reset();
}
private void reset() {
clearOnCommit = false;
entriesToAddOnCommit.clear();
entriesMissedInCache.clear();
}
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
private void unlockMissedEntries() {
for (Object entry : entriesMissedInCache) {
try {
delegate.removeObject(entry);
} catch (Exception e) {
log.warn("Unexpected exception while notifiying a rollback to the cache adapter."
+ "Consider upgrading your cache adapter to the latest version. Cause: " + e);
}
}
}
}
我们存储二级缓存的时候是放入到TransactionalCache.entriesToAddOnCommit这个map中,但是每次查询的时候是从delegate查询的,所以这个二级缓存查询数据库后,缓存是没有立刻生效的。只有当执行了sqlSession的commit或close方法后,它会调用到tcm的commit,在调用到transactionlCache的commit,刷新缓存到delegate了。
总结:
- 二级缓存的设计上,大量运用了装饰器模式,如SynchronizedCache、LoggingCache。
- 二级缓存实现了sqlsession之间的缓存数据共享,属于namespace级别
- 二级缓存的实现由CachingExecutor和一个事务型预缓存TransactionlCache完成。
书山有路勤为径,学海无涯苦作舟
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。