MyBatis 万字进阶

news2024/11/24 22:49:57

文章目录

    • 一. 增, 删, 改 操作
      • 1.1 修改操作
      • 1.2 删除操作
      • 1.3 添加操作
        • 1.3.1 返回受影响行数
        • 1.3.2 返回 id
    • 二. 查询操作
      • 2.1 单表查询
        • 2.1.1 参数占位符 ${} 和 #{}
        • 2.1.2 SQL 注入问题
        • 2.1.3 ${} 的优点
        • 2.1.4 Like 查询
      • 2.2 多表查询
        • 2.2.1 返回类型 resultType
        • 2.2.2 返回字典映射 resultMap
        • 2.2.3 多表联查
          • 查询文章表的详情
          • **查询一个用户的所有文章**
    • 三. 复杂查询-动态SQL
      • 3.1 if 标签
      • 3.2 trim 标签
      • 3.3 where 标签
      • 3.4 set 标签
      • 3.5 foreach 标签

一. 增, 删, 改 操作

1.1 修改操作

实现修改密码功能 , 返回修改行数(affected rows).

首先确定修改密码需要传入的参数 , 用户id , 旧密码 , 新密码

Interface 中声明方法:

//    修改密码
    int updatePassword(@Param("id") Integer id,
                       @Param("password") String password,
                       @Param("newPassword") String newPassword);

xml 中实现方法:

Tips: update 标签中无需添加 resultType , 因为增删改 , 这类操作无需返回结果集 , 只需知道是否成功或 id 即可 , resultType 是指定返回结果类型的属性 , 它告诉 MyBatis 如何将结果集转换为 Java 对象.

<update id="updatePassword">
    update userinfo set password=#{newPassword}
    where id=#{id} and password=#{password}
</update>

单元测试:

@Transactional //正常执行不污染数据库
@Test
void updatePassword() {
    int result = userMapper.updatePassword(1, "admin", "123456");
    System.out.println("修改:" + result);
}

Tips: @Transactional (事务) , 加上这一条注解可以防止数据库被修改 , 执行 SQL 语句时开启一个事务 , 等待执行完毕再 Rollback (回滚).

image-20230511105329228

1.2 删除操作

删除用户 , 返回受影响的行数

Interface 中声明方法:

//    删除密码
    int delById(@Param("id") Integer id);

xml 中实现方法:

<delete id="delById">
    delete from userinfo where id=#{id}
</delete>

单元测试:

@Transactional
@Test
void delById() {
    int id = 1;
    int result = userMapper.delById(1);
    System.out.println("删除结构" + result);
}

image-20230511110241852

1.3 添加操作

1.3.1 返回受影响行数

Interface 中声明方法:

//    添加用户
    int addUser(UserEntity user);

xml 中实现方法:

