手写Mybatis:第19章-二级缓存

文章目录

  • 一、目标:二级缓存
  • 二、设计:二级缓存
  • 三、实现:二级缓存
    • 3.1 工程结构
    • 3.2 二级缓存类图
    • 3.3 二级缓存队列
      • 3.3.1 FIFI缓存策略
      • 3.3.2 事务缓存
      • 3.3.3 事务管理
      • 3.3.4 修改一级缓存
    • 3.4 缓存执行器
      • 3.4.1 执行器接口
      • 3.4.2 执行器抽象基类
      • 3.4.3 缓存执行器
    • 3.5 修改配置
      • 3.5.1 映射器语句类
      • 3.5.2 配置项
      • 3.5.3 默认SqlSession实现类
    • 3.6 映射构建器助手添加缓存
      • 3.6.1 缓存构建器
      • 3.6.2 映射构建器助手
    • 3.7 XML配置构建器
      • 3.7.1 构建器基类
      • 3.7.2 注解配置构建器
      • 3.7.3 XML语言构建器
      • 3.7.4 XML映射构建器
      • 3.7.5 XML配置构建器-全局缓存解析
      • 3.7.6 XML配置构建器
  • 四、测试:二级缓存
    • 4.1 修改配置
      • 4.1.1 修改xml配置
      • 4.1.2 缓存策略配置
    • 4.2 单元测试
  • 五、总结:二级缓存

一、目标:二级缓存

💡 一级缓存是基于会话,那么怎么在会话结束之后依旧可以使用缓存呢?

  • 关于缓存的实现,希望于当会话结束后,再发起的会话还是相同的查询操作,最好也是可以把数据从缓存中获取出来。
  • 二级缓存:以一个 Mapper 为生命周期,在这个 Mapper 内的同一个操作,无论发起几次会话都可以使用缓存来处理数据。
    • 之所以称为之 二级缓存,是因为它在一级缓存会话层上,添加的额外缓存操作,当会话发生 close、commit 操作时则把数据刷到二级缓存中进行保存,直至执行器发生 update 操作时清空缓存。

二、设计:二级缓存

💡 设计二级缓存?

  • 二级缓存的重点在于无论多少个 SqlSession 会话操作同一个 SQL,不管 SqlSession 是否相同,只要 Mappernamespace 相同就能共享数据。所以二级缓存也被称为 namespace 级别的缓存,相当于一级缓存作用域范围更广了。
  • 设计二级缓存,应该为 Mapper XML 解析后的 MappedStatement 映射器语句提供缓存服务。
  • 当有会话的生命周期结束后,应该将会话的数据刷新到二级缓存中,便于后续在同 namespace 下处理相同 SQL 的操作时使用。

在这里插入图片描述

  • 首先要在 XML 的解析中添加关于全局是否使用缓存的操作,此外因为缓存的作用域范围是在 Mappernamespace 级别上,所以这里要为解析 MappedStatement 映射器语句提供缓存策略。
    • 注意:缓存策略一共有四种实现,包括:LRU、FIFO、SOFT、WEAK
  • 当配置了开启二级缓存服务,那么在开启会话创建执行器时,会把执行器使用缓存执行器做一层装饰器的设计使用。
    • 因为需要通过这个方式将事务缓存起来,同时包装结束会话的指令 close、commit 处理一级缓存数据刷新到二级缓存中。
    • 这样在下次执行相同下 namespace 以及同样的 SQL 时就可以直接从缓存中获取数据了。

三、实现:二级缓存

3.1 工程结构

mybatis-step-18
|-src|-main| |-java|   |-com.lino.mybatis|     |-annotations|     | |-Delete.java|     | |-Insert.java|     | |-Select.java|     | |-Update.java|     |-binding|     | |-MapperMethod.java|     | |-MapperProxy.java|     | |-MapperProxyFactory.java|     | |-MapperRegistry.java|     |-builder|     | |-annotations|     | | |-MapperAnnotationBuilder.java|     | |-xml|     | | |-XMLConfigBuilder.java|     | | |-XMLMapperBuilder.java|     | | |-XMLStatementBuilder.java|     | |-BaseBuilder.java|     | |-MapperBuilderAssistant.java|     | |-ParameterExpression.java|     | |-ResultMapResolver.java|     | |-SqlSourceBuilder.java|     | |-StaticSqlSource.java|     |-cache|     | |-decorators|     | | |-FifoCache.java|     | | |-TransactionalCache.java|     | |-impl|     | | |-PrepetualCache.java|     | |-Cache.java|     | |-CacheKey.java|     | |-NullCacheKey.java|     | |-TransactionalCacheManager.java|     |-datasource|     | |-druid|     | | |-DruidDataSourceFacroty.java|     | |-pooled|     | | |-PooledConnection.java|     | | |-PooledDataSource.java|     | | |-PooledDataSourceFacroty.java|     | | |-PoolState.java|     | |-unpooled|     | | |-UnpooledDataSource.java|     | | |-UnpooledDataSourceFacroty.java|     | |-DataSourceFactory.java|     |-executor|     | |-keygen|     | | |-Jdbc3KeyGenerator.java|     | | |-KeyGenerator.java|     | | |-NoKeyGenerator.java|     | | |-SelectKeyGenerator.java|     | |-parameter|     | | |-ParameterHandler.java|     | |-result|     | | |-DefaultResultContext.java|     | | |-DefaultResultHandler.java|     | |-resultset|     | | |-DefaultResultSetHandler.java|     | | |-ResultSetHandler.java|     | | |-ResultSetWrapper.java|     | |-statement|     | | |-BaseStatementHandler.java|     | | |-PreparedStatementHandler.java|     | | |-SimpleStatementHandler.java|     | | |-StatementHandler.java|     | |-BaseExecutor.java|     | |-CachingExecutor.java|     | |-ExecutionPlaceholder.java|     | |-Executor.java|     | |-SimpleExecutor.java|     |-io|     | |-Resources.java|     |-mapping|     | |-BoundSql.java|     | |-CacheBuilder.java|     | |-Environment.java|     | |-MappedStatement.java|     | |-ParameterMapping.java|     | |-ResultFlag.java|     | |-ResultMap.java|     | |-ResultMapping.java|     | |-SqlCommandType.java|     | |-SqlSource.java|     |-parsing|     | |-GenericTokenParser.java|     | |-TokenHandler.java|     |-plugin|     | |-Interceptor.java|     | |-InterceptorChain.java|     | |-Intercepts.java|     | |-Invocation.java|     | |-Plugin.java|     | |-Signature.java|     |-reflection|     | |-factory|     | | |-DefaultObjectFactory.java|     | | |-ObjectFactory.java|     | |-invoker|     | | |-GetFieldInvoker.java|     | | |-Invoker.java|     | | |-MethodInvoker.java|     | | |-SetFieldInvoker.java|     | |-property|     | | |-PropertyNamer.java|     | | |-PropertyTokenizer.java|     | |-wrapper|     | | |-BaseWrapper.java|     | | |-BeanWrapper.java|     | | |-CollectionWrapper.java|     | | |-DefaultObjectWrapperFactory.java|     | | |-MapWrapper.java|     | | |-ObjectWrapper.java|     | | |-ObjectWrapperFactory.java|     | |-MetaClass.java|     | |-MetaObject.java|     | |-Reflector.java|     | |-SystemMetaObject.java|     |-scripting|     | |-defaults|     | | |-DefaultParameterHandler.java|     | | |-RawSqlSource.java|     | |-xmltags|     | | |-DynamicContext.java|     | | |-DynamicSqlSource.java|     | | |-ExpressionEvaluator.java|     | | |-IfSqlNode.java|     | | |-MixedSqlNode.java|     | | |-OgnlCache.java|     | | |-OgnlClassResolver.java|     | | |-SqlNode.java|     | | |-StaticTextSqlNode.java|     | | |-TextSqlNode.java|     | | |-TrimSqlNode.java|     | | |-XMLLanguageDriver.java|     | | |-XMLScriptBuilder.java|     | |-LanguageDriver.java|     | |-LanguageDriverRegistry.java|     |-session|     | |-defaults|     | | |-DefaultSqlSession.java|     | | |-DefaultSqlSessionFactory.java|     | |-Configuration.java|     | |-LocalCacheScope.java|     | |-ResultContext.java|     | |-ResultHandler.java|     | |-RowBounds.java|     | |-SqlSession.java|     | |-SqlSessionFactory.java|     | |-SqlSessionFactoryBuilder.java|     | |-TransactionIsolationLevel.java|     |-transaction|     | |-jdbc|     | | |-JdbcTransaction.java|     | | |-JdbcTransactionFactory.java|     | |-Transaction.java|     | |-TransactionFactory.java|     |-type|     | |-BaseTypeHandler.java|     | |-DateTypeHandler.java|     | |-IntegerTypeHandler.java|     | |-JdbcType.java|     | |-LongTypeHandler.java|     | |-SimpleTypeRegistry.java|     | |-StringTypeHandler.java|     | |-TypeAliasRegistry.java|     | |-TypeHandler.java|     | |-TypeHandlerRegistry.java|-test|-java| |-com.lino.mybatis.test| |-dao| | |-IActivityDao.java| |-plugin| | |-TestPlugin.java| |-po| | |-Activity.java| |-ApiTest.java|-resources|-mapper| |-Activity_Mapper.xml|-mybatis-config-datasource.xml

