PageHelper学习使用

基于mybatis源码和PageHelper源码进行的测试

版本

mybatis3.5.0pageHelper6.0.0

测试用例

依赖
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.15</version>
</dependency>
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope>
</dependency>
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>6.0.0</version>
</dependency>
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.0</version>
</dependency>

如果引入上面这些依赖运行报下面这样的错误
在这里插入图片描述
需要再引入两个依赖,这个两个依赖直接去mybatis源码的pom.xml中找即可

<dependency><groupId>ognl</groupId><artifactId>ognl</artifactId><version>3.2.10</version><scope>compile</scope><optional>true</optional>
</dependency>
<dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.24.1-GA</version><scope>compile</scope><optional>true</optional>
</dependency>

如果还报错,是下面这种错误,也是一样的从mybatis源码的pom.xml中找到依赖引用到自己的项目中
在这里插入图片描述

<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version><optional>true</optional>
</dependency>
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.25</version><optional>true</optional>
</dependency>
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version><optional>true</optional>
</dependency>
<dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.11.1</version><optional>true</optional>
</dependency>
<dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version><optional>true</optional>
</dependency>
日志文件
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%m%n#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%d{yy-MM-dd}][%c]%m%n#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
数据库配置文件
jdbc.type=mysql
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:6608/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
jdbc.username=mybatis
jdbc.password=mybatis
mybatis配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- 配置文件的根元素 -->
<configuration><!-- 属性:定义配置外在化 --><properties resource="jdbc.properties"/><settings><setting name="logImpl" value="LOG4J"/></settings>
<!--分页拦截器--><plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"/></plugins>
<!--环境配置--><environments default="mysql"><environment id="mysql"><transactionManager type="JDBC" /><!-- 配置数据库连接信息 --><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}" /><property name="url" value="${jdbc.url}" /><property name="username" value="${jdbc.username}" /><property name="password" value="${jdbc.password}" /></dataSource></environment></environments><!-- 映射器:指定映射文件或者映射类 --><mappers><mapper resource="CmpRecordMapper.xml"/></mappers></configuration>
映射文件
<?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="org.mybatis.domain.CmpRecordMapper"><select id="getCmpRecordBy" parameterType="java.util.Map" resultType="org.mybatis.domain.CmpRecord"><include refid="selectColumnSql"/><if test="createTimeBegin != null">and create_time &gt;= #{createTimeBegin}</if><if test="createTimeEnd != null">and create_time &lt;= #{createTimeEnd}</if></select><sql id="selectColumnSql">selectid as "id",create_time as "createTime",valid as "valid",create_org_id as "createOrgId",create_org_name as "createOrgName",create_dept_id as "createDeptId",create_dept_name as "createDeptName",creator_id as "creatorId",creator_name as "creatorName",limit_time as "limitTime",category_id as "categoryId",category_name as "categoryName",cmp_reason_id as "cmpReasonId",cmp_reason_name as "cmpReasonName",content_abstract as "contentAbstract"from t_cmp_record where valid = 1</sql>
</mapper>
实体模型
public class CmpRecord {private String id;private Date createTime;private int valid = 1;private String createOrgId;private String createOrgName;private String createDeptId;private String createDeptName;private String creatorId;private String creatorName;private Date limitTime;private String categoryId;private String categoryName;private String cmpReasonId;private String cmpReasonName;private String contentAbstract;public CmpRecord() {}public CmpRecord(String id, Date createTime, int valid, String createOrgId, String createOrgName, String createDeptId, String createDeptName, String creatorId, String creatorName, Date limitTime, String categoryId, String categoryName, String cmpReasonId, String cmpReasonName, String contentAbstract) {this.id = id;this.createTime = createTime;this.valid = valid;this.createOrgId = createOrgId;this.createOrgName = createOrgName;this.createDeptId = createDeptId;this.createDeptName = createDeptName;this.creatorId = creatorId;this.creatorName = creatorName;this.limitTime = limitTime;this.categoryId = categoryId;this.categoryName = categoryName;this.cmpReasonId = cmpReasonId;this.cmpReasonName = cmpReasonName;this.contentAbstract = contentAbstract;}//省略get/set方法@Overridepublic String toString() {return "CmpRecord{" +"id='" + id + '\'' +", valid=" + valid +", createOrgId='" + createOrgId + '\'' +", createOrgName='" + createOrgName + '\'' +", createDeptId='" + createDeptId + '\'' +", createDeptName='" + createDeptName + '\'' +", creatorId='" + creatorId + '\'' +", creatorName='" + creatorName + '\'' +", categoryId='" + categoryId + '\'' +", categoryName='" + categoryName + '\'' +", cmpReasonId='" + cmpReasonId + '\'' +", cmpReasonName='" + cmpReasonName + '\'' +", contentAbstract='" + contentAbstract + '\'' +'}';}
}
Mapper接口
public interface CmpRecordMapper {void saveCmpRecord(CmpRecord record);CmpRecord getCmpRecordId(String id);List<CmpRecord> getCmpRecordBy(Map<String, Object> params);
}
测试方法
public static void testPageHelper() throws IOException {InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();CmpRecordMapper mapper = sqlSession.getMapper(CmpRecordMapper.class);Map<String,Object> params = new HashMap<>();params.put("createTimeBegin","2023-10-01 00:12:02");params.put("createTimeEnd","2023-11-01 00:12:02");PageHelper.startPage(1,2);List<CmpRecord> records = mapper.getCmpRecordBy(null);PageInfo<CmpRecord> datas = new PageInfo<>(records);System.out.println(datas.toString());
}public static void main(String[] args) throws IOException {testPageHelper();
}

