12. MyBatis(二)

news2025/1/16 16:12:29

源码位置:MyBatis_demo

上篇文章我们学习了MyBatis的定义以及增删查改操作,并且学习了如何在xml文件中编写SQL时使用#{}的方式将参数和对象的属性映射到SQL语句中,上篇的内容已经足以应对大部分场景,本篇文章我们就要学习一下MyBatis的进阶操作,拿捏MyBatis实战。

1. 参数占位符 —— #{} 和 ${}

MyBatis中有两种参数占位符,分别是"#{}""${}",上节课我们学习了"#{}"的使用,以上节课根据id查询用户的业务为例:

接口以及xml文件中的SQL实现如下:

public UserInfo getUserById(@Param("uid") Integer id);

把xml文件中的"#{}"改为"${}"

<select id="getUserById" resultType="com.chenshu.mybatis_demo.model.UserInfo">
    select * from userinfo where id = ${uid}
</select>

调用测试方法后成功查到数据:

image.png

我们发现将"#{}"改为"${}"后同样能够查出数据,那不是玩我呢吗?

不要着急,这时我们再编写一个通过username查询的业务,接口以及xml文件中的SQL实现如下:

public List<UserInfo> getUserByUsername(@Param("username") String username);
<select id="getUserByUsername" resultType="com.chenshu.mybatis_demo.model.UserInfo">
    select * from userinfo where username = #{username}
</select>

单元测试:

@Test
void getUserByUsername() {
    List<UserInfo> userInfoList = userMapper.getUserByUsername("zhangsan");
    System.out.println(userInfoList);
}

在使用"#{}"参数占位符的情况下成功取到数据:

image.png

改为"${}"后居然报错了,报错信息:在where子句中有未知的列’zhangsan’

image.png

为什么呢会这样呢?我明明是想要查询usernamezhangsan的列呀,他为什么说没找到名字为"zhangsan"的列呢?原因很简单,两种占位符的区别如下:

1.1 #{}:预编译处理

#{}是预编译处理,等同于JDBC中的"?"占位符,预编译的SQL语句不是有具体数值的语句,而是用(?)来代替具体数据,然后在执行的时候再调用setXXX()方法把具体的数据传入。

<select id="getUserByUsername" resultType="com.chenshu.mybatis_demo.model.UserInfo">
    select * from userinfo where username = #{username}
</select>

预编译的处理下,在数据库中执行的SQL语句为:select * from userinfo where username = "zhangsan";也就是查询usernamezhangsan的数据。

1.2 ${}:字符直接替换

${}是字符直接替换,会直接将传入参数的值放入SQL语句中。

<select id="getUserByUsername" resultType="com.chenshu.mybatis_demo.model.UserInfo">
    select * from userinfo where username = ${username}
</select>

在数据库中执行的SQL语句为:select * from userinfo where username = zhangsan;,这时就变成了在表中查找出所有userinfozhangsan这两个字段值相同的数据;

了解后了两者的差异后,我们直接将xml中的SQL改为下面的形式:

<select id="getUserByUsername" resultType="com.chenshu.mybatis_demo.model.UserInfo">
    select * from userinfo where username = '${username}'
</select>

也成功查询到了数据:

image.png

1.3 ${}的缺陷:SQL注入

以登录功能为例,正常情况下,用户在前端需要传一个账号和密码给后端,后端再把账号和密码放到where子句中查询数据库,如果查到对应的数据,就证明用户名和密码输入正确,登录成功,SQL语句如下:

select * from userinfo where username = 'zhangsan' and password = '123';

输入正确就能查询到数据:

image.png

否则就查不到数据:

image.png

这时我们来使用${}实现一下登录功能:

接口:

public UserInfo userLogin(@Param("username") String username, @Param("password") String password);

xml编写SQL:

<select id="userLogin" resultType="com.chenshu.mybatis_demo.model.UserInfo">
    select * from userinfo where username = '${username}' and password = '${password}'
</select>

单元测试:

@Test
void userLogin() {
    UserInfo userInfo = userMapper.userLogin("zhangsan", "123");
    if (userInfo == null) {
        System.out.println("用户名或密码错误,请重新登录!");
    } else {
        System.out.println("登录成功!");
        System.out.println(userInfo);
    }
}

在输入正确的账号和密码后,打印了下面信息:

image.png

输入错误的密码后,打印了下面信息:

image.png

1.3.1 SQL注入

当有别有用心之人在其不知道密码的情况下,恶意利用SQL语句的特性登录成功,就是SQL注入,下面给大家举个例子:

SQL注入代码:“’ or 1='1”

将传入的密码改为上面的SQL注入语句:

@Test
void userLogin() {
    UserInfo userInfo = userMapper.userLogin("zhangsan", "' or 1='1");
    if (userInfo == null) {
        System.out.println("用户名或密码错误,请重新登录!");
    } else {
        System.out.println("登录成功!");
        System.out.println(userInfo);
    }
}

居然真的查询到相应字段了,该用户也就登录成功了:

image.png

为什么会出现这种情况呢?

原因是将密码写为' or 1='1传入数据库查询时,语句就变成了下面这样:

image.png

此时不管你输入的密码是什么,都会查询出表中的所有数据,这就是SQL注入。

如果是预编译的话,数据库就只会把' or 1='1当作一整个字符串去处理,而不会把它当作SQL脚本去处理,也就不会出现SQL注入的问题,我们现在将xml文件中的SQL改写为#{}的形式:

<select id="userLogin" resultType="com.chenshu.mybatis_demo.model.UserInfo">
    select * from userinfo where username = '${username}' and password = #{password}
</select>

查不到任何数据,登录失败!

image.png

1.4 ${}的使用场景

1.4.1 排序查询

既然预处理这么好用,为什么会存在两种占位符呢?存在即合理,有些特殊情况就是需要使用"${}"来处理的。

当后端需要传一些关键字,比如专栏中给用户提供了选择通过数据库的某一列进行排序文章的功能,实现这个功能就需要使用${}来完成

image.png

这时我们在数据库中就需要使用order by xxx作为排序的依据,如果使用"#{}"的方式,由于后端传入了一个String,就会预编译order by 'xxx'

因此此时需要使用${}来实现排序查询,具体的接口与SQL实现如下:

public List<ArticleInfo> sortArticle(@Param("sort") String sort);
<select id="sortArticle" resultType="com.chenshu.mybatis_demo.model.ArticleInfo">
    select * from articleinfo order by ${sort}
</select>

单元测试:根据createtime字段排序

@Test
void sortArticle() {
    List<ArticleInfo> articleList= articleMapper.sortArticle("createtime");
    for (ArticleInfo articleInfo : articleList) {
        System.out.println(articleInfo);
    }
}

运行结果:成功根据createtime字段的升序打印所有文章列表

image.png

1.4.2 like 模糊查询

a) 使用${}

like 使用 #{} 会报错,因为预编译后的SQL如下:

image.png

这时我们就可以使用${},接口以及实现如下:

public List<ArticleInfo> searchArticle(@Param("title") String title);
<select id="searchArticle" resultType="com.chenshu.mybatis_demo.model.ArticleInfo">
    select * from articleinfo where title like '%${title}%'
</select>

单元测试:

@Test
void searchArticle() {
    List<ArticleInfo> articleList = articleMapper.searchArticle("MyBatis");
    for (ArticleInfo articleInfo : articleList) {
        System.out.println(articleInfo);
    }
}

成功查询到 title 中带 ‘MyBatis’ 的数据:

image.png

b) 使用concat()

concat()是mysql内置函数,可以用作字符串的拼接:

image.png

修改xml中的SQL语句:

<select id="userLogin" resultType="com.chenshu.mybatis_demo.model.UserInfo">
    select * from userinfo where username = '${username}' and password = #{password}
</select>

再次进行单元测试:

image.png

