作者:小凯
沉淀、分享、成长,让自己和他人都能有所收获!

本文的宗旨在于通过简单干净实践的方式教会读者,使用 SpringBoot 配置 MyBatis 并完成对插入、批量插入、修改、查询以及注解事务和编程事务的使用,通过扩展插件开发对指定字段进行加解处理。

此外本文也通过此案例,渗透讲解 DDD 模型中的聚合对象、实体对象和值对象在领域模型中的实践。

一、案例背景
说一千道一万,写的教程,得简单还好看!
为了更好的把 MyBatis 常用的各项功能体现的清晰明了,这里设定了公司雇员和对应薪酬关系的一个开发场景。
在这里插入图片描述

  • 首先,雇员员工和对应的薪资待遇,是一个1v1的关系。
  • 之后,薪资表与调薪表,是一个1vn的关系。每次晋升、普调,都会有一条对应的调薪记录。
  • 最后,有了这样3个表,我们就可以很好的完成,员工的插入、批量插入,和事务操作调薪。

二、领域模型
🌶 模型定义:理解模型概念和设计原则。
在这里插入图片描述
在这里插入图片描述

此场景的业务用于对指定的用户进行晋升加薪调幅,但因为加薪会需要操作3个表,包括:雇员表 - 修改个人Title、薪资表 - 修改薪酬、调薪记录表 - 每一次加薪都写一条记录。

1. model
1.1 值对象

public enum EmployeePostVO {T1("T-1", "初级工程师"),T2("T-2", "初级工程师"),T3("T-3", "中级工程师"),T4("T-4", "中级工程师"),T5("T-5", "高级工程师"),T6("T-6", "高级工程师"),T7("T-7", "架构师"),T8("T-8", "架构师");private final String code;private final String desc;// 省略部分}
  • 当一个实体对象中的一个值,是有多个范围时候,则需要定义出值对象。由于此类的值对象更贴近于当前的场景业务,所以一般不会被定义为共用的枚举。如此此类值范围,都会被定义为值对象。

1.2 实体对象

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class EmployeeEntity {/** 雇员级别 */private EmployeePostVO employeeLevel;/** 雇员岗位Title */private EmployeePostVO employeeTitle;}@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class EmployeeSalaryAdjustEntity {/** 总额调薪 */private BigDecimal adjustTotalAmount;/** 基础调薪 */private BigDecimal adjustBaseAmount;/** 绩效调薪 */private BigDecimal adjustMeritAmount;}
  • 实体对象是对数据库对象的抽象,大多数时候是 1:1 的关系结构,在一些复杂的模型场景中会是1:n的结构。

1.3 聚合对象

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AdjustSalaryApplyOrderAggregate {/** 雇员编号 */private String employeeNumber;/** 调薪单号 */private String orderId;/** 雇员实体 */private EmployeeEntity employeeEntity;/** 雇员调薪实体 */private EmployeeSalaryAdjustEntity employeeSalaryAdjustEntity;}
  • 聚合对象是对实体对象和值对象的封装,代表着一类业务的聚合。通常是作为 service 服务层中入参出现。

2. repository

public interface ISalaryAdjustRepository {String adjustSalary(AdjustSalaryApplyOrderAggregate adjustSalaryApplyOrderAggregate);}
  • 仓储在 DDD 中的设计,是一种依赖倒置关系,由 domain 定义接口,之后由引入 domain 包的基层层 infrastructure 实现功能。
  • 此外,因为是依赖倒置,所以天然的隔离了 PO 数据库持久化对象,不会被对外使用。这个设计是非常巧妙的。当我们从结构上定义了原则,就不会有人乱引用对象了。

3. service

public interface ISalaryAdjustApplyService {String execSalaryAdjust(AdjustSalaryApplyOrderAggregate adjustSalaryApplyOrderAggregate);}
  • 当前场景简单,所以不需要额外的设计模式使用。但如果是复杂场景,必须考虑设计模式,否则代码都写到 SalaryAdjustApplyService 实现类里,那么将非常难维护。
  • 不要只是把聚合对象当充血模型,你的充血结构是整个 domain 下的每一个领域包,也就是让这里的状态与行为看做为一整个结构。

📢 综上,有了这样的模型结构设计定义,相信你也可以很好的拆分自己的业务对象并完成领域功能实现了。

三、配置文件
在这里插入图片描述