在这里插入图片描述

PageHelper源码分析

拦截器PageInterceptor
//这里执行拦截Executor接口的两个query方法
@SuppressWarnings({"rawtypes", "unchecked"})
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),}
)
public class PageInterceptor implements Interceptor {private static final Log log = LogFactory.getLog(PageInterceptor.class);private static boolean debug = false;protected Cache<String, MappedStatement> msCountMap = null;protected CountMsIdGen countMsIdGen = CountMsIdGen.DEFAULT;private volatile Dialect dialect;//这里是方言 是 PageHelperprivate String countSuffix = "_COUNT";private String default_dialect_class = "com.github.pagehelper.PageHelper";public PageInterceptor() {String bannerEnabled = System.getProperty("pagehelper.banner");if (StringUtil.isEmpty(bannerEnabled)) {bannerEnabled = System.getenv("PAGEHELPER_BANNER");}@Overridepublic Object intercept(Invocation invocation) throws Throwable {try {//这里的参数可能是六个,也可能是四个Object[] args = invocation.getArgs();MappedStatement ms = (MappedStatement) args[0];Object parameter = args[1];RowBounds rowBounds = (RowBounds) args[2];ResultHandler resultHandler = (ResultHandler) args[3];Executor executor = (Executor) invocation.getTarget();CacheKey cacheKey;BoundSql boundSql;//参数个数的不同对应上面不同的query方法//由于逻辑关系,只会进入一次if (args.length == 4) {//4 个参数时boundSql = ms.getBoundSql(parameter);cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);} else {//6 个参数时cacheKey = (CacheKey) args[4];boundSql = (BoundSql) args[5];}//这里会使用默认方言 PageHelpercheckDialectExists();//对 boundSql 的拦截处理if (dialect instanceof BoundSqlInterceptor.Chain) {//这里会调用PageHelper.doudSql方法,返回还是原来的BoundSql对象boundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.ORIGINAL, boundSql, cacheKey);}List resultList;//调用方法判断是否需要进行分页,如果不需要,直接返回结果if (!dialect.skip(ms, parameter, rowBounds)) {Future<Long> countFuture = null;//判断是否需要进行 count 查询if (dialect.beforeCount(ms, parameter, rowBounds)) {//这里会判断是否异步进行count查询,一般都是同步count查询,所以会走elseif (dialect.isAsyncCount()) {countFuture = asyncCount(ms, boundSql, parameter, rowBounds);} else {//查询总数Long count = count(executor, ms, parameter, rowBounds, null, boundSql);//处理查询总数,返回 true 时继续分页查询,false 时直接返回if (!dialect.afterCount(count, parameter, rowBounds)) {//当查询总数为 0 时,直接返回空的结果return dialect.afterPage(new ArrayList(), parameter, rowBounds);}}}//这里是分页查询数据resultList = ExecutorUtil.pageQuery(dialect, executor,ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);if (countFuture != null) {Long count = countFuture.get();dialect.afterCount(count, parameter, rowBounds);}} else {//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);}return dialect.afterPage(resultList, parameter, rowBounds);} finally {if (dialect != null) {dialect.afterAll();}}}/*** 异步查询总数*/private Future<Long> asyncCount(MappedStatement ms, BoundSql boundSql, Object parameter, RowBounds rowBounds) {Configuration configuration = ms.getConfiguration();//异步不能复用 BoundSql,因为分页使用时会添加分页参数,这里需要复制一个新的BoundSql countBoundSql = new BoundSql(configuration, boundSql.getSql(), new ArrayList<>(boundSql.getParameterMappings()), parameter);//异步想要起作用需要新的数据库连接,需要独立的事务,创建新的Executor,因此异步查询只适合在独立查询中使用,如果混合增删改操作,不能开启异步Environment environment = configuration.getEnvironment();TransactionFactory transactionFactory = null;if (environment == null || environment.getTransactionFactory() == null) {transactionFactory = new ManagedTransactionFactory();} else {transactionFactory = environment.getTransactionFactory();}//创建新的事务Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), null, false);//使用新的 Executor 执行 count 查询,这里没有加载拦截器,避免递归死循环Executor countExecutor = new CachingExecutor(new SimpleExecutor(configuration, tx));return dialect.asyncCountTask(() -> {try {return count(countExecutor, ms, parameter, rowBounds, null, countBoundSql);} finally {tx.close();}});}/*** Spring bean 方式配置时,如果没有配置属性就不会执行下面的 setProperties 方法,就不会初始化* <p>* 因此这里会出现 null 的情况 fixed #26*/private void checkDialectExists() {if (dialect == null) {synchronized (default_dialect_class) {if (dialect == null) {setProperties(new Properties());}}}}private Long count(Executor executor, MappedStatement ms, Object parameter,RowBounds rowBounds, ResultHandler resultHandler,BoundSql boundSql) throws SQLException {String countMsId = countMsIdGen.genCountMsId(ms, parameter, boundSql, countSuffix);Long count;//先判断是否存在手写的 count 查询MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);if (countMs != null) {count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);} else {if (msCountMap != null) {countMs = msCountMap.get(countMsId);}//自动创建if (countMs == null) {//根据当前的 ms 创建一个返回值为 Long 类型的 mscountMs = MSUtils.newCountMappedStatement(ms, countMsId);if (msCountMap != null) {msCountMap.put(countMsId, countMs);}}count = ExecutorUtil.executeAutoCount(this.dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);}return count;}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {//缓存 count msmsCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);String dialectClass = properties.getProperty("dialect");if (StringUtil.isEmpty(dialectClass)) {dialectClass = default_dialect_class;}Dialect tempDialect = ClassUtil.newInstance(dialectClass, properties);tempDialect.setProperties(properties);String countSuffix = properties.getProperty("countSuffix");if (StringUtil.isNotEmpty(countSuffix)) {this.countSuffix = countSuffix;}// debug模式,用于排查不安全分页调用debug = Boolean.parseBoolean(properties.getProperty("debug"));// 通过 countMsId 配置自定义类String countMsIdGenClass = properties.getProperty("countMsIdGen");if (StringUtil.isNotEmpty(countMsIdGenClass)) {countMsIdGen = ClassUtil.newInstance(countMsIdGenClass, properties);}// 初始化完成后再设置值,保证 dialect 完成初始化dialect = tempDialect;}}
工具类 PageHelper
public class PageHelper extends PageMethod implements Dialect, BoundSqlInterceptor.Chain {private PageAutoDialect autoDialect;//数据库方言 根据你配置的数据库类型选择对应的方言  这里选择的是MySqlDialect.classprivate ForkJoinPool asyncCountService;//需要异步执行sql的线程池 初始化在当前类的 setProperties方法中@Overridepublic boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {Page page = pageParams.getPage(parameterObject, rowBounds);if (page == null) {return true;} else {//设置默认的 count 列if (StringUtil.isEmpty(page.getCountColumn())) {page.setCountColumn(pageParams.getCountColumn());}//设置默认的异步 count 设置if (page.getAsyncCount() == null) {page.setAsyncCount(pageParams.isAsyncCount());}autoDialect.initDelegateDialect(ms, page.getDialectClass());return false;}}@Overridepublic <T> Future<T> asyncCountTask(Callable<T> task) {//异步执行时需要将ThreadLocal值传递,否则会找不到AbstractHelperDialect dialectThreadLocal = autoDialect.getDialectThreadLocal();Page<Object> localPage = getLocalPage();String countId = UUID.randomUUID().toString();return asyncCountService.submit(() -> {try {//设置 ThreadLocalautoDialect.setDialectThreadLocal(dialectThreadLocal);setLocalPage(localPage);return task.call();} finally {autoDialect.clearDelegate();clearPage();}});}@Overridepublic void afterAll() {//这个方法即使不分页也会被执行,所以要判断 nullAbstractHelperDialect delegate = autoDialect.getDelegate();if (delegate != null) {delegate.afterAll();autoDialect.clearDelegate();}clearPage();}@Overridepublic BoundSql doBoundSql(BoundSqlInterceptor.Type type, BoundSql boundSql, CacheKey cacheKey) {Page<Object> localPage = getLocalPage();BoundSqlInterceptor.Chain chain = localPage != null ? localPage.getChain() : null;if (chain == null) {BoundSqlInterceptor boundSqlInterceptor = localPage != null ? localPage.getBoundSqlInterceptor() : null;BoundSqlInterceptor.Chain defaultChain = pageBoundSqlInterceptors != null ? pageBoundSqlInterceptors.getChain() : null;if (boundSqlInterceptor != null) {chain = new BoundSqlInterceptorChain(defaultChain, Arrays.asList(boundSqlInterceptor));} else if (defaultChain != null) {chain = defaultChain;}if (chain == null) {chain = DO_NOTHING;}if (localPage != null) {localPage.setChain(chain);}}return chain.doBoundSql(type, boundSql, cacheKey);}//处理分页参数@Overridepublic Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey) {return autoDialect.getDelegate().processParameterObject(ms, parameterObject, boundSql, pageKey);}//TODO 其他方法省略
}
AbstractHelperDialect
@Overridepublic Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey) {//处理参数Page page = getLocalPage();//如果只是 order by 就不必处理参数if (page.isOrderByOnly()) {return parameterObject;}Map<String, Object> paramMap = null;if (parameterObject == null) {paramMap = new HashMap<String, Object>();} else if (parameterObject instanceof Map) {//解决不可变Map的情况paramMap = new HashMap<String, Object>();paramMap.putAll((Map) parameterObject);} else {paramMap = new HashMap<String, Object>();// sqlSource为ProviderSqlSource时,处理只有1个参数的情况if (ms.getSqlSource() instanceof ProviderSqlSource) {String[] providerMethodArgumentNames = ExecutorUtil.getProviderMethodArgumentNames((ProviderSqlSource) ms.getSqlSource());if (providerMethodArgumentNames != null && providerMethodArgumentNames.length == 1) {paramMap.put(providerMethodArgumentNames[0], parameterObject);paramMap.put("param1", parameterObject);}}//动态sql时的判断条件不会出现在ParameterMapping中,但是必须有,所以这里需要收集所有的getter属性//TypeHandlerRegistry可以直接处理的会作为一个直接使用的对象进行处理boolean hasTypeHandler = ms.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());MetaObject metaObject = MetaObjectUtil.forObject(parameterObject);//需要针对注解形式的MyProviderSqlSource保存原值if (!hasTypeHandler) {for (String name : metaObject.getGetterNames()) {paramMap.put(name, metaObject.getValue(name));}}//下面这段方法,主要解决一个常见类型的参数时的问题if (boundSql.getParameterMappings() != null && boundSql.getParameterMappings().size() > 0) {for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {String name = parameterMapping.getProperty();if (!name.equals(PAGEPARAMETER_FIRST)&& !name.equals(PAGEPARAMETER_SECOND)&& paramMap.get(name) == null) {if (hasTypeHandler|| parameterMapping.getJavaType().equals(parameterObject.getClass())) {paramMap.put(name, parameterObject);break;}}}}}//调用具体数据库方言进行处理 这里是调用的MySqlDialectreturn processPageParameter(ms, paramMap, page, boundSql, pageKey);}

项目中使用PageHelper遇到的问题

分页不正确
原因:PageMethod.LOCAL_PAGE没有及时删除

PageMethod
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();protected static boolean DEFAULT_COUNT = true;/*** 设置 Page 参数** @param page*/public static void setLocalPage(Page page) {LOCAL_PAGE.set(page);}/*** 获取 Page 参数** @return*/public static <T> Page<T> getLocalPage() {return LOCAL_PAGE.get();}/*** 移除本地变量*/public static void clearPage() {LOCAL_PAGE.remove();}//TODO 省略其他方法
}