3.2 二级缓存类图

在这里插入图片描述

  • 整个二级缓存的核心功能逻辑实现,主要以体现在实现 Cache 缓存接口,提供 Mapper XML 解析作用域 namespace 范围的缓存队列的使用上。
  • 通过提供装饰执行器实现模式的 CacheingExecutor 二级缓存执行器,包括会话事务缓存操作,在当会话以 close、commit 方式结束时,将缓存刷新到二级缓存队列中,便于下次相同作用域范围下的同一个查询,可以直接从二级缓存中获取数据。

3.3 二级缓存队列

  • Mybatis 对二级缓存的设计非常灵活,你可以通过配置对缓存的策略做一系列的调整,包括缓存策略:LRU、FIFO、SOFT、WEAK
    • LRU:最近很少使用,主动移除最长时间不被使用的缓存对象。LRU 也是默认的缓存策略。
    • FIFO:先进先出,按对象进入缓存的顺序移除过期对象。
    • SOFT:软引用,基于垃圾回收器状态和软引用规则移除对象。
    • WEAK:弱引用,更主动的基于垃圾回收器状态和弱引用规则移除对象。
  • 同时也提供了相应的数据刷新策略、对象存储限制等。
  • 除此之外,Mybatis 也支持用户自己实现以及跟第三方内存缓存库做集成使用。

3.3.1 FIFI缓存策略

FifoCache.java

package com.lino.mybatis.cache.decorators;import com.lino.mybatis.cache.Cache;
import java.util.Deque;
import java.util.LinkedList;/*** @description: FIFI(first in, first out) cache decorator*/
public class FifoCache implements Cache {private final Cache delegate;private Deque<Object> keyList;private int size;public FifoCache(Cache delegate) {this.delegate = delegate;this.keyList = new LinkedList<>();this.size = 1024;}@Overridepublic String getId() {return delegate.getId();}@Overridepublic void putObject(Object key, Object value) {cycleKeyList(key);delegate.putObject(key, value);}@Overridepublic Object getObject(Object key) {return delegate.getObject(key);}@Overridepublic Object removeObject(Object key) {return delegate.removeObject(key);}@Overridepublic void clear() {delegate.clear();keyList.clear();}@Overridepublic int getSize() {return delegate.getSize();}public void setSize(int size) {this.size = size;}private void cycleKeyList(Object key) {keyList.addLast(key);if (keyList.size() > size) {Object oldestKey = keyList.removeFirst();delegate.removeObject(oldestKey);}}
}
  • FIFO 先进先出队列,基于 Deque 维护了一个链表,其他的操作都包装给 Cache 去完成,属于典型的装饰器模式。
  • FifoCache 所提供的方法实现比较简单,主要包括:存放、获取、移除、清空队列。
  • 另外 cycleKeyList 方法的作用是在增加记录时判断记录是否超过 size 值,以此移除链表的第一个元素,从而达到 FIFO 缓存效果。

3.3.2 事务缓存

  • TransactionalCache 所保存的是会话期间内的缓存数据,当会话结束后则把缓存刷新到二级缓存中。如果是回滚操作则清空缓存。

TransactionalCache.java

package com.lino.mybatis.cache.decorators;import com.lino.mybatis.cache.Cache;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;/*** @description: 事务缓存*/
public class TransactionalCache implements Cache {private Cache delegate;/*** commit时要不要清缓存*/private boolean clearOnCommit;/*** commit 时要添加的元素*/private Map<Object, Object> entriesToAddOnCommit;private Set<Object> entriesMissedInCache;public TransactionalCache(Cache delegate) {// delegate = FifoCachethis.delegate = delegate;// 默认 commit 时不清缓存this.clearOnCommit = false;this.entriesToAddOnCommit = new HashMap<>();this.entriesMissedInCache = new HashSet<>();}@Overridepublic String getId() {return delegate.getId();}@Overridepublic void putObject(Object key, Object value) {entriesToAddOnCommit.put(key, value);}@Overridepublic Object getObject(Object key) {// key: CacheKey 拼装后的哈希码Object object = delegate.getObject(key);if (object == null) {entriesMissedInCache.add(key);}return clearOnCommit ? null : object;}@Overridepublic Object removeObject(Object key) {return null;}@Overridepublic void clear() {clearOnCommit = true;entriesToAddOnCommit.clear();}@Overridepublic int getSize() {return delegate.getSize();}public void commit() {if (clearOnCommit) {delegate.clear();}flushPendingEntries();reset();}public void rollback() {unlockMissedEntries();reset();}public void reset() {clearOnCommit = false;entriesToAddOnCommit.clear();entriesMissedInCache.clear();}/*** 刷新数据到 MappedStatement#Cache 中,也就是把数据填充到 Mapper XML 级别下。* flushPendingEntries 方法把事务缓存下的数据,填充到 FifoCache 中。*/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) {delegate.putObject(entry, null);}}
}
  • TransactionalCache 事务缓存提供了对一级缓存的数据存放和使用的操作。
    • 当一级缓存作用域范围的会话因为 commit、close 结束,则会调用到 flushPengdingEntries 方法。
    • 通过循环处理调用 delegate.putObject(entry.getKey(), entry.getValue()),把数据刷新到二级缓存队列中。
    • 另外 rollback 回滚方法则是一种清空缓存操作。

3.3.3 事务管理

TransactionalCacheManager

package com.lino.mybatis.cache;import com.lino.mybatis.cache.decorators.TransactionalCache;
import java.util.HashMap;
import java.util.Map;/*** @description: 事务缓存管理器*/
public class TransactionalCacheManager {private Map<Cache, TransactionalCache> transactionCaches = new HashMap<>(16);public void clear(Cache cache) {getTransactionalCache(cache).clear();}/*** 得到某个TransactionalCache的值*/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 : transactionCaches.values()) {txCache.commit();}}/*** 回滚时全部回滚*/public void rollback() {for (TransactionalCache txCache : transactionCaches.values()) {txCache.rollback();}}private TransactionalCache getTransactionalCache(Cache cache) {TransactionalCache txCache = transactionCaches.get(cache);if (txCache == null) {txCache = new TransactionalCache(cache);transactionCaches.put(cache, txCache);}return txCache;}
}
  • 事务缓存管理器是对事务缓存的包装操作。
  • 用于在缓存执行器创建期间实例化,包装执行期内的所有事务缓存操作,做批量的提交和回滚时缓存数据刷新的处理。

3.3.4 修改一级缓存

PerpetualCache.java