2. resultMap的使用

上一篇文章我们提到过resultType的使用,作用是将数据库返回的记录和类映射起来,但是当类的属性名与数据库中的列名不同时,就会出现无法映射的问题,这里我来举个例子:

将UserInfo原来的属性名username修改为name:

@Data
public class UserInfo {
    private int id;
    private String username;
    private String password;
    private String photo;
    private Timestamp createTime;
    private Timestamp updateTime;
    private int state;
}

此时再调用一下单元测试的getAll()方法,发现name的值为null:

image.png

这里我们就需要使用resultMap定义一个映射规则来解决字段名和属性名不同的情况:

<resultMap id="userInfoMap" type="com.chenshu.mybatis_demo.model.UserInfo">
    <id column="id" property="id"/>
    <result column="username" property="name"/>
</resultMap>

解释下上面的标签和属性分别代表什么:

  • resultMap标签中的id就是你给该映射规则自定义的名称;
  • resultMap标签中的type就对应着你需要映射的类;
  • id标签是用来映射主键的,使用上推荐不管表中主键字段名和映射类的的属性是否相同,都配置这一项,否则多表查询的时候会出问题;
  • result标签是用来映射普通字段和映射类的属性的
  • idresult标签中都有一个columnproperty属性,是用来配置映射的,分别对应表的字段名类的属性名

getAll()方法对应的SQL中的resultType改为resultMap

<select id="getAll" resultMap="userInfoMap">
    select * from userinfo
</select>

再次测试,成功拿到了name(对应数据库中的username)的值:

image.png

3. 多表查询

  1. 连接表需要查询的字段添加到实体类

image.png

  1. 编写接口的getAll()方法以及xml文件中的SQL
public List<ArticleInfo> getAll();
<select id="getAll" resultType="com.chenshu.mybatis_demo.model.ArticleInfo">
    select a.*, u.username from articleinfo as a
    left join userinfo as u
    on a.uid = u.id
</select>
  1. 单元测试并得到结果
@Test
void getAll() {
    List<ArticleInfo> list = articleMapper.getAll();
    for (ArticleInfo articleInfo : list) {
        System.out.println(articleInfo);
    }
}

image.png

4. 动态SQL

动态SQL是MyBatis的强大特性之一,能够完成不同条件下不同的SQL拼接。
为什么要使用动态SQL呢?给大家引入一个案例:

在注册用户的时候,可能分为必填字段非必填字段,如果添加用户的时候有不确定的字段传入,程序应该如何实现呢?

这时候就有小伙伴提到:大不了多写几个方法让他们重载呗。

这种方式十分不优雅:如果是一个选填字段的话,就需要去写两个方法,两个选填字段的话就要写四个方法,如果有n个选填字段,那么就需要写2^n个方法…

因此我们就需要要使用MyBatis的重要特性 —— 动态SQL,接下来我们就来讲解一下常用的动态SQL标签的使用。

在讲解动态SQL标签之前可以在配置文件中添加一下log-impl用于打印SQL语句,以便对比:

image.png

4.1 <if>标签

前面提到的案例就要使用<if>动态标签来判断了。

这里我们来具体使用下<if>标签,比如用户在注册的时候,必填字段为usernamepassword,选填字段为photo

表结构如下:photo字段默认值为'';

image.png

4.1.1 不使用<if>标签