<insert id="addUser">
    insert into userinfo(username, password) values(#{username},#{password})
</insert>

单元测试:

@Test
void addUser() {
    UserEntity user = new UserEntity();
    user.setUsername("张三");
    user.setPassword("11111");
    int result = userMapper.addUser(user);
    System.out.println(result);
}

image-20230511111048298

1.3.2 返回 id

Interface 中声明方法与之前一致:

int addUserGetId(UserEntity user);

只需在 insert 标签中 , 添加 userGenerateKeys=“true” 生成主键 , keyProperty=“id” 设置主键为 id.

<insert id="addUserGetId" useGeneratedKeys="true" keyProperty="id">
    insert into userinfo(username, password) values(#{username},#{password})
</insert>

单元测试:

此时 MyBatis 会自动将 getId 加入对象中.

@Test
void addUserGetId() {
    UserEntity user = new UserEntity();
    user.setUsername("李四");
    user.setPassword("22222");
    int result = userMapper.addUserGetId(user);
    System.out.println("添加行数" + result);
    System.out.println("Id 为: " + user.getId());
}

image-20230511112049299


二. 查询操作

2.1 单表查询

实现根据用户 id , 查询用户信息的功能.

首先在 Interface 中声明方法:

Tips: 如果不加 @Param 部分电脑会找不到参数 , 因此统一加上 @Param

//    根据 id  查询用户对象 , @Param 相当于给参数起名, 如果名称不一致会报错
    UserEntity getUserById(@Param("id") Integer id);

在 xml 中实现 SQL 命令

<select id="getUserById" resultType="com.example.demo.entity.UserEntity">
    select * from userinfo where id=${id}
</select>

为了不污染数据库 , 使用单元测试来测试代码.

@SpringBootTest //声明当前单元测试的类是运行在 SpringBoot 当中的.
class UserMapperTest {
    @Autowired //属性注入
    private UserMapper userMapper;

    @Test
    void getUserById() {
        UserEntity user = userMapper.getUserById(1);
        System.out.println(user);
    }
}

执行结果如下 , 与数据库中一致

image-20230510164055956

image-20230510164142430

2.1.1 参数占位符 ${} 和 #{}

  • ${} 字符直接替换
  • #{} 占位符预编译处理

直接替换是指 , MyBatis 在处理 ${} 时 , 会直接替换为变量的值.

预处理是指 , MyBatis 在处理 #{} 会替换成 ? , 调用 PreparedStatement 的 set 方法来赋值.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LrF7S6xc-1686045145549)(C:/Users/86178/AppData/Roaming/Typora/typora-user-images/image-20230511093437168.png)]

例如在日常生活中 , 各种商场每天早上开门之前 , 先让各个岗位的员工进入 , 等员工全部就位之后才开门营业. 这是为了防止部分顾客 零元购 , 这样的操作就相当于预处理 , 如果有顾客浑水摸鱼早早进入商场 , 就会被打卡系统识别. 如果是直接替换 , 那么顾客可以随时进入商场且没有任何监管机制 , 这时就可能出现异常.

2.1.2 SQL 注入问题

登录是 SQL 注入经常出现的场景 , 下面我们实现一个博客系统的登录功能来介绍 SQL 注入.

Interface 中声明登录方法:

@Mapper
public interface UserMapper {
//    登录方法
    UserEntity login(UserEntity user);
}

xml 中实现方法:

由于username 和 password 是String 类型的变量 , 而 ${} 会直接替换变量 , 为了保证 SQL 语句正确 , 我们手动加上单引号.

<select id="login" resultType="com.example.demo.entity.UserEntity">
    select * from userinfo where username='${username}' and password='${password}'
</select>

Tips: 方法中传入参数的一个对象 , 在 xml 中实现时 , MyBatis 会自动映射属性名.

单元测试:

@Test
void login() {
    String username = "admin";
    String password = "admin";
    UserEntity inputUser = new UserEntity();
    inputUser.setUsername(username);
    inputUser.setPassword(password);
    UserEntity user = userMapper.login(inputUser);
    System.out.println(user);
}

运行结果正确

image-20230511095105890

但是如果我们在登录时输入: " ’ or 1='1";

@Test
void login() {
    String username = "admin";
    String password =  " ' or 1='1";
    UserEntity inputUser = new UserEntity();
    inputUser.setUsername(username);
    inputUser.setPassword(password);
    UserEntity user = userMapper.login(inputUser);
    System.out.println(user);
}

依然查询到了用户的信息 , 这就是所谓的 SQL 注入.

image-20230511095437011

为什么会出现这样的错误? 这是因为把程序输入参数当做 SQL 指令去执行了.

SQL 中自动隐式类型转换 , 因此 1=‘1’ 一定是正确的.

image-20230511095948000

如何防止 SQL 注入?

由于 #{} 可以预执行 , 那么在它看来花括号中的参数就是一个值 , 相等就能查到 , 不等就查不到 , 不会存在拼接到 SQL 语句中的问题.

<select id="login" resultType="com.example.demo.entity.UserEntity">
    select * from userinfo where username=#{username} and password=#{password}
</select>

image-20230511100537106

#{} 不仅可以防止 SQL 注入 , 还可以根据参数类型自动加单引号 , 那么 ${} 岂不是一无是处 , 结果并非如此. 很多字符串拼接的场景非它不可 , 因此为了数据安全 , 不到万不得已不要使用 , 必须使用的话 , 一定要保证输入参数是可枚举的 , 在其替换变量之前就做检查.

2.1.3 ${} 的优点

我们日常在浏览 , 淘宝 , 京东这样的电商平台时 , 有时需要按各种属性排序 , 实现这样的功能时 , 可选参数有很多 , 因此无法写死 , 需要根据后续用户的选项来拼接.

image-20230510165926172

Interface 中声明:

List<UserEntity> getAllBySort(@Param("Sort") String Sort);

xml 中实现:

<select id="getAllBySort" resultType="com.example.demo.entity.UserEntity">
    select * from userinfo order by id ${Sort}
</select>

单元测试执行:

@Test
void getAllBySort() {
    List<UserEntity> userSort =  userMapper.getAllBySort("desc");
    for(UserEntity user: userSort){
        System.out.println(user);
    }
}

使用 ${} 可以直接替换为需要的字符串 , 但如果使用 #{} 就不能实现排序查询了 , 因为传递的值为 String 会加单引号 , 导致 SQL 语句错误.

2.1.4 Like 查询

当我们需要实现模糊查询的功能时 , #{} 检测到传入值为 String 类型 , 会多加一对单引号 , 变为 ‘%‘username’%’ 导致 SQL 语句出错.

<select id="getListByName" resultType="com.example.demo.entity.UserEntity">
    select * from userinfo where username like '%#{username}%'
</select>

解决方式:

MySQL 内置函数 concat 字符串拼接可以解决此问题.

image-20230510191540498

<select id="getListByName" resultType="com.example.demo.entity.UserEntity">
    select * from userinfo where username like concat('%','#{username}','%')
</select>

索引失效:

模糊查询大致有三种写法:

  • 张%
  • %张
  • %张%

只有第一种会触发索引 , 其余两种都会导致索引失效.

image-20230604195339150


2.2 多表查询

增, 删, 改 这类只需返回受影响行数的操作 , 无需设置返回类型.

<update id="updatePassword">
    update userinfo set password=#{newPassword}
    where id=#{id} and password=#{password}
</update>
<delete id="delById">
    delete from userinfo where id=#{id}
</delete>
<insert id="addUser">
    insert into userinfo(username, password) values(#{username},#{password})
</insert>

然而 , 如果是查询操作 , 即使是查一个用户的名称也要设置返回类型 , 否则就会报错. 因为 MyBatis 无法将数据库中检索到的结果集映射为 java 对象.

2.2.1 返回类型 resultType

MyBatis 中 resultType 用于指定查询结果的类型 , 告诉 MyBatis 将数据库中检索到的结果集映射为 Java 对象.

<select id="getUser" resultType="com.example.User">  select * from user where id = #{id} </select>

上面的例子中,resultType指定了查询结果的类型为com.example.User,表示查询结果将会被转换成一个User对象。

Tips: resultType只适用于单结果查询,如果我们需要进行多结果查询,那么应该使用resultMap来指定查询结果的映射关系。

2.2.2 返回字典映射 resultMap

resultMap 使用场景

  • 字段名和属性名不同 , 可以使用 resultMap 配置映射
  • 一对一和一对多可以使用 resultMap 映射并查询数据

如果数据库中字段名 , 和 java 对象中的不一致 , 那么直接使用 resultType 就会报错. 因为 resultType 直接按照指定类型 , 在数据库和 Java 对象中进行映射 , 如果找不到匹配的就会映射失败.

假设数据库中密码为 password , java 对象中为 pwd.

xml 中设置 resultMap

每个 resultMap 默认都有两个属性 , id 表示该 resultMap 的唯一标识符 , 用于区分MyBatis 中不同 resultMap(可任意起) , type 表示实体类的类型 , property 为 java 对象中的属性 , column 为数据库中对应字段名.

<resultMap id="BaseMap" type="com.example.demo.entity.UserEntity">
    <id property="id" column="id"></id>
    <result property="username" column="username"></result>
    <result property="pwd" column="password"></result>
    <result property="createtime" column="createtime"></result>
</resultMap>

为指定方法设置resultMap

<select id="getListByName" resultMap="BaseMap">
    select * from userinfo where username like concat('%',#{username},'%')
</select>

暴力方法

也可以在 xml 中给 SQL 语句起个别名

<select id="getListByName" resultMap="BaseMap">
    select id,username,password as pwd from userinfo where username like concat('%',#{username},'%')
</select>

2.2.3 多表联查

多表查询时 , 如果一个类中包含另一个对象 , resultType 是查不出包含对象的.

多表查询时 , 通常使用 left/right join 来连接表 , 这样可以把表分为主表和次表.

查询文章表的详情

假设我们要查询文章表的详情 , 但却只能查到作者 id , 为了查到作者姓名 , 必须和作者表联合查询.

image-20230512114105742

image-20230512114246486

首先创建用户表的实体类

@Data
public class ArticleInfo {
    private int id;
    private String title;
    private String content;
    private LocalDateTime createTime;
    private int uid;
    private int rcount;
    private int state;
}

创建一个扩展类 vo(view object ) , 存放扩展信息

继承可以更加简单的将原实体类中的属性继承过来. 直接在原实体类中添加 username 属性 , 这样看似方便却不符合程序设计的单一原则 , 如果后续我们扩展成百上千个属性 , 就会污染原实体类

@Data
public class ArticleInfoVO extends ArticleInfo {
    private String username;
    @Override
    public String toString() {
        return "ArticleInfoVO{" +
                "username='" + username + '\'' +
                "} " + super.toString();
    }
}

Tisp: 扩展类一定要重写 toString() 方法 , 否则 lombok 的 toString() 默认只打印当前类的属性.

创建一个 MyBatis 的 Interface 声明方法:

@Mapper
public interface ArticleMapper {
    //    查询文章详情
    ArticleInfoVO getDetail(@Param("id") Integer id);
}

MyBatis 中创建 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">
    <select id="getDetail" resultType="com.example.demo.entity.vo.ArticleInfoVO">
        select a.*,u.username from articleinfo a 
        left join userinfo u on u.id=a.uid
        where a.id=#{id}
    </select>
</mapper>

单元测试:

@SpringBootTest
class ArticleMapperTest {
    @Autowired
    ArticleMapper articleMapper;

    @Test
    void getDetail() {
        ArticleInfoVO articleInfoVO = articleMapper.getDetail(1);
        System.out.println(articleInfoVO);
    }
}

image-20230512120616171

查询一个用户的所有文章

首先分析清楚谁是主表谁是辅表 , 通过分析可知查询结果中大部分都是文章表的字段 , 所以文章表是主表.

创建 Mybatis 的Interface 声明

@Mapper
public interface ArticleMapper {
  //查询用户的所有文章
    List<ArticleInfoVO> getListByUid(@Param("uid") Integer uid);
}

Mybatis 的 xml 文件中实现该方法

    <select id="getListByUid" resultType="com.example.demo.entity.vo.ArticleInfoVO">
        select a.*,u.username from articleinfo a
        left join userinfo u on a.uid=u.id
        where a.uid=#{uid}
    </select>

进行单元测试

    @Test
    void getListByUid() {
        Integer uid = 1;
        List<ArticleInfoVO> list = articleMapper.getListByUid(uid);
//        使用并行的方式打印用户的信息
//        list.stream().parallel().forEach(System.out::println);
        for (ArticleInfoVO list1: list) {
            System.out.println(list1);
        }
    }

image-20230604203121230


三. 复杂查询-动态SQL

动态 SQL 通常用于需要根据用户输入或其他运行时条件来生成 SQL 语句的场景,比如搜索功能、动态排序、动态筛选等。动态SQL可以使用编程语言中的字符串拼接、条件判断等语法来实现.简而言之 , 动态 SQL 就是允许在 SQL 语句中做条件拼接.

3.1 if 标签

用户添加时 , 可能会出现如下问题:

image-20230604210948905

添加字段分为两种: 必填字段和非必填字段 , 其中 id 就是必填字段 , 其余字段都是非必填字段 , 那么假设我们添加用户时 , 由于 photo 字段不做限制 , 可能会出现出乎意料的结果 , 为了防止出现这种情况我们可以使用标签来解决.

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

其中标签中 test 的内容不是数据库中的字段 , 而是传入对象的属性 , 由 Mybatis 执行. 当输入的 photo 字段为 null 或者为空时 , 不拼接.

如何区别是数据库的字段还是对象的属性?

只需看是否被特殊字符修饰 , 类似于 #{photo} 这种被特殊字符修饰的一定是对象的属性 , 而 photo 则是数据库字段.

单元测试:

    @Transactional
    @Test
    void addUser2() {
        String username = "liliu";
        String password = "123456";
        String photo = "";
        UserEntity user = new UserEntity();
        user.setUsername(username);
        user.setPassword(password);
        user.setPhoto(photo);
        int result = userMapper.addUser2(user);
        System.out.println("添加: "+result);
    }

image-20230604211816809

很明显当我们输入photo 字段为空时 , sql 中并没有拼接.


3.2 trim 标签

如果所有属性都是非必填项 , 就考虑使用 标签结合 标签 , 对多个字段采取动态生成的方式.

标签中有如下属性:

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

上述添加用户的问题 , 如果所有字段都是非必填字段 , 那么按 标签来写的话 , 无论怎么设置逗号的位置 , 必然会出现多逗号或少逗号的情况.

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

因此我们可以改为 配合 的写法

以下代码中 trim 的写法 , 可以去除整个语句块的最后一个逗号 , 并且在 中添加括号.

  • 基于 prefix 配置 , 开始部分加上 (
  • 基于 suffix 配置 , 结束部分加上 )
  • 基于 suffixOverrides 配置会去掉最后一个逗号
    <insert id="addUser3">
        insert into userinfo
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="username!=null and username!=''">
                username,
            </if>
            <if test="password!=null and password!=''">
                password,
            </if>
            <if test="photo!=null and photo!=''">
                photo,
            </if>
        </trim>
        values
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="username!=null and username!=''">
                #{username},
            </if>
            <if test="password!=null and password!=''">
                #{password},
            </if>
            <if test="photo!=null and photo!=''">
                #{photo},
            </if>
        </trim>
    </insert>

执行单元测试:

    @Transactional
    @Test
    void addUser3() {
        String username = "liliu";
        String password = "123456";
        String photo = "";
        UserEntity user = new UserEntity();
        user.setUsername(username);
        user.setPassword(password);
//        user.setPhoto(photo);
        int result = userMapper.addUser3(user);
        System.out.println("添加: "+result);
    }

如果不加 标签 , 只添加用户名密码字段 , 那么密码后面一定会多一个逗号 , 导致 sql 语句出错. 如果使用则正常执行.

image-20230605114414484


3.3 where 标签

where 标签用于生成 SQL 语句中的 where 子句,它的作用是根据指定的条件过滤数据。

通过 id 和 title 查询文章

    <select id="getListByIdOrTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">
        select * from articleinfo
         where
        <trim suffixOverrides="and">
            <if test="id!=null and id > 0">
                id=#{id} and
            </if>
            <if test="title!=null and title!=''">
                title like concat('%',title,'%')
            </if>
        </trim>
    </select>

上述代码 where 中的参数传递有 4 种情况 : 1. id 传, title 不传. 2. id 不传,title传. 3. id传 , title 传. 4. id不传 , title 不传.

通过测试可以发现 , 第四种情况 , where 条件为空会发生 sql 语句错误. 针对这种情况有两种解决方案:

解决方案一: 1=1 解决方法

<select id="getListByIdOrTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">
        select * from articleinfo
         where 1=1
        <trim prefixOverrides="and">
            <if test="id!=null and id > 0">
                and id=#{id}
            </if>
            <if test="title!=null and title!=''">
                and title like concat('%',title,'%')
            </if>
        </trim>
</select>

虽然 1=1 在代码的编译器会被优化不消耗性能 , 但许多公司的代码规范并不推荐这么做.

解决方案二: where 作为 trim 标签的前缀

<select id="getListByIdOrTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">
        select * from articleinfo
        <trim prefix="where" suffixOverrides="and">
            <if test="id!=null and id > 0">
                id=#{id} and
            </if>
            <if test="title!=null and title!=''">
                title like concat('%',title,'%')
            </if>
        </trim>
</select>

当 trim 中生成了代码 , 那么才会添加 里的前缀和后缀 , 如果 trim 中没有代码 , 那么才会添加 中的前缀和后缀.

但是我们可以发现 , 使用 标签会产生很多的顾虑 , 如加前缀还是后缀 , and 或 逗号加在前面还是后面…

这时如果我们使用 标签就可以完美的解决该顾虑.

解决方案三: 标签

<select id="getListByIdOrTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">
        select * from articleinfo
        <where>
            <if test="id!=null and id > 0">
                id=#{id}
            </if>
            <if test="title!=null and title!=''">
                and title like concat('%',title,'%')
            </if>
        </where>
</select>

标签除了 , 可以更简洁方便 , 还可以去除最前面的 and 关键字. 但注意 标签不会去除最后面的关键字.


3.4 set 标签

set 标签和 where 标签的作用一致 , 只不过 set 标签需要搭配 update 来使用.

<update id="updatename">
        update user
        <set>
            <if test="username!=null">
                username=#{username},
            </if>
            <if test="password!=null">
                password=#{username},
            </if>
            <if test="sex!=null">
                sex=#{sex},
            </if>
            <if test="birth!=null">
                birth=#{birth},
            </if>
        </set>
        where id = #{id}
</update>

标签可以去除 , 最后面的关键字 , 但必须注意的是如果 set 标签中没有参数会出现 sql 语句错误. 因此执行该方法之前必须在 controller 层先判断一下传入对象的参数都为是否为空 , 如果都为空则不执行.


3.5 foreach 标签

对集合遍历时可以使用该标签. 标签有如下常用属性:

  • colletion: 存放传递过来集合的名称 , List , Map , Set
  • item: 存放遍历时的每一个对象.
  • open: foreach 的前缀是什么
  • close: foreach 的后缀是什么
  • separator: 每一层遍历的分隔符是什么

根据集合中的 id 批量删除文章

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

标签执行结果

delete from Articleinfo
where id in
(id1, id2 , id3 , id4)

Tips: 执行代码之前必须在 controller 层判断一下传入参数是否都为空 , 如果为空就不在执行 , 否则就等于删库跑路了.

ername},


password=#{username},


sex=#{sex},


birth=#{birth},


where id = #{id}


<set> 标签可以去除 , 最后面的关键字 , 但必须注意的是如果 set 标签中没有参数会出现 sql 语句错误. 因此执行该方法之前必须在 controller 层先判断一下传入对象的参数都为是否为空 , 如果都为空则不执行.

---

### 7.5 foreach 标签

对集合遍历时可以使用该标签. <foreach> 标签有如下常用属性:

- colletion: 存放传递过来集合的名称 , List , Map , Set
- item: 存放遍历时的每一个对象.
- open: foreach 的前缀是什么
- close: foreach 的后缀是什么
- separator: 每一层遍历的分隔符是什么

根据集合中的 id 批量删除文章

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

标签执行结果

delete from Articleinfo
where id in
(id1, id2 , id3 , id4)

Tips: 执行代码之前必须在 controller 层判断一下传入参数是否都为空 , 如果为空就不在执行 , 否则就等于删库跑路了.

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

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

相关文章

Linux内核进程创建流程

本文代码基于Linux5.10 内容主要参考《Linux内核深度解析》余华兵 当Linux内核要创建一个新进程时&#xff0c; 流程大致如下 ret fork(); if (ret 0) {/* 子进程装载程序 */ret execve(filename, argv, envp); } else if (ret > 0) {/* 父进程 */ } 大致可以分为创建新…

pagehelper分页插件(SpringBoot,Mybatis整合前后端分析)

前言&#xff1a;在学习项目的过程中遇到了数据分页的功能&#xff0c;单纯的js前端不能处理大的数据量&#xff0c;需要后端整理好数据发送给前端&#xff0c;那么使用分页插件无疑是个好选择. 目录 pagehelper依赖 接口方法mapper Mybatis Service ServiceImpl PageResu…

[IJCAI 2022] 基于个性化掩码的实用安全联合推荐

Practical and Secure Federated Recommendation with Personalized Mask | SpringerLink 摘要 联合推荐解决了推荐系统的数据筒仓和隐私问题。目前的联合推荐系统主要利用密码学或混淆方法来保护原始评分不被泄露。然而&#xff0c;前者带来了额外的通信和计算成本&#xff0…

day 49 :121. 买卖股票的最佳时机;122. 买卖股票的最佳时机 II;123. 买卖股票的最佳时机 III

买卖股票 121. 买卖股票的最佳时机&#xff1a;一次买入卖出1. 贪心算法2. 动态规划1. dp数组以及下标名义2. 递归公式3. dp数组如何初始化4. 代码 122. 买卖股票的最佳时机 II:可以多次买入卖出2. 动态规划1. dp数组以及下标名义2. 递归公式3. dp数组如何初始化4. 代码 123. 买…

Linux_进程

目录 一.进程概念与子进程 1.进程基本概念 2.通过系统调用创建子进程-fork 二.进程状态 1、一般进程状态 2、Linux操作系统的进程状态 三.环境变量 1.概念 2.环境变量组织与获取 3.配置文件 4.环境变量的全局属性​编辑 5.命令行参数 四.进程优先级 1.查看系统进…

Linux文件系统-磁盘划分

一、磁盘使用 windows系统中&#xff1a; 1、分区 2、格式化 3、自动装载 4、使用 Linux系统中&#xff1a;1、分区 2、格式化 3、手动挂载 &#xff08;挂载到/etc/fstab实现开机自启&#xff09; 4、使用 Linux系统中磁盘使用&#xff1a; 1、分区操作…

rust:cargo 和rustc 以及一点 小技巧

在正式学习 Rust 语言以前&#xff0c;我们需要先学会怎样输出一段文字到命令行&#xff0c;这几乎是学习每一门语言之前必备的技能&#xff0c;因为输出到命令行几乎是语言学习阶段程序表达结果的唯一方式。 在之前的 Hello, World 程序中大概已经告诉了大家输出字符串的方式…

system V共享内存

一、前言 共享内存是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间&#xff0c;这些进程间数据传递将不再涉及到内核&#xff0c;换句话说&#xff0c;进程将不再通过执行进入系统内核的系统调用来传递彼此的数据。 但其实比它好用的进程间通信还有很多种&…

Android HTTP请求方式:HttpClient

1.HttpClient使用流程 基本流程&#xff1a; 2.HttpClient使用示例 1&#xff09;使用HttpClient发送GET请求 直接贴下简单的发送Get请求的代码&#xff1a; public class MainActivity extends Activity implements OnClickListener { private Button btnGet; private WebV…

什么是OSPF被动接口?如何配置?华为、思科、瞻博网络三厂商命令来了

OSPF&#xff08;开放最短路径优先&#xff09;是一种常用的动态路由协议&#xff0c;用于在大型网络中实现路由选择。在OSPF中&#xff0c;被动接口是一种特殊类型的接口&#xff0c;它被用来监测网络中的邻居关系&#xff0c;并接收来自邻居发送的Hello消息。被动接口不主动发…

华为OD机试之在字符串中找出连续最长的数字串(含“+-”号)(Java源码)

在字符串中找出连续最长的数字串(含“”号) 输入描述 请在一个字符串中找出连续最长的数字串&#xff0c;并返回这个数字串。 如果存在长度相同的连续数字串&#xff0c;返回最后一个。 如果没有符合条件的字符串&#xff0c;返回空字符串””。 注意&#xff1a; 数字串可以由…

Ansible进阶2——角色管理

文章目录 一、角色1.1 获取角色方式1.2 角色结构1.3 定义变量和默认变量1.4 使用方法1.5 控制playbook中的任务执行流程 二、红帽企业Linux系统角色2.1 常见系统角色2.2 使用系统时间同步角色 三、自定义角色3.1 创建角色目录结构3.2 编写角色内容3.3 编写总结 四、ansible gal…

【C++】内存管理的基本操作,new与delete的实现原理以及operator new与operator delete函数

文章目录 前言一、new,delete操作内置类型二、new/delete操纵自定义类型3. operator new与operator delete函数4. new/delete实现原理4.malloc/free和new/delete的区别 前言 程序中内存的划分&#xff1a; 栈又叫堆栈–非静态局部变量/函数参数/返回值等等&#xff0c;栈是向…

高考必胜,归来仍是少年!

高考必胜&#xff0c;归来仍是少年&#xff01; 这是小索奇专门为高考生写的文章高考生 我以前给大家弄过一些免费的付费资料&#xff0c;现在看到后台很多伙伴们都在寻找资料&#xff0c;一些没有充分准备的小伙伴此刻一定很匆忙吧&#xff01; 我想对大家说&#xff1a; 高…

基于 FFMPEG 的跨平台视频播放器简明教程(二):基础知识和解封装(demux)

系列文章目录 基于 FFMPEG 的跨平台视频播放器简明教程&#xff08;一&#xff09;&#xff1a;FFMPEG Conan 环境集成 文章目录 系列文章目录前言基础知识视频&#xff0c;你所看到的&#xff01;音频 - 你所听到的声音编解码器 - 压缩数据容器 - 存放音频和视频的地方 解封…

vue3.0与vue2.0的区别简记(基于官方文档)

vue3.0与vue2.0的区别简记&#xff08;基于官方文档&#xff09; 基于vue3.0和vue2.0官方文档简单记录vue3.0版本和2.0版本的区别。 一直没有看文档的习惯&#xff08;就是不爱学习&#xff0c;现在吃了没文化的亏&#xff09;&#xff0c;遇到问题才去补充点食粮&#xff0c…

祝2023高考考生高考顺利!金榜题名

前言&#xff1a;光阴似箭&#xff0c;岁月如梭。明天就是全国每年一次的高考了&#xff0c;我也即将结束我的大一生活成为一名大二的小学长啦嘿嘿。而我今天呢主要是想祝马上要高考的学弟学妹们高考顺利&#xff0c;金榜题名&#xff0c;并且借此机会顺便讲讲我的高考前后的故…

解决python通过pip离线安装flask,numpy报错解决(centos)

1. 离线安装Python https://www.python.org/ftp/python/3.7.1/Python-3.7.1.tgz 解压&#xff0c;编译&#xff0c;安装 tar xzvf Python-3.7.1.tgz ./configuremakemake install 离线环境下如果系统不是完整版安装编译会报错&#xff0c;需要解决依赖问题&#xff0c;如下&am…

5 种常见的 Linux 打包类型:tar、gzip、bzip2、zip 、 7z

在 Linux 系统中&#xff0c;打包和压缩文件是常见的操作。不同的打包类型适用于不同的用途和需求。本文将详细介绍 5 种常见的 Linux 打包类型&#xff0c;包括tar、gzip、bzip2、zip 和 7z&#xff0c;以及它们的特点、使用方法和适用场景。 1. tar tar&#xff08;tape arc…

音悦台项目测试报告

文章目录 项目背景项目功能测试计划与设计功能测试自动化测试 测试结果功能测试结果UI自动化测试结果 项目背景 现如今人们的生活压力大&#xff0c;容易使人疲惫&#xff0c;为了使得人们在闲暇之余可以听音乐放松&#xff0c;为此设计出一款轻量的听音乐网站&#xff0c;快速…