而且并不是每一次都有问题,这个其实取决于我们启动服务所使用的容器,比如tomcat,在其内部处理请求是通过线程池的方式。甚至现在的很多容器是基于netty的,都是通过线程池,复用线程来增加服务的并发量。
假设线程1持有没有被清除的page参数,不断调用同一个方法,后面两个请求使用的是线程2和线程3没有问题,再一个请求轮到线程1了,此时就会出现问题了。

解决方案

手动调用PageHelper.clearPage方法

public static void testPageHelper() throws IOException {InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();CmpRecordMapper mapper = sqlSession.getMapper(CmpRecordMapper.class);Map<String,Object> params = new HashMap<>();params.put("createTimeBegin","2023-10-01 00:12:02");params.put("createTimeEnd","2023-11-01 00:12:02");PageHelper.startPage(1,2);List<CmpRecord> records = mapper.getCmpRecordBy(null);PageInfo<CmpRecord> datas = new PageInfo<>(records);//TODO 手动调用PageHelper.clearPage();System.out.println(datas.toString());}

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

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

相关文章

怎么实现抖音引导到微信小程序丨数灵通教你

抖音是一款热门的社交媒体应用&#xff0c;许多用户希望能够通过抖音跳转到微信小程序&#xff0c;以实现更多的引流和推广效果。以下是关于抖音跳转到微信小程序的科普信息&#xff1a; 1.优势和用途&#xff1a;通过抖音跳转到微信小程序可以带来多个优势和用途&#xff1a; …

Redis核心技术与实战【学习笔记】 - 1.Redis为什么高性能

作为键值数据库&#xff0c;Redis 的应用非常广泛&#xff0c;如果你是后端工程师&#xff0c;我猜你出去面试&#xff0c;八成都会被问到与它相关的性能问题。比如说&#xff0c;为了保证数据的可靠性&#xff0c;Redis 需要在磁盘上读写 AOF 和 RDB&#xff0c;但在高并发场景…

CSS3基础知识总结

目录 一、CSS3 边框 1.border-radius&#xff1a;圆角边框 2.box-shadow&#xff1a;添加阴影 3.border-image&#xff1a;图片边框 二、CSS3 渐变 1.线性渐变(Linear Gradients) a.由上到下&#xff08;默认&#xff09; b.从左到右 c.对角 d.使用角度 2.径向渐变(…

在 EggJS 中实现 Redis 上锁

配置环境 下载 Redis Windows 访问 https://github.com/microsoftarchive/redis/releases 选择版本进行下载 - 勾选 [配置到环境变量] - 无脑下一步并安装 命令行执行&#xff1a;redis-cli -v 查看已安装的 Redis 版本&#xff0c;能成功查看就表示安装成功啦~ Mac brew i…

【项目日记(四)】第一层: 线程缓存的具体实现

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:项目日记-高并发内存池⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你做项目   &#x1f51d;&#x1f51d; 开发环境: Visual Studio 2022 项目日…

PHP“引用”漏洞

今日例题&#xff1a; <?php highlight_file(__FILE__); error_reporting(0); include("flag.php"); class just4fun { var $enter; var $secret; } if (isset($_GET[pass])) { $pass $_GET[pass]; $passstr_replace(*,\*,$pass); } $o unser…

Redis(秒杀活动、持久化之RDB、AOF)

目录 秒杀活动 一、测压工具jmete的使用 二、java实现秒杀活动 1、myseckillcontroller 2、先启动pos请求添加商品&#xff0c;再启动jmeter进行压测 Redis持久化 一 、Redis持久化之RDB 1.RDB是什么 2. 备份是如何执行的 3.Fork 4. RDB持久化流程 5. dump.rdb文件 6…

自定义错误页面在Spring Boot中的实现

引言 在SpringBoot中&#xff0c;常用的异常处理有两种&#xff1a;一种是 BasicErrorController&#xff0c;另一种是 ControllerAdvice。BasicErrorController 用于处理非Controller抛出的异常&#xff0c;而ControllerAdvice 用于处理Controller抛出的异常&#xff0c;对于…

安全基础~通用漏洞2

文章目录 知识补充盲注Boolean盲注延时盲注报错注入二次注入 知识补充 盲注常用 if(条件,5,0) #条件成立 返回5 反之 返回0 left(database(),1)&#xff0c;database() #left(a,b)从左侧截取a的前b位 盲注 盲注就是在注入过程中&#xff0c;获取的数据不能回显至前端页面。 …

图像分类】【深度学习】【轻量级网络】【Pytorch版本】EfficientNet_V2模型算法详解

【图像分类】【深度学习】【轻量级网络】【Pytorch版本】EfficientNet_V2模型算法详解 文章目录 【图像分类】【深度学习】【轻量级网络】【Pytorch版本】EfficientNet_V2模型算法详解前言EfficientNet_V2讲解自适应正则化的渐进学习(Progressive Learning with adaptive Regul…

计算机毕业设计 | SpringBoot+vue养老院管理系统 社区老人健康管理(附源码)

1&#xff0c;绪论 对于目前的中国来讲&#xff0c;人口老龄化日益严重&#xff0c;目前最新统计数据表明&#xff0c;60岁以上老人所占的人口比例已达到接近20%。这给我们国家带来了严重的养老压力&#xff0c;同时也严重削弱了我们人力大国的人力优势。对于这些老年人来讲&a…

行测-判断:4.逻辑判断

行测-判断&#xff1a;4.逻辑判断 1. 翻译推理&#xff08;命题&#xff0c;长句-> 短句&#xff09; 解题思维&#xff1a; 先翻译再推理 1.1 前 ➡ 后 典型关联词&#xff1a; 如果。。那么。。 注意Keywords&#xff1a; 只要&#xff0c;就&#xff0c;如果 逆否等价…

IaC基础设施即代码:使用Terraform 连接huaweicloud华为云 并创建后端OBS

目录 一、实验 1.环境 2.huaweicloud华为云创建用户 3.Windows使用Terraform 连接 huaweicloud 4.Windows给Terraform项目添加huaweicloud华为云OBS &#xff08;实现代码与资源分离&#xff09; 二、问题 1. Windows terraform 初始化失败 2.Terraform 初始化后端资源失…

Java面试题六

1、您对微服务有何了解&#xff1f; 微服务&#xff0c;又称微服务 架 构&#xff0c;是一种架构风格&#xff0c;它将应用程序构建为以业务领域为模型的小型自治服务集合 。 通俗地说&#xff0c;你必须看到蜜蜂如何通过对齐六角形蜡细胞来构建它们的蜂窝状物。他们最初从使用…

Django、Flask 与 Javascirpt 之间传值与数据转换

常见问题&#xff1a;JavaScript 如何处理Django、Flask传递的数据库数据 Django 、Flask从数据库读出的数据通常保存为&#xff1a;对象列表、字典列表&#xff0c;或 tuple列表形式 # 用object_list 对象列表表示数据库记录 [<Article: id1,title星际穿越影评,body作为一…

嵌入式linux学习之实践操作

# 前沿 ##1. 安装交叉编译器 * 在 开发板光盘 A- 基础资料 ->5 、开发工具 ->1 、交叉编译器 路径下找到 st-example-image-qt wayland-openstlinux-weston-stm32mp1-x86_64-toolchain-3.1-snapshot.sh 。将它拷贝到 Ubuntu 虚拟机上。 拷贝到 Ubuntu 后&#xff0…

oracle19.22的patch已发布

2024年01月16日,oracle发布了19.22的patch 具体patch如下 Reserved for Database - Do not edit or delete (Doc ID 19202401.9) 文档ID规则如下 19(版本)+年份(202x)+(季度首月01,04,07,10).9 往期patch no信息和下载参考文档 oracle 19C Release Update patch num…

【Leetcode】2865. 美丽塔 I

文章目录 题目思路代码结果 题目 题目链接 给你一个长度为 n 下标从 0 开始的整数数组 maxHeights 。 你的任务是在坐标轴上建 n 座塔。第 i 座塔的下标为 i &#xff0c;高度为 heights[i] 。 如果以下条件满足&#xff0c;我们称这些塔是 美丽 的&#xff1a; 1 < hei…

【Web前端实操11】定位实操_照片墙(无序摆放)

设置一个板块&#xff0c;将照片随意无序摆放在墙上&#xff0c;从而形成照片墙。本来效果应该是很唯美好看的&#xff0c;就像这种&#xff0c;但是奈何本人手太笨&#xff0c;只好设置能达到照片墙的效果就可。 代码如下&#xff1a; <!DOCTYPE html> <html lang&…

uniapp+vue3+ts--编写微信小程序对接e签宝签署时跳转刷脸效果(人脸识别)中间页代码

uniappvue3ts–编写微信小程序对接e签宝签署时跳转刷脸效果&#xff08;人脸识别&#xff09;中间页代码 e签宝内嵌H5方式集成签署页的文档说明&#xff1a;https://open.esign.cn/doc/opendoc/case3/ahb0sg 签署时跳转刷脸效果示意图&#xff1a; 1. 在文件夹新建一个文件&a…