手写Mybatis:第18章-一级缓存
文章目录
- 一、目标:一级缓存
- 二、设计:一级缓存
- 三、实现:一级缓存
- 3.1 工程结构
- 3.2 一级缓存类图
- 3.3 一级缓存实现
- 3.3.1 定义缓存接口
- 3.3.2 实现缓存接口
- 3.3.3 创建缓存KEY
- 3.3.4 NULL值缓存key
- 3.4 定义缓存机制、占位符和修改配置文件
- 3.4.1 定义缓存机制
- 3.4.2 定义占位符符
- 3.4.3 修改映射器语句类
- 3.4.4 修改配置项
- 3.5 在会话中缓存使用
- 3.5.1 创建缓存KEY
- 3.5.2 抽象执行器实现
- 3.6 解析缓存机制
- 四、测试:一级缓存
- 4.1 修改XML配置文件
- 4.2 单元测试
- 4.2.1 两次查询
- 4.2.2 提交会话
- 4.2.3 关闭会话
- 五、总结:一级缓存
一、目标:一级缓存
💡 什么是一级缓存?
- 在数据库的一次会话中,有时候我们可能需要反复的执行完全相同查询语句。
- 如果不采取一些优化的手段,那么每一次查询都会查询一次数据库,而在极短的会话周期内几乎反复查询出来的结果也是完全相同的
- 与内存获取相比,相同的数据再查询数据库的代价是很大的,如果系统的调用量较大,那么这可能造成很大的资源浪费。、
- 所以结合我们实现的 ORM 框架,在一个会话周期内添加缓存操作,当会话结束
commit/close/clear时,则进行清空缓存。
- 所以结合我们实现的 ORM 框架,在一个会话周期内添加缓存操作,当会话结束
二、设计:一级缓存
💡 一级缓存如何设计?
- mybatis的一级缓存:主要在于一次 session 会话周期内,将相同的执行语句结果缓存起来,避免重复执行数据操作。
- 当发生一切影响 session 会话的操作时,都会清空缓存,避免发生胀肚。
https://article-images.zsxq.com/Fq6qNr4inPGf3qav-TzvEntASm4n
- 在 Mybatis 的 XML 配置文件中,可以设置本地缓存的机制,如果不设置则是默认
SESSION级别,也就是使用一级缓存保存会话生命周期内的数据。- 如果设置为 STATEMENT 则不使用一级缓存。
- SqlSession 的工作主要交给 Executor 执行器完成,负责数据库的各种操作。
- 当创建一个 SqlSession 对象时,Mybatis 会为这个 SqlSession 创建一个新的 Executor 执行器。
- 而缓存的工具包也是在执行器的创建时构建出来的。
- 基于缓存的创建,在会话周期内保存查询结果数据,当一个 session 会话内发生了改变数据的行为包括:
insert/delete/update则清空缓存。 - 另外当主动执行
close、commit、clear操作时,也要顺便把缓存数据清空。这样才能尽最大可能的提高查询效率的同时,降低发生脏读的可能。
三、实现:一级缓存
3.1 工程结构
mybatis-step-17
|-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| | |-impl| | | |-PrepetualCache.java| | |-Cache.java| | |-CacheKey.java| | |-NullCacheKey.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| | |-ExecutionPlaceholder.java| | |-Executor.java| | |-SimpleExecutor.java| |-io| | |-Resources.java| |-mapping| | |-BoundSql.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 一级缓存类图