  • 工程中关于 MyBatis 的使用,在 xfg-dev-tech-app 下进行统一配置。因为所有配置信息都放到一起,比较方便管理,也利于线上上线后,提取配置文件。

四、功能实现
接下来我们介绍一些关于 MyBatis 的使用功能,但你可以带着 DDD 的思想来看这些内容实现时所在的位置,这会让你不只是学习 MyBatis 也能学会一些 DDD 的设计。

1. 插入&批量插入
源码:cn.bugstack.xfg.dev.tech.infrastructure.dao.IEmployeeDAO

@Mapper
public interface IEmployeeDAO {void insert(EmployeePO employee);void insertList(List<EmployeePO> list);void update(EmployeePO employeePO);EmployeePO queryEmployeeByEmployNumber(String employNumber);}

xml:employee_mapper.xml

<insert id="insert" parameterType="cn.bugstack.xfg.dev.tech.infrastructure.po.EmployeePO">INSERT INTO employee(employee_number, employee_name, employee_level, employee_title, create_time, update_time)VALUES(#{employeeNumber}, #{employeeName}, #{employeeLevel}, #{employeeTitle}, now(), now())
</insert><insert id="insertList" parameterType="java.util.List">INSERT INTO employee(employee_number, employee_name, employee_level, employee_title, create_time, update_time)VALUES<foreach collection="list" item="item" separator=",">(#{item.employeeNumber}, #{item.employeeName}, #{item.employeeLevel}, #{item.employeeTitle}, now(), now())</foreach>
</insert>
  • 使用配置文件的方式比较好维护,当然也可以尝试使用 MyBatis 提供的注解方式,完成数据的操作。

2. 事务&注解编程
Spring 提供的事务分为注解事务和编程事务,编程事务可以更细粒度的控制。

Spring Boot 事务管理的级别可以通过 @Transactional 注解的 isolation 属性进行配置。常见的事务隔离级别有以下几种:

  1. DEFAULT:使用底层数据库的默认隔离级别。MySQL 默认为 REPEATABLE READ,Oracle 默认为 READ COMMITTED。
  2. READ_UNCOMMITTED:最低的隔离级别,允许读取未提交的数据变更,可能会导致脏读、不可重复读和幻读问题。
  3. READ_COMMITTED:允许读取已经提交的数据变更,可以避免脏读问题,但可能会出现不可重复读和幻读问题。
  4. REPEATABLE_READ:保证同一事务中多次读取同一数据时,结果始终一致,可以避免脏读和不可重复读问题,但可能会出现幻读问题。
  5. SERIALIZABLE:最高的隔离级别,可以避免脏读、不可重复读和幻读问题,但会影响并发性能。
    在 Spring Boot 中,默认的事务隔离级别为 DEFAULT。如果没有特殊需求,建议使用默认隔离级别。

SpringBoot 事务的传播行为可以通过 @Transactional 注解的 propagation 属性进行配置。常用的传播行为有以下几种:

  • Propagation.REQUIRED:默认的传播行为,如果当前存在事务,则加入该事务,否则新建一个事务;
  • Propagation.SUPPORTS:如果当前存在事务,则加入该事务,否则以非事务的方式执行;
  • Propagation.MANDATORY:如果当前存在事务,则加入该事务,否则抛出异常;
  • Propagation.REQUIRES_NEW:无论当前是否存在事务,都会新建一个事务,如果当前存在事务,则将当前事务挂起;
  • Propagation.NOT_SUPPORTED:以非事务的方式执行操作,如果当前存在事务,则将当前事务挂起;
  • Propagation.NEVER:以非事务的方式执行操作,如果当前存在事务,则抛出异常;
  • Propagation.NESTED:如果当前存在事务,则在该事务的嵌套事务中执行,否则新建一个事务。嵌套事务是独立于外部事务的,但是如果外部事务回滚,则嵌套事务也会回滚。
    除了传播行为,@Transactional 注解还可以配置其他属性,例如隔离级别、超时时间、只读等。

2.1 注解事务
源码:cn.bugstack.xfg.dev.tech.infrastructure.repository.SalaryAdjustRepository

@Transactional(rollbackFor = Exception.class, timeout = 350, propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
public String adjustSalary(AdjustSalaryApplyOrderAggregate adjustSalaryApplyOrderAggregate) {String employeeNumber = adjustSalaryApplyOrderAggregate.getEmployeeNumber();String orderId = adjustSalaryApplyOrderAggregate.getOrderId();EmployeeEntity employeeEntity = adjustSalaryApplyOrderAggregate.getEmployeeEntity();EmployeeSalaryAdjustEntity employeeSalaryAdjustEntity = adjustSalaryApplyOrderAggregate.getEmployeeSalaryAdjustEntity();EmployeePO employeePO = EmployeePO.builder().employeeNumber(employeeNumber).employeeLevel(employeeEntity.getEmployeeLevel().getCode()).employeeTitle(employeeEntity.getEmployeeTitle().getDesc()).build();// 更新岗位employeeDAO.update(employeePO);EmployeeSalaryPO employeeSalaryPO = EmployeeSalaryPO.builder().employeeNumber(employeeNumber).salaryTotalAmount(employeeSalaryAdjustEntity.getAdjustTotalAmount()).salaryMeritAmount(employeeSalaryAdjustEntity.getAdjustMeritAmount()).salaryBaseAmount(employeeSalaryAdjustEntity.getAdjustBaseAmount()).build();// 更新薪酬employeeSalaryDAO.update(employeeSalaryPO);EmployeeSalaryAdjustPO employeeSalaryAdjustPO = EmployeeSalaryAdjustPO.builder().employeeNumber(employeeNumber).adjustOrderId(orderId).adjustTotalAmount(employeeSalaryAdjustEntity.getAdjustTotalAmount()).adjustBaseAmount(employeeSalaryAdjustEntity.getAdjustMeritAmount()).adjustMeritAmount(employeeSalaryAdjustEntity.getAdjustBaseAmount()).build();// 写入流水employeeSalaryAdjustDAO.insert(employeeSalaryAdjustPO);return orderId;
}
  • 这个事务所做的内容,就是前面提到的调整薪资的处理。它的具体操作就是放到仓储层实现。
  • 注意事务注解的配置。

2.2 编程事务
2.2.1 事务模板
在这里插入图片描述

  • 使用编程事务,需要在这里创建出一个事务模板,当然你不创建也可以使用。只不过这样统一的配置会更加方便。

2.2.2 事务使用

private TransactionTemplate transactionTemplate;
@Override
public void insertEmployeeInfo(EmployeeInfoEntity employeeInfoEntity) {transactionTemplate.execute(new TransactionCallbackWithoutResult() {@Overrideprotected void doInTransactionWithoutResult(TransactionStatus status) {try {EmployeePO employeePO = EmployeePO.builder().employeeNumber(employeeInfoEntity.getEmployeeNumber()).employeeName(employeeInfoEntity.getEmployeeName()).employeeLevel(employeeInfoEntity.getEmployeeLevel()).employeeTitle(employeeInfoEntity.getEmployeeTitle()).build();employeeDAO.insert(employeePO);EmployeeSalaryPO employeeSalaryPO = EmployeeSalaryPO.builder().employeeNumber(employeeInfoEntity.getEmployeeNumber()).salaryTotalAmount(employeeInfoEntity.getSalaryTotalAmount()).salaryMeritAmount(employeeInfoEntity.getSalaryMeritAmount()).salaryBaseAmount(employeeInfoEntity.getSalaryBaseAmount()).build();employeeSalaryDAO.insert(employeeSalaryPO);} catch (Exception e) {status.setRollbackOnly();e.printStackTrace();}}});
}
  • 之后就可以手动处理事务了,因为手动的处理可以更细节的控制,也可以根据返回的结果,手动回滚。而不非得异常回滚。

3. 插件&数据加密
使用 MyBatis 时,也会经常会用到插件开发。尤其是做一些数据的加解密、路由、日志等,都可以基于插件实现。

那么这里实现一个对指定字段加解密的处理,比如雇员的姓名、薪资、级别是可以隐藏的,避免被有心之人盗取。

源码:cn.bugstack.xfg.dev.tech.plugin.FieldEncryptionAndDecryptionMybatisPlugin

@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class FieldEncryptionAndDecryptionMybatisPlugin implements Interceptor {/*** 密钥,必须是16位*/private static final String KEY = "1898794876567654";/*** 偏移量,必须是16位*/private static final String IV = "1233214566547891";@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object[] args = invocation.getArgs();MappedStatement mappedStatement = (MappedStatement) args[0];Object parameter = args[1];String sqlId = mappedStatement.getId();if (parameter != null && (sqlId.contains("insert") || sqlId.contains("update")) ) {String columnName = "employeeName";if (parameter instanceof Map) {List<Object> parameterList = (List<Object>) ((Map<?, ?>) parameter).get("list");for (Object obj : parameterList) {if (hasField(obj, columnName)) {String fieldValue = BeanUtils.getProperty(obj, columnName);String encryptedValue = encrypt(fieldValue);BeanUtils.setProperty(obj, columnName, encryptedValue);}}} else {if (hasField(parameter, columnName)) {String fieldValue = BeanUtils.getProperty(parameter, columnName);String encryptedValue = encrypt(fieldValue);BeanUtils.setProperty(parameter, columnName, encryptedValue);}}}Object result = invocation.proceed();if (result != null && sqlId.contains("query")) {// 查询操作,解密String columnName = "employeeName";if (result instanceof List) {List<Object> resultList = (List<Object>) result;for (Object obj : resultList) {if (!hasField(obj, columnName)) continue;String fieldValue = BeanUtils.getProperty(obj, columnName);if (StringUtils.isBlank(fieldValue)) continue;String decryptedValue = decrypt(fieldValue);BeanUtils.setProperty(obj, columnName, decryptedValue);}}}return result;}public String encrypt(String content) throws Exception {Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");byte[] raw = KEY.getBytes();SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES");IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes());cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);byte[] encrypted = cipher.doFinal(content.getBytes());return Base64.getEncoder().encodeToString(encrypted);}/*** AES解密** @param content 密文* @return 明文* @throws Exception 异常*/public String decrypt(String content) throws Exception {Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");byte[] raw = KEY.getBytes();SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES");IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes());cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);byte[] encrypted = Base64.getDecoder().decode(content);byte[] original = cipher.doFinal(encrypted);return new String(original);}public boolean hasField(Object obj, String fieldName) {Class<?> clazz = obj.getClass();while (clazz != null) {try {Field field = clazz.getDeclaredField(fieldName);return true;} catch (NoSuchFieldException e) {clazz = clazz.getSuperclass();}}return false;}}

在这里插入图片描述

  • 首先通过注解配置,拦截指定范围内的信息 Intercepts 之后在 intercept 接口实现方法中,获取 MappedStatement 这个 MyBatis的映射核心类。
  • 有了 AES 的加解密,就可以对指定的字段 employeeName 对插入数据库的字段进行加密,同时还可以在读取的时候解密。
    五、测试验证
    1. 调薪
@Test
public void test_execSalaryAdjust() {AdjustSalaryApplyOrderAggregate adjustSalaryApplyOrderAggregate = AdjustSalaryApplyOrderAggregate.builder().employeeNumber("10000001").orderId("100908977676001").employeeEntity(EmployeeEntity.builder().employeeLevel(EmployeePostVO.T3).employeeTitle(EmployeePostVO.T3).build()).employeeSalaryAdjustEntity(EmployeeSalaryAdjustEntity.builder().adjustTotalAmount(new BigDecimal(100)).adjustBaseAmount(new BigDecimal(80)).adjustMeritAmount(new BigDecimal(20)).build()).build();String orderId = salaryAdjustApplyService.execSalaryAdjust(adjustSalaryApplyOrderAggregate);log.info("调薪测试 req: {} res: {}", JSON.toJSONString(adjustSalaryApplyOrderAggregate), orderId);
}
23-07-15.13:23:11.514 [main            ] INFO  HikariDataSource       - HikariPool-1 - Start completed.
23-07-15.13:23:11.910 [main            ] INFO  ISalaryAdjustApplyServiceTest - 调薪测试 req: {"employeeEntity":{"employeeLevel":"T3","employeeTitle":"T3"},"employeeNumber":"10000001","employeeSalaryAdjustEntity":{"adjustBaseAmount":80,"adjustMeritAmount":20,"adjustTotalAmount":100},"orderId":"100908977676002"} res: 100908977676002

2. 查询

@Test
public void test_queryEmployInfo() {EmployeeInfoEntity employeeInfoEntity = employeeService.queryEmployInfo("10000001");log.info("测试结果:{}", JSON.toJSONString(employeeInfoEntity));
}
23-07-15.13:24:54.000 [main            ] INFO  HikariDataSource       - HikariPool-1 - Start completed.
23-07-15.13:24:54.490 [main            ] INFO  IEmployeeServiceTest   - 测试结果:{"employeeLevel":"T-3","employeeName":"小哥哥","employeeNumber":"10000001","employeeTitle":"中级工程师","salaryBaseAmount":5200.00,"salaryMeritAmount":5200.00,"salaryTotalAmount":5200.00}
  • 执行完调薪后,就可以来看下这个用户的薪资待遇是多少了。

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

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

相关文章

Maui劝退:用windows直接真机调试iOS,无须和Mac配对

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

【极客日常】后端任务动态注入执行策略的一种技术实现

近期做项目时遇到一个场景&#xff0c;是需要在后端任务执行时动态注入策略。具体而言&#xff0c;笔者负责的后端服务&#xff0c;可以理解是会在线上服务发布时&#xff0c;对服务风险做实时扫描&#xff0c;那么这个扫描就需要根据当前线上服务发布上下文&#xff0c;匹配对…

8. JVM类装载的执行过程

1. JVM介绍和运行流程-CSDN博客 2. 什么是程序计数器-CSDN博客 3. java 堆和 JVM 内存结构-CSDN博客 4. 虚拟机栈-CSDN博客 5. JVM 的方法区-CSDN博客 6. JVM直接内存-CSDN博客 7. JVM类加载器与双亲委派模型-CSDN博客 8. JVM类装载的执行过程-CSDN博客 9. JVM垃圾回收…

Linux操作系统之信号:信号的产生

前言&#xff1a;上篇文章我们大致讲解了信号的有关概念&#xff0c;为大家引入了信号的知识点。但光知道那些是远远不够的。本篇文章&#xff0c;我将会为大家自己的讲解一下信号的产生的五种方式&#xff0c;希望对大家有所帮助。一、键盘&#xff08;硬件&#xff09;产生信…

pdf拆分

文章目录 背景目标实现下载 背景 好不容易下载的1000页行业报告&#xff0c;领导非要按章节拆分成20份&#xff01;学术论文合集需要按作者拆分投稿&#xff0c;手动分页到怀疑人生…客户发来加密合同&#xff0c;要求每5页生成独立文档&#xff0c;格式还不能乱&#xff01; …

vue3使用mermaid生成图表,并可编辑

效果图实际代码<template><div class"mermaid-container" style"z-index: 99999" ref"wrapperRef"><!-- 控制栏 --><div class"control-bar"><div class"control-bar-flex control-bar-tab-wrap"…

tcp/quic 的滑动窗口

一、滑动窗口 rwnd&#xff1a; 接收端窗口&#xff0c;接收方在每次发送ACK确认报文时&#xff0c;会包含一个 rwnd (Receive Window Size) 字段&#xff0c;指明自己当前剩余的接收缓冲区大小&#xff08;即可用窗口&#xff09;&#xff0c;这里是否是socket的接收缓冲区&am…

JVM监控及诊断工具-命令行篇

18.1. 概述 性能诊断是软件工程师在日常工作中需要经常面对和解决的问题&#xff0c;在用户体验至上的今天&#xff0c;解决好应用的性能问题能带来非常大的收益。 Java 作为最流行的编程语言之一&#xff0c;其应用性能诊断一直受到业界广泛关注。可能造成 Java 应用出现性能…

Jenkins 版本升级与插件问题深度复盘:从 2.443 到 2.504.3 及功能恢复全解析

前言&#xff1a;问题溯源与升级必要性 在 Jenkins 持续集成体系中&#xff0c;插件生态是其强大功能的核心驱动力。然而&#xff0c;某次例行维护中&#xff0c;团队对 Jenkins 2.443 环境的插件进行批量升级后&#xff0c;意外触发连锁反应 &#xff1a; SSH Server 插件功能…

Ribbon实战

一、前置知识 1.1 负载均衡定义 负载均衡指的是将网络请求通过不同的算法分配到不同的服务器上的技术&#xff0c;从而提升系统的性能。 1.2 负载均衡工具 负载均衡工具可以分分为客户端负载均衡工具和服务端负载均衡工具&#xff0c;它们的区别如下。 表1-1 负载均衡工具…

cs285学习笔记(一):课程总览

根据 Fall 2023 学期的官方课程日程&#xff0c;这里是 CS 285 全课程的 Lecture 大纲及内容摘要&#xff0c;详细对应周次和主题&#xff0c;方便你快速定位每节课要点、相关作业与视频资源 &#x1f3af; 官方课程地址 YouTobe 视频地址 blibli视频(带中文字幕) &#x…

OkHttp SSE 完整总结(最终版)

1. SSE 基础概念 什么是 SSE&#xff1f; SSE&#xff08;Server-Sent Events&#xff09;是一种 Web 标准&#xff0c;允许服务器向客户端推送实时数据。 核心特点 单向通信&#xff1a;服务器 → 客户端 基于 HTTP 协议&#xff1a;使用 GET 请求 长连接&#xff1a;连…

聚宽sql数据库传递

自建数据库从聚宽到Q-MT自动化交易实战 从接触聚宽以来一直都是手动跟单&#xff0c;在网上看到许多大佬的自动交易文章&#xff0c;心里也不禁十分痒痒。百说不如一练&#xff0c;千讲不如实干。经过一番努力&#xff0c;终于成功实盘了&#xff0c;效果还可以&#xff0c;几…

es里为什么node和shard不是一对一的关系

提问&#xff1a; 既然多个shard会被分配到同一个node上&#xff0c;那么为什么不把多个shard合并成一个然后存在当前node上呢&#xff0c;简而言之也就是让node和shard形成一对一的关系呢 &#xff1f;非常好的问题&#xff0c;这正是理解Elasticsearch分片&#xff08;shard…

浅谈npm,cnpm,pnpm,npx,nvm,yarn之间的区别

首先做一个基本的分类 名称描述npm,cnpm,yarn,pnpm都是Javascript包管理器nvm是Node.js版本控制器npx命令行工具 I.npm,cnpm,yarn,pnpm npm (Node Package Manager) npm是Node.js默认的包管理器&#xff0c;随Node.js的安装会一起安装。使用npm可以安装&#xff0c;发布&…

滑动窗口-76.最小覆盖子串-力扣(LeetCode)

一、题目解析1.不符合要求则返回空串("")2.子串中重复字符的数量要不少于t中该字符的数量二、算法原理解法1&#xff1a;暴力枚举哈希表这里的暴力枚举也可以优化&#xff0c;即在包含t中元素处枚举&#xff0c;如在A、B和C处开始枚举&#xff0c;减少不必要的枚举 解…

从零构建搜索引擎 build demo search engine from scratch

从零构建搜索引擎 build demo search engine from scratch 我们每天都会使用搜索引擎&#xff1a;打开google等搜索引擎&#xff0c;输入关键词&#xff0c;检索出结果&#xff0c;这是一次搜索&#xff1b;当打开历史记录旁边的&#x1f50d;按钮&#xff0c;输入关键词&#…

pytorch小记(二十九):深入解析 PyTorch 中的 `torch.clip`(及其别名 `torch.clamp`)

pytorch小记&#xff08;二十九&#xff09;&#xff1a;深入解析 PyTorch 中的 torch.clip&#xff08;及其别名 torch.clamp&#xff09;深入解析 PyTorch 中的 torch.clip&#xff08;及其别名 torch.clamp&#xff09;一、函数签名二、简单示例三、广播支持四、与 Autograd…

快速分页wpf

/*没有在xaml设置上下文window.context是因为 命名空间一直对应不上 所以在xaml.cs 里面绑定*/ <Window x:Class"DataGrid.views.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft…

如何彻底禁用 Chrome 自动更新

如何彻底禁用 Chrome 自动更新 随着谷歌将 Chrome 浏览器版本升级至 138&#xff0c;它即将彻底抛弃对 Manifest V2 扩展的支持。许多用户希望将浏览器版本锁定在 138&#xff0c;以继续使用 uBlock Origin、Tampermonkey 等常用扩展。 本文总结了四种有效方法&#xff0c;帮助…