MyBatis使用教程详解<下>
回顾上一篇博文,我们讲了如何使用注解/XML的方式来操作数据库,实际上,一个Mapper接口的实现,这两种方式是可以并存的.
上一篇博文中,我们演示的都是比较简单的SQL语句,没有设计到复杂的逻辑,本篇博文会讲解复杂SQL的实现及一些细节处理.话不多说,让我们开始吧.
一. #{}和${}
1.1 ${}的使用
不论使用注解还是XML,我们通常使用#{}向SQL语句中传递参数.实际上,${}也起到了传递参数的效果.
先来传递一个Integer类型的参数.
这次我们使用的是user表和User实体类.
在UserMapper中定义一个updateAge方法.
//使用${}传递参数
@Update("update user set age=${age} where id=${id}")int updateAge(Integer age,Integer id);
针对该方法生成对应的测试方法.
@Testvoid updateAge() {int result=userMapper.updateAge(28,21);log.info("更新条目: "+result);}
根据日志打印结果,可以发现更新成功了
但是我们这次的重点不是更新结果,而是生成的SQL语句.
同学们先回想一下使用#{}传递参数时的SQL语句...
接着我们使用${}来传递String类型的参数.
在UserMapper接口中定义updateName方法.
@Update("update user set name=${name} where id=${id}")int updateName(String name,Integer id);
然后生成对应的测试方法.
@Testvoid updateName() {int result=userMapper.updateName("马小跳",22);log.info("更新条目: "+result);}
运行测试方法,我们会发现这次代码抛出了异常.
如果还没有发现抛出异常的原因,不妨把打印的SQL语句复制到MySQL命令行上看看.
看到熟悉的界面,不知道童鞋们有没有想起来SQL语句中的字符串需要加上''或者""
所以我们可以手动给注解中的SQL语句加上''
@Update("update user set name='${name}' where id=${id}")int updateName(String name,Integer id);
再次运行测试方法,可以正常进行更新.
1.2 #和$的区别
看到这个小标题,如果同学们觉得不过就是加不加引号的区别,那就太小看它们两个了.
#{}使用的是预编译的方式进行数据库操作,${}使用的是即时编译.
预编译会使用占位符?提前给要传入的参数占位,即时编译则是直接将传递的参数插入到SQL语句中.
来看下二者SQL语句的打印,屏幕前的你或许就不那么懵逼了.
下面详细列举一下预编译和即时编译的区别:
1. 预编译的性能更高.
一个SQL语句从编译到执行需要一下三步.
预编译SQL,编译一次之后会将编译后的SQL语句缓存起来,如果后面再次执行了该语句,不会再次编译(只是进行了传参操作),省去了前面的两个步骤.
而即时编译每执行一条SQL语句,都要进行上述的全部流程.
2. 预编译更加安全,可以防止SQL注入.
如果想根据查询指定姓名的user,在UserMapper中定义selectByName方法.
吸取上文的经验,如果使用${}传参,我们需要加上''
@Select("select * from user where name='${name}'")List<User> selectByName(String name);
编写对应的测试方法并输入下面这条数据.
@Testvoid selectByAge() {List<User> list=userMapper.selectByName("' or 1='1");log.info("根据name查询user:{}",list);}
观察打印的日志,我们会发现查询出来的是user表中的全部数据.
来看一下SQL语句.
这就相当于你提供了一个接口,客户输入姓名即可获取到自己的个人信息,然而hacker可以通过SQL注入的方式获取到全部客户的信息...
肯定有童鞋要问了,#{}的方式难道不是给字符串类型的参数加上''吗,这样不也有SQL注入的风险吗?
我们来试验一下.
@Select("select * from user where name=#{name}")List<User> selectByName2(String name);
来看一下查询结果
实际上,通过#{}传递参数并不仅仅是判断参数类型,然后添上引号这样简单.{}中的参数只在占位符的位置起效,而不会作为SQL的部分语句执行.
举个栗子~就好像你买了一瓶可乐,然后去禁止自带饮料的餐厅就餐,手边的可乐是不能喝的.
1.3 $的使用场景
你们肯定会疑惑,既然$有这么多的缺点,以后的所有场景全部无脑使用#不就万事大吉了?
#{}也有解决不了的情况.
1.3.1 排序
来实现一个方法,根据用户指定的排序方式对查询结果按照Id排序并返回.
我们先来使用#{}进行传参.
@Select("select * from user order by id #{order}")List<User> selectByIdOrder(String order);
在测试类中生成对应的测试方法.
@Testvoid selectByIdOrder() {List<User> userList=userMapper.selectByIdOrder("desc");System.out.println(userList);}
我们会发现抛出了异常,是因为#{}偷偷加了引号.
这种情况下就不得不使用${}的方式传入参数了.
@Select("select * from user order by id ${order}")List<User> selectByIdOrder(String order);
这次我们如愿拿到了排序后的结果.
但是在上文中提到,使用${}未免会有SQL注入的风险,如果不得不使用${},我们该如何规避呢?
这种情况下,我们就可以直接把代码写死,比如编写两个方法,根据用户输入的参数决定调用哪种方法进行查询.
1.3.2 模糊查询
来回顾一下模糊查询的SQL语句.
%表示零个或多个字符,_表示一个字符.
这种情况下,如果我们使用#{}进行模糊查询
@Select("select * from user where name like '%#{name}%'")List<User> selectLikeName(String name);
@Testvoid selectLikeName() {List<User> list=userMapper.selectLikeName("玉");System.out.println(list);}
这时候代码会抛出一个异常~
如果换成${}+单引号的方式.
@Select("select * from user where name like '%${name}%'")List<User> selectLikeName(String name);
就可以拿到相应结果.
这种情况下,我们又该怎么防止SQL注入呢?实际上,SQL提供了一种连接字符串的方法.
使用concat函数连接字符串
于是乎,我们就可以在模糊查询中也使用#{}进行传参了.
@Select("select * from user where name like concat('%',#{name},'%')")List<User> selectLikeName(String name);
同样拿到了正确的结果.
二. 动态SQL的实现
动态SQL是MyBatis强大的特性之一,能够完成不同条件下不同的SQL拼接.
2.1 什么是动态SQL
比如我们要填写下面这张基本信息表,其中昵称是必填项.
可以看到, 客户可能会选择不同的信息填写.如果程序猿要针对每个<input>标签都判断一下是否有填写内容的话,未免太过麻烦.在这种情况下该怎么插入客户的信息呢?
动态SQL就是根据传入参数的不同,编译不同的SQL语句.
MyBatis提供了几个标签帮助我们实现动态SQL.
因为使用XML的方式实现动态SQL比较简单,所以下面主要使用XML的方式进行展示,如何使用注解进行动态SQL将会在本文末尾稍加演示.
2.2 <if>标签
这次我们准备的是Article表及其对应的实体类.
如果用户没有输入title或者content等字段的信息,我们便不进行插入.
那么该如何使用动态SQL来实现呢?
来看代码,首先定义一个ArticleMapper接口.
@Mapper
public interface ArticleMapper {int insertArticle(Article article);
}
接着创建一个对应的XML文件(配置文件中要设置好XML文件的路径)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.ArticleMapper"><!--对应insertArticle方法--><insert id="insertArticle">insert into article(title,content,author_id,create_time,update_time)values(#{title},#{content},#{authorId},#{createTime},#{updateTime});</insert></mapper>
一般情况下,我们应该写的SQL语句是下面这样式的.
但是如果用户没有输入对应的字段值,我们需要修改这个SQL语句.该如何动态删除其中的某些字段呢?
可以借助<if>标签这样实现insertArticle方法.
<insert id="insertArticle">insert into article(<if test="title!=null">title,</if><if test="content!=null">content,</if><if test="authorId!=null">author_id,</if><if test="createTime!=null">create_time,</if><if test="updateTime!=null">update_time</if>)values(<if test="title!=null">#{title},</if><if test="content!=null">#{content},</if><if test="authorId!=null">#{authorId},</if><if test="createTime!=null">#{createTime},</if><if test="updateTime!=null">#{updateTime}</if>)</insert>
<if>标签内的test相当于一个判断语句,其中判断的title,content等变量是java对象的属性名,如果返回的是false,<if>标签内的内容将不会出现在SQL语句中.
话不多说,演示一下就能明白了.按照之前的套路,需要先创建一个测试类,然后对insertArticle方法进行测试.
@SpringBootTest
@Slf4j
class ArticleMapperTest {@Autowiredprivate ArticleMapper mapper;@Testvoid insertArticle() {Article article=new Article();article.setTitle("黑猫爱上了白猫");article.setContent("从前有一只黑猫...");article.setCreateTime(new Date());article.setUpdateTime(new Date());int result= mapper.insertArticle(article);log.info("插入条目: "+result);}
}
从打印结果上看到,我们确实插入成功了.
注意看预编译的SQL语句, 因为我们并没有对article的authorId等成员变量进行设置,所以动态生成的SQL语句中也没有出现这些字段.
但是这段代码有个Bug,童鞋们可以仔细思考一下,如果我没有设置插入文章的updateTime值会产生什么后果?
不妨来试试.
@Testvoid insertArticle() {Article article=new Article();article.setTitle("白猫不喜欢黑猫");article.setContent("从前有一只白猫...");article.setCreateTime(new Date());int result= mapper.insertArticle(article);log.info("插入条目: "+result);}
可以看到,程序抛出了异常,因为()中多了两个','
肯定有聪明的小朋友会说,如果将','放在字段前面是不是就不会产生这种情况了?(见下图)
如果这样编写SQL语句,当用户没有输入title字段时,同样会产生多余的','
那么该如何去掉这个顽固的','呢?既然一个标签不够,我们就使用两个标签来解决~
2.3 <trim>标签
先来讲解一下<trim>标签的用法----<trim>标签有四个属性:
- prefix: 表示前缀,如果trim标签内有内容,编译的SQL语句就会自动加上这个前缀
- suffix: 表示后缀,如果trim标签内有内容,编译的SQL语句就会自动加上这个后缀
- prefixOverrides: 如果整个语句块的前缀为该属性的值,则将这个前缀去除
- suffixOverrides: 如果整个语句块的后缀为该属性的值,则将这个后缀去除
结合代码,更好理解~
现在我们重新写一下insertArticle方法.
<insert id="insertArticle">insert into article<trim prefix="(" suffix=")" suffixOverrides=","><if test="title!=null">title</if><if test="content!=null">content,</if><if test="authorId!=null">author_id,</if><if test="createTime!=null">create_time,</if><if test="updateTime!=null">update_time,</if></trim>values<trim prefix="(" suffix=")" suffixOverrides="," ><if test="title!=null">#{title},</if><if test="content!=null">#{content},</if><if test="authorId!=null">#{authorId},</if><if test="createTime!=null">#{createTime},</if><if test="updateTime!=null">#{updateTime}</if></trim></insert>
再来运行一下刚才的测试方法.
@Testvoid insertArticle() {Article article=new Article();article.setTitle("白猫不喜欢黑猫");article.setContent("从前有一只白猫...");article.setCreateTime(new Date());int result= mapper.insertArticle(article);log.info("插入条目: "+result);}
可以看到,这次即使没有给updateTime变量赋值,我们也成功插入了.
下面来仔细对比一下xml文件和动态生成的SQL语句.
可以发现,即使我们没有设置updateTime的变量值,但是设置了createTime的值,createTime字段后面的 ',' 却被删除了,这就是suffixOverrides的功劳.
同样,每个trim语句块的前后都有(),这是因为我们设置了suffix和prefix.
那么,如果trim语句块中没有内容会怎样呢?我们来测试一下.
可以从打印的SQL语句中看到,trim标签中的prefix和suffix并没有添加.
2.4 <where>标签
<where>标签有如下特性:
- 如果<where>标签中没有内容,where也不会在SQL语句中存在
- 如果<where>标签中有内容,会自动在该语句块的最前面加上where
- <where>内语句块会自动去掉最前面的and
现在在ArticleMapper中编写selectArticle方法.
List<Article> selectArticles(Article article);
重点来了~来看看where标签是怎么使用的吧!
先来看看如果不使用动态SQL该怎么编写.(为了简便,这里的查询只设置了三个条件)
<select id="selectArticles" resultType="com.example.demo.entity.Article">select * from article where title=#{title} and content=#{content} and id=#{id}</select>
现在我们根据原始的SQL语句编写带有<where>标签的动态SQL.
<select id="selectArticles" resultType="com.example.demo.entity.Article">select * from article<where><if test="title!=null">title=#{title}</if><if test="content!=null">and content=#{content}</if><if test="id!=null"> and id=#{id}</if></where></select>
生成对应的测试方法并运行
@Testvoid selectArticles() {Article article=new Article();article.setContent("昨日11.27");List<Article> articleList=mapper.selectArticles(article);log.info("查询结果,ArticleList:{}",articleList);}
来对比一下xml中方法的实现和实际运行时编译的SQL语句.
可以看到,where语句块自动删除了最前面的and.
同学们想一想,如果我们输入的参数中所有字段均为Null,是不是就相当于查询article表的全部内容?
来实践一下~
可以看到,当<where>标签中没有任何内容时,where也不会出现在SQL语句中.
实际上,根据where标签的特性,我们可以使用<trim>标签来代替<where>标签,一起来看看是怎么实现的吧.
测试方法请读者自行编写.
2.5 <set>标签
<set>标签用在uodate语句中,具有以下特性:
- 当<set>标签中没有内容时,set也不会存在于SQL语句中
- 如果<set>标签中有内容,会自动在该语句块的前面加上set
- set后面的语句块会自动去掉末尾的','
结合代码更好地理解一下~
现在ArticleMapper中定义一个updateArticle方法
int updateArticleById(Article article);
在xml文件中实现这个方法,下面是不使用标签时的update语句.
如果换成<set>标签进行动态SQL的编写...
<update id="updateArticleById">update article <set><if test="title!=null">title=#{title},</if><if test="content!=null">content=#{content},</if><if test="authorId!=null">author_id=#{authorId}</if></set>where id=#{id}</update>
生成对应的测试方法并运行.
@Testvoid updateArticleById() {Article article=new Article();article.setContent("今天开始努力敲代码");article.setId(15);log.info("更新条目: "+mapper.updateArticleById(article));}
对比一下生成的SQL语句和xml文件中方法的实现.
可以看到,set标签自动删除了语句块中末尾的","
如果语句块为空,<set>标签也不会存在,我们来试验一下.
@Testvoid updateArticleById() {Article article=new Article();
// article.setContent("今天开始努力敲代码");article.setId(15);log.info("更新条目: "+mapper.updateArticleById(article));}
从打印的语句中看到,set也不存在,但实际上,这种情况是不可能发生的,如果我们要更新某个字段,就必然输入该字段更新后的值.
2.6 <foreach>标签
<foreach>标签通常用于批量操作,我们可以使用这个标签遍历java中的某个数据集合,进行指定操作.
下面列举该标签的四个属性:
- collection: 该属性的值为传入的参数(类型可以是List,Set,Map或者数组等)
- item: 给传入集合的每个元素进行统一命名,命名的值是item的值(相当于Java语法中的for-each,要给每个从集合中取出的元素命名才能拿到该对象)
- open: 标签内语句块要添加的前缀
- close: 标签内语句块要添加的后缀
- separator: 各个元素之间要添加的分隔符
下面使用一个SQL语句来说明一下:
target: 查询id为13,14,15,16的article
可以看到,上图中的查询语句中,我们传入的数据集合是13,14,15,16,每个元素之间用","分割,in后面的语句块中,需要在数据集合前后加上().
下面来演示一下批量删除操作,先在ArticleMapper中定义一个方法
int deleteArticle(List<Integer> ids);
在xml文件中写具体实现,下面先来写如果没有使用<foreach>标签...我们需要在in后面的()中填入要删除的所有id
下面是使用<foreach>的动态SQL语句.
<delete id="deleteArticle">delete from article where id in<foreach collection="ids" item="id" open="(" close=")" separator=",">#{id}</foreach></delete>
生成对应的测试方法并进行测试.
@Testvoid deleteArticle() {List<Integer> ids=new ArrayList<>();ids.add(14);ids.add(16);ids.add(18);int result= mapper.deleteArticle(ids);log.info("删除条目: "+result);}
来看一下动态生成的SQL语句.
我们不妨猜一下,如果传入的集合中没有值,<foreach>会不会存在?
结果是抛出了异常,因为我们的SQL语句有误.当然<foreach>整个语句块都会消失.不过此处只是好奇,这种操作在现实生活中当然也不会存在!
2.7 <include>标签
回顾一下我们已经编写的动态SQL语句,同学们不难发现有很多代码是十分冗余的,比如下面这个insertArticle方法
<insert id="insertArticle">insert into article<trim prefix="(" suffix=")" suffixOverrides=","><if test="title!=null">title,</if><if test="content!=null">content,</if><if test="authorId!=null">author_id,</if><if test="createTime!=null">create_time,</if><if test="updateTime!=null">update_time,</if></trim>values<trim prefix="(" suffix=")" suffixOverrides="," ><if test="title!=null">#{title},</if><if test="content!=null">#{content},</if><if test="authorId!=null">#{authorId},</if><if test="createTime!=null">#{createTime},</if><if test="updateTime!=null">#{updateTime}</if></trim></insert>
于是乎,我们就可以使用<include>标签实现代码的复用.
<sql>: 定义可重用的代码片段,里面的内容可以是语句,也可以是字段.
<include>: 通过属性refid,指定要包含的sql片段
下面我们就来修改一下这个insertArticle方法.可以看到,重复的语句是<trim>语句块,于是就可以把它扔到<sql>语句块中,并指定一个id
<sql id="insert"><trim prefix="(" suffix=")" suffixOverrides=","><if test="title!=null">title,</if><if test="content!=null">content,</if><if test="authorId!=null">author_id,</if><if test="createTime!=null">create_time,</if><if test="updateTime!=null">update_time,</if></trim></sql>
接下来直接在需要使用该代码片段的地方添加一个<include>标签即可.
<insert id="insertArticle">insert into article<include refid="insert"></include>values<include refid="insert"></include></insert>
运行我们之前写过的测试方法.
@Testvoid insertArticle() {Article article=new Article();for(int i=0;i<6;i++){article.setTitle("今日11.28");article.setContent("昨日11.27");int result= mapper.insertArticle(article);log.info("插入条目: "+result);}}
会发现没有任何不同~
2.8 使用注解进行动态SQL
在上一篇博文中,我们分别使用注解和XML的方式实现了Mapper层接口的方法,但是很容易发现,使用注解的方式会更简单,只需要在定义的方法上面编写SQL语句即可.
实际上,我们往往使用XML的方式编写动态SQL,使用注解编写简单的SQL.
因为使用注解来编写动态SQL比较麻烦.
下面简单演示一下<where>标签在注解中的实现.
@Select("<script>" +"select * from article" +"<where> " +"<if test='title!=null'>title=#{title}</if>" +"<if test='content!=null'>and content=#{content}</if>"+"<if test='id!=null'>and id=#{id}</if>"+"</where>"+"</script>")List<Article> selectArticles(Article article);
可以看到,需要在SQL语句的开头添加<script>标签,然后对照这xml文件copy一遍...
运行测试方法,可以正常运行,但是过程未免复杂了些~
其余标签不再进行演示,感兴趣的读者可自行实验.
三. 数据库连接池
在JDBC阶段,我们主要使用了四个对象----DataSource,Connection,PreStatement,ResultSet .其中DataSource指定一个数据源,我们可以从中获取数据库连接,即Connection对象.
简单回顾一下"池":当我们需要取对象时,从池中取即可,当我们不再需要这个对象时,把它重新放回池即可.
"池"帮助我们省去了创建/销毁对象的开销,尤其是频繁创建/销毁的场景下.
常见的数据库连接池有: C3P0,DBCP,Druid,HiKari...
其中HiKari是Spring默认使用的数据库连接池,我们可以从Spring打印的日志中发现它的痕迹.
当然了,如果我们想更换连接池,只需要引入相应的依赖即可.
比如把连接池换成Druid,我们需要在pom.xml文件中添加对应的依赖
然后再次运行启动Spring,就可以从日志中发现这次使用的数据库连接池是Druid了.
相关文章:

MyBatis使用教程详解<下>
回顾上一篇博文,我们讲了如何使用注解/XML的方式来操作数据库,实际上,一个Mapper接口的实现,这两种方式是可以并存的. 上一篇博文中,我们演示的都是比较简单的SQL语句,没有设计到复杂的逻辑,本篇博文会讲解复杂SQL的实现及一些细节处理.话不多说,让我们开始吧. 一. #{}和${} …...

C++基础 -17-继承中 基类与派生构造和析构调用顺序
首先声明 定义了派生类会同时调用基类和派生的构造函数 定义了派生类会同时调用基类和派生的析构函数 那么顺序如何如下图 构造由上往下顺序执行 析构则完全相反 #include "iostream"using namespace std;class base {public:base(){cout << "base-bui…...

uniapp实现表单弹窗
uni.showModal({title: 删除账户,confirmColor:#3A3A3A,cancelColor:#999999,confirmText:确定,editable:true,//显示content:请输入“delete”删除账户,success: function (res) {console.log(res)if(res.confirm){if(res.contentdelete){console.log(123123123213)uni.setSto…...
Ajax 是什么? 如何创建一个 Ajax?
Ajax(Asynchronous JavaScript and XML)是一种使用客户端JavaScript发送异步HTTP请求到服务器的技术,以便在不重新加载整个页面的情况下更新部分网页内容。 使用Ajax的原因有很多,以下是其中一些: 改善用户体验&…...

【LeetCode】101. 对称二叉树
101. 对称二叉树 难度:简单 题目 给你一个二叉树的根节点 root , 检查它是否轴对称。 示例 1: 输入:root [1,2,2,3,4,4,3] 输出:true示例 2: 输入:root [1,2,2,null,3,null,3] 输出&#…...

O-Star|再相识
暑去秋来,岁月如梭,几名"O-Star"们已经入职一段时间,在这期间他们褪去青涩,逐渐适应了公司的工作环境和文化,迈向沉稳~ 为了进一步加深校招生之间的交流与了解,提高校招生的凝聚力和…...

最新PHP熊猫头图片表情斗图生成源码
这是一款能生成熊猫头表情斗图的自适应系统源码,无论是在电脑还是手机上都可以正常使用!这个源码集成了搜狗搜索图片接口,可以轻松地一键搜索数百万张图片,并且还包含了表情制作等功能模块。对于一些新站来说,这是一个…...

子虔科技出席2023WAIC“智能制造融合创新论坛”
7月7日,2023世界人工智能大会(WAIC)闵行会场在大零号湾举办。子虔科技联合创始人周洋作为专家嘉宾受邀参与智能制造融合创新论坛大会。会上探讨了工业和制造业数字化转型的机遇、挑战和对策。其中,周洋提到,工业制造业…...

递归算法学习——二叉树的伪回文路径
1,题目 给你一棵二叉树,每个节点的值为 1 到 9 。我们称二叉树中的一条路径是 「伪回文」的,当它满足:路径经过的所有节点值的排列中,存在一个回文序列。 请你返回从根到叶子节点的所有路径中 伪回文 路径的数目。 示例…...

Android端极致画质体验之HDR播放
高动态范围HDR视频通过扩大亮度分量的动态范围(从100cd/m2到1000cd/m2),以及采用更宽的色彩空间BT2020,提供极致画质体验。从Android10开始,支持HDR视频播放。 一、HDR技术 HDR技术标准包括:Dolby-Vision、HDR10、HLG、PQ。支持…...

【Java SE】带你在String类世界中遨游!!!
🌹🌹🌹我的主页🌹🌹🌹 🌹🌹🌹【Java SE 专栏】🌹🌹🌹 🌹🌹🌹上一篇文章:带你走近Java的…...
Android: ListView + ArrayAdapter 简单应用
容器与适配器: http://t.csdnimg.cn/ZfAJ7 activity_main.xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"h…...

前端:实现二级菜单(点击实现二级菜单展开)
效果 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width, i…...

Spark-java版
SparkContext初始化 相关知识 SparkConf 是SparkContext的构造参数,储存着Spark相关的配置信息,且必须指定Master(比如Local)和AppName(应用名称),否则会抛出异常;SparkContext 是程序执行的入口…...

RabbitMQ消息模型之Work Queues
Work Queues Work Queues,也被称为(Task Queues),任务模型,也是官网给出的第二个模型,使用的交换机类型是直连direct,也是默认的交换机类型。当消息处理比较耗时的时候,可能生产消息…...

vue3+ts 实现时间间隔选择器
需求背景解决效果视频效果balancedTimeElement.vue 需求背景 实现一个分片的时间间隔选择器,需要把显示时间段显示成图表,涉及一下集中数据转换 [“02:30-05:30”,“07:30-10:30”,“14:30-17:30”]‘[(2,5),(7,10),(14,17)]’[4, 5, 6, 7, 8, 9, 10, …...
PTA 魔法优惠券
7-83 魔法优惠券 分数 25 全屏浏览题目 作者 陈越 单位 浙江大学 在火星上有个魔法商店,提供魔法优惠券。每个优惠劵上印有一个整数面值K,表示若你在购买某商品时使用这张优惠劵,可以得到K倍该商品价值的回报!该商店还免费赠送…...

P8A110-A120经典赛题
Web应用程序SQL Inject安全攻防 任务环境说明: 服务器场景:WebServ2003(用户名:administrator;密码:空)服务器场景操作系统:Microsoft Windows2003 Server 服务器场景安装服务/工…...

文件基础知识
计算机中的流:在C语言中将通过输入/输出设备(键盘、内存、显示器、网络等)之间的数据传输抽象表述为“流”。 1、文本流和二进制流 在文本流中输入输出的数据是一系列的字符,可以被修改在二进制流中输入输出数据是一系列字节&am…...

二叉树OJ题之二
今天我们一起来看一道判断一棵树是否为对称二叉树的题,力扣101题, https://leetcode.cn/problems/symmetric-tree/ 我们首先先来分析这道题,要判断这道题是否对称,我们首先需要判断的是这颗树根节点的左右子树是否对称࿰…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...

从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...

视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...

论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...