请选择 进入手机版 | 继续访问电脑版
搜索
房产
装修
汽车
婚嫁
健康
理财
旅游
美食
跳蚤
二手房
租房
招聘
二手车
教育
茶座
我要买房
买东西
装修家居
交友
职场
生活
网购
亲子
情感
龙城车友
找美食
谈婚论嫁
美女
兴趣
八卦
宠物
手机

Mybaits 源码解析 (九)----- 全网最详细,没有之一:一级缓存和二级缓存

[复制链接]
查看: 90|回复: 0

7860

主题

1万

帖子

3万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
31862
发表于 2019-11-9 15:36 | 显示全部楼层 |阅读模式
像Mybatis、Hibernate这样的ORM框架,封装了JDBC的大部分操纵,极大的简化了我们对数据库的操纵。
在现实项目中,我们发现在一个事务中查询一样的语句两次的时候,第二次没有举行数据库查询,间接返回了成果,现实这类情况我们便可以称为缓存。
Mybatis的缓存级别

一级缓存


  • MyBatis的一级查询缓存(也叫作当地缓存)是基于org.apache.ibatis.cache.impl.PerpetualCache 类的 HashMap当地缓存,其感化域是SqlSession,myBatis 默许一级查询缓存是开启状态,且不能封闭。
  • 在同一个SqlSession中两次实行类似的 sql查询语句,第一次实行终了后,会将查询成果写入到缓存中,第二次会从缓存中间接获得数据,而不再到数据库及第行查询,这样就淘汰了数据库的拜候,从而进步查询服从。
  • 基于PerpetualCache 的 HashMap当地缓存,其存储感化域为 Session,PerpetualCache 工具是在SqlSession中的Executor的localcache属性傍边寄存,当 Session flush 或 close 以后,该Session中的全数 Cache 就将清空。
二级缓存


  • 二级缓存与一级缓存其机制类似,默许也是采取 PerpetualCache,HashMap存储,差别在于其存储感化域为 Mapper(Namespace),每个Mapper中有一个Cache工具,寄存在Configration中,而且将其放进当前Mapper的全数MappedStatement傍边,而且可自界说存储源,如 Ehcache。


  • Mapper级别缓存,界说在Mapper文件的标签并必要开启此缓存
用下面这张图描摹一级缓存和二级缓存的关系。
Mybaits 源码解析 (九)----- 全网最详细,没有之一:一级缓存和二级缓存  游戏 1168971-20191031112956850-1740606388

CacheKey

在 MyBatis 中,引入缓存的目标是为进步查询服从,低落数据库压力。既然 MyBatis 引入了缓存,那末大家思考过缓存中的 key 和 value 的值别离是什么吗?大家大要很轻易能回答出 value 的内容,不就是 SQL 的查询成果吗。那 key 是什么呢?是字符串,照旧其他什么工具?假如是字符串的话,那末大家首先能想到的是用 SQL 语句作为 key。但这是差池的,比如:
  1. SELECT * FROM user where id > ?