- 以 XMLConfigBuilder 配置构建器为入口,解析 XML 配置中关于缓存机制的配置。
- 关闭一级缓存,可以通过
LocalCacheScope配置的方式进行处理。
- 关闭一级缓存,可以通过
- 接下来就是在会话周期内,以创建 SqlSession 构建出 Executor 执行器初始化缓存组件,在执行查询操作时存放数据。
- 当会话周期内有执行
insert/update/delete以及关闭、提交、清空等操作时,在缓存组件中删除相关的缓存数据。
- 当会话周期内有执行
- 具体的缓存操作通过 PerpetualCache 以及永久缓存实现类,实现 Cache 缓存接口来完成,其中关于缓存的 ID 字段,使用 CacheKey 为查询操作创建索引。
- 注意:一般缓存框架的数据结构基本上都是
Key-Value方式存储,Mybatis 对于其 Key 的生成采取规则为:[mappedStatementId + offset + limit + SQL + queryParams + environment]生成一个哈希码作为 Key 使用。
- 注意:一般缓存框架的数据结构基本上都是
3.3 一级缓存实现
- 在 Mybatis 框架中有一个单独提供的
cache包,用于处理数据的缓存操作,一级缓存、二级缓存的操作都是这个包下提供的服务。
3.3.1 定义缓存接口
Cache.java
package com.lino.mybatis.cache;/*** @description: 缓存接口*/
public interface Cache {/*** 获取ID,每个缓存都有唯一的ID标识** @return ID*/String getId();/*** 存入值** @param key 键* @param value 值*/void putObject(Object key, Object value);/*** 获取值** @param key 键* @return 值*/Object getObject(Object key);/*** 删除值** @param key 键* @return 值*/Object removeObject(Object key);/*** 清空*/void clear();/*** 获取缓存大小** @return 缓存大小*/int getSize();
}
- 缓存接口主要提供了数据的存放、获取、删除、清空,以及数量大小的获取。
3.3.2 实现缓存接口
PerpetualCache.java
package com.lino.mybatis.cache.impl;import com.alibaba.fastjson.JSON;
import com.lino.mybatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;/*** @description: 一级缓存,在 Session 生命周期内一直保持,每创建新的 OpenSession 都会创建一个缓存器 PerpetualCache*/
public class PerpetualCache implements Cache {private Logger logger = LoggerFactory.getLogger(PerpetualCache.class);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) {Object obj = cache.get(key);if (null != obj) {logger.info("一级缓存 \r\nkey:{} \r\nval:{}", key, JSON.toJSONString(obj));}return obj;}@Overridepublic Object removeObject(Object key) {return cache.remove(key);}@Overridepublic void clear() {cache.clear();}@Overridepublic int getSize() {return cache.size();}
}
- 一般缓存的实现类也叫永远缓存,使用 HashMap 存放数据,因为这个缓存是配合整个会话周期内使用和销毁。
- 所以使用 HashMap 比较简单,不需要太多的容量和大小限制。
- 基本这个类里的操作都是对 HashMap 的存放、删除和清空的基本操作。
3.3.3 创建缓存KEY
- 通常使用 HashMap 的时候 Key 都是一个 String 的值,那么这里因为需要对查询的信息以及 SQL 做一个 ID 使用,但这样都拼装下来就太长了。
- 所以在缓存 Key 的实现中,基于这些信息创建了一个新的 HashCode 作为 KEY 使用。
CacheKey.java
package com.lino.mybatis.cache;import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;/*** @description: 缓存key,一般缓存框架的数据结构基本上都是 key-value 方式存储* Mybatis 对于其 key 的生成采取规则为:[mappedStatementId + offset + limit + SQL + queryParams + environment]生成一个哈希码*/
public class CacheKey implements Cloneable, Serializable {private static final long serialVersionUID = 1146682552656046210L;public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();private static final int DEFAULT_MULTIPLYEP = 37;private static final int DEFAULT_HASHCODE = 17;private int multiplier;private int hashcode;private long checksum;private int count;private List<Object> updateList;public CacheKey() {this.hashcode = DEFAULT_HASHCODE;this.multiplier = DEFAULT_MULTIPLYEP;this.count = 0;this.updateList = new ArrayList<>();}public CacheKey(Object[] objects) {this();updateAll(objects);}public int getUpdateCount() {return updateList.size();}public void update(Object object) {if (object != null && object.getClass().isArray()) {int length = Array.getLength(object);for (int i = 0; i < length; i++) {Object element = Array.get(object, i);doUpdate(element);}} else {doUpdate(object);}}private void doUpdate(Object object) {// 计算hash值,校验码int baseHashCode = object == null ? 1 : object.hashCode();count++;checksum += baseHashCode;baseHashCode *= count;hashcode = multiplier + hashcode + baseHashCode;updateList.add(object);}public void updateAll(Object[] objects) {for (Object o : objects) {update(o);}}@Overridepublic boolean equals(Object object) {if (this == object) {return true;}if (!(object instanceof CacheKey)) {return false;}final CacheKey cachekey = (CacheKey) object;if (hashcode != cachekey.hashcode) {return false;}if (checksum != cachekey.checksum) {return false;}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 (thisObject == null) {if (thatObject != null) {return false;}} else {if (!thisObject.equals(thatObject)) {return false;}}}return true;}@Overridepublic int hashCode() {return hashcode;}@Overridepublic String toString() {StringBuilder returnValue = new StringBuilder().append(hashcode).append(":").append(checksum);for (Object obj : updateList) {returnValue.append(":").append(obj);}return returnValue.toString();}@Overridepublic CacheKey clone() throws CloneNotSupportedException {CacheKey clonedCacheKey = (CacheKey) super.clone();clonedCacheKey.updateList = new ArrayList<>(updateList);return clonedCacheKey;}}
doUpdate:哈希计算- Mybatis 对于其 Key 的生成规则采取规则为
[mappedStatementId + offset + limit + SQL + queryParams + environment]生成一个哈希码。 - 而
doUpdate方法的object入参对象就是用于拼装哈希值的具体操作。
- Mybatis 对于其 Key 的生成规则采取规则为
equals:哈希 equal- 如果遇到相同哈希值,避免对象重复,那么 CacheKey 缓存 Key 重写了
equals对比方法。 - 这也是为什么在
doUpdate计算哈希方法时,把对象添加到updateList.add(object)集合中,就是用于这里的equals判断使用
- 如果遇到相同哈希值,避免对象重复,那么 CacheKey 缓存 Key 重写了
3.3.4 NULL值缓存key
NullCacheKey.java
package com.lino.mybatis.cache;/*** @description: NULL值缓存key*/
public class NullCacheKey extends CacheKey {private static final long serialVersionUID = 3704229911977019465L;public NullCacheKey() {super();}
}
3.4 定义缓存机制、占位符和修改配置文件
- 在 Mybatis 框架中默认情况下以及缓存是开启使用的,但是也支持用户可以自主关闭以及缓存。
<settings><!--缓存级别:SESSION/STATEMENT--><setting name="localCacheScope" value="SESSION"/>
</settings>
localCacheScope缓存机制的属性值有两个,SESSION、STATEMENT。STATEMENT:关闭一级缓存。
3.4.1 定义缓存机制
LocalCacheScope.java
package com.lino.mybatis.session;/*** @description: 本地缓存机制*/
public enum LocalCacheScope {/*** 本地缓存*/SESSION, STATEMENT
}
- LocalCacheScope 缓存机制是个枚举值配置,分别为:
SESSION, STATEMENTSESSION:为默认值,支持使用一级缓存。STATEMENT:不支持使用一级缓存。
3.4.2 定义占位符符
ExecutionPlaceholder.java
package com.lino.mybatis.executor;/*** @description: 占位符*/
public enum ExecutionPlaceholder {/*** 占位符*/EXECUTION_PLACEHOLDER
}
3.4.3 修改映射器语句类
MappedStatement.java
package com.lino.mybatis.mapping;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;...public MappedStatement() {}...public boolean isFlushCacheRequired() {return flushCacheRequired;}
}
3.4.4 修改配置项
Configuration.java
package com.lino.mybatis.session;import com.lino.mybatis.binding.MapperRegistry;
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.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: 配置项*/
public class Configuration {/*** 环境*/protected Environment environment;/*** 是否使用自动生成键值对*/protected boolean useGeneratedKeys = false;/*** 缓存机制,默认不配置的情况是 SESSION*/protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;...public LocalCacheScope getLocalCacheScope() {return localCacheScope;}public void setLocalCacheScope(LocalCacheScope localCacheScope) {this.localCacheScope = localCacheScope;}...}
- 添加
LocalCacheScope缓存机制。
3.5 在会话中缓存使用
- Mybatis 的会话创建 SqlSession 其实主要操作都集中在 Executor 执行器的抽象类中,通过抽象类的创建实例化
new PerpetualCache("LocalCache")一级缓存,并在执行器中完成缓存存放、使用、删除等操作。
3.5.1 创建缓存KEY
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);
}
- Executor 执行器接口有2个查询方法,一个含有缓存 Key,另外一个不含有。
- 含有缓存 Key 的
query方法,会被另外一个不含有缓存 Key 的query方法调用。
3.5.2 抽象执行器实现
BaseExecutor
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 {private org.slf4j.Logger logger = LoggerFactory.getLogger(BaseExecutor.class);protected Configuration configuration;protected Transaction transaction;protected Executor wrapper;/*** 本地缓存*/protected PerpetualCache localCache;private boolean closed;/*** 查询堆栈*/protected int queryStack = 0;public BaseExecutor(Configuration configuration, Transaction transaction) {this.configuration = configuration;this.transaction = transaction;this.wrapper = this;this.localCache = new PerpetualCache("LocalCache");}@Overridepublic int update(MappedStatement ms, Object parameter) throws SQLException {if (closed) {throw new RuntimeException("Executor was closed.");}clearLocalCache();return doUpdate(ms, parameter);}@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {if (closed) {throw new RuntimeException("Executor was closed.");}// 清理局部缓存,查询堆栈为0则清理。queryStack 避免递归调用清理if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();}List<E> list;try {queryStack++;// 根据cacheKey从localCache中查询数据list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list == null) {list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}if (queryStack == 0) {if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {clearLocalCache();}}return list;}private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);try {list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}// 存入缓存localCache.putObject(key, list);return list;}@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);}/*** 更新方法** @param ms 映射器语句* @param parameter 参数* @return 返回的是受影响的行数* @throws SQLException SQL异常*/protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;/*** 查询方法** @param ms 映射器语句* @param parameter 参数* @param rowBounds 分页记录限制* @param resultHandler 结果处理器* @param boundSql SQL对象* @param <E> 返回的类型* @return List<E>* @throws SQLException SQL异常*/protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;@Overridepublic Transaction getTransaction() {if (closed) {throw new RuntimeException("Executor was closed.");}return transaction;}@Overridepublic void commit(boolean required) throws SQLException {if (closed) {throw new RuntimeException("Cannot commit, transaction is already closed.");}clearLocalCache();if (required) {transaction.commit();}}@Overridepublic void rollback(boolean required) throws SQLException {if (!closed) {try {clearLocalCache();} finally {if (required) {transaction.rollback();}}}}@Overridepublic void clearLocalCache() {if (!closed) {localCache.clear();}}@Overridepublic CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {if (closed) {throw new RuntimeException("Executor was closed.");}CacheKey cacheKey = new CacheKey();cacheKey.update(ms.getId());cacheKey.update(rowBounds.getOffset());cacheKey.update(rowBounds.getLimit());cacheKey.update(boundSql.getSql());List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();for (ParameterMapping parameterMapping : parameterMappings) {Object value;String propertyName = parameterMapping.getProperty();if (boundSql.hasAdditionalParameter(propertyName)) {value = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}cacheKey.update(value);}if (configuration.getEnvironment() != null) {cacheKey.update(configuration.getEnvironment().getId());}return cacheKey;}@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;closed = true;}}/*** 关闭语句** @param statement 语句*/protected void closeStatement(Statement statement) {if (statement != null) {try {statement.close();} catch (SQLException ignore) {}}}
}
query:查询,不含缓存方法- 在
BaseExecutor#query主要新增加了关于缓存 Key 的创建,创建后调用重载的另外一个含有缓存 Key 的query方法。
- 在
创建缓存KEY
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {if (closed) {throw new RuntimeException("Executor was closed.");}CacheKey cacheKey = new CacheKey();cacheKey.update(ms.getId());cacheKey.update(rowBounds.getOffset());cacheKey.update(rowBounds.getLimit());cacheKey.update(boundSql.getSql());List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();for (ParameterMapping parameterMapping : parameterMappings) {Object value;String propertyName = parameterMapping.getProperty();if (boundSql.hasAdditionalParameter(propertyName)) {value = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}cacheKey.update(value);}if (configuration.getEnvironment() != null) {cacheKey.update(configuration.getEnvironment().getId());}return cacheKey;
}
- 缓存 Key 的创建,需要依赖于:
[mappedStatementId + offset + limit + SQL + queryParams + environment]信息构建出一个哈希值。 - 所以这里把这些对应的信息分别传递给
cacheKey#update方法。
查询数据缓存
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {if (closed) {throw new RuntimeException("Executor was closed.");}// 清理局部缓存,查询堆栈为0则清理。queryStack 避免递归调用清理if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();}List<E> list;try {queryStack++;// 根据cacheKey从localCache中查询数据list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list == null) {list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}if (queryStack == 0) {if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {clearLocalCache();}}return list;
}
- 在
query查询操作中,判断queryStack是否为0且是否刷新请求,如果是则会操作清空缓存。 - 接下来
queryStack自增以后通过localCache获取缓存数据。- 首次查询这个数据是空的,这个时候会进入到
queryFromDatabase方法从数据库查询数据并返回结果。 - 此外在 XML 配置中的缓存机制
LocalCacheScope会在这里判断,如果不是SESSION机制,则清空缓存。
- 首次查询这个数据是空的,这个时候会进入到
存放缓存数据
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);try {list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}// 存入缓存localCache.putObject(key, list);return list;
}
- 在一个会话内,首次执行
query查询的时候,会把数据库查询到的数据,使用localCache.putObject(key, list)存放缓存中。 - 同时在存放前,是使用占位符占位,查询后先清空再存放数据。
删除缓存数据
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {if (closed) {throw new RuntimeException("Executor was closed.");}clearLocalCache();return doUpdate(ms, parameter);
}@Override
public void commit(boolean required) throws SQLException {if (closed) {throw new RuntimeException("Cannot commit, transaction is already closed.");}clearLocalCache();if (required) {transaction.commit();}
}@Override
public void rollback(boolean required) throws SQLException {if (!closed) {try {clearLocalCache();} finally {if (required) {transaction.rollback();}}}
}@Override
public void clearLocalCache() {if (!closed) {localCache.clear();}
}@Override
public 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;closed = true;}
}
- 在前面,
insert、delete、update,都是调用执行器的update方法进行处理的。 - 这里的
update、commit、rollback、close,都会调用到clearLocalCache执行缓存清空。 - 因为
clearLocalCache也是对外的,所以你也可以在缓存机制为SESSION级别下,手动清空缓存操作。
3.6 解析缓存机制
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 {.../*** 解析配置:类型别名、插件、对象工厂、对象包装工厂、设置、环境、类型转换、映射器** @return Configuration*/public Configuration parse() {try {// 插件添加pluginElement(root.element("plugins"));// 设置settingElement(root.element("settings"));// 环境environmentsElement(root.element("environments"));// 解析映射器mapperElement(root.element("mappers"));} catch (Exception e) {throw new RuntimeException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}return configuration;}.../*** <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.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope")));}...}
- 基于 XMLConfigBuilder 配置构建器,解析配置在 XML 文件中的缓存机制,并把解析处理出来的内容放到
Configuration配置项中。
四、测试:一级缓存
4.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><plugins><plugin interceptor="com.lino.mybatis.test.plugin.TestPlugin"><property name="test00" value="100"/><property name="test01" value="200"/></plugin></plugins><settings><!--缓存级别:SESSION/STATEMENT--><setting name="localCacheScope" value="SESSION"/></settings><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url"value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><mappers><!--XML配置--><mapper resource="mapper/Activity_Mapper.xml"/></mappers>
</configuration>
4.2 单元测试
4.2.1 两次查询
ApiTest.java
@Testpublic void test_queryActivityById() {// 1.获取映射器对象IActivityDao dao = sqlSession.getMapper(IActivityDao.class);// 2.测试验证Activity activity = new Activity();activity.setActivityId(100001L);logger.info("测试结果:{}", JSON.toJSONString(dao.queryActivityById(activity)));logger.info("测试结果:{}", JSON.toJSONString(dao.queryActivityById(activity)));}
- 在单元测试验证中,开启
session后执行力两次相同的查询。验证缓存的使用
测试结果
14:16:03.602 [main] INFO c.l.m.d.pooled.PooledDataSource - Created connention 1610525991.
拦截SQL:SELECT activity_id, activity_name, activity_desc, create_time, update_timeFROM activitywhere activity_id = ?
14:16:03.617 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:100001
14:16:03.617 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果:{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}
14:16:03.617 [main] INFO c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IActivityDao.queryActivityById parameter:{"activityId":100001}
14:16:03.617 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:activityId propertyType:class java.lang.Long
14:16:03.617 [main] INFO c.l.m.cache.impl.PerpetualCache - 一级缓存
key:-1917147986:525044925:com.lino.mybatis.test.dao.IActivityDao.queryActivityById:0:2147483647:SELECT activity_id, activity_name, activity_desc, create_time, update_timeFROM activitywhere activity_id = ?:100001:development
val:[{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}]
14:16:03.617 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果:{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}

- 从测试结果缓存的日志打印和断点调试缓存查询中可以看到,执行第二次查询的时候,就可以通过缓存获取数据了,证明一级缓存生效了。
4.2.2 提交会话
ApiTest.java
@Test
public void test_queryActivityById() {// 1.获取映射器对象IActivityDao dao = sqlSession.getMapper(IActivityDao.class);// 2.测试验证Activity activity = new Activity();activity.setActivityId(100001L);logger.info("测试结果:{}", JSON.toJSONString(dao.queryActivityById(activity)));// 提交会话sqlSession.commit();logger.info("测试结果:{}", JSON.toJSONString(dao.queryActivityById(activity)));
}
测试结果
14:19:50.865 [main] INFO c.l.m.d.pooled.PooledDataSource - Created connention 1610525991.
拦截SQL:SELECT activity_id, activity_name, activity_desc, create_time, update_timeFROM activitywhere activity_id = ?
14:19:50.880 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:100001
14:19:50.880 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果:{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}
14:19:50.880 [main] INFO c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IActivityDao.queryActivityById parameter:{"activityId":100001}
14:19:50.880 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:activityId propertyType:class java.lang.Long
拦截SQL:SELECT activity_id, activity_name, activity_desc, create_time, update_timeFROM activitywhere activity_id = ?
14:19:50.880 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:100001
14:19:50.880 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果:{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}

- 添加
sqlSession.commit(),执行会话提交,则会清空缓存,此时的二次查询已经不会从缓存中获取数据,而是要去数据库读取。
4.2.3 关闭会话
ApiTest.java
@Test
public void test_queryActivityById() {// 1.获取映射器对象IActivityDao dao = sqlSession.getMapper(IActivityDao.class);// 2.测试验证Activity activity = new Activity();activity.setActivityId(100001L);logger.info("测试结果:{}", JSON.toJSONString(dao.queryActivityById(activity)));// 提交会话sqlSession.commit();// 关闭会话sqlSession.close();logger.info("测试结果:{}", JSON.toJSONString(dao.queryActivityById(activity)));
}
测试结果
14:22:22.512 [main] INFO c.l.m.d.pooled.PooledDataSource - Created connention 1610525991.
拦截SQL:SELECT activity_id, activity_name, activity_desc, create_time, update_timeFROM activitywhere activity_id = ?
14:22:22.512 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:100001
14:22:22.527 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果:{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}
14:22:22.527 [main] INFO c.l.m.d.pooled.PooledDataSource - Returned connection 1610525991 to pool.
14:22:22.527 [main] INFO c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IActivityDao.queryActivityById parameter:{"activityId":100001}
14:22:22.527 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:activityId propertyType:class java.lang.Longjava.lang.RuntimeException: Executor was closed.at com.lino.mybatis.executor.BaseExecutor.createCacheKey(BaseExecutor.java:177)at com.lino.mybatis.executor.BaseExecutor.query(BaseExecutor.java:107)at com.lino.mybatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:54)at com.lino.mybatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:39)at com.lino.mybatis.binding.MapperMethod.execute(MapperMethod.java:50)at com.lino.mybatis.binding.MapperProxy.invoke(MapperProxy.java:36)at com.sun.proxy.$Proxy4.queryActivityById(Unknown Source)at com.lino.mybatis.test.ApiTest.test_queryActivityById(ApiTest.java:48)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)at org.junit.runners.ParentRunner.run(ParentRunner.java:236)at org.junit.runner.JUnitCore.run(JUnitCore.java:157)at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)