package com.lino.mybatis.cache.impl;import com.lino.mybatis.cache.Cache;import java.util.HashMap;
import java.util.Map;/*** @description: 一级缓存,在 Session 生命周期内一直保持,每创建新的 OpenSession 都会创建一个缓存器 PerpetualCache*/
public class PerpetualCache implements Cache {private String id;/*** 使用HashMap存放一级缓存数据,session 生命周期较短,正常情况下数据不会一直在缓存存放*/private Map<Object, Object> cache = new HashMap<>(16);public PerpetualCache(String id) {this.id = id;}@Overridepublic String getId() {return id;}@Overridepublic void putObject(Object key, Object value) {cache.put(key, value);}@Overridepublic Object getObject(Object key) {return cache.get(key);}@Overridepublic Object removeObject(Object key) {return cache.remove(key);}@Overridepublic void clear() {cache.clear();}@Overridepublic int getSize() {return cache.size();}
}
  • 移除一级缓存日志打印

3.4 缓存执行器

3.4.1 执行器接口

Executor.java

package com.lino.mybatis.executor;import com.lino.mybatis.cache.CacheKey;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import java.sql.SQLException;
import java.util.List;/*** @description: 执行器*/
public interface Executor {/*** 结果处理器*/ResultHandler NO_RESULT_HANDLER = null;/*** 更新** @param ms        映射器语句* @param parameter 参数* @return 返回的是受影响的行数* @throws SQLException SQL异常*/int update(MappedStatement ms, Object parameter) throws SQLException;/*** 查询,含缓存** @param ms            映射器语句* @param parameter     参数* @param rowBounds     分页记录限制* @param resultHandler 结果处理器* @param key           缓存key* @param boundSql      SQL对象* @param <E>           返回的类型* @return List<E>* @throws SQLException SQL异常*/<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException;/*** 查询** @param ms            映射器语句* @param parameter     参数* @param rowBounds     分页记录限制* @param resultHandler 结果处理器* @param <E>           返回的类型* @return List<E>* @throws SQLException SQL异常*/<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;/*** 获取事务** @return 事务对象*/Transaction getTransaction();/*** 提交** @param required 是否请求执行* @throws SQLException SQL异常*/void commit(boolean required) throws SQLException;/*** 回滚** @param required 是否请求执行* @throws SQLException SQL异常*/void rollback(boolean required) throws SQLException;/*** 关闭** @param forceRollback 是否强制回滚*/void close(boolean forceRollback);/*** 清理session缓存*/void clearLocalCache();/*** 创建缓存key** @param ms              映射器语句* @param parameterObject 参数对象* @param rowBounds       分页记录限制* @param boundSql        SQL对象* @return 缓存key*/CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);/*** 设置执行器** @param executor 执行器*/void setExecutorWrapper(Executor executor);
}
  • 添加 setExecutorWrapper 设置执行器方法。

3.4.2 执行器抽象基类

BaseExecutor.java

package com.lino.mybatis.executor;import com.lino.mybatis.cache.CacheKey;
import com.lino.mybatis.cache.impl.PerpetualCache;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ParameterMapping;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.LocalCacheScope;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import com.lino.mybatis.type.TypeHandlerRegistry;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;/*** @description: 执行器抽象基类*/
public abstract class BaseExecutor implements Executor {...@Overridepublic void setExecutorWrapper(Executor executor) {this.wrapper = wrapper;}@Overridepublic void close(boolean forceRollback) {try {try {rollback(forceRollback);} finally {transaction.close();}} catch (SQLException e) {logger.warn("Unexpected exception on closing transaction. Cause: " + e);} finally {transaction = null;localCache = null;closed = true;}}...
}

3.4.3 缓存执行器

  • 缓存执行器是一个装饰器模式,将 SimpleExecutor 做一层包装,提供缓存的能力。
  • 因为这样的包装后就可以将 SimpleExecutor 中的一级缓存以及相应的能力进行使用,在二级缓存 CachingExecutor 执行器中完成缓存在会话周期内的流转操作。

CachingExecutor.java