复制代码
id > 1 和 id > 10 查出来的成果大如果差别的,所以我们不能简单的利用 SQL 语句作为 key。从这里可以看出来,运转时参数将会影响查询成果,是以我们的 key 应当涵盖运转时参数。除此之外呢,假如举行分页查询也会致使查询成果差别,是以 key 也应当涵盖分页参数。综上,我们不能利用简单的 SQL 语句作为 key。应当考虑利用一种复合工具,能涵盖可影响查询成果的因子。在 MyBatis 中,这类复合工具就是 CacheKey。下面来看一下它的界说。
  1. public class CacheKey implements Cloneable, Serializable {    private static final int DEFAULT_MULTIPLYER = 37;    private static final int DEFAULT_HASHCODE = 17;    // 乘子,默以为37    private final int multiplier;    // CacheKey 的 hashCode,综合了各类影响因子    private int hashcode;    // 校验和    private long checksum;    // 影响因子个数    private int count;    // 影响因子聚集    private List updateList;        public CacheKey() {        this.hashcode = DEFAULT_HASHCODE;        this.multiplier = DEFAULT_MULTIPLYER;        this.count = 0;        this.updateList = new ArrayList();    }        /** 每当实行更新操纵时,表现有新的影响因子参加盘算      *  当不停有新的影响因子参加盘算时,hashcode 和 checksum 将会变得愈发复杂和随机。这样可低落辩说率,使 CacheKey 可在缓存中更均匀的散布。     */    public void update(Object object) {            int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);        // 自增 count        count++;        // 盘算校验和        checksum += baseHashCode;        // 更新 baseHashCode        baseHashCode *= count;        // 盘算 hashCode        hashcode = multiplier * hashcode + baseHashCode;        // 保存影响因子        updateList.add(object);    }        /**     *  CacheKey 终极要作为键存入 HashMap,是以它必要覆盖 equals 和 hashCode 方式     */    public boolean equals(Object object) {        // 检测能否为同一个工具        if (this == object) {            return true;        }        // 检测 object 能否为 CacheKey        if (!(object instanceof CacheKey)) {            return false;        }        final CacheKey cacheKey = (CacheKey) object;        // 检测 hashCode 能否相当        if (hashcode != cacheKey.hashcode) {            return false;        }        // 检测校验和能否类似        if (checksum != cacheKey.checksum) {            return false;        }        // 检测 coutn 能否类似        if (count != cacheKey.count) {            return false;        }        // 假如上面的检测都经过了,下面别离对每个影响因子举行比力        for (int i = 0; i < updateList.size(); i++) {            Object thisObject = updateList.get(i);            Object thatObject = cacheKey.updateList.get(i);            if (!ArrayUtil.equals(thisObject, thatObject)) {                return false;            }        }        return true;    }    public int hashCode() {        // 返回 hashcode 变量        return hashcode;    }}
复制代码
当不停有新的影响因子参加盘算时,hashcode 和 checksum 将会变得愈发复杂和随机。这样可低落辩说率,使 CacheKey 可在缓存中更均匀的散布。CacheKey 终极要作为键存入 HashMap,是以它必要覆盖 equals 和 hashCode 方式。
一级缓存源码分解

一级缓存的测试

同一个session查询
  1. public static void main(String[] args) {    SqlSession session = sqlSessionFactory.openSession();    try {        Blog blog = (Blog)[b]session[/b].selectOne("queryById",1);        Blog blog2 = (Blog)[b]session[/b].selectOne("queryById",1);    } finally {        session.close();    }}
复制代码
结论:只要一个DB查询
两个session别离查询
  1. public static void main(String[] args) {    SqlSession session = sqlSessionFactory.openSession();    SqlSession session1 = sqlSessionFactory.openSession();    try {        Blog blog = (Blog)[b]session[/b].selectOne("queryById",17);        Blog blog2 = (Blog)[b]session1[/b].selectOne("queryById",17);    } finally {        session.close();    }}
复制代码
结论:举行了两次DB查询
同一个session,举行update以后再次查询
  1. public static void main(String[] args) {    SqlSession session = sqlSessionFactory.openSession();    try {        Blog blog = (Blog)[b]session.selectOne[/b]("queryById",17);        blog.setName("llll");      [b]  session.update[/b]("updateBlog",blog);                Blog blog2 = (Blog)[b]session.selectOne[/b]("queryById",17);    } finally {        session.close();    }}
复制代码
结论:举行了两次DB查询
总结:在一级缓存中,同一个SqlSession下,查询语句类似的SQL会被缓存,假照实行增删改操纵以后,该缓存就会被删除
建立缓存工具PerpetualCache

我们往返顾一下建立SqlSession的进程
  1. SqlSession session = sessionFactory.openSession();public SqlSession openSession() {    return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);}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);        //建立SQL实行器        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;}public Executor newExecutor(Transaction transaction, ExecutorType executorType) {    executorType = executorType == null ? this.defaultExecutorType : executorType;    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;    Object executor;    if (ExecutorType.BATCH == executorType) {        executor = new BatchExecutor(this, transaction);    } else if (ExecutorType.REUSE == executorType) {        executor = new ReuseExecutor(this, transaction);    } else {        //默许建立SimpleExecutor        executor = new SimpleExecutor(this, transaction);    }    if (this.cacheEnabled) {        //开启二级缓存就会用CachingExecutor装潢SimpleExecutor        executor = new CachingExecutor((Executor)executor);    }    Executor executor = (Executor)this.interceptorChain.pluginAll(executor);    return executor;}public SimpleExecutor(Configuration configuration, Transaction transaction) {    super(configuration, transaction);}protected BaseExecutor(Configuration configuration, Transaction transaction) {    this.transaction = transaction;    this.deferredLoads = new ConcurrentLinkedQueue();    [b]//建立一个缓存工具,PerpetualCache并不是线程平安的    //但SqlSession和Executor工具在凡是情况下只能有一个线程拜候,而且拜候完成以后立即烧毁。也就是session.close();    this.localCache = new PerpetualCache("LocalCache");    [/b]this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");    this.closed = false;    this.configuration = configuration;    this.wrapper = this;}