- 新增
sqlSession.close(),当执行会话关闭后,调用rollback(forceRollback)方法,缓存会被清空,同时因为会话已经关闭,所以再执行的查询会报错:Executor was closed。
五、总结:一级缓存
- 一级缓存属于轻量级缓存,仅限于在一次 session 会话内完成,所以整个模型也可以简单的理解为:
- 使用 HashMap 存放缓存数据,当有发生对数据库的操作,则进行缓存清空。
- 通常如果说你的应用是独立的单体应用,或者开发体量较小的运营后台类应用,可能不会发生任何由于缓存所产生的脏读问题。但当你的应用是分布式部署,并且你的 session 会话过长,执行了大范围的 select 操作,那么要注意此时数据的有效性。
- 如果都是类似这样的场景,你可能需要关闭一级缓存,或者在关键节点及时手动清空缓存。
- 缓存的设计比较小巧,整个结构并不算复杂,但它的设计贯穿了整个 session 的生命周期,这也是提醒我们在设计一个业务流程时,要考虑全局的流程状态流转,避免一小部分的问题影响全局的结果。
- 另外关于 CacheKey 缓存 Key 的哈希设计也可以借鉴,如果有大长字符串拼接需要作为 key 使用的场景。
相关文章:
手写Mybatis:第18章-一级缓存
文章目录 一、目标:一级缓存二、设计:一级缓存三、实现:一级缓存3.1 工程结构3.2 一级缓存类图3.3 一级缓存实现3.3.1 定义缓存接口3.3.2 实现缓存接口3.3.3 创建缓存KEY3.3.4 NULL值缓存key 3.4 定义缓存机制、占位符和修改配置文件3.4.1 定…...
哈夫曼编码实现文件的压缩和解压
程序示例精选 哈夫曼编码实现文件的压缩和解压 如需安装运行环境或远程调试,见文章底部个人QQ名片,由专业技术人员远程协助! 前言 这篇博客针对《哈夫曼编码实现文件的压缩和解压》编写代码,代码整洁,规则࿰…...
解决六大痛点促进企业更好使用生成式AI,亚马逊云科技顾凡采访分享可用方案
亚马逊云科技大中华区战略业务发展部总经理顾凡在接受21世纪经济报道记者专访时表示,生成式人工智能将从四个方面为企业带来机遇:第一是创造全新的客户体验;第二是提高企业内部员工的生产力;第三是帮助企业提升业务运营效率&#…...
Qt 定时器放在线程中执行,支持随时开始和停止定时器。
前言:因为项目需要定时检查网络中设备是否能连通,需要定时去做ping操作,若是网络不通,则ping花费时间比较久(局域网大概4秒钟才能结束,当然如果设置超时时间啥的,也能很快返回,就是会…...
java 过滤器 接口(API)验证入参,验签(sign) Demo
java 过滤器 接口(API)验证入参,验签(sign) Demo 一、思路 1、配置yml文件; 2、创建加载配置文件类; 3、继承 OncePerRequestFilter 重写方法 doFilterInternal; 4、注册自定义过滤器; 二、步骤 1、配置yml文件; ###系…...
独家!微信正在灰测一款全新消金产品
来源 | 镭射财经(leishecaijing) 「镭射财经」独家获悉,微信将推出一款名为“微信分期”的新消费信贷产品,目前该产品正处于小范围灰测阶段,还未正式上线。上线后,微信将运营微信分付和微信分期两款自营消…...
阿秀C++笔记-学习记录
81.C中的组合和继承相比的优缺点 在C中组合一对象系用描述对象包对象系组一个拥对象例其变合类的含的现。这的量类当有员被创建。 以下一个示例,展示了在C中如何实现组合关系: class Engine {// Engine class definition... };class Car {Engine engi…...
前端入门到入土?
文章目录 前言http和https的区别,https加密的原理是?区别https的加密原理 TCP为什么要三次握手?proxy代理的原理?内存泄漏?什么是内存泄漏?为什么会有内存泄漏?内存泄漏的情况?如何防…...
架构设计基础设施保障IaaS之网络
目录 1 DNS运用1.1 DNS功能作用1.2 DNS配置实践 2 DNS生产最佳实践方案2.1 全球加速功能2.2 不同运营商的加速方案2.3 全球业务高可用方案2.4 跨地域负载均衡 3 DNS域名劫持解决方案4 CDN剖析4.1 CDN原理4.2 缓存过期配置处理流程4.3 缓存配置规则 5 CDN运用6 CDN最佳实践方案6…...
zabbix安装部署
前期准备:安装mysql数据库和nginx 一、下载zabbix rpm -Uvh https://repo.zabbix.com/zabbix/4.4/rhel/7/x86_64/zabbix-release-4.4-1.el7.noarch.rpm yum-config-manager --enable rhel-7-server-optional-rpms yum install epel-release numactl yum install…...
零碎的C++
构造函数和析构函数 构造函数不能是虚函数,而析构函数可以是虚函数。原因如下: 构造函数不能是虚函数,因为在执行构造函数时,对象还没有完全创建,还没有分配内存空间,也没有初始化虚函数表指针。如果构造…...
模糊测试面面观 | 模糊测试是如何发现异常情况的?
协议模糊测试是一种用于评估通信协议、文件格式和API实现系统安全性和稳定性的关键技术。在模糊测试过程中,监视器扮演着关键角色,它们能够捕获异常情况、错误响应、资源利用等,为测试人员提供有价值的信息,有助于发现潜在漏洞和问…...
C#备份数据库文件
c#备份数据库文件完整代码 sqlServer 存储过程: USE [PSIDBase] GO /****** Object: StoredProcedure [dbo].[sp_BackupDB] Script Date: 2023/8/31 16:49:02 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GOALTER procedure [dbo].[sp_BackupDB]…...
行军遇到各种复杂地形怎么处理?
行军遇到各种复杂地形怎么处理? 【安志强趣讲《孙子兵法》第30讲】 【原文】 凡军好高而恶下,贵阳而贱阴,养生而处实,军无百疾,是谓必胜。 【注释】 阳,太阳能照到的地方。阴,太阳照不到的地方。…...
Python Number(数字).............................................
Python Number 数据类型用于存储数值。 数据类型是不允许改变的,这就意味着如果改变 Number 数据类型的值,将重新分配内存空间。 以下实例在变量赋值时 Number 对象将被创建: var1 1 var2 10您也可以使用del语句删除一些 Number 对象引用。 del语句…...
设置 Hue Server 与 Hue Web 界面之间的会话超时时间
设置 Hue Server 与 Hue Web 界面之间的会话超时时间 在 CDH 的 Hue 中,Auto Logout Timeout 参数表示用户在不活动一段时间后将自动注销(登出)的超时时间。当用户在 Hue 中处于不活动状态超过该设定时间时,系统将自动注销用户&am…...
openGauss学习笔记-57 openGauss 高级特性-并行查询
文章目录 openGauss学习笔记-57 openGauss 高级特性-并行查询57.1 适用场景与限制57.2 资源对SMP性能的影响57.3 其他因素对SMP性能的影响57.4 配置步骤 openGauss学习笔记-57 openGauss 高级特性-并行查询 openGauss的SMP并行技术是一种利用计算机多核CPU架构来实现多线程并行…...
软考(1)-面向对象的概念
目录 一. 软考基本信息 1. 软考时间: 2. 软考科目: 3.专业知识介绍 -- 综合知识考点分布 4. 专业介绍 -- 软件设计考点分布 二. 面向对象概念 1. 封装 考点一:对象 考点二:封装private 2. 继承 考点三:类 考…...
深度学习推荐系统(四)WideDeep模型及其在Criteo数据集上的应用
深度学习推荐系统(四)Wide&Deep模型及其在Criteo数据集上的应用 在2016年, 随着微软的Deep Crossing, 谷歌的Wide&Deep以及FNN、PNN等一大批优秀的深度学习模型被提出, 推荐系统全面进入了深度学习时代, 时至今日&#x…...
第十二章 YOLO的部署实战篇(中篇)
cuda教程目录 第一章 指针篇 第二章 CUDA原理篇 第三章 CUDA编译器环境配置篇 第四章 kernel函数基础篇 第五章 kernel索引(index)篇 第六章 kenel矩阵计算实战篇 第七章 kenel实战强化篇 第八章 CUDA内存应用与性能优化篇 第九章 CUDA原子(atomic)实战篇 第十章 CUDA流(strea…...
Arduino轻量URL编解码库:RFC 3986兼容的嵌入式urlencode/urldecode实现
1. 项目概述URLCode 是一个专为 Arduino 平台设计的轻量级 URL 编解码库,其核心目标是提供符合 RFC 3986 标准的application/x-www-form-urlencoded格式字符串的编码(urlencode)与解码(urldecode)能力。该库不依赖 Ard…...
智能体设计模式详解 B# 附录E:命令行中的 AI 智能体
【全景】基于双向协同的能力融合设计 Agent设计模式 V1:基于双向协同的能力融合设计 39种设计模式分层清单 A#0 智能体设计模式全景(上):大模型如何“思考”?(认知视角导论) Agent Design Pattern Catalogue: A Collection of Architectural Patterns for Foundation Mo…...
大模型解决方案专家,火山方舟:用大模型赋能企业,成本、效果、落地难题一网打尽!
火山方舟作为大模型解决方案专家,依托豆包大模型家族及智能模型路由等技术,打造企业级服务平台。核心价值在于解决模型效果、推理成本、落地难度三大挑战。提供更强模型能力、更低成本推理、更易落地应用三大解决方案,助力企业高效落地AI应用…...
Intel集成显卡加速PyTorch:从环境搭建到模型训练实战指南
1. 为什么选择Intel集成显卡加速PyTorch? 很多朋友刚接触深度学习时,第一反应都是"得买块N卡"。但你可能不知道,手头的Intel集成显卡也能跑PyTorch,而且效果还不错。我去年给团队配开发机时,就专门测试过Int…...
2024年Image Caption数据集全攻略:从COCO到TextCaps的实战选择指南
2024年Image Caption数据集实战指南:从基础到行业落地的深度解析 当算法工程师第一次接触图像描述任务时,面对琳琅满目的数据集选择往往会陷入困惑——COCO的通用性、TextCaps的文本理解要求、VizWiz的特殊场景适用性,每个数据集都有其独特的…...
PPTist:重新定义在线演示文稿创作体验
PPTist:重新定义在线演示文稿创作体验 【免费下载链接】PPTist PowerPoint-ist(/pauəpɔintist/), An online presentation application that replicates most of the commonly used features of MS PowerPoint, allowing for the editing a…...
【openbmc8】mctp pldm
文章目录 1.mctp协议 1.1 mctp通用报文 1.2 mctp over i2c packet format 2.驱动分析 2.1 mctp pcie vdm 2.1 用户层操作代码流程 2.2 用户层操作测试 3.dbus适配 1.mctp协议 1.1 mctp通用报文 谁分配EID谁就是bus owner。mctp建立关联后都用EID(类似ip地址)通信:下图最后…...
别再只用BCE了!用PyTorch实现ASL损失函数,搞定多标签分类中的样本不均衡
多标签分类新范式:PyTorch实战ASL损失函数解决样本不均衡难题 在图像标注、医学诊断或文本情感分析等多标签分类任务中,我们常常遇到一个棘手问题——某些标签的出现频率可能比其他标签高出几个数量级。想象一下,当你构建一个商品标签系统时&…...
OBS Studio高级玩家指南:用这5个隐藏功能让你的直播画质翻倍
OBS Studio高级玩家指南:用这5个隐藏功能让你的直播画质翻倍 如果你已经熟悉OBS Studio的基础操作,却总感觉直播画质离专业级差一口气,这篇文章将带你解锁那些被90%用户忽略的核弹级功能。从多轨道音频的精细控制到动态比特率的智能适配&…...
效率提升300%:OpenClaw+Phi-3-vision-128k-instruct重构我的学术工作流
效率提升300%:OpenClawPhi-3-vision-128k-instruct重构我的学术工作流 1. 从手动到自动的学术工作流革命 作为一名每天需要处理大量文献、实验数据和演示材料的科研工作者,我曾经花费近40%的工作时间在重复性文档处理上——截图标注、图表整理、笔记归…...
