【JavaEE进阶】MyBatis表查询

news2024/11/24 7:20:25

文章目录

  • 一. 使用MyBatis完成数据库的操作
    • 1. MyBatis程序中sql语句的即时执行和预编译
      • 1.1 即时执行(${})
      • 1.2 预编译(#{})
      • 1.3 即时执行和预编译的优缺点
    • 2. 单表的增删改等操作
      • 2.1 增加操作
      • 2.2 修改操作
      • 2.3 删除操作
      • 2.4 like(模糊查询)操作
      • 2.5 实体类中的属性和数据库表中的字段名不一致出现的问题的三种解决方式
    • 3. 多表查询
      • 3.1 多表查询(一对一)
      • 3.2 多表查询(一对多)
  • 二. 动态SQL的使用
    • 1. < if >标签
    • 2. < trim >标签
    • 3. < where >标签
    • 4. < set >标签
    • 5. < foreach >标签
  • 单元测试

在上一篇博客中我们简单了解了MyBatis的创建与使用,接下来我们进一步的学习MyBatis的相关知识。
注:此博客中测试案例所使用的单元测试在文末有教程.

一. 使用MyBatis完成数据库的操作

1. MyBatis程序中sql语句的即时执行和预编译

我们在JDBC中在构造sql语句的时候,常常给字段的值用问号?代替,最后在使用方法对这些?进行赋值,这是预编译
使用预编译的好处可以防止sql注入。当然还有一种sql的执行方式就是即时执行
SQL注入是一种常见的安全漏洞,它利用了未正确过滤或转义用户输入的数据,导致恶意用户可以在执行SQL查询时插入恶意的SQL代码。
下面我们来了解一下MyBatis程序中的即使执行和预编译的构建方式.

1.1 即时执行(${})

就像下面我们写道的根据某个字段查询单个信息的时候,我们传递了参数,在xml文件中对相应的字段进行赋值的时候使用${}这种方式就是构造sql语句即时执行的方式。

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

在这里插入图片描述
我们从运行结果看到,执行的sql语句是直接被赋值的,并没有使用?.

1.2 预编译(#{})

这种写法在程序执行的时候,我们可以看到sql语句中id的值先是被?将位置占着的。这里?表示的是只能是值,而不能是sql语句,这就防止了sql注入。

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

在这里插入图片描述

1.3 即时执行和预编译的优缺点

即时执行(${}):
优点:

  • 当我们逛淘宝的时候,筛选商品点击价格按照从低到高,这个时候传递的就是SQL命令。从低到高传递的就是asc,从高到低传递的就是desc。使用${}可以实现排序查询,而使用#{}就不能实现排序查询,因为当使用#{}查询时,如果传递的值为String就会加单引号,就会导致sql错误.

缺点:

  • 它的执行不安全,存在sql注入.
  • 在使用${}时,如果传入的参数是字符串类型的数据,还需要再构造sql的语句的时候使用单引号将传入的参数引住'${}'

SQL注入是一种常见的安全漏洞,它利用了未正确过滤或转义用户输入的数据,导致恶意用户可以在执行SQL查询时插入恶意的SQL代码。
查询数据库可以看到用户名和密码都是admin.
在这里插入图片描述
正常情况下,用户只能通过密码来输入.

    //用户登录的场景
    UserEntity login(@Param("username")String username,@Param("password")String password);
    <select id="login" resultType="com.example.demo.entity.UserEntity">
        select * from userinfo where username='${username}' and password='${password}'
    </select>
//单元测试代码
@Test
    void login() {
        UserEntity user = userMapper.login("admin","admin");
        System.out.println(user);
    }

在这里插入图片描述
但是非法情况下,我们给password的属性填写一个语句就可以登录成功:

@Test
    void login() {
        UserEntity user = userMapper.login("admin","'or 1='1");
        System.out.println(user);
    }

在这里插入图片描述
由于and的优先级高,先执行前面的结果为false.但是后面的or表示的是两个表达式中只要有一个表达式为真,那么最后的结果就为真,那么1=‘1’,这个表达式就为真.
这就是最简单的SQL注入.

预编译(#{}):
优点:

  • 它的执行是安全的,可以防止sql注入。预编译他会将传入的值当成value来看待,判断这个value是否和数据库中这个字段中的值是否相等,相等就会执行成功,不相等会查找不到.
  • 在使用#{}这种写法的时候,如果我们传递的参数是字符串类型的,我们不需要使用单引号(’ ')将#{}括起来,执行的时候,他会自动给value添加单引号。

缺点:

  • 不能传递SQL命令,当传递SQL命令的时候他会给这个命令自动添加单引号(’ '),但是给SQL命令添加单引号SQL语句就会报错。
//UserMapper类下:
    //传递排序规则
    List<UserEntity> getAllByOrder(@Param("myorder")String myorder);
//UserMapper.xml
	    <select id="getAllByOrder" resultType="com.example.demo.entity.UserEntity">
        select * from userinfo order by id #{myorder}
    </select>
//单元测试
    @Test
    void getAllByOrder() {
        List<UserEntity> list = userMapper.getAllByOrder("desc");
        System.out.println(list);
    }

在这里插入图片描述
当时当我们将#{}换成${},再次运行就会执行成功。

select * from userinfo order by id ${myorder}

在这里插入图片描述
总结:
${}#{}的区别在于替换方式和安全性。${}是简单的字符串替换,直接将参数值拼接到SQL语句中,没有安全处理,存在安全风险和SQL注入风险;而#{}是预编译处理,将参数放入PreparedStatement中,使用占位符进行替换,提供了类型转换、安全处理和预编译功能,更加安全可靠。因此,为了防止SQL注入攻击和保证系统的安全性,推荐使用#{}作为参数占位符。

2. 单表的增删改等操作

在上述博客中,我们简单介绍了<select>标签.详情见:MyBatis项目创建与使用
接下来,我们来实现用户的增删改操作,对应使用MyBatis的标签如下:
<insert>标签:插入语句.
<update>标签:修改语句.
<delete>标签:删除语句.

2.1 增加操作

添加操作在接口中声明方法的时候,定义的返回值类型是int,因为默认的返回值是受影响的行数,在XML文件实现add方法时,也不需要规定返回值类型。

//UserMapper类下:
    //添加用户
    int add(UserEntity user);
//UserMapper.xml:
    <insert id="add">
        insert into userinfo(username,password,photo)values(#{username},#{password},#{photo})
    </insert>	  
//单元测试:
    @Test
    void add() {
        UserEntity user = new UserEntity();
        user.setUsername("张三");
        user.setPassword("123");
        user.setPhoto("/image/default.png");
        int result = userMapper.add(user);
        System.out.println("受影响的行数: "+result);
    }   

在这里插入图片描述
在这里插入图片描述
特殊的添加:返回自增id
之前的方法默认情况下返回的是受影响的行数,如果想要返回自增id,具体实现如下。

//UserMapper类下:
	//添加操作:返回自增id
    int insert(UserEntity user);
//UserMapper.xml:
    <insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        insert into userinfo(username,password,photo)values(#{username},#{password},#{photo})
    </insert>	  
//单元测试:
    @Test
    void insert() {
        UserEntity user = new UserEntity();
        user.setUsername("李四");
        user.setPassword("123");
        user.setPhoto("");
        //因为在接口中声明的 insert 方法的参数为 user,所以测试的时候可以直接将 user对象传给这个insert方法
        int result = userMapper.insert(user);
        System.out.println("受影响的行数: "+result+" | id: "+user.getId());
    }

在XML文件中的insert标签中添加useGenerateKeys,keyColumnkeyProperty属性。

useGenerateKeys: 表示获取数据库中开启自增主键的值。在insert标签中表示的意思为获取本次添加的成员的自增主键的值。默认值为false.
keyColumn: 表示设置自增主键在数据表中的字段名。
keyProperty: 表示将获取到的自增主键的值赋值给keyProperty所指的属性(实体类).
在这里插入图片描述
在这里插入图片描述

2.2 修改操作

修改的实现和删除一样在xml文件中的update标签中不用设置返回值类型(resultMap或者resultType),默认的返回值是受影响的行数,所以在UserMapper接口中声明方法的时候,返回值类型为int。

//UserMapper类下:
    //修改操作
    int update(UserEntity user);
//UserMapper.xml:
    <update id="update">
        update userinfo set username=#{username} where id=#{id}
    </update>	  
//单元测试:
    @Test
    void update() {
        UserEntity user = new UserEntity();
        user.setId(1);
        user.setUsername("管理员");
        int result = userMapper.update(user);
        System.out.println("受影响的行数"+result);
    }

在这里插入图片描述
在这里插入图片描述

2.3 删除操作

删除信息,默认返回的是受影响的行数,所以我们在声明方法的时候设置的返回值类型为int.

//UserMapper类下:
    //删除操作
    int delById(@Param("id")Integer id);    
//UserMapper.xml:
    <delete id="delById">
        delete from userinfo where id=#{id}
    </delete>	  
//单元测试:
    @Test
    void delById() {
        int id = 2;
        int result = userMapper.delById(id);
        System.out.println("受影响的行数:"+result);
    }

在这里插入图片描述
在这里插入图片描述
MyBatis中开启事务的注解:@Transactional
在这里插入图片描述
在这里插入图片描述

2.4 like(模糊查询)操作

like查询,我们按照学习MySQL是使用的语法在XML文件中构造sql语句,在执行的时候会出现报错的问题。

//UserMapper类下:
      //like查询
    List<UserEntity> getLikeList(@Param("username")String username);  
//UserMapper.xml:
    <select id="getLikeList" resultType="com.example.demo.entity.UserEntity">
        select * from userinfo where username like %#{username}%
    </select>	  
//单元测试:
    @Test
    void getLikeList() {
        String username = "三";
        List<UserEntity> list = userMapper.getLikeList(username);
        System.out.println(list);
    }

在这里插入图片描述
我们可以看到,使用预编译的方式不行,那我们可以尝试使用即时执行的方式,这种方式执行确实结果是对的,但是这里使用即时执行的方式并不满足使用它的条件,会出现sql注入的情况。

解决方案:

  1. 第一种解决方法是在XML中继续直接使用#{username},我们在业务代码中给username赋值为%三%

    //UserMapper类下:
          //like查询
        List<UserEntity> getLikeList(@Param("username")String username);  
    //UserMapper.xml:
        <select id="getLikeList" resultType="com.example.demo.entity.UserEntity">
            select * from userinfo where username like #{username}
        </select>	  
    //单元测试:
        @Test
        void getLikeList() {
            String username = "%三%";
            List<UserEntity> list = userMapper.getLikeList(username);
            System.out.println(list);
        }
    

    在这里插入图片描述
    虽然这种方式可以解决问题,但是在业务代码中写,看起来就是不太好看。

  2. 第二种解决方法是使用SQL语法中的concat字段,对多个字符进行拼接。

    //UserMapper类下:
          //like查询
        List<UserEntity> getLikeList(@Param("username")String username);  
    //UserMapper.xml:
        <select id="getLikeList" resultType="com.example.demo.entity.UserEntity">
            select * from userinfo where username like concat('%',#{username},'%')
        </select>	  
    //单元测试:
        @Test
        void getLikeList() {
            String username = "三";
            List<UserEntity> list = userMapper.getLikeList(username);
            System.out.println(list);
        }
    

    在这里插入图片描述
    这种写法既不会出现单引号套单引号的问题,在业务代码中进行like查询传参的时候,也没有出现使用多余的符号的问题。

2.5 实体类中的属性和数据库表中的字段名不一致出现的问题的三种解决方式

MyBatis是通过实体类的属性名称和数据库中的字段名进行映射的,如果实体类中的属性名和数据库表中的字段名不同,在进行查询的时候,出现的结果中字段的值会为null.
在这里插入图片描述
解决方法:

  1. 将实体类中的属性名修改成和数据库表中的数据修改成一致的。这种方式只适合于当前这个实体类,只有你一个人使用了,如果其他人的代码中也使用了你创建的实体类,那么就不能使用这种方式来修改了。
  2. 使用SQL语句中的as对数据表中的字段名进行重命名,让字段名等于创建的实体类的属性名。
        <select id="getAllByOrder" resultType="com.example.demo.entity.UserEntity">
            select id,username as name from userinfo order by id ${myorder}
        </select>
    
  3. 定义一个resultMap,将属性名和字段名进行手动映射。
    在这里插入图片描述
    在这里插入图片描述

3. 多表查询

3.1 多表查询(一对一)

这里我们查询一篇文章对应的作者的名字,站在文章的角度进行多表联合查询就是一对一的情况。
使用注解的方式在MyBaits程序中构造SQL语句,我们想要使用SQL的查询,就可以在接口中的方法上加上注解@Select,想要使用删除,可以在接口的方法上添加@Delete,想要使用插入可以在方法上添加@Insert,想要实现修改可以在方法上添加@Update,然后将要执行的sql语句写在这些注解的参数中即可。

  1. 创建文章实体类
package com.example.demo.entity;

import lombok.Data;
 
import java.time.LocalDateTime;
 
@Data
public class ArticleInfo {
    private int id;
    private String title;
    private  String content;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private  int uid;
    private int rcount;
    private int state;
    //链表字段
    private  String username;
}
  1. 定义接口
package com.example.demo.mapper;
 
import com.example.demo.entity.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
 
import java.util.List;
 
@Mapper
public interface ArticleMapper {
    @Select("select articleinfo.*,userinfo.username from articleinfo left join userinfo on articleinfo.uid=userinfo.id")
    List<ArticleInfo> getAll();
}
  1. 创建单元测试
package com.example.demo.mapper;
 
import com.example.demo.entity.ArticleInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
 
import java.util.List;
 
import static org.junit.jupiter.api.Assertions.*;
 
@SpringBootTest
class ArticleMapperTest {
    @Autowired
    private ArticleMapper articleMapper;
 
    @Test
    void getAll() {
        List<ArticleInfo> list = articleMapper.getAll();
        System.out.println(list);
    }
}

运行结果:
在这里插入图片描述

3.2 多表查询(一对多)

一对多的多表查询,这里我们将查询步骤分为三步:

  1. 根据id找到用户信息
  2. 根据uid查询文章列表
  3. 然后将得到的文章信息和用户信息进行组装即可
  1. 首先我们需要在userinfo类(用户实体类)中添加一个alist属性,最后用来将得到文章信息组装到userinfo对象中。
package com.example.demo.model;
 
import lombok.Data;
 
import java.time.LocalDateTime;
import java.util.List;
 
@Data
public class Userinfo {
    private int id;
    private String username;
    private String password;
    private String photo;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private int state;
    private List<ArticleInfo> alist;
}
  1. 然后在数据持久层的UserinfoMapper类和ArticleMapper类中添加查询的方法
package com.example.demo.entity;
 
import lombok.Data;
 
import java.time.LocalDateTime;
import java.util.List;
 
@Data
public class Userinfo {
    private int id;
    private String username;
    private String password;
    private String photo;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private int state;
    private List<ArticleInfo> alist;
}
  1. UserMapperTest1单元测试类中创建一个getUserList方法,在这个方法中调用上述两个方法,最后调用setAlist方法,将getListByUid方法中得到的文章列表添加到userinfo对象中,就完成了多表查询的一对多的情况
package com.example.demo.mapper;
 
import com.example.demo.entity.ArticleInfo;
import com.example.demo.entity.Userinfo;
import com.example.demo.entity.ArticleInfo;
import com.example.demo.entity.Userinfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
 
import java.util.List;
 
import static org.junit.jupiter.api.Assertions.*;
 
@SpringBootTest//不能省略,告诉当前的测试程序,目前项目是运行在Spring容器中的
class UserMapperTest1 {
    @Autowired
    private UserinfoMapper userinfoMapper;
    @Autowired
    private ArticleMapper articleMapper;
    @Test
    void getUserList(){
        int uid = 1;
        //1.根据uid查询userinfo
        Userinfo userinfo = userinfoMapper.getUserById2(uid);
        //2.根据uid查询文章列表
        List<ArticleInfo> list = articleMapper.getListByUid(uid);
        //3.组装数据
        userinfo.setAlist(list);
        System.out.println(userinfo);
    }
 
}

在这里插入图片描述

二. 动态SQL的使用

动态sql是MyBatis的强大特性之一,能够完成不同条件下不同的sql拼接。

1. < if >标签

我们在上网时,经常需要填写一些表单,其中有些选项是必填的,有些是选填的,那么这个时候在MyBatis程序中按照XML的方式构造sql语句时,是不能完全胜任的。比如填通讯信息的时候,出现了一个选填项是填写QQ号,如果不填这个选项,前端传给后端代码中的这个数据的值为null,现在规定让这一项在数据库中默认为空,如果如不使用标签,那么在XML中是无法完成这个规定。在数据库中null和空是两个概念。

语法:

<!--  test中的表达式是满足使用多个条件  -->
<if test="表达式">
    <!-- 满足表达式的条件,就会进入执行其中的内容  --> 
    ....
</if>
  1. 在接口中声明方法
    //动态sql添加操作<if>
    int add2(Userinfo userinfo);
  1. 在XML文件中实现动态sql,这里再sql语句中添加标签用来判断是否设置了photo的值,如果没有设置,那就不添加这个字段在sql语句中,如果添加这个字段的值,就会在sql语句中添加这个字段。
    <insert id="add2">
        insert into userinfo(username,password
        <if test="photo != null">
            ,photo
        </if>
        )values(#{username},#{password}
        <if test="photo != null">
            ,#{photo}
        </if>
        )
    </insert>
  1. 测试单元
//给对象的属性设置值得时候,给photo属性添加值
    @Test
    void add2() {
        Userinfo userinfo = new Userinfo();
        userinfo.setUsername("张三");
        userinfo.setPassword("123");
        userinfo.setPhoto("cat.png");
        int result = userMapper.add2(userinfo);
        System.out.println("执行的结果: "+result);
    }
  1. 运行结果:
    在这里插入图片描述
    可以看到如果给photo没有设置值,那么在数据看中photo这一列是空的,不会出现null.这就解决了表单中可选项的填写问题了。如果填了表单中的可选项就会将值保存在数据库中,如果没有填写可选项,那么数据库中这个字段就不会有值。
    在这里插入图片描述

2. < trim >标签

上面我们说的表单中存在某个选填项,假设表单上所有的选项都是选填的,那么使用<if>标签就不能满足我们的需求了。因为在判断给字段是否传值时,使用<if>标签将字段包裹起来了,但是字段和字段之间要使用逗号隔开,所以我们还需要将逗号拼接上。但是我们不知道用户选填了那些字段,所以将逗号拼接上之后,还需要考虑逗号不能出现在开始的字段前面,结束的字段后面不能出现逗号。这个就需要使用<trim>标签中的属性来解决了。

<trim>标签的属性:
prefix:表示整个语句块,以prefix的值作为前缀
suffix:表示整个语句块,以suffix的值作为后缀
prefixOverrides:表示整个语句块要去除掉的前缀
suffixOverrides:表示整个语句块要去除掉的后缀

  1. 在接口中声明方法
    /*
    * 动态sql <trim>标签
    * */
    int add3(Userinfo userinfo);
  1. 在XML文件中实现方法,由于<trim>标签中的prefixsuffix属性可以添加整个语句块的前缀和后缀,所以这里我们直接使用这两个属性拼接括号,我们在<if>标签中将逗号拼接在字段的后面,使用suffixOverrides属性指定要去除语句块中某个后缀(逗号),整个时候就会将语句块中最后一个字段之后的逗号去掉。
    <insert id="add3">
        insert into userinfo
            <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="username != null">
                    username,
                </if>
                <if test="password != null">
                    password,
                </if>
                <if test="photo!=null">
                    photo,
                </if>
 
            </trim>
            values
            <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="username != null">
                    #{username},
                </if>
                <if test="password != null">
                    #{password},
                </if>
                <if test="photo != null">
                    #{photo},
                </if>
            </trim>
    </insert>
  1. 单元测试,这里我们只添加名字和密码,不添加照片的属性。
    @Test
    void add3() {
        Userinfo userinfo = new Userinfo();
        userinfo.setUsername("李四");
        userinfo.setPassword("666");
        int result = userMapper.add2(userinfo);
        System.out.println("执行的结果: "+result);
    }
  1. 执行结果
    可以看到执行结果中字段中没有将photo拼接上,并且语句块中结尾的字段之后也没有逗号。数据保存在数据库中,也是按照我们的预想执行的,没有添加照片字段的值,photo列为空,不是null.
    在这里插入图片描述
    在这里插入图片描述

3. < where >标签

MyBatis 中多个都是非必传参数的解决方案:

  1. 1=1解决方案
    在这里插入图片描述
  2. <trim>标签解决:
    在这里插入图片描述
  3. <where>标签方案:
    在这里插入图片描述
    注意: <where>只会自动帮你去除最前面的 and 关键字,使用 where 标签不会自动帮你去除最后面的and关键字.
    MyBatis中,<where>标签并不只能用于生成 AND 条件,它可以用于生成任何类型的条件语句(包括AND和OR)。
    例如:
<select id="getUserList" parameterType="Map" resultType="User">
  SELECT *
  FROM users
  WHERE age = #{age}
  <where>
    <if test="name != null">
      OR name = #{name}
    </if>
  </where>
</select>

上述示例中,无论age的值是什么,都会根据name的值生成OR条件。

4. < set >标签

<set>标签和<where>标签在sql语句中添加方式相同,只不过where标签用在查询,set标签用在修改。但是<set>标签是去掉代码块的后缀的,而<where>标签是去掉代码块的前缀的。使用<set>标签可以避免在更新操作中出现多余的逗号和无效的更新字段。

<update id="updateUser" parameterType="User">
  UPDATE users
  <set>
    <if test="name != null">
      name = #{name},
    </if>
    <if test="age != null">
      age = #{age},
    </if>
    <if test="email != null">
      email = #{email},
    </if>
  </set>
  WHERE id = #{id}
</update>

在上述示例中,<set>标签用于动态生成set子句。根据传入的参数值判断是否生成相应的更新字段,如果参数值为null,则不会生成相应的更新语句。
注意,在生成set子句时,每个更新字段末尾都会有一个逗号,即使是最后一个字段。这是因为在动态SQL中,可以通过条件判断来控制是否生成该字段,但为了简化逻辑和代码,可以在每个字段之后都加上逗号,不影响SQL的语法正确性。
另外,需要注意使用占位符(如#{name})来引用参数值,而不是直接拼接参数值。这样可以避免SQL注入攻击和确保参数值的正确性。
通过使用<set>标签,可以根据条件动态生成UPDATE语句中的字段和对应的值,提高灵活性并避免不必要的逗号和无效的更新字段。

5. < foreach >标签

在MyBatis中,<foreach>标签用于循环遍历集合或数组,并将其中的元素逐个应用到SQL语句中的特定位置,以便生成动态SQL。
<foreach>标签通常与动态SQL一起使用,可以在in子句中动态生成多个值或者在批量插入/更新操作中循环处理多个数据。
下面是一个使用<foreach>标签的示例:

<select id="getUserByIdList" parameterType="List" resultType="User">
  SELECT *
  FROM users
  WHERE id IN
  <foreach item="id" collection="list" open="(" separator="," close=")">
    #{id}
  </foreach>
</select>

在上述示例中,<foreach>标签将会循环遍历传入的List类型参数list,并将每个元素存储到id变量中。循环体中的#{id}表示动态插入当前迭代的值。
生成的SQL语句可能类似于以下形式(假设list包含 [1, 2, 3]):

SELECT *
FROM users
WHERE id IN (1, 2, 3)

<foreach>标签的常用属性:
collection:指定要遍历的集合或数组。
item:指定当前元素的别名。
index:指定当前元素的索引。
open:指定循环开始时的字符。
close:指定循环结束时的字符。
separator:指定每个元素之间的分隔符。

需要注意的是,<foreach>标签也可以用于批量插入或更新操作中,通过循环处理多个数据。此时,可以将循环体中的SQL片段放置在合适的位置来重复执行插入或更新。

通过使用<foreach>标签,可以实现对集合或数组的循环遍历,动态生成包含多个值的SQL语句,并在动态SQL中灵活地处理多个数据。

单元测试

  1. 在要测试的类上右键Generate
    在这里插入图片描述
  2. 点击Test
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

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

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

相关文章

星际争霸之小霸王之小蜜蜂(六)--让子弹飞

目录 前言 一、添加子弹设置 二、创建子弹 三、创建绘制和移动子弹函数 四、让子弹飞 五、效果 总结 前言 小蜜蜂的基本操作已经完成了&#xff0c;现在开始编写子弹的代码了。 一、添加子弹设置 在我的预想里&#xff0c;我们的小蜜蜂既然是一只猫&#xff0c;那么放出的子弹…

基于小波神经网络的短时交通流量预测Matlab代码

1案例背景 1.1小波理论 小波分析是针对傅里叶变换的不足发展而来的。傅里叶变换是信号处理领域中应用最广泛的一种分析手段,然而它有一个严重不足,就是变换时抛弃了时间信息,通过变换结果无法判断某个信号发生的时间,即傅里叶变换在时域中没有分辨能力。小波是长度有限、平均为…

分布式与微服务相关知识

分布式与微服务 1.zookeeper是什么2.zookeeper保证数据一致性3.zookeeper的快速领导者选举是怎么实现的4.CAP理论5.BASE理论6.分布式id生成方案&#xff08;1&#xff09;UUID&#xff08;2&#xff09;数据库自增序列&#xff08;3&#xff09;Leaf-segment&#xff08;4&…

Linux下的系统编程——vim/gcc编辑(二)

前言&#xff1a; 在Linux操作系统之中有很多使用的工具&#xff0c;我们可以用vim来进行程序的编写&#xff0c;然后用gcc来生成可执行文件&#xff0c;最终运行程序。下面就让我们一起了解一下vim和gcc吧 目录 一、vim编辑 1.vim的三种工作模式 2.基本操作之跳转字符 &a…

实现外网访问本地服务

最近开发需要其他项目组的人访问我本地服务测试,但又不在同一个地方,不能使用内网访问,所以需要外网访问本地服务功能. 条件: 1.需要一台具备公网IP的服务器 我用的服务器是windows,电脑也是Windows系统 2.下载frp 软件,只需要下载一份就可以了,分别放到服务器上和本地目录既…

2011-2021年全国各省绿色创新效率数据(原始数据+测算结果)

2011-2021年全国各省绿色创新效率数据&#xff08;原始数据测算结果) 2011-2021年全国各省绿色创新效率 1、时间&#xff1a;2011-2021年 2、范围&#xff1a;全国31省市 3、来源&#xff1a;各省年鉴、科技年鉴、环境年鉴 4、指标&#xff1a;地区、编号、年份、R&D人…

设计模式大白话——命令模式

命令模式 一、概述二、经典举例三、代码示例&#xff08;Go&#xff09;四、总结 一、概述 ​ 顾名思义&#xff0c;命令模式其实和现实生活中直接下命令的动作类似&#xff0c;怎么理解这个命令是理解命令模式的关键&#xff01;&#xff01;&#xff01;直接说结论是很不负责…

树形结构的快速生成

背景 相信大家都遇到过树形结构&#xff0c;像是文件列表、多级菜单、评论区的设计等等&#xff0c;我们都发现它有很多层级&#xff0c;第一级可以有多个&#xff0c;下边的每一个层级也可以有多个&#xff1b;有的可以设计成无限层级的&#xff0c;有的只能设计成两级。那么…

工程师使用IT服务台软件可以解决哪些问题?

现如今企业数字化建设已初具规模&#xff0c;业务系统基本已告一段落&#xff0c;而下一步关注的重点则从技术转向管理&#xff0c;如何能让这些系统更好运行起来&#xff0c;如何提高管理效率已是重中之重。在此向您推荐一款高效的IT服务管理工具——ServiceDesk Plus&#xf…

elementui的el-tabs标签页样式修改

一、官网样式&#xff1a; 二、修改样式 1.去掉下划线 效果&#xff1a; 代码: /* 去掉tabs标签栏下的下划线 */ ::v-deep .el-tabs__nav-wrap::after {position: static !important;/* background-color: #fff; */ } 2.改变下划线颜色 效果&#xff1a; 代码&#xff1a;…

使用VisualStudio制作上位机(三)

文章目录 使用VisualStudio制作上位机(三)第三部分:GUI内部函数设计使用VisualStudio制作上位机(三) Author:YAL 第三部分:GUI内部函数设计 这一部分,主要实现CAN设备的打开 将CAN厂家的二次开发文件添加到工程里调用相关函数打开或关闭CAN首先,添加“类文件”,类主…

死锁的典型情况、产生的必要条件和解决方案

前言 死锁&#xff1a;多个线程同时被阻塞&#xff0c;他们中的一个或全部都在等待某个资源被释放。由于线程被无限期地阻塞&#xff0c;因此程序不可能正常终止。 目录 前言 一、死锁的三种典型情况 &#xff08;一&#xff09;一个线程一把锁 &#xff08;二&#xff09;…

聊一聊a_bogus

前言 可以关注我哟&#xff0c;一起学习&#xff0c;主页有更多练习例子 如果哪个练习我没有写清楚&#xff0c;可以留言我会补充 如果有加密的网站可以留言发给我&#xff0c;一起学习共享学习路程 如侵权&#xff0c;联系我删除 此文仅用于学习交流&#xff0c;请勿于商用&a…

保护隐私为先的话,最好是不登录用ChatGPT,6种方法助你轻松接入-纯分享

ChatGPT是OpenAI研发的强大AI语言模型&#xff0c;用户可以通过它进行有意义的对话&#xff0c;并获取问题解答。但是&#xff0c;一些用户可能更倾向于在不需要创建账号或不登录的情况下使用ChatGPT。在这篇指南中&#xff0c;我们将探讨各种无需账号即可访问ChatGPT的方法。无…

续二:《你的医书是假的!批评付施威的《DDD诊所——聚合过大综合症》

DDD领域驱动设计批评文集 “软件方法建模师”不再考查基础题 《软件方法》各章合集 我写了一篇文章&#xff0c;批评付施威的《DDD诊所——聚合过大综合症》&#xff08;以下简称《DDD诊所》&#xff09;&#xff0c;文章是《你的医书是假的&#xff01;批评付施威的《DDD诊…

【AI模型】Windows端深度学习环境配置

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍Windows端深度学习环境配置。 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;下次更新不…

zhm_real/MotionPlanning运动规划库中A*算法源码详细解读

本文主要对zhm_real/MotionPlanning运动规划库中A*算法源码进行详细解读&#xff0c;即对astar.py文件中的内容进行详细的解读&#xff0c;另外本文是 Hybrid A * 算法源码解读的前置文章&#xff0c;为后续解读Hybrid A * 算法源码做铺垫。 astar.py文件中的源码如下&#xff…

python pipenv环境部署django项目实践

将代码上传到服务器&#xff1a; 安装pipenv&#xff1a; pip3 install pipenv 安装项目虚拟环境&#xff1a; cd /www/wwwroot/python-django pipenv install 如果提示python版本问题&#xff0c;修改Pipfile文件内的python版本即可。 然后进入虚拟环境安装依赖包&#x…

Java学数据结构(1)——抽象数据类型ADT 表List、栈Stack和队列Qeue

目录 引出抽象数据类型&#xff08;abstract data type,ADT&#xff09;表ListArrayList,Vector, LinkedListArrayList手动实现与分析Vector的分析&#xff08;线程安全&#xff09;LinkedList 的手动实现与分析 栈stack—后进先出java中stack源码分析栈的应用&#xff1a;检查…

Android 市场的变化,影响多少开发被迫……

前言 Android 开发在2010年时&#xff0c;广受市场需要&#xff0c;那时候在一线城市很容易拿到10K的起步薪资&#xff0c;Android开发的市场空缺大概有30万左右。那时引起了大量java开发者开始学习Android开发&#xff0c;招聘市场面试要求上只要有一定java语法基础&#xff…