public int add(UserInfo userInfo);
<insert id="add">
    insert into userinfo(username, photo, password)
    values (
            #{name},
            #{photo},
            #{password})
</insert>

单元测试代码:

@Test
void add() {
    UserInfo userInfo = new UserInfo();
    userInfo.setName("zhangsan");
    userInfo.setPassword("123");
    userMapper.add(userInfo);
}

日志信息如下:我们发现SQL语句被预编译成了Preparing: insert into userinfo(username, photo, password) values ( ?, ?, ?),并且photo字段传入了一个null

==>  Preparing: insert into userinfo(username, photo, password) values ( ?, ?, ?)
==>  Parameters: zhangsan(String), null, 123(String)
<==  Updates: 1

表中插入了如下字段:

image.png


4.1.2 使用<if>标签

需要在test属性内写判断的语句,如果"photo != null",那么就拼接if标签里面的内容,否则就不拼接:

<insert id="add">
    insert into userinfo(
                    username,
                    <if test="photo != null">
                        photo,
                    </if>
                    password
                    )
    values (
        #{name},
        <if test="photo != null">
            #{photo},
        </if>
        #{password}
    )
</insert>

插入一条"lisi"的数据(不带photo值):

@Test
void add() {
    UserInfo userInfo = new UserInfo();
    userInfo.setName("lisi");
    userInfo.setPassword("123");
    userMapper.add(userInfo);
}

日志信息如下:由于没有传入photo参数,因此SQL语句此时被编译成了insert into userinfo( username, password ) values ( ?, ? ),并且只传入了usernamepassword两个参数

==>  Preparing: insert into userinfo( username, password ) values ( ?, ? )
==>  Parameters: lisi(String), 123(String)
<==  Updates: 1

对比表中的两条数据,我们发现在不使用if标签的时候photo插入的值为null,使用if标签的时候photo的值为默认值''

image.png

使用if标签的意义: 由于NULL''在MySQL中是不同的,因此你要使用一个查询语句select * from userinfo where photo = '';,就无法查询到zhangsan这条记录:

image.png

甚至当photo标签的字段上加了nou null约束的话,在不使用if标签的情况下会直接报错

4.2 <trim>标签

前面我们在使用if标签的时候选择了使用一种取巧的方式,把使用if标签的地方放在参数的中间位置,如果位于最后面的话,会出现问题,像下面这样:

<insert id="add">
    insert into userinfo(
                    username,
                    password,
                    <if test="photo != null">
                        photo
                    </if>
                    )
    values (
        #{name},
        #{password},
        <if test="photo != null">
            #{photo}
        </if>
    )
</insert>

如果photo=null的话,不拼接photo的内容,此时的SQL语句就变成了insert into userinfo( username, password, ) values ( ?, ?, ),由于最后一个参数不能以逗号结尾,因此SQL会报语法错误:

image.png

这时候我们就需要去使用一个trim标签去操作,

<trim>标签中有如下属性:

  • prefix:表示整个语句块,以prefix的值为前缀
  • suffix:表示整个语句块,以suffix的值作为后缀
  • prefixOverrides:表示整个语句块要去除掉的前缀
  • suffixOverrides:表示整个语句块要去除掉的后缀

假设我们三个参数(username, password, photo)都是可选参数,如何巧妙地使用trim标签来处理呢?

改造xml文件中的SQL:使用trim标签的prefixsuffix以及suffixOverrides参数

<insert id="add">
    insert into userinfo
                    <trim prefix="(" suffix=")" suffixOverrides=",">
                        <if test="name != null">
                            username,
                        </if>
                        <if test="password != null">
                            password,
                        </if>
                        <if test="photo != null">
                            photo
                        </if>
                    </trim>
    values
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="name != null">
                #{name},
            </if>
            <if test="password != null">
                #{password},
            </if>
            <if test="photo != null">
                #{photo}
            </if>
        </trim>
</insert>

这里我需要多提一嘴,如果trim标签里的所有if语句都不生效,就不会添加前后缀,而是直接编译成:insert into userinfo values,因此在使用时至少传入一个参数

单元测试:

@Test
void add() {
    UserInfo userInfo = new UserInfo();
    userInfo.setName("wangwu");
    userInfo.setPassword("123");
    userMapper.add(userInfo);
}

日志信息如下:此时就算把可选字段作为最后一个参数,trim标签会帮我们吧最后一个','删掉,并且在开头和结尾分别添加上'('')'

==> Preparing: insert into userinfo ( username, password ) values ( ?, ? )
==> Parameters: wangwu(String), 123(String)
<== Updates: 1

4.3 <where>标签

where标签自然是为了where子句而设计的,当where子句中只有一个条件并且不确定传不传入的时候,可以使用一个if标签把整个where子句包裹起来。

但是当通过多个参数组合查询的时候就需要用到where标签了,比如用户根据uid+title来查询文章的操作时,又不确定传不传入的情况下:

select * from articleinfo where title = 'MyBatis入门' and uid = 1;
public List<ArticleInfo> selectByCondition(ArticleInfo articleInfo);

where标签的写法十分简单,作用如下:

  • 根据where标签的内容,决定要不要拼接where
  • 去掉最首个参数的and关键字,让sql符合数据库的执行标准

也可以使用<trim prefix = "where" prefixOverrides = "and">标签替换

<select id="selectByCondition" resultType="com.chenshu.mybatis_demo.model.ArticleInfo">
    select * from articleinfo
    <where>
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="uid != null">
            and uid = #{uid}
        </if>
    </where>
</select>

使用多种组合查询进行测试,首先要先把实体类中所有int类型的属性的类型改为Integer,否则在不传入参数的时候默认为0,而不是null

@Data
public class ArticleInfo {
    private Integer id;
    private String title;
    private String content;
    private Timestamp createtime;
    private Timestamp updatetime;
    private Integer uid;
    private Integer rcount;
    private Integer state;
    private String username;
}

接下来我将传入不同的参数进行测试。


1. 什么参数也不传:

@Test
void selectByCondition() {
    ArticleInfo articleInfo = new ArticleInfo();
    List<ArticleInfo> list = articleMapper.selectByCondition(articleInfo);
    for (ArticleInfo article : list) {
        System.out.println(article);
    }
}

打印SQL日志如下:

==>  Preparing: select * from articleinfo
==> Parameters: 
<==    Columns: id, title, content, createtime, updatetime, uid, rcount, state
<==        Row: 1, Spring Boot入门, <<BLOB>>, 2024-04-15 01:21:43, 2024-04-15 01:21:43, 1, 1, 1
<==        Row: 2, MyBatis入门, <<BLOB>>, 2024-04-15 01:21:45, 2024-04-15 01:21:45, 1, 1, 1
<==        Row: 3, MyBatis的插入操作, <<BLOB>>, 2024-04-16 01:07:49, 2024-04-16 01:07:49, 1, 1, 1
<==        Row: 4, 修改标题, <<BLOB>>, 2024-04-16 01:29:37, 2024-04-16 01:29:37, 1, 1, 1
<==        Row: 6, MyBatis添加并返回自增id, <<BLOB>>, 2024-04-16 01:53:53, 2024-04-16 01:53:53, 1, 1, 1
<==      Total: 5

2. 只传uid:

@Test
void selectByCondition() {
    ArticleInfo articleInfo = new ArticleInfo();
    articleInfo.setUid(1);
    List<ArticleInfo> list = articleMapper.selectByCondition(articleInfo);
    for (ArticleInfo article : list) {
        System.out.println(article);
    }
}

打印SQL日志如下:

==>  Preparing: select * from articleinfo WHERE uid = ?
==> Parameters: 1(Integer)
<==    Columns: id, title, content, createtime, updatetime, uid, rcount, state
<==        Row: 1, Spring Boot入门, <<BLOB>>, 2024-04-15 01:21:43, 2024-04-15 01:21:43, 1, 1, 1
<==        Row: 2, MyBatis入门, <<BLOB>>, 2024-04-15 01:21:45, 2024-04-15 01:21:45, 1, 1, 1
<==        Row: 3, MyBatis的插入操作, <<BLOB>>, 2024-04-16 01:07:49, 2024-04-16 01:07:49, 1, 1, 1
<==        Row: 4, 修改标题, <<BLOB>>, 2024-04-16 01:29:37, 2024-04-16 01:29:37, 1, 1, 1
<==        Row: 6, MyBatis添加并返回自增id, <<BLOB>>, 2024-04-16 01:53:53, 2024-04-16 01:53:53, 1, 1, 1
<==      Total: 5

3. 传多个参数:

@Test
void selectByCondition() {
    ArticleInfo articleInfo = new ArticleInfo();
    articleInfo.setUid(1);
    articleInfo.setTitle("MyBatis入门");
    List<ArticleInfo> list = articleMapper.selectByCondition(articleInfo);
    for (ArticleInfo article : list) {
        System.out.println(article);
    }
}

打印SQL日志如下:

==>  Preparing: select * from articleinfo WHERE title = ? and uid = ?
==> Parameters: MyBatis入门(String), 1(Integer)
<==    Columns: id, title, content, createtime, updatetime, uid, rcount, state
<==        Row: 2, MyBatis入门, <<BLOB>>, 2024-04-15 01:21:45, 2024-04-15 01:21:45, 1, 1, 1
<==      Total: 1

4.4 <set>标签

其实set标签与update标签很像,作用是:

  • 根据set标签里的内容决定要不要拼接"set"
  • 去掉最后一个参数的',',让sql符合数据库的执行标准

可以使用<trim prefix="set" suffixOverrides=",">替换。

具体使用:

public void updateUserInfo(UserInfo userInfo);
<update id="updateUserInfo">
    update userinfo
        <set>
            <if test="name != null">
                username=#{name},
            </if>
            <if test="password != null">
                password=#{password},
            </if>
            <if test="photo != null">
                photo=#{photo},
            </if>
            <if test="createtime != null">
                createtime = #{createtime},
            </if>
            <if test="updatetime != null">
                updatetime = #{updatetime},
            </if>
        </set>
    where id = #{id}
</update>

单元测试代码如下,修改字段为1的用户的usernamepassword以及photo字段:

@Test
void updateUserInfo() {
    UserInfo userInfo = new UserInfo();
    userInfo.setId(1);
    userInfo.setName("zhang");
    userInfo.setPassword("12345");
    userInfo.setPhoto("doge.png");
    userMapper.updateUserInfo(userInfo);
}

修改前表的数据为以下记录:

image.png

运行代码后,日志打印信息如下:

==>  Preparing: update userinfo SET username=?, password=?, photo=? where id = ?
==> Parameters: zhang(String), 12345(String), doge.png(String), 1(Integer)
<==    Updates: 1

成功修改了下面三个字段:

image.png

4.5 <foreach>标签

当后端传入参数为集合(如List,Set,MapArray)并需要进行遍历时可以使用该标签,foreach标签有以下属性:

  • collection:绑定方法中用@Param定义的别名
  • item:用于给遍历时的每一个对象取一个名字
  • open:语句块开头的字符串
  • close:语句块结束的字符串
  • separator:每次遍历之间间隔的字符串

示例:根据多个文章id来删除文章数据

原SQL语句:

delete from articleinfo where id in(3,4,5)

具体实现:

public int deleteByIds(@Param("ids") List<Integer> ids);

注意事项:foreach标签中的collection中的值对应接口方法中定义的别名

<delete id="deleteByIds">
    delete from articleinfo
    where id in
    <foreach collection="ids" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</delete>

此时表数据如下,我想要删除id字段为7,8,9文章:

image.png

单元测试:

@Test
void deleteByIds() {
    List<Integer> list = new ArrayList<>();
    list.add(7);
    list.add(8);
    list.add(9);
    int ret = articleMapper.deleteByIds(list);
    System.out.println("删除了:" + ret);
}

运行后打印日志信息如下:

==>  Preparing: delete from articleinfo where id in ( ? , ? , ? )
==> Parameters: 7(Integer), 8(Integer), 9(Integer)
<==    Updates: 3

成功删除了三条记录:

image.png

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

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

相关文章

Jmeter 测试Dubbo接口-实例

1、Dubbo插件准备 ①把jmeter-plugins-dubbo-2.7.4.1-jar-with-dependencies.jar包放在D:\apache-jmeter-5.5\lib\ext目录 ②重新打开Jmeter客户端 在线程组-添加-取样器-dubbo simple&#xff0c;添加dubbo接口请求 2、Jmeter测试lottery接口 ①配置zookeeper参数 由于dub…

MapReduce案例-电影网站数据统计分析

本文适合大数据初学者学习MapReduce统计分析业务问题的步骤和基础的MapReduce编程方法&#xff0c;初步掌握Hadoop对计算任务的管理。 本文末尾有全部数据集和完整代码连接。 1.准备工作 安装Hadoop:Hadoop 3.3.2 离线安装-CSDN博客 按照好Hadoop之后要检查一下datanode运行情况…

Python数据容器(三)

一.tuple&#xff08;元组&#xff09; 1.元组同列表一样&#xff0c;都可以封装多个、不同类型的元素在内。 但最大的不同点在于&#xff1a;元组一旦定义完成&#xff0c;就不可修改。 2.元组定义&#xff1a;定义元组使用小括号&#xff0c;且使用逗号隔开各个数据&#…

大气的免费wordpress模板

国产的wordpress模板&#xff0c;更适合中国人使用习惯&#xff0c;更符合中国老板的审美的大气wordpress企业官网建站模板。 WordPress模板&#xff0c;也称为主题&#xff0c;是用于定义WordPress网站或博客外观和功能的预设计文件集。这些模板使用HTML、CSS和PHP代码构建&a…

ARM之栈与方法

ARM之栈与方法 计算机中的栈是一种线性表&#xff0c;它被限定只能在一端进行插入和删除操作&#xff08;先进后出&#xff09;。通常将可以插入和删除操作的一端称为栈顶&#xff0c;相对的一端为栈底。 通常栈有递增堆栈&#xff08;向高地址方向生长&#xff09;、递减堆栈…

视频监控平台的超大任务文件导入功能,如何通过日志判断导入是否成功

目录 一、概述 &#xff08;一&#xff09;编写目的 &#xff08;二&#xff09;适用情况 &#xff08;三&#xff09;导入相关参数说明 二、文件导入说明 &#xff08;一&#xff09; 日志文件路径 &#xff08;二&#xff09;不同情况下的说明和提示 1、 所有数据正确…

便携式手提Camera Link 模拟源测试设备

便携式手提Camera Link 模拟源测试设备 平台简介 便携式手提CameraLink模拟源测试设备&#xff0c;以PCIe的Camera link 播出卡和X86主板为基础&#xff0c;构建便携式的手提设备。 平台默认操作系统为win7 64位系统&#xff1b;具备丰富的外设接口&#xff0c;如VGA、HDMI、千…

患者关系管理系统功能详解

脉购健康管理系统&#xff08;软件&#xff09;包含&#xff1a;客户开卡、健康档案、问卷调查、问诊表、自动设置标签、自动随访、健康干预、健康调养、历年指标趋势分析、疾病风险评估、饮食/运动/心理健康建议、同步检查报告数据、随访记录、随访电话录音、健康阶段总结、打…

ELK日志采集系统

1.什么是ELK ELK 是一套流行的数据搜索、分析和可视化解决方案&#xff0c;由三个开源项目组成&#xff0c;每个项目的首字母合起来形成了“ELK”这一术语&#xff1a; Elasticsearch (ES): Elasticsearch 是一个基于 Apache Lucene 构建的分布式、实时搜索与分析引擎。它能够…

AI绘画 究竟在哪些方面降低了门槛

AI绘画的产物是图像。图像对人类的认知、情感和文化发展起着重要的作用&#xff0c;包括信息传递、创造性表达、历史记录、审美享受和交流。 从原来的纸笔调色板到数字时代的数字板、绘图软件&#xff0c;再到AI绘画时代&#xff0c;任何人都可以用几行简单的文字创作出高质量…

Python实现贪吃蛇

提供学习或者毕业设计使用,功能基本都有,不能和市场上正式游戏相提比论,请理性对待!通过购买专栏或者CSDN问答提问,采纳后,私信博主。提供源码! 说明:需要的话联系博主!谢谢。 代码: import pygame import random import tkinter as tk from tkinter import mess…

免费建筑su模型网站:让设计师的创意飞翔!

对于设计师而言&#xff0c;建筑SU模型是表达创意、规划空间、实现设计概念的重要工具。为了满足设计师对优质模型的需求&#xff0c;有许多网站提供免费的建筑SU模型下载服务&#xff0c;这些网站无疑为设计师们提供了广阔的创意空间和无尽的灵感。 1.建e网&#xff1a;拥有百…

图神经网络与分子表征:7. LEFTNet

在执行性质预测任务时&#xff0c;我们需要考虑两个问题&#xff1a;1. 如何正确的将图结构进行编码&#xff1f;2. 如何汇聚编码信息预测整个分子的任务&#xff1f; LEFTNet 就是通过回答上述问题来进行模型设计的。 原文地址 算法设计 原文中&#xff0c;作者定义了三个图…

SpringBoot + Redis实现用户信息登录的缓存

&#x1f34e;前言 &#x1f350;项目的背景 背景&#xff1a;&#x1f349;当我们在完成用户信息登录时&#xff0c;我们往往每次都会在数据库中查询用户的记录&#xff0c;生成token并返回给前端&#xff0c;不过这样会有一定的问题。 &#x1f350;造成的问题 问题&#xf…

【Linux】文件描述符——万字详解

目录​​​​​​​ 前言 预备知识 复习C语言的文件接口 写方式打开文件 追加方式打开文件 读方式打开文件 系统的文件接口 open close write read 文件描述符 0 & 1 & 2 理解文件描述符 文件描述符的分配规则 重定向的本质 dup2 理解Linux下一切…

vue+springboot+websocket实时聊天通讯功能

前言 在我的前一篇文章里 vuespringboot实现聊天功能 &#x1f388;&#x1f388;&#x1f388;&#x1f388;&#x1f388;&#x1f388;&#x1f388; 实现了最最基础的聊天功能&#xff0c;可以通过聊天互相给对方发送信息 &#x1f388;&#x1f388;&#x1f388;&…

每日一题 — 最小覆盖子串

76. 最小覆盖子串 - 力扣&#xff08;LeetCode&#xff09; 解法一&#xff1a;暴力遍历哈希表 解法二&#xff1a;滑动窗口哈希表 定义left和right初始化为零&#xff0c;固定left&#xff0c;先向右遍历right&#xff0c;放到哈希表中这个时候我们需要统计有效字符的个数&…

企业智能EDM邮件群发推广软件哪个好?

精准、高效的电子邮件营销已经成为企业触达潜在客户、维护现有客户关系以及提升品牌影响力不可或缺的手段。在这其中&#xff0c;云衔科技以其独树一帜的智能EDM邮件营销系统解决方案&#xff0c;为企业带来了革命性的市场推广体验。 云衔科技凭借前瞻性的战略眼光和深厚的AI技…

抖音在线点赞任务发布接单运营平台PHP网站源码 多个支付通道+分级会员制度

抖音在线点赞任务发布接单运营平台PHP网站源码&#xff0c;多个支付通道分级会员制度。 介绍 1、代理裂变&#xff0c;静态返佣/动态返佣均可设置。 2、自动机器人做任务&#xff0c;任务时间可设置&#xff0c;机器人价格时间可设置。 3、后台可设置注册即送X天机器人。 …

RT-Thread在Win10下编译出现 unsupported pickle protocol: 5解决方案

调试背景&#xff1a; 在WIN10下编译RT-Thread源码&#xff1a;对象处理器平台是Microchip SAMA5D27-SOM1-EK评估板。 unsupported pickle protocol: 5 编译出现报错:ValueError : unsupported pickle protocol: 5 $ scons scons: Reading SConscript files ... Newlib ver…