package com.lino.mybatis.executor;import com.alibaba.fastjson.JSON;
import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.cache.CacheKey;
import com.lino.mybatis.cache.TransactionalCacheManager;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.util.List;/*** @description: 二级缓存执行器*/
public class CachingExecutor implements Executor {private Logger logger = LoggerFactory.getLogger(BaseExecutor.class);private Executor delegate;private TransactionalCacheManager tcm = new TransactionalCacheManager();public CachingExecutor(Executor delegate) {this.delegate = delegate;delegate.setExecutorWrapper(this);}@Overridepublic int update(MappedStatement ms, Object parameter) throws SQLException {return delegate.update(ms, parameter);}@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {Cache cache = ms.getCache();if (cache != null) {flushCacheIfRequired(ms);if (ms.isUseCache() && resultHandler == null) {@SuppressWarnings("unchecked")List<E> list = (List<E>) tcm.getObject(cache, key);if (list == null) {list = delegate.query(ms, parameter, rowBounds, resultHandler, key, boundSql);// cache:缓存队列实现类,FIFO// key:哈希值 [mappedStatementId + offset + limit + SQL + queryParams + environment]// list:查询的数据tcm.putObject(cache, key, list);}// 打印调试日志,记录二级缓存获取数据if (logger.isDebugEnabled() && cache.getSize() > 0) {logger.debug("二级缓存: {}", JSON.toJSONString(list));}return list;}}return delegate.query(ms, parameter, rowBounds, resultHandler, key, boundSql);}@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {// 1.获取绑定SQLBoundSql boundSql = ms.getBoundSql(parameter);// 2.创建缓存keyCacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);return query(ms, parameter, rowBounds, resultHandler, key, boundSql);}@Overridepublic Transaction getTransaction() {return delegate.getTransaction();}@Overridepublic void commit(boolean required) throws SQLException {delegate.commit(required);tcm.commit();}@Overridepublic void rollback(boolean required) throws SQLException {try {delegate.rollback(required);} finally {if (required) {tcm.rollback();}}}@Overridepublic void close(boolean forceRollback) {try {if (forceRollback) {tcm.rollback();} else {tcm.commit();}} finally {delegate.close(forceRollback);}}@Overridepublic void clearLocalCache() {delegate.clearLocalCache();}@Overridepublic CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);}@Overridepublic void setExecutorWrapper(Executor executor) {throw new UnsupportedOperationException("This method should not be called");}private void flushCacheIfRequired(MappedStatement ms) {Cache cache = ms.getCache();if (cache != null && ms.isFlushCacheRequired()) {tcm.clear(cache);}}
}
  • CachingExecutor 实现类中主要注意的点是会话中数据查询时的缓存使用,在 query 方法中执行的 delegate.<E>query 操作。
  • 其实这个 delegate 就是 SimpleExecutor 实例化的对象,当缓存数据随着会话周期处理完后,则存放到 MappedStatement 所提供的 Cache 缓存队列中,也就是 FifoCache 先进先出缓存实现类。
  • 另外关于缓存的流转会调用 TransactionalCacheManager 事务缓存管理器进行操作,从会话作用域范围,通过会话的结束,刷新提交到二级缓存或者清空处理。

3.5 修改配置

3.5.1 映射器语句类

MappedStatement

package com.lino.mybatis.mapping;import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import java.util.Collections;
import java.util.List;/*** @description: 映射器语句类*/
public class MappedStatement {private String resource;private Configuration configuration;private String id;private SqlCommandType sqlCommandType;private SqlSource sqlSource;Class<?> resultType;private LanguageDriver lang;private List<ResultMap> resultMaps;private boolean flushCacheRequired;private KeyGenerator keyGenerator;private String[] keyProperties;private String[] keyColumns;private Cache cache;private boolean useCache;public MappedStatement() {}/*** 获取SQL对象** @param parameterObject 参数* @return SQL对象*/public BoundSql getBoundSql(Object parameterObject) {// 调用 SqlSource#getBoundSqlreturn sqlSource.getBoundSql(parameterObject);}public static class Builder {private MappedStatement mappedStatement = new MappedStatement();public Builder(Configuration configuration, String id, SqlCommandType sqlCommandType, SqlSource sqlSource, Class<?> resultType) {mappedStatement.configuration = configuration;mappedStatement.id = id;mappedStatement.sqlCommandType = sqlCommandType;mappedStatement.sqlSource = sqlSource;mappedStatement.resultType = resultType;mappedStatement.keyGenerator = configuration.isUseGeneratedKeys()&& SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();}public MappedStatement build() {assert mappedStatement.configuration != null;assert mappedStatement.id != null;mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);return mappedStatement;}public Builder resource(String resource) {mappedStatement.resource = resource;return this;}public String id() {return mappedStatement.id;}public Builder resultMaps(List<ResultMap> resultMaps) {mappedStatement.resultMaps = resultMaps;return this;}public Builder keyGenerator(KeyGenerator keyGenerator) {mappedStatement.keyGenerator = keyGenerator;return this;}public Builder keyProperty(String keyProperty) {mappedStatement.keyProperties = delimitedStringToArray(keyProperty);return this;}public Builder cache(Cache cache) {mappedStatement.cache = cache;return this;}public Builder flushCacheRequired(boolean flushCacheRequired) {mappedStatement.flushCacheRequired = flushCacheRequired;return this;}public Builder useCache(boolean useCache) {mappedStatement.useCache = useCache;return this;}}private static String[] delimitedStringToArray(String in) {if (in == null || in.trim().length() == 0) {return null;} else {return in.split(",");}}public Configuration getConfiguration() {return configuration;}public String getId() {return id;}public SqlCommandType getSqlCommandType() {return sqlCommandType;}public SqlSource getSqlSource() {return sqlSource;}public Class<?> getResultType() {return resultType;}public LanguageDriver getLang() {return lang;}public List<ResultMap> getResultMaps() {return resultMaps;}public String[] getKeyProperties() {return keyProperties;}public KeyGenerator getKeyGenerator() {return keyGenerator;}public String getResource() {return resource;}public String[] getKeyColumns() {return keyColumns;}public boolean isFlushCacheRequired() {return flushCacheRequired;}public Cache getCache() {return cache;}public boolean isUseCache() {return useCache;}
}
  • 添加 Cache 缓存对象

3.5.2 配置项

Configuration.java

package com.lino.mybatis.session;import com.lino.mybatis.binding.MapperRegistry;
import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.cache.decorators.FifoCache;
import com.lino.mybatis.cache.impl.PerpetualCache;
import com.lino.mybatis.datasource.druid.DruidDataSourceFactory;
import com.lino.mybatis.datasource.pooled.PooledDataSourceFactory;
import com.lino.mybatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.lino.mybatis.executor.CachingExecutor;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.SimpleExecutor;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.executor.resultset.DefaultResultSetHandler;
import com.lino.mybatis.executor.resultset.ResultSetHandler;
import com.lino.mybatis.executor.statement.PreparedStatementHandler;
import com.lino.mybatis.executor.statement.StatementHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.plugin.Interceptor;
import com.lino.mybatis.plugin.InterceptorChain;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.factory.DefaultObjectFactory;
import com.lino.mybatis.reflection.factory.ObjectFactory;
import com.lino.mybatis.reflection.wrapper.DefaultObjectWrapperFactory;
import com.lino.mybatis.reflection.wrapper.ObjectWrapperFactory;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.scripting.LanguageDriverRegistry;
import com.lino.mybatis.scripting.xmltags.XMLLanguageDriver;
import com.lino.mybatis.transaction.Transaction;
import com.lino.mybatis.transaction.jdbc.JdbcTransactionFactory;
import com.lino.mybatis.type.TypeAliasRegistry;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;/*** @description: 配置项* @author: lingjian* @createDate: 2022/11/7 21:32*/
public class Configuration {/*** 环境*/protected Environment environment;/*** 是否使用自动生成键值对*/protected boolean useGeneratedKeys = false;/*** 默认启用缓存,cacheEnabled = true/false*/protected boolean cacheEnabled = true;/*** 缓存机制,默认不配置的情况是 SESSION*/protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;/*** 映射注册机*/protected MapperRegistry mapperRegistry = new MapperRegistry(this);/*** 映射的语句,存在Map里*/protected final Map<String, MappedStatement> mappedStatements = new HashMap<>(16);/*** 缓存,存在Map里*/protected final Map<String, Cache> caches = new HashMap<>(16);/*** 结果映射,存在Map里*/protected final Map<String, ResultMap> resultMaps = new HashMap<>(16);/*** 键值生成器,存在Map里*/protected final Map<String, KeyGenerator> keyGenerators = new HashMap<>(16);/*** 插件拦截器链*/protected final InterceptorChain interceptorChain = new InterceptorChain();/*** 类型别名注册机*/protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();/*** 脚本语言注册器*/protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();/*** 类型处理器注册机*/protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();/*** 对象工厂*/protected ObjectFactory objectFactory = new DefaultObjectFactory();/*** 对象包装工厂*/protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();/*** 准备资源列表*/protected final Set<String> loadedResources = new HashSet<>();/*** 数据库ID*/protected String databaseId;public Configuration() {typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);typeAliasRegistry.registerAlias("DRUID", DruidDataSourceFactory.class);typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);typeAliasRegistry.registerAlias("FIFO", FifoCache.class);languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);}public void addMappers(String packageName) {mapperRegistry.addMappers(packageName);}public <T> void addMapper(Class<T> type) {mapperRegistry.addMapper(type);}public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);}public boolean hasMapper(Class<?> type) {return mapperRegistry.hasMapper(type);}public void addMappedStatement(MappedStatement ms) {mappedStatements.put(ms.getId(), ms);}public MappedStatement getMappedStatement(String id) {return mappedStatements.get(id);}public TypeAliasRegistry getTypeAliasRegistry() {return typeAliasRegistry;}public Environment getEnvironment() {return environment;}public void setEnvironment(Environment environment) {this.environment = environment;}public String getDatabaseId() {return databaseId;}public ObjectFactory getObjectFactory() {return objectFactory;}public boolean isUseGeneratedKeys() {return useGeneratedKeys;}public void setUseGeneratedKeys(boolean useGeneratedKeys) {this.useGeneratedKeys = useGeneratedKeys;}public LocalCacheScope getLocalCacheScope() {return localCacheScope;}public void setLocalCacheScope(LocalCacheScope localCacheScope) {this.localCacheScope = localCacheScope;}/*** 生产执行器** @param transaction 事务* @return 执行器*/public Executor newExecutor(Transaction transaction) {Executor executor = new SimpleExecutor(this, transaction);// 配置开启缓存,创建 CachingExecutor(默认就是有缓存)装饰着模式if (cacheEnabled) {executor = new CachingExecutor(executor);}return executor;}/*** 创建语句处理器** @param executor        执行器* @param mappedStatement 映射器语句类* @param parameter       参数* @param rowBounds       分页记录限制* @param resultHandler   结果处理器* @param boundSql        SQL语句* @return StatementHandler 语句处理器*/public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler = new PreparedStatementHandler(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);// 嵌入插件,代理对象statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}/*** 创建结果集处理器** @param executor        执行器* @param mappedStatement 映射器语句类* @param boundSql        SQL语句* @return ResultSetHandler 结果集处理器*/public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {return new DefaultResultSetHandler(executor, mappedStatement, resultHandler, rowBounds, boundSql);}/*** 创建元对象** @param object 原对象* @return 元对象*/public MetaObject newMetaObject(Object object) {return MetaObject.forObject(object, objectFactory, objectWrapperFactory);}/*** 创建类型处理器注册机** @return TypeHandlerRegistry 类型处理器注册机*/public TypeHandlerRegistry getTypeHandlerRegistry() {return typeHandlerRegistry;}/*** 是否包含资源** @param resource 资源* @return 是否*/public boolean isResourceLoaded(String resource) {return loadedResources.contains(resource);}/*** 添加资源** @param resource 资源*/public void addLoadedResource(String resource) {loadedResources.add(resource);}/*** 获取脚本语言注册机** @return languageRegistry 脚本语言注册机*/public LanguageDriverRegistry getLanguageRegistry() {return languageRegistry;}/*** 获取参数处理器** @param mappedStatement 映射器语言类型* @param parameterObject 参数对象* @param boundSql        SQL语句* @return ParameterHandler参数处理器*/public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {// 创建参数处理器ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);return parameterHandler;}/*** 获取默认脚本语言驱动** @return 脚本语言驱动*/public LanguageDriver getDefaultScriptingLanguageInstance() {return languageRegistry.getDefaultDriver();}public ResultMap getResultMap(String id) {return resultMaps.get(id);}public void addResultMap(ResultMap resultMap) {resultMaps.put(resultMap.getId(), resultMap);}public void addKeyGenerator(String id, KeyGenerator keyGenerator) {keyGenerators.put(id, keyGenerator);}public KeyGenerator getKeyGenerator(String id) {return keyGenerators.get(id);}public boolean hasKeyGenerators(String id) {return keyGenerators.containsKey(id);}public void addInterceptor(Interceptor interceptorInstance) {interceptorChain.addInterceptor(interceptorInstance);}public boolean isCacheEnabled() {return cacheEnabled;}public void setCacheEnabled(boolean cacheEnabled) {this.cacheEnabled = cacheEnabled;}public void addCache(Cache cache) {caches.put(cache.getId(), cache);}public Cache getCache(String id) {return caches.get(id);}}
  • newExecutor 装饰缓存执行。

3.5.3 默认SqlSession实现类

DefaultSqlSession.java

package com.lino.mybatis.session.defaults;import com.alibaba.fastjson.JSON;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.util.List;/*** @description: 默认sqlSession实现类*/
public class DefaultSqlSession implements SqlSession {...@Overridepublic void close() {executor.close(false);}...
}

3.6 映射构建器助手添加缓存

3.6.1 缓存构建器

CacheBuilder.java

package com.lino.mybatis.mapping;import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.cache.decorators.FifoCache;
import com.lino.mybatis.cache.impl.PerpetualCache;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.SystemMetaObject;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;/*** @description: 缓存构建器, 建造者模式*/
public class CacheBuilder {private String id;private Class<? extends Cache> implementation;private List<Class<? extends Cache>> decorators;private Integer size;private Long clearInterval;private boolean readWrite;private Properties properties;private boolean blocking;public CacheBuilder(String id) {this.id = id;this.decorators = new ArrayList<>();}public CacheBuilder implementation(Class<? extends Cache> implementation) {this.implementation = implementation;return this;}public CacheBuilder addDecorator(Class<? extends Cache> decorator) {if (decorator != null) {this.decorators.add(decorator);}return this;}public CacheBuilder size(Integer size) {this.size = size;return this;}public CacheBuilder clearInterval(Long clearInterval) {this.clearInterval = clearInterval;return this;}public CacheBuilder readWrite(boolean readWrite) {this.readWrite = readWrite;return this;}public CacheBuilder blocking(boolean blocking) {this.blocking = blocking;return this;}public CacheBuilder properties(Properties properties) {this.properties = properties;return this;}public Cache build() {setDefaultImplementations();Cache cache = newBaseCacheInstance(implementation, id);setCacheProperties(cache);if (PerpetualCache.class.equals(cache.getClass())) {for (Class<? extends Cache> decorator : decorators) {// 使用装饰者模式包装cache = newCacheDecoratorInstance(decorator, cache);// 额外属性设置setCacheProperties(cache);}}return cache;}private void setDefaultImplementations() {if (implementation == null) {implementation = PerpetualCache.class;if (decorators.isEmpty()) {decorators.add(FifoCache.class);}}}private void setCacheProperties(Cache cache) {if (properties != null) {MetaObject metaCache = SystemMetaObject.forObject(cache);for (Map.Entry<Object, Object> entry : properties.entrySet()) {String name = (String) entry.getKey();String value = (String) entry.getValue();if (metaCache.hasSetter(name)) {Class<?> type = metaCache.getSetterType(name);if (String.class == type) {metaCache.setValue(name, value);} else if (int.class == type || Integer.class == type) {metaCache.setValue(name, Integer.valueOf(value));} else if (long.class == type || Long.class == type) {metaCache.setValue(name, Long.valueOf(value));} else if (short.class == type || Short.class == type) {metaCache.setValue(name, Short.valueOf(value));} else if (byte.class == type || Byte.class == type) {metaCache.setValue(name, Byte.valueOf(value));} else if (float.class == type || Float.class == type) {metaCache.setValue(name, Float.valueOf(value));} else if (boolean.class == type || Boolean.class == type) {metaCache.setValue(name, Boolean.valueOf(value));} else if (double.class == type || Double.class == type) {metaCache.setValue(name, Double.valueOf(value));} else {throw new RuntimeException("Unsupported property type for cache: '" + name + "' of type " + type);}}}}}private Cache newBaseCacheInstance(Class<? extends Cache> cacheClass, String id) {Constructor<? extends Cache> cacheConstructor = getBaseCacheConstructor(cacheClass);try {return cacheConstructor.newInstance(id);} catch (Exception e) {throw new RuntimeException("Could not instantiate cache implementation (" + cacheClass + "). Cause: " + e, e);}}private Constructor<? extends Cache> getBaseCacheConstructor(Class<? extends Cache> cacheClass) {try {return cacheClass.getConstructor(String.class);} catch (Exception e) {throw new RuntimeException("Invalid base cache implementation (" + cacheClass + ").  " +"Base cache implementations must have a constructor that takes a String id as a parameter.  Cause: " + e, e);}}private Cache newCacheDecoratorInstance(Class<? extends Cache> cacheClass, Cache base) {Constructor<? extends Cache> cacheConstructor = getCacheDecoratorConstructor(cacheClass);try {return cacheConstructor.newInstance(base);} catch (Exception e) {throw new RuntimeException("Could not instantiate cache decorator (" + cacheClass + "). Cause: " + e, e);}}private Constructor<? extends Cache> getCacheDecoratorConstructor(Class<? extends Cache> cacheClass) {try {return cacheClass.getConstructor(Cache.class);} catch (Exception e) {throw new RuntimeException("Invalid cache decorator (" + cacheClass + ").  " +"Cache decorators must have a constructor that takes a Cache instance as a parameter.  Cause: " + e, e);}}
}

3.6.2 映射构建器助手

MapperBuilderAssistant.java

package com.lino.mybatis.builder;import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.cache.decorators.FifoCache;
import com.lino.mybatis.cache.impl.PerpetualCache;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.mapping.*;
import com.lino.mybatis.reflection.MetaClass;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.TypeHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;/*** @description: 映射构建器助手,建造者*/
public class MapperBuilderAssistant extends BaseBuilder {private String currentNamespace;private String resource;private Cache currentCache;public MapperBuilderAssistant(Configuration configuration, String resource) {super(configuration);this.resource = resource;}public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, List<ResultFlag> flags) {Class<?> javaTypeClass = resolveResultJavaType(resultType, property, null);TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, null);ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, column, javaTypeClass);builder.typeHandler(typeHandlerInstance);builder.flags(flags);return builder.build();}private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {if (javaType == null && property != null) {try {MetaClass metaResultType = MetaClass.forClass(resultType);javaType = metaResultType.getSetterType(property);} catch (Exception ignore) {}}if (javaType == null) {javaType = Object.class;}return javaType;}public String getCurrentNamespace() {return currentNamespace;}public void setCurrentNamespace(String currentNamespace) {this.currentNamespace = currentNamespace;}public String applyCurrentNamespace(String base, boolean isReference) {if (base == null) {return null;}if (isReference) {if (base.contains(".")) {return base;}} else {if (base.startsWith(currentNamespace + ".")) {return base;}if (base.contains(".")) {throw new RuntimeException("Dots are not allowed in element names, please remove it from " + base);}}return currentNamespace + "." + base;}/*** 添加映射器语句*/public MappedStatement addMappedStatement(String id, SqlSource sqlSource, SqlCommandType sqlCommandType,Class<?> parameterType, String resultMap, Class<?> resultType,boolean flushCache, boolean useCache,KeyGenerator keyGenerator, String keyProperty, LanguageDriver lang) {// 给id加上namespace前缀:com.lino.mybatis.test.dao.IUserDao.queryUserInfoByIdid = applyCurrentNamespace(id, false);// 是否时select语句boolean isSelect = sqlCommandType == SqlCommandType.SELECT;MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlCommandType, sqlSource, resultType);statementBuilder.resource(resource);statementBuilder.keyGenerator(keyGenerator);statementBuilder.keyProperty(keyProperty);// 结果映射, 给 MappedStatement#resultMapssetStatementResultMap(resultMap, resultType, statementBuilder);setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);MappedStatement statement = statementBuilder.build();// 映射语句信息,建造完存放到配置项中configuration.addMappedStatement(statement);return statement;}private void setStatementCache(boolean isSelect, boolean flushCache, boolean useCache, Cache cache, MappedStatement.Builder statementBuilder) {flushCache = valueOrDefault(flushCache, !isSelect);useCache = valueOrDefault(useCache, !isSelect);statementBuilder.flushCacheRequired(flushCache);statementBuilder.useCache(useCache);statementBuilder.cache(cache);}private void setStatementResultMap(String resultMap, Class<?> resultType, MappedStatement.Builder statementBuilder) {// 因为暂时还没有在 Mapper XML 中配置 Map 返回结果,所以这里返回的是 nullresultMap = applyCurrentNamespace(resultMap, true);List<ResultMap> resultMaps = new ArrayList<>();if (resultMap != null) {String[] resultMapNames = resultMap.split(",");for (String resultMapName : resultMapNames) {resultMaps.add(configuration.getResultMap(resultMapName.trim()));}}/** 通常使用 resultType 即可满足大部分场景* <select id="queryUserInfoById" resultType="com.lino.mybatis.test.po.User">* 使用 resultType 的情况下,Mybatis 会自动创建一个 ResultMap,基于属性名称映射列到 JavaBean 的属性上。*/else if (resultType != null) {ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(configuration, statementBuilder.id() + "-Inline", resultType, new ArrayList<>());resultMaps.add(inlineResultMapBuilder.build());}statementBuilder.resultMaps(resultMaps);}public ResultMap addResultMap(String id, Class<?> type, List<ResultMapping> resultMappings) {// 补全ID全路径,如:com.lino.mybatis.test.dao.IActivityDao + activityMapid = applyCurrentNamespace(id, false);ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(configuration, id, type, resultMappings);ResultMap resultMap = inlineResultMapBuilder.build();configuration.addResultMap(resultMap);return resultMap;}public Cache useNewCache(Class<? extends Cache> typeClass,Class<? extends Cache> evictionClass,Long flushInterval,Integer size,boolean readWrite,boolean blocking,Properties props) {// 判断为null,则用默认值typeClass = valueOrDefault(typeClass, PerpetualCache.class);evictionClass = valueOrDefault(evictionClass, FifoCache.class);// 建造者模式构建 cache [currentNamespace=com.lino.mybatis.test.dao.IActivityDao]Cache cache = new CacheBuilder(currentNamespace).implementation(typeClass).addDecorator(evictionClass).clearInterval(flushInterval).size(size).readWrite(readWrite).blocking(blocking).properties(props).build();// 添加缓存configuration.addCache(cache);currentCache = cache;return cache;}private <T> T valueOrDefault(T value, T defaultValue) {return value == null ? defaultValue : value;}
}

3.7 XML配置构建器

3.7.1 构建器基类

BaseBuilder.java

package com.lino.mybatis.builder;import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.TypeAliasRegistry;
import com.lino.mybatis.type.TypeHandler;
import com.lino.mybatis.type.TypeHandlerRegistry;/*** @description: 构建器的基类,建造者模式*/
public class BaseBuilder {protected final Configuration configuration;protected final TypeAliasRegistry typeAliasRegistry;protected final TypeHandlerRegistry typeHandlerRegistry;...protected Boolean booleanValueOf(String value, Boolean defaultValue) {return value == null ? defaultValue : Boolean.valueOf(value);}
}

3.7.2 注解配置构建器

MapperAnnotationBuilder.java

package com.lino.mybatis.builder.annotation;import com.lino.mybatis.annotations.Delete;
import com.lino.mybatis.annotations.Insert;
import com.lino.mybatis.annotations.Select;
import com.lino.mybatis.annotations.Update;
import com.lino.mybatis.binding.MapperMethod;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;/*** @description: 注解配置构建器 Mapper*/
public class MapperAnnotationBuilder {.../*** 解析语句** @param method 方法*/private void parseStatement(Method method) {Class<?> parameterTypeClass = getParameterType(method);LanguageDriver languageDriver = getLanguageDriver(method);SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);if (sqlSource != null) {final String mappedStatementId = type.getName() + "." + method.getName();SqlCommandType sqlCommandType = getSqlCommandType(method);KeyGenerator keyGenerator;String keyProperty = "id";if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();} else {keyGenerator = new NoKeyGenerator();}boolean isSelect = sqlCommandType == SqlCommandType.SELECT;String resultMapId = null;if (isSelect) {resultMapId = parseResultMap(method);}// 调用助手类assistant.addMappedStatement(mappedStatementId,sqlSource,sqlCommandType,parameterTypeClass,resultMapId,getReturnType(method),false,false,keyGenerator,keyProperty,languageDriver);}}...}

3.7.3 XML语言构建器

XMLStatementBuilder.java

package com.lino.mybatis.builder.xml;import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.executor.keygen.SelectKeyGenerator;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Element;
import java.util.List;
import java.util.Locale;/*** @description: XML语言构建器*/
public class XMLStatementBuilder extends BaseBuilder {.../*** 解析语句(select|insert|update|delete)* <select* id="selectPerson"* parameterType="int"* parameterMap="deprecated"* resultType="hashmap"* resultMap="personResultMap"* flushCache="false"* useCache="true"* timeout="10000"* fetchSize="256"* statementType="PREPARED"* resultSetType="FORWARD_ONLY">* SELECT * FROM PERSON WHERE ID = #{id}* </select>*/public void parseStatementNode() {String id = element.attributeValue("id");// 参数类型String parameterType = element.attributeValue("parameterType");Class<?> parameterTypeClass = resolveAlias(parameterType);// 外部应用 resultMapString resultMap = element.attributeValue("resultMap");// 结果类型String resultType = element.attributeValue("resultType");Class<?> resultTypeClass = resolveAlias(resultType);// 获取命令类型(select|insert|update|delete)String nodeName = element.getName();SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));boolean isSelect = sqlCommandType == SqlCommandType.SELECT;boolean flushCache = Boolean.parseBoolean(element.attributeValue("flushCache", String.valueOf(!isSelect)));boolean useCache = Boolean.parseBoolean(element.attributeValue("useCache", String.valueOf(isSelect)));// 获取默认语言驱动器Class<?> langClass = configuration.getLanguageRegistry().getDefaultDriverClass();LanguageDriver langDriver = configuration.getLanguageRegistry().getDriver(langClass);// 解析<selectKey>processSelectKeyNodes(id, parameterTypeClass, langDriver);// 解析成SqlSource,DynamicSqlSource/RawSqlSourceSqlSource sqlSource = langDriver.createSqlSource(configuration, element, parameterTypeClass);// 属性标记【仅对insert有用】,MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值String keyProperty = element.attributeValue("keyProperty");KeyGenerator keyGenerator = null;String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerators(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() :new NoKeyGenerator();}// 调用助手类builderAssistant.addMappedStatement(id,sqlSource,sqlCommandType,parameterTypeClass,resultMap,resultTypeClass,flushCache,useCache,keyGenerator,keyProperty,langDriver);}.../*** <selectKey keyProperty="id" order="AFTER" resultType="long">* SELECT LAST_INSERT_ID()* </selectKey>*/private void parseSelectKeyNode(String id, Element nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver) {String resultType = nodeToHandle.attributeValue("resultType");Class<?> resultTypeClass = resolveClass(resultType);boolean executeBefore = "BEFORE".equals(nodeToHandle.attributeValue("order", "AFTER"));String keyProperty = nodeToHandle.attributeValue("keyProperty");// 默认String resultMap = null;boolean flushCache = false;boolean useCache = false;KeyGenerator keyGenerator = new NoKeyGenerator();// 解析成SqlSource,DynamicSqlSource/RawSqlSourceSqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);SqlCommandType sqlCommandType = SqlCommandType.SELECT;// 调用助手类builderAssistant.addMappedStatement(id,sqlSource,sqlCommandType,parameterTypeClass,resultMap,resultTypeClass,flushCache,useCache,keyGenerator,keyProperty,langDriver);// 给id加上namespace前缀id = builderAssistant.applyCurrentNamespace(id, false);// 存放键值生成器配置MappedStatement keyStatement = configuration.getMappedStatement(id);configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));}}

3.7.4 XML映射构建器

XMLMapperBuilder.java

package com.lino.mybatis.builder.xml;import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.builder.ResultMapResolver;
import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.mapping.ResultFlag;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.mapping.ResultMapping;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;/*** @description: XML映射构建器*/
public class XMLMapperBuilder extends BaseBuilder {.../*** 配置mapper元素* <mapper namespace="org.mybatis.example.BlogMapper">* <select id="selectBlog" parameterType="int" resultType="Blog">* select * from Blog where id = #{id}* </select>* </mapper>** @param element 元素*/private void configurationElement(Element element) {// 1.配置namespaceString namespace = element.attributeValue("namespace");if ("".equals(namespace)) {throw new RuntimeException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);// 2.配置cachecacheElement(element.element("cache"));// 3.解析resultMapresultMapElement(element.elements("resultMap"));// 4.配置select|insert|update|deletebuildStatementFromContext(element.elements("select"), element.elements("insert"), element.elements("update"), element.elements("delete"));}/*** <cache eviction="FIFO" flushInterval="600000" size="512" readOnly="true"/>*/private void cacheElement(Element context) {if (context == null) {return;}// 基础配置信息String type = context.attributeValue("type", "PERPETUAL");Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);// 缓存队列 FIFOString eviction = context.attributeValue("eviction", "FIFO");Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);Long flushInterval = Long.valueOf(context.attributeValue("flushInterval"));Integer size = Integer.valueOf(context.attributeValue("size"));boolean readWrite = !Boolean.parseBoolean(context.attributeValue("readOnly", "false"));boolean blocking = !Boolean.parseBoolean(context.attributeValue("blocking", "false"));// 解析额外属性信息:<property name="cacheFile" value="/tmp/xxx-cache.tmp"/>List<Element> elements = context.elements();Properties props = new Properties();for (Element element : elements) {props.setProperty(element.attributeValue("name"), element.attributeValue("value"));}// 构建缓存builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);}...
}
  • cacheElement 缓存标签的解析作用于 configurationElement 解析环节中。解析后创建 Cache 存放到 Configuration 配置项对应的属性值。
  • 同时创建出来的缓存会被记录到 MappedStatement 映射语句类的属性上,便于在缓存执行器中使用。

3.7.5 XML配置构建器-全局缓存解析

<settings><!--全局缓存:true/false--><setting name="cacheEnabled" value="true"/><!--缓存级别:SESSION/STATEMENT--><setting name="localCacheScope" value="STATEMENT"/>
</settings>
  • Config XML 中配置全局缓存为开启,这个时候可以关闭一级缓存。

3.7.6 XML配置构建器

XMLConfigBuilder.java

package com.lino.mybatis.builder.xml;import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.datasource.DataSourceFactory;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.plugin.Interceptor;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.LocalCacheScope;
import com.lino.mybatis.transaction.TransactionFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;
import javax.sql.DataSource;
import java.io.InputStream;
import java.io.Reader;
import java.util.List;
import java.util.Properties;/*** @description: XML配置构建器,建造者模式,集成BaseBuilder*/
public class XMLConfigBuilder extends BaseBuilder {.../*** <settings>* <!--缓存级别:SESSION/STATEMENT-->* <setting name="localCacheScope" value="SESSION"/>* </settings>*/private void settingElement(Element context) {if (context == null) {return;}List<Element> elements = context.elements();Properties props = new Properties();for (Element element : elements) {props.setProperty(element.attributeValue("name"), element.attributeValue("value"));}configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope")));}...}
  • 解析全局配置的操作,再结合 XMLConfigBuilder 配置构建器中对配置解析扩展,添加 cacheEnabled 即可。
  • 这样就可以把是否开启二级缓存的操作保存配置项目。
  • 默认情况下,二级缓存是关闭的。

四、测试:二级缓存

4.1 修改配置

4.1.1 修改xml配置

mybatis-config-datasource.xml

<?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>...<settings><!--全局缓存:true/false--><setting name="cacheEnabled" value="true"/><!--缓存级别:SESSION/STATEMENT--><setting name="localCacheScope" value="STATEMENT"/></settings>...
</configuration>
  • 缓存的执行策略为二级缓存、一级缓存和数据库,所以这类的缓存配置可以根据代码测试阶段调整为一级、二级,交叉开启和关闭进行验证。

4.1.2 缓存策略配置

Activity_Mapper.xml

<?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.lino.mybatis.test.dao.IActivityDao"><cache eviction="FIFO" flushInterval="600000" size="512" readOnly="true"/><resultMap id="activityMap" type="com.lino.mybatis.test.po.Activity"><id column="id" property="id"/><result column="activity_id" property="activityId"/><result column="activity_name" property="activityName"/><result column="activity_desc" property="activityDesc"/><result column="create_time" property="createTime"/><result column="update_time" property="updateTime"/></resultMap><select id="queryActivityById" parameterType="com.lino.mybatis.test.po.Activity" resultMap="activityMap">SELECT activity_id, activity_name, activity_desc, create_time, update_timeFROM activity<trim prefix="where" prefixOverrides="AND | OR" suffixOverrides="and"><if test="null != activityId">activity_id = #{activityId}</if></trim></select></mapper>
  • cache 标签为二级缓存的使用策略,你可以配置 FIFO、LRU 等不同的缓存策略。

4.2 单元测试

ApiTest.java

@Test
public void test_queryActivityById() throws IOException {// 1.从SqlSessionFactory中获取SqlSessionReader reader = Resources.getResourceAsReader("mybatis-config-datasource.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);// 2.请求对象Activity activity = new Activity();activity.setActivityId(100001L);// 3.第一组:SqlSession// 3.1 开启sessionSqlSession sqlSession01 = sqlSessionFactory.openSession();// 3.2 获取映射器对象IActivityDao dao01 = sqlSession01.getMapper(IActivityDao.class);logger.info("测试结果01:{}", JSON.toJSONString(dao01.queryActivityById(activity)));sqlSession01.close();// 4.第二组:SqlSession// 4.1 开启sessionSqlSession sqlSession02 = sqlSessionFactory.openSession();// 4.2 获取映射器对象IActivityDao dao02 = sqlSession02.getMapper(IActivityDao.class);logger.info("测试结果02:{}", JSON.toJSONString(dao02.queryActivityById(activity)));sqlSession02.close();
}
  • 在单元测试中,分别两次开启 openSession 操作,并在第一次开启会话查询数据后执行会话关闭。
    • 因为实际代码实现,无论是 commit、close 结束会话,都会把一级缓存数据刷新到二级缓存,所以两个方式都可以。
  • 当会话关闭后开始执行第二次的会话开启,验证数据是否从二级缓存中获取数据,因为在二级缓存中添加了日志判断,所以可以通过打印日志进行验证。

测试结果

10:43:34.690 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 899644639.
拦截SQLSELECT activity_id, activity_name, activity_desc, create_time, update_timeFROM activitywhere activity_id = ?
10:43:34.700 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:100001
10:43:34.700 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果01{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}
10:43:34.700 [main] INFO  c.l.m.d.pooled.PooledDataSource - Returned connection 899644639 to pool.
10:43:34.700 [main] INFO  c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IActivityDao.queryActivityById parameter:{"activityId":100001}
10:43:34.700 [main] INFO  c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:activityId propertyType:class java.lang.Long
10:43:34.700 [main] DEBUG c.lino.mybatis.executor.BaseExecutor - 二级缓存: [{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}]
10:43:34.700 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果02{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}

在这里插入图片描述