复制代码
我只是简单的贴了代码,大家可以看我之前的博客,我们可以看到DefaultSqlSession中有SimpleExecutor工具,SimpleExecutor工具中有一个PerpetualCache,一级缓存的数据就是存储在PerpetualCache工具中,SqlSession封闭的时候会清空PerpetualCache
一级缓存实现

再来看BaseExecutor中的query方式是怎样实现一级缓存的,executor默许实现为CachingExecutor
CachingExecutor
  1. public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {    BoundSql boundSql = ms.getBoundSql(parameter);    [b]//利用sql和实行的参数天生一个key,假如同一sql差别的实行参数的话,将会天生差别的key    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);    [/b]return query(ms, parameter, rowBounds, resultHandler, key, boundSql);}@Overridepublic  List 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 list = (List) 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;        }    }        [b]// 间接来到这里    // 实现为BaseExecutor.query()    return delegate.[/b][b] query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);[/b]}
复制代码
如上,在拜候一级缓存之前,MyBatis 首先会挪用 createCacheKey 方式建立 CacheKey。下面我们来看一下 createCacheKey 方式的逻辑:
  1. public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {    if (closed) {        throw new ExecutorException("Executor was closed.");    }    // 建立 CacheKey 工具    CacheKey cacheKey = new CacheKey();    // 将 MappedStatement 的 id 作为影响因子举行盘算    cacheKey.update(ms.getId());    // RowBounds 用于分页查询,下面将它的两个字段作为影响因子举行盘算    cacheKey.update(rowBounds.getOffset());    cacheKey.update(rowBounds.getLimit());    // 获得 sql 语句,并举行盘算    cacheKey.update(boundSql.getSql());    List parameterMappings = boundSql.getParameterMappings();    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();    for (ParameterMapping parameterMapping : parameterMappings) {        if (parameterMapping.getMode() != ParameterMode.OUT) {            // 运转时参数            Object value;                // 当前大段代码用于获得 SQL 中的占位符 #{xxx} 对应的运转时参数,            // 前文有类似分析,这里疏忽了            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) {        // 获得 Environment id 遍历,并让其参加盘算        cacheKey.update(configuration.getEnvironment().getId());    }    return cacheKey;}
复制代码
如上,在盘算 CacheKey 的进程中,有很多影响因子参加了盘算。比如 MappedStatement 的 id 字段,SQL 语句,分页参数,运转时变量,Environment 的 id 字段等。经过让这些影响因子参加盘算,可以很好的区分差别查询请求。所以,我们可以简单的把 CacheKey 看做是一个查询请求的 id。有了 CacheKey,我们便可以利用它读写缓存了。
SimpleExecutor(BaseExecutor)
  1. @SuppressWarnings("unchecked")@Overridepublic  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 (closed) {        throw new ExecutorException("Executor was closed.");    }    if (queryStack == 0 && ms.isFlushCacheRequired()) {        clearLocalCache();    }    List list;    try {        queryStack++;        [b]// 看这里,先从localCache中获得对应CacheKey的成果值        list = resultHandler == null ? (List) localCache.getObject(key) : null;        [/b]if (list != null) {            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);        } else {            [b]// 假如缓存中没有值,则从DB中查询            list =[/b][b] queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);[/b]        }    } finally {        queryStack--;    }    if (queryStack == 0) {        for (DeferredLoad deferredLoad : deferredLoads) {            deferredLoad.load();        }        deferredLoads.clear();        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {            clearLocalCache();        }    }    return list;}
复制代码
BaseExecutor.queryFromDatabase()
我们先来看下这类缓存中没有值的情况,看一下查询后的成果是怎样被放置到缓存中的
  1. private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {    List list;    localCache.putObject(key, EXECUTION_PLACEHOLDER);    try {        [b]// 1.实行查询,获得list        list =[/b][b] doQuery(ms, parameter, rowBounds, resultHandler, boundSql);[/b]    } finally {        localCache.removeObject(key);    }    [b]// 2.将查询后的成果放置到localCache中,key就是我们适才封装的CacheKey,value就是从DB中查询到的list    localCache.putObject(key, list);    [/b]if (ms.getStatementType() == StatementType.CALLABLE) {        localOutputParameterCache.putObject(key, parameter);    }    return list;}
复制代码
我们来看看 localCache.putObject(key, list);
PerpetualCache

PerpetualCache 是一级缓存利用的缓存类,内部利用了 HashMap 实现缓存功用。它的源码以下:
  1. public class PerpetualCache implements Cache {    private final String id;    [b]private Map cache = new HashMap();    [/b]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) {        // 存储键值对到 HashMap        cache.put(key, value);    }    @Override    public Object getObject(Object key) {        // 查找缓存项        return cache.get(key);    }    @Override    public Object removeObject(Object key) {        // 移除缓存项        return cache.remove(key);    }    @Override    public void clear() {        cache.clear();    }        // 省略部分代码}
复制代码
总结:可以看到localCache本质上就是一个Map,key为我们的CacheKey,value为我们的成果值,能否是很简单,只是封装了一个Map而已。
扫除缓存

SqlSession.update()
当我们举行更新操纵时,会实行以下代码
  1. @Overridepublic 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.");    }    [b]//每次实行update/insert/delete语句时城市扫除一级缓存。    clearLocalCache();    // 然后再举行更新操纵    return[/b][b] doUpdate(ms, parameter);[/b]} @Overridepublic void clearLocalCache() {    if (!closed) {        [b]// 间接将Map清空[/b][b]        localCache.clear();[/b]        localOutputParameterCache.clear();    }}
复制代码
session.close();
  1. //DefaultSqlSessionpublic void close() {    try {        [b]this.executor.close(this.isCommitOrRollbackRequired(false));        [/b]this.closeCursors();        this.dirty = false;    } finally {        ErrorContext.instance().reset();    }}//BaseExecutorpublic void close(boolean forceRollback) {    try {        try {            [b]this[/b][b].rollback(forceRollback);[/b]        } finally {            if (this.transaction != null) {                this.transaction.close();            }        }    } catch (SQLException var11) {        log.warn("Unexpected exception on closing transaction.  Cause: " + var11);    } finally {        this.transaction = null;        this.deferredLoads = null;        this.localCache = null;        this.localOutputParameterCache = null;        this.closed = true;    }}public void rollback(boolean required) throws SQLException {    if (!this.closed) {        try {            [b]this.clearLocalCache();            [/b]this.flushStatements(true);        } finally {            if (required) {                this.transaction.rollback();            }        }    }}public void clearLocalCache() {    if (!this.closed) {        [b]// 间接将Map清空        this.localCache.clear();        [/b]this.localOutputParameterCache.clear();    }}
复制代码
当封闭SqlSession时,也会清楚SqlSession中的一级缓存
总结


  • 一级缓存只在同一个SqlSession中同享数据
  • 在同一个SqlSession工具实行类似的sql并参数也要类似,缓存才有用。
  • 假如在SqlSession中实行update/insert/detete语句大要session.close();的话,SqlSession中的executor工具会将一级缓存清空。
二级缓存源码分解

二级缓存构建在一级缓存之上,在收到查询请求时,MyBatis 首先会查询二级缓存。若二级缓存未命中,再去查询一级缓存。与一级缓存差别,二级缓存和具体的命名空间绑定,一个Mapper中有一个Cache,类似Mapper中的MappedStatement公用一个Cache,一级缓存则是和 SqlSession 绑定。一级缓存不存在并发题目二级缓存可在多个命名空间间同享,这类情况下,会存在并发题目,比方多个差别的SqlSession 会同时实行类似的SQL语句,参数也类似,那末CacheKey是类似的,就会形成多个线程并发拜候类似CacheKey的值,下面首先来看一下拜候二级缓存的逻辑。
二级缓存的测试

二级缓存必要在Mapper.xml中设备标签
  1.              select * from blog where id = #{id}                update Blog set name = #{name},url = #{url} where id=#{id}     
复制代码
差别的session举行类似的查询
  1. public static void main(String[] args) {    SqlSession session = sqlSessionFactory.openSession();    SqlSession session1 = sqlSessionFactory.openSession();    try {        Blog blog = (Blog)session.selectOne("queryById",17);        Blog blog2 = (Blog)session1.selectOne("queryById",17);    } finally {        session.close();    }}
复制代码
结论:实行两次DB查询
第一个session查询完成以后,手动提交,在实行第二个session查询
  1. public static void main(String[] args) {    SqlSession session = sqlSessionFactory.openSession();    SqlSession session1 = sqlSessionFactory.openSession();    try {       [b] Blog blog [/b][b]= (Blog)session.selectOne("queryById",17[/b][b]);        session.commit();[/b]      [b]   Blog blog2 [/b][b]= (Blog)session1.selectOne("queryById",17[/b][b]);[/b]    } finally {        session.close();    }}
复制代码
结论:实行一次DB查询
第一个session查询完成以后,手动封闭,在实行第二个session查询
  1. public static void main(String[] args) {    SqlSession session = sqlSessionFactory.openSession();    SqlSession session1 = sqlSessionFactory.openSession();    try {      [b]  Blog blog [/b][b]= (Blog)session.selectOne("queryById",17);        session.close();         Blog blog2 = (Blog)session1.selectOne("queryById",17[/b][b]);[/b]    } finally {        session.close();    }}
复制代码
结论:实行一次DB查询
总结:二级缓存的生效必须在session提交或封闭以后才会面效
标签的分解

依照之前的对Mybatis的分析,对blog.xml的分解工作重要交给XMLConfigBuilder.parse()方式来实现的
Mybaits 源码解析 (九)----- 全网最详细,没有之一:一级缓存和二级缓存  游戏 ContractedBlock
Mybaits 源码解析 (九)----- 全网最详细,没有之一:一级缓存和二级缓存  游戏 ExpandedBlockStart
  1. 1 // XMLConfigBuilder.parse() 2 public Configuration parse() { 3     if (parsed) { 4         throw new BuilderException("Each XMLConfigBuilder can only be used once."); 5     } 6     parsed = true; 7     parseConfiguration(parser.evalNode("/configuration"));// 在这里 8     return configuration; 9 }10  11 // parseConfiguration()12 // 既然是在blog.xml中增加的,那末我们就间接看关于mappers标签的分解13 private void parseConfiguration(XNode root) {14     try {15         Properties settings = settingsAsPropertiess(root.evalNode("settings"));16         propertiesElement(root.evalNode("properties"));17         loadCustomVfs(settings);18         typeAliasesElement(root.evalNode("typeAliases"));19         pluginElement(root.evalNode("plugins"));20         objectFactoryElement(root.evalNode("objectFactory"));21         objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));22         reflectionFactoryElement(root.evalNode("reflectionFactory"));23         settingsElement(settings);24         // read it after objectFactory and objectWrapperFactory issue #63125         environmentsElement(root.evalNode("environments"));26         databaseIdProviderElement(root.evalNode("databaseIdProvider"));27         typeHandlerElement(root.evalNode("typeHandlers"));28         // 就是这里29         mapperElement(root.evalNode("mappers"));30     } catch (Exception e) {31         throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);32     }33 }34 35 36 // mapperElement()37 private void mapperElement(XNode parent) throws Exception {38     if (parent != null) {39         for (XNode child : parent.getChildren()) {40             if ("package".equals(child.getName())) {41                 String mapperPackage = child.getStringAttribute("name");42                 configuration.addMappers(mapperPackage);43             } else {44                 String resource = child.getStringAttribute("resource");45                 String url = child.getStringAttribute("url");46                 String mapperClass = child.getStringAttribute("class");47                 // 依照我们本例的设备,则间接走该if判定48                 if (resource != null && url == null && mapperClass == null) {49                     ErrorContext.instance().resource(resource);50                     InputStream inputStream = Resources.getResourceAsStream(resource);51                     XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());52                     // 天生XMLMapperBuilder,并实行其parse方式53                     mapperParser.parse();54                 } else if (resource == null && url != null && mapperClass == null) {55                     ErrorContext.instance().resource(url);56                     InputStream inputStream = Resources.getUrlAsStream(url);57                     XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());58                     mapperParser.parse();59                 } else if (resource == null && url == null && mapperClass != null) {60                     Class mapperInterface = Resources.classForName(mapperClass);61                     configuration.addMapper(mapperInterface);62                 } else {63                     throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");64                 }65             }66         }67     }68 }
复制代码
View Code我们来看看分解Mapper.xml
  1. // XMLMapperBuilder.parse()public void parse() {    if (!configuration.isResourceLoaded(resource)) {        // 分解mapper属性        configurationElement(parser.evalNode("/mapper"));        configuration.addLoadedResource(resource);        bindMapperForNamespace();    }     parsePendingResultMaps();    parsePendingChacheRefs();    parsePendingStatements();} // configurationElement()private void configurationElement(XNode context) {    try {        String namespace = context.getStringAttribute("namespace");        if (namespace == null || namespace.equals("")) {            throw new BuilderException("Mapper's namespace cannot be empty");        }        builderAssistant.setCurrentNamespace(namespace);        cacheRefElement(context.evalNode("cache-ref"));        [b]// 终极在这里看到了关于cache属性的处置惩罚        cacheElement(context.evalNode("cache"[/b][b]));[/b]        parameterMapElement(context.evalNodes("/mapper/parameterMap"));        resultMapElements(context.evalNodes("/mapper/resultMap"));        sqlElement(context.evalNodes("/mapper/sql"));        [b]// 这里会将天生的Cache包装到对应的MappedStatement        buildStatementFromContext(context.evalNodes("select|insert|update|delete"[/b][b]));[/b]    } catch (Exception e) {        throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);    }} // cacheElement()private void cacheElement(XNode context) throws Exception {    if (context != null) {        <strong>//分解标签的type属性,这里我们可以自界说cache的实现类,比如redisCache,假如没有自界说,这里利用和一级缓存类似的PERPETUAL        String type = context.getStringAttribute("type", "PERPETUAL");        Class                    SELECT * FROM user     
复制代码
仍然很简单, RedisCache 在保存缓存数据和获得缓存数据时,利用了Java的序列化和反序列化,是以必要保证被缓存的工具必须实现Serializable接口。
也可以自己实现cache
实现自己的cache
  1. package com.chenhao.mybatis.cache;import org.apache.ibatis.cache.Cache;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.ValueOperations;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;/** * @author chenhao * @date 2019/10/31. */public class RedisCache implements Cache {    private final String id;    private static ValueOperations valueOs;    private static RedisTemplate template;    public static void setValueOs(ValueOperations valueOs) {        RedisCache.valueOs = valueOs;    }    public static void setTemplate(RedisTemplate template) {        RedisCache.template = template;    }    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();    public RedisCache(String id) {        if (id == null) {            throw new IllegalArgumentException("Cache instances require an ID");        }        this.id = id;    }    @Override    public String getId() {        return this.id;    }    @Override    public void putObject(Object key, Object value) {        valueOs.set(key.toString(), value, 10, TimeUnit.MINUTES);    }    @Override    public Object getObject(Object key) {        return valueOs.get(key.toString());    }    @Override    public Object removeObject(Object key) {        valueOs.set(key.toString(), "", 0, TimeUnit.MINUTES);        return key;    }    @Override    public void clear() {        template.getConnectionFactory().getConnection().flushDb();    }    @Override    public int getSize() {        return template.getConnectionFactory().getConnection().dbSize().intValue();    }    @Override    public ReadWriteLock getReadWriteLock() {        return this.readWriteLock;    }}
复制代码
Mapper中设备自己实现的Cache
复制代码


免责声明:假如加害了您的权益,请联系站长,我们会实时删除侵权内容,感谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Copyright © 2006-2014 妈妈网-中国妈妈第一,是怀孕、育儿、健康等知识交流传播首选平台 版权所有 法律顾问:高律师 客服电话:0791-88289918
技术支持:迪恩网络科技公司  Powered by Discuz! X3.2
快速回复 返回顶部 返回列表