当前位置: 首页 > news >正文

mybatis中的缓存(一级缓存、二级缓存)

文章目录

  • 前言
  • 一、MyBatis 缓存概述
  • 二、一级缓存
    • 1_初识一级缓存
    • 2_一级缓存命中原则
      • 1_StatementId相同
      • 2_查询参数相同
      • 3_分页参数相同
      • 4_sql 语句
      • 5_环境
    • 3_一级缓存的生命周期
      • 1_缓存的产生
      • 2_缓存的销毁
      • 3_网传的一些谣言
    • 4_一级缓存核心源码
    • 5_总结
  • 三、二级缓存
    • 1_开启二级缓存
    • 2_二级缓存命中原则
    • 3_二级缓存的产生
    • 4_缓存失效情况
    • 5_二级缓存核心源码
  • 五、缓存机制的注意事项
  • 六_总结

前言

MyBatis 是一个优秀的持久层框架,它不仅简化了数据库交互的开发过程,还提供了强大的缓存机制以提升性能。本文将详细介绍 MyBatis 的缓存机制,包括一级缓存、二级缓存的工作原理、配置和注意事项。

一、MyBatis 缓存概述

为了减轻数据库的访问压力,mybatis提供了缓存功能,如果命中缓存将直接返回缓存中的结果实例,不再需要查询数据库。

在这里插入图片描述

MyBatis 提供了两级缓存机制:

  1. 一级缓存(Local Cache):也称为本地缓存,是 SqlSession 级别的缓存。
  2. 二级缓存(Global Cache):也称为全局缓存,是 SqlSessionFactory 映射级别的缓存。

二、一级缓存

一级缓存是 SqlSession 级别的缓存,在同一个 SqlSession 期间,相同的查询结果会被缓存并复用,减少数据库访问次数。它的作用范围是 SqlSession,默认是开启的。

1_初识一级缓存

为了方便读者验证一级缓存的存在,先将mybatis最基础的环境搭建完成。(基础环境没有与sprig整合)

所需的pom.xml依赖:

<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.34</version>
</dependency>
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.11</version>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version>
</dependency>
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope>
</dependency>

首先是maybatis的基础配置文件 ,配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource) 以及决定事务作用域和控制方式的事务管理器(TransactionManager)

这里是一个最简单的基础配置,resources\mybaties.xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!--    控制台打印--><settings><setting name="logImpl" value="org.apache.ibatis.logging.stdout.StdOutImpl"/></settings><environments default="development"><environment id="development"><!--    使用jdbc并配置数据源--><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://127.0.0.1:3306/bilibili"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><mappers><mapper resource="mappers/tempMapper.xml"/></mappers>
</configuration>

sql映射文件 resources/mappers/mapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shen.spring.dao.TempDao"><select id="getById" parameterType="int" resultType="com.shen.spring.entity.TempEntity">select * from temp where id = #{id}</select>
</mapper>

测试使用的表及其数据所需sql:

drop table if exists temp;
create table temp
(id int auto_increment primary key ,value1 varchar(100) null ,value2 varchar(100) null 
);
insert into  temp(value1, value2) VALUES ('111111','aaaaaa');
insert into  temp(value1, value2) VALUES ('222222','bbbbbb');
insert into  temp(value1, value2) VALUES ('333333','cccccc');
insert into  temp(value1, value2) VALUES ('444444','dddddd');

对应的实体对象:

import lombok.Data;
import lombok.ToString;/*** @author shenyang* @version 1.0* @info SpringBoot17* @since 2024/7/22 下午3:41*/
@Data
@ToString
public class TempEntity {private Integer id;private String value1;private String value2;
}

对应的查询接口:

import com.shen.spring.entity.TempEntity;/*** @author shenyang* @version 1.0* @info SpringBoot17* @since 2024/7/22 下午3:42*/
public interface TempDao {TempEntity getById(int id);
}

最后编写测试用例证明一级缓存的存在:

package com.shen.spring;import com.shen.spring.entity.TempEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.junit.Test;import java.io.IOException;
import java.io.InputStream;/*** @author shenyang* @version 1.0* @info SpringBoot17* @since 2024/7/22 下午3:58*/
@Slf4j
public class TempDaoTest {@Testpublic void test() throws IOException {//读取xml文件InputStream inputStream =Resources.getResourceAsStream("mybaties.xml");//通过SqlSessionFactoryBuilder创建sqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//拿到执行sql语句的一个会话SqlSession sqlSession = sqlSessionFactory.openSession();// 第一次查询,会从数据库获取数据TempEntity tempEntity1 = sqlSession.selectOne("com.shen.spring.dao.TempDao.getById", 1);log.info("{}",tempEntity1);// 第二次查询,会从缓存获取数据TempEntity tempEntity2 = sqlSession.selectOne("com.shen.spring.dao.TempDao.getById", 1);log.info("{}",tempEntity2);System.out.println(tempEntity1 == tempEntity2);}
}

输出结果:

在这里插入图片描述

根据控制台输出结果可以发现第一次查询有sql语句日志输出,代表查询了数据库。第二次没有sql语句输出,没有查询数据库,而是走的缓存。并且这两次查询都是返回同一个对象(控制台结果打印true)。这正验证了mybatis一级缓存的存在。

另外,如果不了解SqlSession 这个mybatis提供的 java API 可以去官网上查看:Mybatis-Java API。

最后,给一个上述测试代码的时序图作为结尾吧:

  • 当 SqlSession 执行查询操作时,首先会在缓存中查找是否有相同的查询结果。
  • 如果缓存中存在相同的结果,直接返回。
  • 如果缓存中不存在,执行数据库查询,并将结果存入缓存中。

在这里插入图片描述

2_一级缓存命中原则

所谓的命中原则就是指:Mybatis是怎样判断某两次查询是完全相同的查询?

为什么这么说呢?因为如果两次查询时完全相同的查询,且上一次查询时是有缓存的,mybatis就不会查询数据库而是直接返回上一次查询的结果。

1_StatementId相同

先看第一个条件两次查询的StatementId必须相同,否则无法命中缓存,即使两个查询语句、参数等完全一样。这个StatementId其实就是我们定义的daoclass内的方法名:

在这里插入图片描述
现在我们进行测试(我的dao层和mapper.xml文件已经修改完成了,这里就不附上完整步骤了)

修改测试代码:

 @Testpublic void test() throws IOException {InputStream inputStream =Resources.getResourceAsStream("mybaties.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();TempEntity tempEntity1 = sqlSession.selectOne("com.shen.spring.dao.TempDao.getById1", 1);log.info("{}",tempEntity1);TempEntity tempEntity2 = sqlSession.selectOne("com.shen.spring.dao.TempDao.getById2", 1);log.info("{}",tempEntity2);System.out.println(tempEntity1 == tempEntity2);sqlSession.close();//关闭sqlSession  关闭sql会话}

输出结果:

在这里插入图片描述

2_查询参数相同

要求两次查询的查询参数相同(这里不做过多测试 )。

在这里插入图片描述

但是这里还有一个问题这里的参数不是指SqlSession调用selectXXX()方法中的参数,而是指最终执行sql语句中的参数。

也就是说:要求传递给SQL的查询参数必须相同,否则无法命中缓存

比如如下例子:

<select id="getById3" parameterType="java.util.Map" resultType="com.shen.spring.entity.TempEntity">select * from temp where id = #{id}
</select>

测试方法:

 @Testpublic void test() throws IOException {InputStream inputStream =Resources.getResourceAsStream("mybaties.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();Map<String,Object> params1 = new HashMap<>();params1.put("id",1);params1.put("test",2);TempEntity tempEntity1 = sqlSession.selectOne("com.shen.spring.dao.TempDao.getById3", params1);Map<String,Object> params2 = new HashMap<>();params2.put("id",1);params2.put("test",2);log.info("{}",tempEntity1);TempEntity tempEntity2 = sqlSession.selectOne("com.shen.spring.dao.TempDao.getById3", params2);log.info("{}",tempEntity2);System.out.println(tempEntity1 == tempEntity2);}

虽然这里的两次调用传递的Map对象不是同一个,但是因为传递给sql中的参数是相同的,所以可以查询到缓存(所以只有一次sql查询的日志输出):

在这里插入图片描述

3_分页参数相同

分页参数必须相同,否则无法命中缓存。缓存的粒度是整个分页查询结果,而不是结果中的每个对象

注意:这里的分页我们用的比较少,这里的分页实际上是把数据库中的所有数据都查出来做个物理分页。而不是在数据库层面上用sql脚本进行分页。如下图,这里的 RowBounds就是用来分页的。

在这里插入图片描述

这里的分页查询方法,即使两次查询传递的不是同一个RowBounds的实例,只要它们的传递参数相同就会命中缓存。反之参数不同就不会命中缓存。

<select id="list"  resultType="com.shen.spring.entity.TempEntity">select * from temp where 1=1
</select>

测试方法:

//dao下添加
List<TempEntity> list();
//测试方法
@Test
public void test2() throws IOException {InputStream inputStream =Resources.getResourceAsStream("mybaties.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();RowBounds rowBounds1 = new RowBounds(0,1);List<TempEntity> tempEntity1 = sqlSession  //注意这里的参数列表要对上,第三个参数的位置才是RowBounds .selectList("com.shen.spring.dao.TempDao.list", null,rowBounds1);log.info("{}",tempEntity1);RowBounds rowBounds2 = new RowBounds(0,2);//两次参数不同List<TempEntity> tempEntity2 = sqlSession .selectList("com.shen.spring.dao.TempDao.list",null, rowBounds2);log.info("{}",tempEntity2);System.out.println(tempEntity1 == tempEntity2);
}

结果如下:

执行的sql也刚好验证了是查询了整个数据,然后进行物理分页。

在这里插入图片描述

4_sql 语句

要求传递给JDBC的SQL语句必须完全相同

看如下案例:

mapper.xml文件,注意这两个查询的查询结果完全一样,因为1=1必然是真,不过Mybatis是不考虑这个的:

<select id="getById4" parameterType="java.util.Map" resultType="com.shen.spring.entity.TempEntity"><if test="type == 1">select * from temp where id = #{id}</if><if test="type == 2">select * from temp where 1=1 and id = #{id}</if>
</select>

dao层接口中的方法

TempEntity getById4(Map map);

测试代码:

@Test
public void test4() throws IOException {InputStream inputStream =Resources.getResourceAsStream("mybaties.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();Map<String,Object> params1 = new HashMap<>();params1.put("id",1);params1.put("type",1);TempEntity tempEntity1 = sqlSession.selectOne("com.shen.spring.dao.TempDao.getById4", params1);Map<String,Object> params2 = new HashMap<>();params2.put("id",1);params2.put("type",2);//根据动态sql会执行不同的sql语句log.info("{}",tempEntity1);TempEntity tempEntity2 = sqlSession.selectOne("com.shen.spring.dao.TempDao.getById4", params2);log.info("{}",tempEntity2);System.out.println(tempEntity1 == tempEntity2);
}

运行结果:

在这里插入图片描述

5_环境

要求执行环境必须相同

其实我们在mapper.xml中 可以配置多个环境:

在这里插入图片描述

并且,在使用 SqlSessionFactoryBuilder().build(??) 方法构建 SqlSessionFactory 对象时其实还可以指定所使用环境。

public SqlSessionFactory build(InputStream inputStream, String environment) {return this.build((InputStream)inputStream, environment, (Properties)null);
}

不过这里我们无法进行测试,因为环境的切换,必然会导致创建不同的SqlSession对象。不过二级缓存中可以执行这个测试。

3_一级缓存的生命周期

学习到现在是不是一值有个疑惑? Mybatis一级缓存是什么时候产生的?又是什么时候销毁的?我们向下继续进行了解。

1_缓存的产生

我们的第一印象是:

在这里插入图片描述

执行<select/>类型的statement的时候会产生缓存。

但是真的是这样么?我们进行如下测试–使用update方法调用<select\>标签:

@Test
public void testSelectAsUpdate() throws IOException {InputStream inputStream =Resources.getResourceAsStream("mybaties.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();sqlSession.update("com.shen.spring.dao.TempDao.getById", 1);sqlSession.update("com.shen.spring.dao.TempDao.getById", 1);
}

执行结果:

在这里插入图片描述

根据执行的结果可以知道,产生缓存的实际上是SqlSession的select方法:

在这里插入图片描述

2_缓存的销毁

缓存创建了之后又是如何被销毁的呢?先说结论:

  • 当前的 SqlSession 关闭。
  • 执行 INSERTUPDATEDELETE 操作(与表无关)。
  • 调用了 SqlSessionclearCache() 方法主动清除缓存。
  • 调用了 SqlSessionCommit()Rollback

接下来进行验证:

SqlSession 关闭缓存会被销毁

由于SqlSession会话关闭之后再次使用SqlSession会报错,想要进行验证需要特殊的方法。

通过debug的方式查看,这个LocalCache(一个HashMap)就是我们一级缓存存储的地方:

在这里插入图片描述

了解了类图结构之后,我们就可以通过反射的方式拿到一级缓存。

在这里插入图片描述

测试代码:

@Test
public void test5() throws IOException, NoSuchFieldException, IllegalAccessException {InputStream inputStream =Resources.getResourceAsStream("mybaties.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();// 第一次查询,会从数据库获取数据TempEntity tempEntity1 = sqlSession.selectOne("com.shen.spring.dao.TempDao.getById", 1);log.info("{}",tempEntity1);//从sqlSession的实现类DefaultSqlsession的成员变量中拿到cachingExecutorField executorField = sqlSession.getClass().getDeclaredField("executor");executorField.setAccessible(true);CachingExecutor cachingExecutor =(CachingExecutor) executorField.get(sqlSession);//从实例中获取//从CachingExecutor的成员变量中拿到SimpleExecutorField declaredField =cachingExecutor.getClass().getDeclaredField("delegate");declaredField.setAccessible(true);SimpleExecutor simpleExecutor =(SimpleExecutor) declaredField.get(cachingExecutor);//从SimpleExecutor中的父类中的成员变量中拿到PerpetualCache字段Field localCacheField =simpleExecutor.getClass().getSuperclass().getDeclaredField("localCache");localCacheField.setAccessible(true);PerpetualCache perpetualCache = (PerpetualCache) localCacheField.get(simpleExecutor);//最后从perpetualCache中拿到存储缓存的MapField cacheField = perpetualCache.getClass().getDeclaredField("cache");cacheField.setAccessible(true);Map<Object,Object> map= (Map<Object, Object>) cacheField.get(perpetualCache);//遍历结果Set<Map.Entry<Object, Object>> entries = map.entrySet();int size1 = entries.size();System.out.println(size1);if (size1>0){for (Map.Entry<Object, Object> entry : entries) {log.info("Map中数据结果为:{} = {}",entry.getKey(),entry.getValue());}}sqlSession.close();//会话关闭后再次遍历int size2 = entries.size();System.out.println(size2);if (size2>0){for (Map.Entry<Object, Object> entry : entries) {log.info("Map中数据结果为:{} = {}",entry.getKey(),entry.getValue());}}
}

运行结果,sqlSession关闭后缓存使用的map中没有元素了,恰好证明了第一个结论:

在这里插入图片描述

这里其他的情况并不像第一条这样比较特殊:第二第三条感兴趣的可以自行进行验证,参考我们之前的代码,这里我就不一一验证了。

对第二条的 “与表无关” 进行一个解释,这里的与表无关指的是即使我们执行 INSERTUPDATEDELETE 操作与<Select/>的不是同一张表也会清除缓存。

<select id="getById" parameterType="int" resultType="com.shen.spring.entity.TempEntity">select * from temp where id = #{id}
</select>
<select id="getById2" parameterType="int" resultType="com.shen.spring.entity.TempEntity">select * from test where id = #{id}
</select>
@Test
public void test9() throws IOException {//读取xml文件InputStream inputStream =Resources.getResourceAsStream("mybaties.xml");//通过SqlSessionFactoryBuilder创建sqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//拿到执行sql语句的一个会话SqlSession sqlSession = sqlSessionFactory.openSession();// 第一次查询,会从数据库获取数据TempEntity tempEntity1 = sqlSession.selectOne("com.shen.spring.dao.TempDao.getById", 1);log.info("{}",tempEntity1);//执行更新操作,并且与Session1操作的不是同一个表 这里是test表sqlSession.update("com.shen.spring.dao.TempDao.getById2", 1);// 第二次查询,会从缓存获取数据TempEntity tempEntity2 = sqlSession.selectOne("com.shen.spring.dao.TempDao.getById", 1);log.info("{}",tempEntity2);System.out.println(tempEntity1 == tempEntity2);
}

运行结果:

在这里插入图片描述

至于第三条,在执行 SqlSession.clearCache()之后,缓存会被清空,第二次查询会查数据库。

3_网传的一些谣言

有些人总说Mybatis一级缓存会存在脏读问题,这是不正确的,仔细想想设计的人如果发现有这么大的问题会不会让一级缓存一直默认开启,肯定是不能接受的。

相反,我认为它反而解决了脏读的问题。我们看如下例子:

在这里插入图片描述

根据如上图不难猜到网上的错误理解:由于Session2已经将张三的年龄由18岁修改为20岁,而Session1第二次查询由于查询的是缓存所以得到的张三年龄是18岁,产生了脏读(这里的脏读不是指的数据库中的那种查到了未提交的数据,而是网上的错误理解即:mybatis查询不到最新的数据,也就是查询到了以前的脏数据)。

首先强调,由于关闭Session、执行Commit、执行RollBack都会清空Mybatis一级缓存,所以实际上:Mybatis一级缓存的生命周期是在数据库事务的生命周期之内的

我们先回顾一下数据库事务中的脏读问题,如下图:

在这里插入图片描述
数据库中的脏读(读未提交)概念如下:事务1读取了事务2修改但是尚未提交的数据,如果事务2发生回滚则事务1读取的数据就变成了错误数据,也称为脏数据

我们看看两次的对比:

在这里插入图片描述

所以这里我们可以发现,反而mybatis解决了数据库事务的脏读问题,即使数据库事务发生脏读,mybatis也不会发生脏读问题。

而且我们到现在也可以很容易看出来(根据第一张图,查不到Session2更新并提交的数据),一级缓存甚至解决了不可重复读和幻读的问题

总结:

问题read uncommittedread committedrepeatable readserializableMyBatis 一级缓存
丢失更新避免避免避免避免
脏读避免避免避免避免避免
不可重复读避免避免避免避免
幻读避免避免避免

4_一级缓存核心源码

接下来笔者将带领大家了解Mybatis的核心源码,加深对Mybatis一级缓存的理解。

还记得我们之前画的那张类图吗?SqlSession调用 SelectOne方法其实调用的是它的默认实现也就是 DefaultSqlSession中的方法(并且可以看到selectOne其实调用的是selectList方法):

public <T> T selectOne(String statement, Object parameter) {//调用了selectList方法List<T> list = this.selectList(statement, parameter);if (list.size() == 1) {return list.get(0);} else if (list.size() > 1) {throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());} else {return null;}
}

在 selectList调用链的最深处可以看到这个方法:

private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {List var6;try {//将我们定义的mapper.xml中的内容转换成对象 即 com.shen.spring.dao.TempDao.getByIdMappedStatement ms = this.configuration.getMappedStatement(statement);var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);} catch (Exception var10) {Exception e = var10;throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}return var6;
}

this.executor.query这段关键代码所在的位置在Executor的实现类CachingExecutor中:

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameterObject);//这段代码是Mybatis缓存生成key依赖的参数,也就是我们一级缓存的命中原则CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

这里的 CacheKey 创建策略源码在BaseExecutor.class中:

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSqif (this.closed) {throw new ExecutorException("Executor was closed.");} else {CacheKey cacheKey = new CacheKey();//update是对传递的对像的hashcode进行混合运算//StatementIdcacheKey.update(ms.getId());//分页参数cacheKey.update(rowBounds.getOffset());cacheKey.update(rowBounds.getLimit());//sql语句cacheKey.update(boundSql.getSql());//拿到方法的参数,对方法参数进行循环List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();Iterator var8 = parameterMappings.iterator();while(var8.hasNext()) {ParameterMapping parameterMapping = (ParameterMapping)var8.next();if (parameterMapping.getMode() != ParameterMode.OUT) {String propertyName = parameterMapping.getProperty();Object value;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 = this.configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}//方法参数cacheKey.update(value);}}

除了 CacheKey 我们可以看到下面还有一个查询的方法this.query:

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {//这个Cache 是Mybatis二级缓存的内容 我们没有开启肯定是个nullCache cache = ms.getCache();if (cache != null) {````}//还是会走到此处 delegate是个装饰模式 Executor类型,  实现类是SimpleExecutor(SimpleExecutor extends BaseExecutor)return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

虽然 this.delegate.query中 delegate的实现类是SimpleExecutor 但是,SimpleExecutor 中并没有query方法。

SimpleExecutor extends BaseExecutor ,所以此方法在BaseExecutor 中。

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(if (this.closed) {throw new ExecutorException("Executor was closed.");} else {if (this.queryStack == 0 && ms.isFlushCacheRequired()) {this.clearLocalCache();}List list;try {++this.queryStack;//此处为关键代码,会查询一级缓存list = resultHandler == null ? (List)this.localCache.getObject(key) : null;if (list != null) {this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {//如果一级缓存不命中走此处,查询数据库list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, }} finally {--this.queryStack;}if (this.queryStack == 0) {····略}return list;}
}

this.localCache是个 PerpetualCache类的实例,内部是使用HashMap维护缓存结构的,他的内部结构与getObject方法的代码如下:

ublic class PerpetualCache implements Cache {private final String id;//用来维护缓存的HashMapprivate final Map<Object, Object> cache = new HashMap();public PerpetualCache(String id) {this.id = id;}public String getId() {return this.id;}public int getSize() {return this.cache.size();}public void putObject(Object key, Object value) {this.cache.put(key, value);}//getObject方法public Object getObject(Object key) {return this.cache.get(key);}
}

根据核心源码解读对类图的补充,蓝色部分是主要的接口,其余的类都是它们的实现类:

在这里插入图片描述

缓存销毁中close方法的源码,在BaseExecutor.class中:

public void close(boolean forceRollback) {try {try {this.rollback(forceRollback);} finally {if (this.transaction != null) {this.transaction.close();}}} catch (SQLException var11) {SQLException e = var11;log.warn("Unexpected exception on closing transaction.  Cause: " + e);} finally {this.transaction = null;this.deferredLoads = null;//清空缓存this.localCache = null;this.localOutputParameterCache = null;this.closed = true;}
}

commit提交方法的源码也在 BaseExecutor.class中:

public void commit(boolean required) throws SQLException {if (this.closed) {throw new ExecutorException("Cannot commit, transaction is already closed");} else {//清除缓存的代码this.clearLocalCache();this.flushStatements();if (required) {this.transaction.commit();}}
}

可以看到commit方法调用了this.clearLocalCache()方法清除缓存:

public void clearLocalCache() {//当前会话未被关闭if (!this.closed) {//调用map的clear方法this.localCache.clear();this.localOutputParameterCache.clear();}
}

update更新操作也会调用clearLocalCache()方法清空缓存,而 Insertdelect方法内部会调用update方法执行任务。

5_总结

一级缓存也叫本地缓存,它默认会启用,并且不能关闭。一级缓存存在于SqlSession的生命周期中,即它是SqlSession级别的缓存。在同一个 SqlSession 中查询时,MyBatis 会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个Map对象中。如果同一个SqlSession 中执行的方法和参数完全一致,那么通过算法会生成相同的键值,当Map 缓存对象中己经存在该键值时,则会返回缓存中的对象。

其设计理念即在一个Session内:

  • 不过期
  • 不更新
  • 不限制
  • 一般情况下Session生存时间很短
  • 执行Update会销毁缓存,而不是更新缓存
  • 支持主动销毁缓存

不限制是指如果一直缓存是没有上限的,可能撑爆jvm内存。

与Spring集成的时候SqlSession的相关操作权不在我们的手中,所以有一些注意事项,可以自己尝试:

如果与Spring集成的时候如果没有开启事务,在一个方法内,每次请求,Spring都会关闭旧的session再创建新的session,此时一级缓存无效。

开启事务,在一个事务内,Spring通过ThreadLocal始终使用同一个Session,所以此时一级缓存在事务内有效。

三、二级缓存

存在弊端,实际使用的人比较少。

二级缓存存在于SqlSessionFactory 的生命周期中,即它是SqlSessionFactory级别的缓存。在多个 SqlSession 之间共享缓存数据。

1_开启二级缓存

需要在MyBatis 的全局配置settings 中有一个参数cacheEnabled,这个参数是二级缓存的全局开关,默认值
是true ,初始状态为启用状态。Mybatis.xml:

在这里插入图片描述

<settings><setting name="cacheEnabled" value="true"/>
</settings>

MyBatis 的二级缓存是和命名空间绑定的,即二级缓存需要配置在Mapper.xml 映射文件中。在保证二级缓存的全局配置开启的情况下,给Mapper.xml 开启二级缓存只需要在Mapper. xml 中添加如下代码:

<cache />

在这里插入图片描述

配置缓存刷新间隔、大小、读写策略等(可选):

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
  • eviction:缓存回收策略,默认值为 LRU(Least Recently Used)。
  • flushInterval:缓存刷新间隔时间(毫秒)。
  • size:缓存大小。
  • readOnly:是否只读。

返回值对象需要实现Serializable接口,如:TempEntity implements Serializable。

如果成功,可以在控制台看到二级缓存的打印日志:

在这里插入图片描述

2_二级缓存命中原则

先说结论:与一级缓存的命中原则一模一样。一级缓存中的5条放在这里也是适用的。

可自行测试。测试的时候不要忘了使用同一个SqlSessionFactory创建不同的SqlSession进行测试,并且进行第5条环境测试的时候别忘了这里的流不能读两次。

3_二级缓存的产生

  1. 满足一级缓存产生的条件
  2. Close Session 或者 Commit Session。(不包括别的如:rollback)
  3. <select/>标签中没有设置 userCache = "false

在这里插入图片描述

自行验证哦。

4_缓存失效情况

二级缓存会在以下几种情况下被销毁:

  • 执行 INSERTUPDATEDELETE 操作。刷新缓存的参数flushCache没有设置成false(默认为true)
  • 配置了缓存刷新间隔时间,时间到了缓存会自动失效。

rollback 不会销毁缓存。

eviction清除策略配置参数

  • LRU:最近最少使用,移除最长时间不被使用的对象。 —> LinlHashMap。
  • FIFO:先进先出,按对象进入缓存的顺序来移除它们。-----> LinkedList。
  • SOFT:软引用,基于GC和软引用规则移除对象。------>SoftReference。
  • Weak:弱引用,基于GC和弱引用规则移除对象。------>WeakReference。

这些清除策略mybatis内都有对应的源码实现,XXXXCache。

size引用数目配置参数: 缓存引用的最大数目,默认1024.

还有个需要注意的点,MyBatis 的二级缓存是和命名空间(mapper)绑定的。如果存在两个不同的mapper.xml管理dao层操作。执行更新的是mapper1,不会将mapper2管理的缓存清空。

比如下方示例,TestMapper操作test表,TempMapper操作temp表,一个mapper下的sqlSession的更新不会导致整个SqlSessionFactory的失效:

@Test
public void test8() throws IOException {//读取xml文件InputStream inputStream =Resources.getResourceAsStream("mybaties.xml");//通过SqlSessionFactoryBuilder创建sqlSessionFactorySqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);//拿到执行sql语句的第一个会话SqlSession sqlSession1 = sqlSessionFactory.openSession();// 第一次查询,会从数据库获取数据 操作temp表TempEntity tempEntity1 = sqlSession1.selectOne("com.shen.spring.dao.TempDao.getById", 1);log.info("{}",tempEntity1);//关闭会话,并保存二级缓存sqlSession1.commit();//执行更新操作sqlSession1.update("com.shen.spring.dao.TestDao.getById", 1);sqlSession1.commit();//拿到第二个会话SqlSession sqlSession2 = sqlSessionFactory.openSession();// 第二次查询,会从缓存获取数据TempEntity tempEntity2 = sqlSession2.selectOne("com.shen.spring.dao.TempDao.getById", 1);log.info("{}",tempEntity2);System.out.println(tempEntity1 == tempEntity2);//虽然命中缓存会但是会输出false,由于序列化的关系
}

返回结果,虽然命中缓存会但是会输出false,由于序列化的关系。输出两次sql日志:

在这里插入图片描述

如果两个mapper操作同一个表也是一样的,一个mapper更新也不会让另一个mapper销毁缓存。

无论是单线程、多线程、多实例的情况下都会产生脏读。(一级缓存没有脏读问题)

不能跨SqlSessionFactory的原因 缓存不能跨SqlSessionFactory:

public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;
}
public class Configuration {····protected final Map<String, MappedStatement> mappedStatements;protected final Map<String, Cache> caches;····public Configuration(Environment environment) {this();this.environment = environment;}public Configuration() {···this.caches = new StrictMap("Caches collection");
}

而且多个SqlSessionFactory会存在脏读,可以使用自定义缓存+redis解决。

5_二级缓存核心源码

我们之前查看一级缓存时,发现过一段二级缓存的代码: Cache cache = ms.getCache();

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {//这个Cache 是Mybatis二级缓存的内容 我们没有开启肯定是个nullCache cache = ms.getCache();if (cache != null) {this.flushCacheIfRequired(ms);//缓存刷新属性如果设置了,进行的一些动作if (ms.isUseCache() && resultHandler == null) {//看看标签中是否禁用了缓存 resultHandler默认就是nullthis.ensureNoOutParams(ms, boundSql);//如果开启二级缓存要求我们没有输出参数,不然方法里面会报错。List<E> list = (List)this.tcm.getObject(cache, key);//查询二级缓存if (list == null) {//跟最下面的一样是一级缓存的过程list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);this.tcm.putObject(cache, key, list);//查询出来结果放入二级缓存中}return list;}}//还是会走到此处 delegate是个装饰模式 Executor类型,  实现类是SimpleExecutor(SimpleExecutor extends BaseExecutor)return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

从中可以看到,是先走二级缓存,查询不到才走一级缓存的逻辑。并且 tcm 是存储二级缓存的对象 TransactionalCacheManager 类型,结构如下:

public class TransactionalCacheManager {private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap();····public void putObject(Cache cache, CacheKey key, Object value) {//放入缓存的方法this.getTransactionalCache(cache).putObject(key, value);}//最终调用此方法private TransactionalCache getTransactionalCache(Cache cache) {//如果没有则新建一个return (TransactionalCache)MapUtil.computeIfAbsent(this.transactionalCaches, cache, TransactionalCache::new);}
}

TransactionalCacheManager 对象中维护了一个 HashMapkey是一个java的Cache接口,其实就是我们维护的一个个开启了二级缓存的Mapper文件所对应的缓存对象,value是个TransactionalCache 就是针对一级缓存所使用的那些Cache的实现类的进一步包装(还是装饰模式)。

TransactionalCache 的功能就是,将我们查询到的结果不是直接放到缓存对象中,而是放入entriesToAddOnCommit 中。

public class TransactionalCache implements Cache {private static final Log log = LogFactory.getLog(TransactionalCache.class);private final Cache delegate;private boolean clearOnCommit;//执行commit时清空缓存private final Map<Object, Object> entriesToAddOnCommit;//当我们执行commit时即将放入二级缓存中的对象private final Set<Object> entriesMissedInCache;//当前session未命中的缓存//更新操作会调用此方法public void clear() {//commit时会用到这个参数 this.clearOnCommit = true;//仅仅清空本次执行的二级缓存,让其无效this.entriesToAddOnCommit.clear();}//提交后会调用到此方法public void commit() {//如果clear方法被调用,clearOnCommit会变成true,执行清空二级缓存操作if (this.clearOnCommit) {//执行清空二级缓存的操作this.delegate.clear();}//调用此方法this.flushPendingEntries();this.reset();}private void flushPendingEntries() {Iterator var1 = this.entriesToAddOnCommit.entrySet().iterator();//对map结构进行循环,根据key和value,让其放入了我们二级缓存最终保存的对象中while(var1.hasNext()) {Map.Entry<Object, Object> entry = (Map.Entry)var1.next();this.delegate.putObject(entry.getKey(), entry.getValue());}var1 = this.entriesMissedInCache.iterator();while(var1.hasNext()) {Object entry = var1.next();if (!this.entriesToAddOnCommit.containsKey(entry)) {this.delegate.putObject(entry, (Object)null);}}}
}

上述类中的commit方法,再SqlSessionclose时也会被调用。

二级缓存的类图,右侧是三个接口,与一级缓存的类图一样

在这里插入图片描述

五、缓存机制的注意事项

  1. 一致性问题:缓存会导致数据一致性问题,尤其是在高并发环境下,需要谨慎使用。
  2. 数据更新:当数据发生变化时,需要及时更新缓存,以免返回过期数据。
  3. 缓存策略:根据具体业务需求配置合理的缓存策略,如缓存回收策略、刷新间隔等。
  4. 序列化:二级缓存中的对象需要实现序列化接口,以确保缓存对象能够正确存储和读取。

六_总结

一级缓存也叫本地缓存,它默认会启用,并且不能关闭。一级缓存存在于SqlSession的生命周期中,即它是SqlSession级别的缓存。在同一个 SqlSession 中查询时,MyBatis 会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个Map对象中。如果同一个SqlSession 中执行的方法和参数完全一致,那么通过算法会生成相同的键值,当Map 缓存对象中己经存在该键值时,则会返回缓存中的对象。

二级缓存存在于SqlSessionFactory 的生命周期中,即它是SqlSessionFactory级别的缓存也是 Mapper 映射级别的缓存,需要配置并适用于多个 SqlSession 共享缓存数据。在使用缓存时需要注意数据的一致性问题,根据实际需求选择合适的缓存策略和配置。

可以使用redis和mybatis整合让二级缓存到redis中而不是jvm中。

通过合理使用 MyBatis 缓存机制,可以显著提高系统的性能,降低数据库的负载,是优化 MyBatis 应用性能的重要手段之一。

相关文章:

mybatis中的缓存(一级缓存、二级缓存)

文章目录 前言一、MyBatis 缓存概述二、一级缓存1_初识一级缓存2_一级缓存命中原则1_StatementId相同2_查询参数相同3_分页参数相同4_sql 语句5_环境 3_一级缓存的生命周期1_缓存的产生2_缓存的销毁3_网传的一些谣言 4_一级缓存核心源码5_总结 三、二级缓存1_开启二级缓存2_二级…...

实现自动化采购:食堂采购系统源码开发详解

本篇文章&#xff0c;笔者将详细介绍食堂采购系统的开发过程&#xff0c;从需求分析、系统设计到实现和测试&#xff0c;为您全面解析如何构建一个高效的自动化采购系统。 一、需求分析 1.采购计划管理 2.供应商管理 3.订单管理 4.库存管理 5.财务管理 6.数据分析与报告 …...

linux、windows、macos清空本地DNS缓存

文章目录 Linux&#xff1a;Windows&#xff1a;macOS&#xff1a; Linux&#xff1a; 对于使用systemd的操作系统&#xff08;如CentOS 7、Ubuntu 16.04&#xff09;&#xff0c;可以使用以下命令重启systemd-resolved服务来清除缓存&#xff1a; sudo systemctl restart sys…...

领夹麦克风哪个品牌好,电脑麦克风哪个品牌好,热门麦克风推荐

​在信息快速传播的时代&#xff0c;直播和视频创作成为了表达与交流的重要方式。对于追求卓越声音品质的创作者而言&#xff0c;一款性能卓越的无线麦克风宛如一把利剑。接下来&#xff0c;我要为大家介绍几款备受好评的无线麦克风&#xff0c;这些都是我在实际使用中体验良好…...

【第5章】Spring Cloud之Nacos服务注册和服务发现

文章目录 前言一、提供者1. 引入依赖2.配置 Nacos Server 地址3. 开启服务注册 二、消费者1. 引入依赖2.配置 Nacos Server 地址3. 开启服务注册 三、服务列表四、服务发现1. 获取服务列表2. 测试2.1 获取所有服务2.2 根据服务名获取服务信息 五、更多配置项总结 前言 本节通过…...

Springboot 启动时Bean的创建与注入(一)-面试热点-springboot源码解读-xunznux

Springboot 启动时Bean的创建与注入&#xff0c;以及对应的源码解读 文章目录 Springboot 启动时Bean的创建与注入&#xff0c;以及对应的源码解读构建Web项目流程图&#xff1a;堆栈信息&#xff1a;堆栈信息简介堆栈信息源码详解1、main:10, DemoApplication (com.xun.demo)2…...

单调栈(随缘复习到了,顺手刷了)

也是不知道为什么突然又复习到单调栈了&#xff0c;所以顺手刷了三道题&#xff0c;总结一下 P6503 [COCI2010-2011#3] DIFERENCIJA 思路&#xff1a;这题是要求每个子区间里面的最大值和最小值的差&#xff0c;我们一开始想的必然是纯暴力呀&#xff0c;但是一看这数据&#…...

学习测试10-3自动化 web自动化

web自动化 chrome驱动下载地址&#xff1a; https://registry.npmmirror.com/binary.html?pathchromedriver/ https://googlechromelabs.github.io/chrome-for-testing/#stable观察Google版本&#xff0c;下相应的驱动 运行代码试试&#xff0c;成功Google就会弹出 from se…...

安防视频监控EasyCVR视频汇聚平台修改配置后无法启动的原因排查与解决

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台基于云边端一体化架构&#xff0c;兼容性强、支持多协议接入&#xff0c;包括国标GB/T 28181协议、部标JT808、GA/T 1400协议、RTMP、RTSP/Onvif协议、海康Ehome、海康SDK、大华SDK、华为SDK、宇视SDK、乐橙SDK、萤石云SD…...

爬虫学习2:爬虫爬取网页的信息与图片的方法

爬虫爬取网页的信息与图片的方法 爬取人物信息 import requestshead {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0" } # 这是get请求带参数的模式…...

MySQL定时备份数据,并上传到oss

1.环境准备 1.安装阿里云的ossutil 2.安装mysql 2.编写脚本 脚本内容如下 #!/bin/bash # 数据库的配置信息&#xff0c;根据自己的情况进行填写 db_hostlocalhost db_usernameroot db_passwordroot db_namedb_root # oss 存贮数据的bucket地址 bucket_namerbsy-backup-buck…...

极速删除 node_modules 仅3 秒()

今天教大家如何快速删除 node_modules 依赖的一个小秘诀&#xff0c;告别繁琐&#xff01;&#xff01;&#xff01; 前言 作为前端开发者&#xff0c;相信大家都曾经历过删除 node_modules 文件夹时的漫长等待。 尤其是在处理那些依赖库繁多的项目时&#xff0c;删除操作…...

vue this.$refs 动态拼接

业务需要&#xff0c;refs是不固定的 <vxe-grid refgridWarehouse v-bind"gridWarehouseOptions" v-if"tableHeight" :height"tableHeight":expand-config"{iconOpen: vxe-icon-square-minus, iconClose: vxe-icon-square-plus}"c…...

一次搞定!中级软件设计师备考通关秘籍

大家好&#xff0c;我是小欧&#xff01; 今天我们来聊聊软考这个话题。要是你准备参加计算机技术与软件专业技术资格&#xff08;软考&#xff09;&#xff0c;那么这篇文章就是为你量身定做的。话不多说&#xff0c;咱们直接进入正题。 什么是软考&#xff1f; 软考&#xf…...

第十六讲 python中的序列-列表简介-特点-常用方法-创建-添加-删除-访问-切片-排序-复制-反转

目录 1. 序列的本质和内存结构 2.列表 2.1 列表简介 2.2 列表的特点 2.3 列表对象的常用方法大全: 2.4 列表的创建 2.4.1 使用方括号 [] 2.4.2 使用 list() 函数 2.4.3 使用 range() 函数 2.4.3.1 range的基本用法 2.4.3.2 返回值 2.4.3.3 range的使用例子 2.4.3.4 range的使…...

大模型日报 2024-07-22

大模型日报 2024-07-22 大模型资讯 谷歌将在ICML 2024展示机器学习研究成果 摘要: 谷歌研究人员将在ICML 2024会议上展示他们在机器学习领域的探索&#xff0c;从理论到应用&#xff0c;构建解决深层问题的ML系统。 代理符号学习&#xff1a;优化AI系统符号组件的框架 摘要: 大…...

Electron 的open-file事件

在 Electron 中,open-file 事件是一个重要的事件,它允许开发者在应用程序已经运行的情况下,通过文件打开请求(如双击文件或在命令行中使用 open 命令打开文件)来捕获文件路径。以下是对 open-file 事件的详细解析: 触发条件 应用已经打开。用户通过双击与应用程序关联的…...

前端面试 vue 接口权限控制

接口权限目前一般采用jwt的形式来验证&#xff0c;没有通过的话一般返回401&#xff0c;跳转到登录页面重新进行登录 对于 jwt的理解 &#xff08;前端接口权限的控制主要通过接口权限配置和JWT&#xff08;‌Json Web Token&#xff09;‌技术来实现。‌ 首先&#xff0c;‌…...

【DevOps系列】构建Devops系统

开始介绍 那就着手开始干吧。先介绍一下我们的工具链。 主要工具&#xff1a;GitHub、Jenkins、Kubernetes、Ansible、Prometheus和JMeter 着手动 1. 设置GitHub作为源代码仓库 登录GitHub: 打开浏览器并访问 https://github.com&#xff0c;使用您的GitHub账户登录。 创建…...

ABAP打印WORD的解决方案

客户要求按照固定格式输出到WORD模板中,目前OLE和DOI研究了均不太适合用于这种需求。 cl_docx_document类可以将WORD转化为XML文件,利用替换字符串方法将文档内容进行填充同 时不破坏WORD现有格式。 首先需要将WORD的单元格用各种预定义的字符进行填充,为后续替换作准备…...

emr部署hive并适配达梦数据库

作者&#xff1a;振鹭 一、达梦 用户、数据库初始化 1、创建hive的元数据库 create tablespace hive_meta datafile /dm8/data/DAMENG/hive_meta.dbf size 100 autoextend on next 1 maxsize 2048;2、创建数据库的用户 create user hive identified by "hive12345&quo…...

王春城:怎么用精益思维重塑企业战略规划格局?

当下&#xff0c;企业战略规划的灵活性和适应性变得至关重要。传统的战略规划方法往往过于僵化和静态&#xff0c;难以应对市场的不确定性和变化。因此&#xff0c;引入精益思维来重塑企业战略规划格局&#xff0c;成为了许多企业寻求突破和创新的途径。具体步骤如深圳天行健企…...

git reset

git reset [--soft | --mixed | --hard] [HEAD] 表格版 原始内容reset前reset命令reset后本地工作区暂存区本地仓库本地工作区暂存区本地仓库本地工作区暂存区本地仓库READMEREADMEREADMEREADMEREADMEREADME--soft HEADREADMEREADMEREADMEa.txta.txtb.txtb.txtb.txtb.txtc.tx…...

E17.【C语言】练习:sizeof和strlen的辨析

先回顾http://t.csdnimg.cn/aYHl6 1. char acX[] "abcdefg"; char acY[] { a,b,c,d,e,f,g}; 以下说法正确的是( ) A.数组acX和数组acY等价 B.数组acX和数组acY的长度相同 C.sizeof(acX)>sizeof (acY) D.strlen (acX)>strlen (acY) 分析&#xff1a;…...

便携气象站:科技助力气象观测

在科技飞速发展的今天&#xff0c;便携气象站以其轻便、高效、全面的特点&#xff0c;正逐渐改变着气象观测的传统模式。这款小巧而强大的设备&#xff0c;不仅为气象学研究和气象灾害预警提供了有力支持&#xff0c;更为户外活动、农业生产等领域带来了诸多便利。 便携气象站是…...

php 存储复杂的json格式查询(如:经纬度)

在开发中&#xff0c;有时我们可能存了一些复杂json格式不知道怎么查。我这里提供给大家参考下&#xff1a; 一、先上表数据格式&#xff08;location字段的possiton经纬度以逗号分开的&#xff09; {"title":"澳海文澜府","position":"11…...

UDP网口(1)概述

文章目录 1.计算机网络知识在互联网中的应用2.认识FPGA实现UDP网口通信3.FPGA实现UDP网口通信的方案4.FPGA实现UDP网口文章安排5.传送门 1.计算机网络知识在互联网中的应用 以在浏览器中输入淘宝网为例&#xff0c;介绍数据在互联网是如何传输的。我们将要发送的数据包称作A&a…...

Linux - 进程的概念、状态、僵尸进程、孤儿进程及进程优先级

进程基本概念 课本概念&#xff1a;在编程或软件工程的上下文中&#xff0c;进程通常被视为正在执行的程序的实例。当你启动一个应用程序时&#xff0c;操作系统会为这个程序创建一个进程。每个进程都有自己的独立内存空间&#xff0c;可以运行自己的指令序列&#xff0c;并可能…...

Gradle依赖报告:项目依赖树的X光机

Gradle依赖报告&#xff1a;项目依赖树的X光机 在复杂的软件项目中&#xff0c;依赖管理是确保应用正常构建和运行的关键。Gradle作为一个强大的构建工具&#xff0c;提供了依赖报告功能&#xff0c;帮助开发者分析和理解项目的依赖树。本文将详细介绍如何在Gradle中使用依赖报…...

开源XDR-SIEM一体化平台 Wazuh (1)基础架构

简介 Wazuh平台提供了XDR和SIEM功能&#xff0c;保护云、容器和服务器工作负载。这些功能包括日志数据分析、入侵和恶意软件检测、文件完整性监控、配置评估、漏洞检测以及对法规遵从性的支持。详细信息可以参考Wazuh - Open Source XDR. Open Source SIEM.官方网站 Wazuh解决…...

从零开始:构建基于深度学习的实时跌倒检测系统(UI界面+YOLO代码+数据集)

注意看文末的结局与声明 一、引言 1. 项目背景与动机 在老年人和高危职业环境中&#xff0c;跌倒是一种常见的事故&#xff0c;可能导致严重的伤害甚至致命。实时跌倒检测系统可以及时发现并报警&#xff0c;提供紧急救助。通过深度学习技术&#xff0c;可以提高跌倒检测的准…...

【策略模式在项目中的实际应用】

业务场景 最最近项目中有这样的一个业务场景&#xff1a; 用户下单->管理员审核->配送员接单->配送中->送达–>签收->完成 整个业务以这种流程的形式存在&#xff0c;每个流程状态的业务不一样&#xff0c;考虑到多种状态如果直接写一个接口肯定会嵌套太多…...

昇思25天学习打卡营第14天|计算机视觉

昇思25天学习打卡营第14天 文章目录 昇思25天学习打卡营第14天FCN图像语义分割语义分割模型简介网络特点数据处理数据预处理数据加载训练集可视化 网络构建网络流程 训练准备导入VGG-16部分预训练权重损失函数自定义评价指标 Metrics 模型训练模型评估模型推理总结引用 打卡记录…...

将json数组格式转成数组

start cmd [ 27 01 f7 01 24 38 02 b7 42 6e ee 2f 69 46 72 21 74 44 c4 22 7a 92 d8 6a de 66 61 b1 1e 2f de ee 5c 31 57 db df 01 31 2d c9 01 01 c0 FB ] set_ulpk {“jsonrpc”:“2.0”,“type”:2,“id”:0,“method”:“method”,“message”:{“VALUE”:[56,2,183,66…...

接口测试之测试原则、测试用例、测试流程详解

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、接口的介绍 软件测试中&#xff0c;常说的接口有两种&#xff1a;图形用户接口&#xff08;GUI&#xff0c;人与程序的接口&#xff09;、应用程序编程接口&…...

证书上的服务器名错误解决方法

方法 win r &#xff0c;输入mmc 点击文件——>添加/删除管理单元 找到证书——> 添加 根据自己的存放选择存放位置 点击控制台根节点——> 受信任的根证书颁发机构——>导入 若还出现问题&#xff0c;则参考https://blog.csdn.net/mm120138687/article/details/…...

前端:上传2进制图片

1、let formData new FormData(); 2、添加要传的字段&#xff1a;formData.append("avatarfile", data); &#xff08;key,value&#xff09; 3、上传文件 function uploadImg() {// 1定义FormDatalet formData new FormData();// 2添加字段formData.append("…...

web前端 React 框架面试200题(三)

面试题 65. 在使用 React Router时&#xff0c;如何获取当前页面的路由或浏览器中地址栏中的地址&#xff1f; 参考回答&#xff1a; 在当前组件的 props中&#xff0c;包含 location属性对象&#xff0c;包含当前页面路由地址信息&#xff0c;在 match中存储当前路由的参数等…...

交流负载箱:电力系统的节能利器

交流负载箱是模拟电网中实际负载的装置&#xff0c;它能够精确地模拟各种电器设备的耗电情况&#xff0c;为电力系统的节能提供了重要的工具。在电力系统中&#xff0c;交流负载箱的应用非常广泛&#xff0c;它可以用于电力系统的设计、运行和维护&#xff0c;以及电力设备的测…...

【思科】链路聚合实验配置和背景

【思科】链路聚合实验配置和背景 背景链路聚合基本概念链路聚合聚合接口 思科链路聚合协议01.PAgP协议02.LACP协议 思科链路聚合模式LACP协议模式PAgP协议模式ON模式 实验准备配置二层链路聚合LACP协议模式SW1SW2PC1PC2查看LACP聚合组建立情况查看LACP聚合端口情况查看逻辑聚合…...

使用 vue-element-plus-admin 框架遇到的问题记录

项目打包遇到的问题&#xff1a; 打包语句&#xff1a;pnpm run build:pro 报错信息&#xff1a; Error: [vite]: Rollup failed to resolve import "E:/workplace_gitee/xxx/node_modules/.pnpm/element-plus2.5.5_vue3.4.15/node_modules/element-plus/es/components…...

从零开始手写STL库:List

从零开始手写STL库–List部分 Github链接&#xff1a;miniSTL 文章目录 从零开始手写STL库–List部分List是什么&#xff1f;List需要包含什么函数1&#xff09;基础成员函数2&#xff09;核心功能3)其他功能 基础成员函数的编写核心功能的编写其他功能编写总结 List是什么&am…...

蒙特卡洛采样

目录 蒙特卡洛采样的计算逻辑计算步骤:1. 定义问题2. 确定采样范围3. 生成随机样本点4. 计算函数值5. 估计期望值或积分值6. 计算误差具体示例:1. 定义问题2. 确定采样范围3. 生成随机样本点4. 计算函数值5. 估计积分值6. 计算误差总结蒙特卡洛采样是一种通过随机生成样本点来…...

Apache虚拟主机VirtualHost配置项详解

在Apache中,VirtualHost容器用于定义一个虚拟主机的配置,它允许在单一的物理服务器上托管多个不同的网站,每个网站可以有自己的域名、文档根目录、错误日志等。VirtualHost内的配置项非常灵活,可以包含从基本的网站信息到高级的URL重写和安全设置。 以下是一些常见的Virtu…...

OpenAI从GPT-4V到GPT-4O,再到GPT-4OMini简介

OpenAI从GPT-4V到GPT-4O&#xff0c;再到GPT-4OMini简介 一、引言 在人工智能领域&#xff0c;OpenAI的GPT系列模型一直是自然语言处理的标杆。随着技术的不断进步&#xff0c;OpenAI推出了多个版本的GPT模型&#xff0c;包括视觉增强的GPT-4V&#xff08;GPT-4 with Vision&…...

从人工巡检到智能防控:智慧油气田安全生产的新视角

一、背景需求 随着科技的飞速发展&#xff0c;视频监控技术已成为各行各业保障安全生产、提升管理效率的重要手段。特别是在油气田这一特殊领域&#xff0c;由于其工作环境复杂、安全风险高&#xff0c;传统的监控方式已难以满足实际需求。因此&#xff0c;基于视频监控AI智能…...

【黑马java基础】Lamda, 方法引用,集合{Collection(List, Set), Map},Stream流

文章目录 JDK8新特性&#xff1a;Lambda表达式认识Lambda表达式Lambda表达式的省略规则 JDK8新特性&#xff1a;方法引用静态方法的引用实例方法的引用特定类型方法的引用构造器的应用 集合➡️Collection单列集合体系Collection的常用方法Collection的遍历方法迭代器增强for循…...

Stable Diffusion 使用详解(1)---- 提示词及相关参数

目录 背景 提示词 内容提示词 人物及主体特征 场景 环境光照 画幅视角 注意事项及示例 标准化提示词 画质等级 风格与真实性 具体要求 背景处理 光线与色彩 负向提示词 小结 常用工具 另外几个相关参数 迭代步数 宽度与高度 提示词引导系数 图片数量 背景…...

数据结构和算法(刷题) - 无序数组排序后的最大相邻差

无序数组排序后的最大相邻差 问题&#xff1a;一个无序的整型数组&#xff0c;求出该数组排序后的任意两个相邻元素的最大差值&#xff1f;要求时间和空间复杂度尽可能低。 三种方法&#xff1a; 排序后计算比较 简介&#xff1a;用任意一种时间复杂度为 O ( n log ⁡ n ) O…...

HOW - React 处理不紧急的更新和渲染

目录 useDeferredValueuseTransitionuseIdleCallback 在 React 中&#xff0c;有一些钩子函数可以帮助你处理不紧急的更新或渲染&#xff0c;从而优化性能和用户体验。 以下是一些常用的相关钩子及其应用场景&#xff1a; useDeferredValue 用途&#xff1a;用于处理高优先级…...