  • 从断点调试和打印的日志看,此时框架已经可以在两次会话中完成数据的二次缓存使用。

五、总结:二级缓存

  • 可以从二级缓存的设计实现上,看到 Mybatis 框架在这里运用了大量的装饰器模式,如 CachingExecutor 执行器和 Cache 接口的各类实现。
    • 这样的设计的好处可以在不破坏原有逻辑的前提下,完成功能通过配置开关的自由开启使用。
  • 虽然二级缓存在 Mybatis 框架中也是一个不错的设计,但由于系统架构逐步从单体到分布式以后,单个实例的数据缓存并没有太大的意义。
    • 因为在分布式架构下,用户的每次请求会分散到不同应用实例上,那么单个缓存这个时候大概率就没法起到作用了。
    • 所以在使用 Mybatis 时通常并不会开启二级缓存,而是使用 Redis 这样的框架来预热数据库数据使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/120995.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为OD机试 - 等和子数组最小和 - 深度优先搜索(Java 2022 Q4 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#xff09;》…

企业电子招标采购系统源码java 版本 Spring Cloud + Spring Boot

项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及…

ARM编程模型-指令流水线

流水线技术通过多个功能部件并行工作来缩短程序执行时间&#xff0c;提高处理器核的效率和吞吐率&#xff0c;从而成为微处理器设计中最为重要的技术之一。 1. 3级流水线 到ARM7为止的ARM处理器使用简单的3级流水线&#xff0c;它包括下列流水线级。 &#xff08;1&#xff0…

机器人中的数值优化(五)——信赖域方法

本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考&#xff0c;主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等&#xff0c;本系列文章篇数较多&#xff0c;不定期更新&#xff0c;上半部分介绍无约束优化&#xff0c;…

ubuntu22.04搭建verilator仿真环境

概述 操作系统为 Ubuntu(22.04.2 LTS)&#xff0c;本次安装verilator开源verilog仿真工具&#xff0c;进行RTL功能仿真。下面构建版本为5.008的verilator仿真环境。先看一下我系统的版本&#xff1a; 安装流程 安装依赖 sudo apt-get install git perl python3 make autoc…

肖sir__设计测试用例方法之边界值03_(黑盒测试)

设计测试用例方法之边界值 边界点定义 上点&#xff1a;边界上的点 离点&#xff1a;离上点最近的点&#xff08;即上点左右两边最邻近的点&#xff09; 内点&#xff1a;在域范围内的点 案例&#xff1a;qq号&#xff1a;5-12位 闭区间&#xff1a; 离点&#xff1a;5 位 &…

计算机组成原理学习记录(更新中)

文章目录 仅做个人记录计组的学习中认为容易记错的点或是个人认为的要点&#xff0c;如有错误&#xff0c;请多包涵。 学习资源为b站网课&#xff1a;王道计算机考研 计算机组成原理 大部分图片来自该网课 &#xff08;1&#xff09;冯诺依曼型计算机由五个部分组成&#xff…

ajax day2

1、 2、控制弹框显示和隐藏&#xff1a; 3、右键tr&#xff0c;编辑为html&#xff0c;可直接复制tr部分的代码 4、删除时&#xff0c;点击删除按钮&#xff0c;可以获取图书id&#xff1a; 5、编辑图书 快速赋值表单元素内容&#xff0c;用于回显&#xff1a; 6、hidden …

Spring AOP与静态代理/动态代理

文章目录 一、代理模式静态代理动态代理代理模式与AOP 二、Spring AOPSping AOP用来处理什么场景jdk 动态代理cglib 动态代理面试题&#xff1a;讲讲Spring AOP的原理与执行流程 总结 一、代理模式 代理模式是一种结构型设计模式&#xff0c;它允许对象提供替代品或占位符&…

Android片段

如果你希望应用根据不同的环境有不同的外观和行为&#xff0c;这种情况下就需要片段&#xff0c;片段是可以由不同活动重用的模块化代码组件。 片段&#xff08;Fragment&#xff09;是活动&#xff08;Activity&#xff09;的一种模块化部分&#xff0c;表示活动中的行为或界面…

Gin学习记录2——路由

路由 一. 常规路由二. 动态路由三. 带参数的路由3.1 GET3.2 POST3.3 绑定 四. 简单的路由组五. 文件分组 一. 常规路由 package mainimport ("net/http""github.com/gin-gonic/gin" )func index(ctx *gin.Context) {ctx.String(http.StatusOK, "Hell…

八个针对高级职位的高级 JavaScript 面试题

JavaScript 是一种功能强大的语言&#xff0c;是网络的主要构建块之一。这种强大的语言也有一些怪癖。例如&#xff0c;您是否知道 0 -0 的计算结果为 true&#xff0c;或者 Number("") 的结果为 0&#xff1f; 问题是&#xff0c;有时这些怪癖会让你摸不着头脑&…

Python 操作 Excel

之前看过一篇文章&#xff0c;说一个工作多年的老员工&#xff0c;处理数据时只会用复制粘贴到 Excel &#xff0c;天天加班工作还完不成&#xff0c;后来公司就招了一个会 Python 的新人&#xff0c;结果分分钟就处理完成。所以工作中大家经常会使用 Excel 去处理以及展示数据…

AI工人操作行为流程规范识别算法

AI工人操作行为流程规范识别算法通过yolov7python网络模型框架&#xff0c;AI工人操作行为流程规范识别算法对作业人员的操作行为进行实时分析&#xff0c;根据设定算法规则判断操作行为是否符合作业标准规定的SOP流程。Yolo意思是You Only Look Once&#xff0c;它并没有真正的…

【Cortex-M3权威指南】学习笔记4 - 异常

目录 实现 CM3流水线CM3 详细框图CM3 总线接口总线连接模板 异常异常类型优先级定义优先级组 向量表中断输入于挂起NMI中断挂起 Fault 类异常总线 faults存储器管理 faults用法 faults SVC 与 PendSV 实现 CM3 流水线 CM3 处理器使用 3 级流水线&#xff0c;分别是&#xff1a;…

【从0学习Solidity】2. 值类型详解

Solidity极简入门: 2. 值类型 博主简介&#xff1a;不写代码没饭吃&#xff0c;一名全栈领域的创作者&#xff0c;专注于研究互联网产品的解决方案和技术。熟悉云原生、微服务架构&#xff0c;分享一些项目实战经验以及前沿技术的见解。关注我们的主页&#xff0c;探索全栈开发…

etcd分布式存储

etcd分布式存储 etcd简介etcd下载安装etcd常用命令etcd配置参数etcd集群golang操作etcd

Android大厂需要刷的(999道)面试题

想必大家都在为今年的金九银十做准备&#xff0c;今年也是最为艰难的一年。作为程序员从未感觉到如此艰难&#xff0c;身边不是被辞退就是找不到工作。先不说2023年应届生毕业即失业&#xff0c;作为开发15年的老Android程序员&#xff0c;现在也在和300个人挣一个岗位。 肉少…

嵌入式学习笔记(12)汇编写启动代码之设置栈和调用C语言

C语言运行时需求和栈的意义 “C语言运行时&#xff08;runtime&#xff09;”需要一定的条件&#xff0c;这些条件由汇编来提供。C语言运行时主要是需要栈。 C语言和栈的关系&#xff1a;C语言中的局部变量都是用栈来实现的。如果我们汇编部分没有给C部分预先设置合理合法的栈…

QT实现TCP通信(服务器与客户端搭建)

一、TCP通信框架 二、QT中的服务器操作 创建一个QTcpServer类对象&#xff0c;该类对象就是一个服务器调用listen函数将该对象设置为被动监听状态&#xff0c;监听时&#xff0c;可以监听指定的ip地址&#xff0c;也可以监听所有主机地址&#xff0c;可以通过指定端口号&#x…