Mybatis复杂查询及动态SQL

文章目录

  • 一. 较复杂的查询操作
    • 1. 参数占位符#{}和${}
    • 2. SQL注入
    • 3. like查询
    • 4. resultType与resultMap
    • 5. 多表查询
      • 5.1. 一对一表映射
      • 5.2. 一对多表映射
  • 二. 动态SQL
    • 1. if标签
    • 2. trim标签
    • 3. where标签
    • 4. set标签
    • 5. foreach标签

本篇中使用的数据表即基础映射类都是基于上一篇博客的,如果有需要可以移步: MyBatis配置及单表操作

一. 较复杂的查询操作

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

#{}:预处理符,生成的执行 sql 中将id=#{6}替换为id=?,最后使用6替换了?作为参数。
${}:替换符,生成的执行 sql 中将id=${6}替换为id=6,直接替换,属于字符串拼接。

🍂两种占位符都可以正常使用的场合:传入的参数类型是数值类型。

使用${}

select * from userinfo where id=${id}
select * from userinfo where id=6

使用#{}

select * from userinfo where id=#{id}
select * from userinfo where id=?

对于这两种参数占位符,都能使用的情况下,更建议使用#{}就使用#{},因为${}存在 SQL 注入的问题,以及在特殊场景下如果传入的类型是字符串也会出问题。

🍂只能使用#{}而不能使用${}的场合:传入参数类型为 String

使用${}

select * from userinfo where username=${username}
//实际执行的语句 (比如前端通过 URL 传入的参数)
Preparing: select * from userinfo where username=张三

但是在 sql 中通过字符串来查询数据,是需要加上引号的,而使用${}生成的sql并没有带引号,因此不适用于字符串参数的sql。

使用#{}

select * from userinfo where username=#{username}
//实际执行语句
select * from userinfo where username=?

由于使用#{}是将目标参数替换为占位符,然后利用 JDBC 中的占位符机制实现 sql 语句的填充,最终是会带上引号的,所以使用#{}构造的 sql 是可以正常运行的,并且没有 SQL 注入的问题。

🎯所以,#{}相比于${},它支持所有类型的参数,包括数值类与字符串类,而${}支持数值类型,不支持字符串类型的参数,但也可以在原来 sql 里面为${}外面加上一对引号来使用。

select * from userinfo where username='${username}'
//实际执行语句
select * from userinfo where username='张三'

当传递的参数为字符串类型的时候,虽然加上一对引号,使用${}也可以使用,但是${}存在 SQL 注入问题,所以仍然不推荐,有关 SQL 注入后面在下文有介绍。

🍂大部分场合下,使用#{}都可以解决,但还是存在一小部分只能是${}来处理的。

如当我们需要按照升序或者逆序得到数据库查询结果的时候,这种场合就只能使用${},使用#{}会报错,我们来演示一下。

首先,我们在Mapper接口中声明一个方法:作用就是按照排序获取结果集

List<Userinfo> getAllByOrder(@Param("myOrder") String myOrder);

我们再去xml文件中去写 sql 语句:首先我们使用$进行演示

<select id="getAllByOrder" resultType="com.example.demo.model.UserInfo">select * from userinfo order by id ${myOrder}
</select>

写一个单元测试,代码很简单,就是调用 sql,然后得到结果集:

@Test
void getOrderList() {List<UserInfo> userMappers = userMapper.getOrderList("desc");System.out.println(userMappers);
}

单元测试结果:
可以正常查询,得到的结果与预期也是相同的。
img
我们再来试一试使用#{}来构造 sql 语句:

<select id="getOrderList" resultType="com.example.demo.model.UserInfo">select * from userinfo order by createtime #{order};
</select>

重新执行单元测试,看下面的结果:
img
我们发现程序报错了,这是因为使用了desc的字符串(带引号)替换了占位符,而我们所需要的不是一个desc字符串,而是直接一个desc的关键字(不带引号),所以 sql 也抛出了语法错误,这里最终执行的 sql 如下:

select * from userinfo order by createtime ‘desc’;

而期望执行的 sql 为:

select * from userinfo order by createtime desc;

所以在传递 sql 命令(关键字)的时候,不能使用#{},只能使用${}

总之,如果不得不使用${},那么传递的参数一定要能被穷举,此时才能在业务代码中对传递的值进行安全校验,否则是不能使用的。

2. SQL注入

SQL 注入就是使用${}实现一些特殊的语句,来达到非法获取数据的目的,如不通过正确的密码获取某账户的信息,下面我们来演示一下,就以登录的例子来演示,SQL 注入可以在不知道密码的前提下登录成功,并且获取到用户的相关信息。

首先我将数据库只保留一个用户信息,方便演示 SQL 注入问题:
img

1️⃣第一步,在Mapper接口中定义方法login,返回登录成功的用户对象。

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

2️⃣第二步,在xml文件中编写 sql 语句,我们需要演示 SQL 注入,所以我们使用${}来构造 sql 语句。

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

3️⃣第三步,编写单元测试,传入有注入问题的 sql 语句' or 1='1,此时不需要密码就能拿到相关的用户信息。

@Test
void login() {String username = "admin";String password = "' or 1='1";UserInfo userInfo = userMapper.login(username, password);System.out.println(userInfo);
}

结果如下:
我们在不知道用户密码的情况下,就登录成功,并拿到了用户的信息。
img
最终执行的 sql 语句为:

select * from userinfo where username='admin' and password='' or 1='1';

相当于它在原来条件判断的语句下,后面有加上一个或的逻辑,并且或后面的表达式为true,这样就使得原来的 SQL 语句中的条件判断部分一定为真,所以就在没有正确密码的情况下拿到了用户的基本信息。

所以我们能不使用${}就不使用${},它存在 SQL 注入问题,如果必须使用${}则需要验证一下传递的参数是否合法,比如上面定义排序的 sql,传递的参数只能是desc或者是asc,如果不是就不能执行这条 SQL,防止 SQL 注入情况的发生。

3. like查询

在 Mybatis 中使用like查询比较特殊,因为直接使用#{}会报错,而使用${},由于输入的字符串情况很多,无法做到枚举,验证比较困难,无法避免 SQL 注入问题。

首先,我们来演示使用#{}进行like查询。

1️⃣第一步,声明方法。

List<UserInfo> getLikeList(@Param("username") String username);

2️⃣第二步,xml 中实现 执行 sql。

<select id="getLikeList" resultType="com.example.demo.model.UserInfo">select * from userinfo where username like '%#{username}%'
</select>

3️⃣第三步,单元测试。

@Test
void getLikeList() {String username = "a";List<UserInfo> list = userMapper.getLikeList(username);System.out.println(list);
}

结果如下:

报错了,因为#{}会被替换成一个字符串,而在这个%#{username}%语句中#{username}是不能带上引号的,带上就违背 SQL 语法,执行就会出错。

img
使用#{}会多出一对引号,而使用${}又无法穷举出所有情况进行验证,就可能有 SQL 注入问题,所以这里是不能直接使用#{}的,我们需要搭配 MySQL 内置的字符串拼接语句concat

我们将 sql 改为使用concat进行字符串拼接:

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

重新执行单元测试,此时就可以正常执行了。
img

4. resultType与resultMap

resultType表示数据库返回的数据映射在 java 程序中所对应的类型,只要定义类中的属性与数据库中表的字段名字一致就没有任何问题,但是如果不一致,冲突的属性就无法获取到数据库查询的结果。

比如用户名属性在数据库中的名字是username,而在 java 程序类中的属性名为name,此时通过 mybatis 将数据传递到程序中的对象时,获取到的name属性为null,就不能正确地获取到对应的属性值,为了解决这个数据库字段与类中中字段不匹配的问题,我们需要使用到resultMap

resultMap 的使用方式就是在xml文件中设置<resultMap>标签,至少需要设置两个属性,一个是id表示你这个 resultMap 标签的名字,还有一个是type属性,它表示映射到程序中类的类型,需包含包名。

这个标签里面需要设置至少两个子标签,一个是id标签,另外一个是result标签,前者表示主键,后者表示数据库表中普通的列,这两种标签也是至少需要设置两个属性,一个是column表示数据库表中的字段名,另外一个是property表示程序类中的对应属性名,如果只是在单表进行查询,只设置不同字段名的映射就可以了,但是如果是多表查询,必须将数据表中所有的字段与类中所有的属性生成映射关系。

就像下面这样,图中类属性与数据表字段是相同的,实际情况可以存在不同的字段名:
img

所以,当类中的属性和数据库表中的字段名不一致,面对查询结果为null的这种情况,有三种解决方案:

  1. 将类中的属性和数据库表的字段名改成一致的。
  2. 使用 sql 语句中的 as 进行列名(字段名)重命名,让列名(字段名)等于属性名即可。
  3. 就是上面介绍的,定义一个 resultMap,将属性名和字段名进行手动映射。

5. 多表查询

5.1. 一对一表映射

一对一关系就是对于一个属性只与另外一个属性有关系的映射,这就是一对一的关系,举个例子,对于一篇博客,它只会对应到一个用户,那么博客与用户的关系就是一对一的关系,下面我们尝试在 mybatis 中实现一对一多表联查。

首先我们将数据库中的博客表与 java 程序中的博客类对应起来,就是按照数据库中的博客表建立一个类:

img

@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 UserInfo userInfo;
}

目前文章表中只有一条数据,如下图:
img
1️⃣第一步,创建Mapper接口和对应的xml文件。

img

img

2️⃣第二步,在接口中声明方法和在 xml 中写 sql 标签与语句。

// 根据文章 id 获取文章对象, 显示用户信息
public ArticleInfo getArticleById(@Param("id") Integer id);
<select id="getArticleById" resultType="com.example.demo.model.ArticleInfo">select * from articleinfo where id=#{id};
</select>

3️⃣第三步,编写单元测试并执行。

img

结果如下:

由于我们数据表的字段与类的属性名是一致的,那些普通的属性都一一对应上了,都成功被赋值了,但是由于UserInfo类在数据表中没有,所以并没有得到 UserInfo 对象。
img

🍂如果我们想要拿到这个对象,我们可以使用resultMap,而且 sql 也需要修改,想要实现一对一多表查询,需要设置多表查询 sql 语句,我们使用左外连接进行多表查询:

<select id="getArticleById" resultMap="BaseMap">select a.*, u.* from articleinfo as a left join userinfo as u on  a.uid=u.id where a.id=#{id};
</select>

此外,我们除了设置usernameArticleinfo类中每个属性与数据表的映射之外,我们还要在Articleinfo类对应的resultMap中使用association标签。最少需要设置两个属性,一个是property表示在主表Articleinfo中对应副表UserInfo映射对象的变量名,另外一个是副表UserInfo对应的resultMap

Articleinfo类对应的 resultMap:

<resultMap id="BaseMap" type="com.example.demo.model.Articleinfo"><id column="id" property="id"></id><result column="title" property="title"></result><result column="content" property="content"></result><result column="createtime" property="createtime"></result><result column="updatetime" property="updatetime"></result><result column="uid" property="uid"></result><result column="rcount" property="rcount"></result><result column="state" property="state"></result><association property="userInfo" resultMap="com.example.demo.dao.UserMapper.BaseMap"></association>
</resultMap>

UserInfo类对应的 resultMap:

<resultMap id="BaseMap" type="com.example.demo.model.UserInfo"><id column="id" property="id"></id><result column="username" property="username"></result><result column="password" property="password"></result><result column="photo" property="photo"></result><result column="createtime" property="createtime"></result><result column="updatetime" property="updatetime"></result><result column="state" property="state"></result>
</resultMap>

注意多表查询时要将 resultMap 中的所有字段进行映射,如果UserInfo类的 resultMap 没有将所有的属性都与数据库的表映射,就会造成获取到的userInfo对象中的数据不完整,假设只设置了idname的映射,那就只能获取到的对象idname的值,其他字段是获取不到的。

将两张表的 resultMap 映射好后,我们运行同样的单元测试代码,结果如下:
img
🎯但此时仍然存在一个问题,我们所建的两个表存在名字相同的字段,可能会出现数据覆盖的情况,如两个表的主键都叫id,但是id在两个表的含义是不同的,在用户表它表示用户id,在文章表它表示文章的id,现在我们将获取两表的数据的id改为不相同,再来看一看单元测试运行的结果:
img
img
从正常的逻辑上看,由于不存在id1的用户,所以获取到UserInfo对象应该为null才对,但是运行的结果却存在UserInfo对象,并且与文章表的重名字段都被赋值了文章表中的数据,为了解决这个问题,我们必须在文章表(主表)的resultMap中设置属性columnPrefix,它的值随便设置,作用是识别副表字段时加上一段前缀,如我们给用户表的字段加上前缀u_,此时 sql 中就不能使用*来一次表示所有元素了,需要一个一个单独设置,并将字段全部重命名,带上u_前缀 。

association字段设置:

<association property="userInfo" columnPrefix="u_" resultMap="com.example.demo.mapper.UserMapper.BaseMap" ></association>

sql 语句需要将用户表的字段全部重命名:

<select id="getArticleById" resultMap="BaseMap">select a.*, u.id as u_id,u.username as u_username,u.password as u_password,u.photo as u_photo,u.createtime as u_createtime,u.updatetime as u_updatetime,u.state as u_statefrom articleinfo as a left join userinfo as u on  a.uid=u.id where a.id=#{id};
</select>

img

我们将userInfo对应的用户表的id再改回为1
img
再次执行单元测试代码:
img
此时是能够获取到相应的数据的,所以如果两个表字段重名了,进行多表查询时,需要设置columnPrefix属性,这样才能够避免不同表同名字段数据覆盖的问题。

所以,在创建数据库的数据表时,尽量不要让表与表中的字段重名。

🍂除了基于xml实现多表查询,还可以基于注解实现,使用注解就简单了不少,直接设置参数为相应的 sql 即可,比如再声明一个getArticleById2方法,使用注解实现具体的执行 sql。

// 根据文章 id 获取文章对象, 显示用户信息 (基于注解)
@Select("select a.*, u.* from articleinfo as a left join userinfo as u on  a.uid=u.id where a.id=#{id};")
ArticleInfo getArticleById2(@Param("id") Integer id);

单元测试代码:

@Test
void getArticleById2() {ArticleInfo articleinfo = articleMapper.getArticleById(1);System.out.println(articleinfo);
}

结果如下:

img

5.2. 一对多表映射

一对多的关系,就是对于一个属性,它对映着多个其他的属性,比如用户与博客之间的关系,一个用户可以对应多篇博客,那么用户与博客之间的关系就是一对多的关系;我们再尝试使用 mybatis 实现多对多的多表联查。

🍂下面我们以用户表为主,文章表为辅,来演示如何进行一对多关系的多表查询。

既然是一对多的关系,那我们可以在UserInfo类中加上一个储存ArticleInfo对象的List,来储存用户发布或所写的文章。

@Data
public class UserInfo {private Integer id;private String username;private String password;private String photo;private String createtime;private String updatetime;private Integer state;// 联表字段private List<Articleinfo> aList;
}

实现多表查询的大致过程如下:

  • 在 Mapper 接口中声明方法,我们声明一个方法,就是通过用户 id 获取用户信息以及对应的文章列表。
// 根据 id 查询用户信息, 显示相应文章信息
UserInfo getUserAndArticlesById(@Param("uid") Integer id);
  • xml文件当中写resultMap映射关系,与一对一多表查询不同的是,我们需要设置collection标签,而不是association标签。
  • xml文件的resultMap标签中至少设置 resultMap 名字id,对应映射的类type等属性,里面需要设置数据表与类中所有属性的映射,以及设置collection标签,需要设置property属性表示需映射的对象名,设置 resultMap 即副表的resultMap路径,由于你无法保证表与表之间是否存在重名字段,需要设置columnPrefix为副表的字段添加上一个前缀,防止重名数据覆盖。
<collectionproperty="aList"resultMap="com.example.demo.mapper.ArticleMapper.BaseMap"columnPrefix="a_">
</collection>
  • 在对应的xml文件当中写 sql 标签以及语句。
<select id="getUserAndArticlesById" resultMap="BaseMap">select u.*, a.id as a_id,a.title as a_title,a.content as a_content,a.createtime as a_createtime,a.updatetime as a_updatetime,a.uid as a_uid,a.rcount as a_rcount,a.state as a_statefrom userinfo as u left join articleinfo as a on u.id=a.uid where u.id=#{uid}
</select>
  • 编写单元测试代码,验证结果。
@Test
void getUserAndArticlesById() {Integer id = 1;UserInfo userInfo = userMapper.getUserAndArticlesById(id);System.out.println(userInfo);
}

结果如下:
img

🍂同样也可以使用注解完成,实现起来更简单。

// 根据 id 查询用户信息, 显示相应文章信息 (基于注解)
@Select("select u.*, a.* from userinfo as u left join articleinfo as a on u.id=a.uid where u.id=#{uid};")
UserInfo getUserAndArticlesById2(@Param("uid") Integer id);

单元测试代码:

@Test
void getUserAndArticlesById2() {Integer id = 1;UserInfo userInfo = userMapper.getUserAndArticlesById(id);System.out.println(userInfo);
}

结果如下:

img

🍂上面的方式都是对两张表联合后进行的查询,是一步实现的,其实也可以分步实现,比如这里可以先根据uid查询到用户信息,再由uid查询到相应文章信息,可以使用多线程分别执行这两步效率更高,然后将文章信息整合到用户对象中的aList字段中即可。

定义方法,这里使用注解实现相应执行 sql。

// 根据用户 id 查询用户信息。
@Select("select * from userinfo where id=#{id}")
UserInfo getUserById2(@Param("id")Integer id);// 根据用户 id 查询相应文章信息 
@Select("select * from articleinfo where uid=#{uid}")
List<ArticleInfo> getListByUid(@Param("uid")Integer uid);

单元测试代码:

@Test
void getUserList() {int uid = 1;// 定义线程池ThreadPoolExecutor threadPool =new ThreadPoolExecutor(5, 10,100, TimeUnit.SECONDS,new LinkedBlockingQueue<>(100));final Object[] resultArray = new Object[2];threadPool.submit(new Runnable() {@Overridepublic void run() {// 1. 根据 uid 查询 userinforesultArray[0] = userMapper.getUserById2(uid);}});threadPool.submit(new Runnable() {@Overridepublic void run() {// 2. 根据 uid 查询查询文章列表resultArray[1] = articleMapper.getListByUid(uid);}});// 组装数据 (等线程池执行完成之后)while (threadPool.getTaskCount() !=threadPool.getCompletedTaskCount()) {}UserInfo userinfo = (UserInfo) resultArray[0];userinfo.setAList((List<ArticleInfo>) resultArray[1]);System.out.println(userinfo);
}

执行测试方法,结果如下:

img

二. 动态SQL

动态 SQL 是 MyBatis 的强大特性之一了,如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号;利用动态 SQL,可以彻底摆脱这种痛苦;使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

之前介绍的 mybatis 增删查改,所传的参数都是一定会传入的,但是在实际情况中,很多参数都是非必传参数,使用动态 SQL 就可以解决传入的参数是非必传参数的情况。

动态 SQL 可以解决多余符号的问题,如,等。

1. if标签

if标签的作用就是判断一个参数是否有值,如果没有值就将对应的参数隐藏。

语法:

<if test="表达式">sql
</if>
// 例如
<if test="参数!=null">sql部分语句
</if>

当表达式为真,则插入if标签中的sql,否则不插入。

我们以在用户表中插入一条记录为例,其中插入的字段中头像photo不是必传的参数:

方法声明:

// 使用动态sql插入数据
int addUser(UserInfo userInfo);

动态SQL语句:
其中的photo是非必传参数,我们使用if标签来判断它是否有值,没有值就不插入目标的 sql 语句。

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

单元测试代码:

@Test
void addUser() {UserInfo userinfo = new UserInfo();userinfo.setUsername("zhangsan");userinfo.setPassword("123");int result = userMapper.addUser(userinfo);System.out.println("受影响行数: " + result);
}

在单元测试代码中,没有给 photo 赋值,if 标签会判断它为空,不会生成插入 photo 字段的 sql。

结果如下:

img
数据库查询结果:

img
再来试一试给 photo 传值的情况,此时生成的 sql 有三个参数:

@Test
void addUser() {UserInfo userinfo = new UserInfo();userinfo.setUsername("lisi");userinfo.setPassword("123");userinfo.setPhoto("default.png");int result = userMapper.addUser(userinfo);System.out.println("受影响行数: " + result);
}

结果如下:
最终生成的语句多了一个photo参数。
img

数据库结果:

img

2. trim标签

if标签可以实现非必传参数 sql 的构造,在极端情况下,有很多个非必传参数,此时如果只使用if标签构造出的 sql 语句很有可能会多出一个,,因为有很多非必传参数,如果只传来一个参数,由于不确定后面是否还会有参数,因此会预留一个,,此时如果没有其他参数,就会多出一个,

trim标签可以去除 sql 语句前后多余的指定字符,它需要搭配if标签使用。

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

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

语法:

<trim prefix="前缀符", suffix="后缀符", prefixOverrides="去除多余的前缀字符", suffixOverrides="去除多余的后缀字符"><if test="表达式">...</if>......
</trim>

假设usernamepasswordphoto都是非必传参数,但是至少传递一个,我们来写插入语句的动态 SQL。

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

单元测试代码:

@Test
void addUser2() {UserInfo userInfo = new UserInfo();userInfo.setUsername("wangwu");userInfo.setPassword("12345622");int result = userMapper.addUser(userInfo);System.out.println("受影响行数: " + result);
}

运行结果与生成的 sql 语句:

我们发现多余的逗号被自动去除了。

img

3. where标签

where标签主要是替换where关键字使用,可以动态的生成条件,如果 sql 中没有使用到where(没有查询条件),就不会生成where,存在查询条件,就会生成含有 where 的查询 sql 语句,并且可以自动去除前面多余的and

此时我们 userinfo 表中的数据如下:

img
假设我们可以根据idusernamepassward中的一个或者几个获取用户信息。

<select id="getUserListByWhere" resultType="com.example.demo.model.UserInfo">select * from userinfo<where><if test="id>0">id=#{id}</if><if test="username!=null">and username=#{username}</if><if test="password!=null">and  password=#{password}</if></where>
</select>

单元测试代码:

这里只传入一个username看是否可以自动删除前面多余的and

@Test
void getListByWhere() {UserInfo userinfo = new UserInfo();userinfo.setUsername("wangwu");List<UserInfo> list = userMapper.getUserListByWhere(userinfo);System.out.println(list);
}

结果如下:
发现自动生成了where语句并删除了多余的and
img
如果我们不设置任何参数,查询一个null值,就不会生成where语句。

img

img
以上<where>标签也可以使用 <trim prefix="where" prefixOverrides="and"> 替换,和以下写法效果是一样的。

<select id="getUserListByWhere" resultType="com.example.demo.model.UserInfo">select * from userinfo<trim prefix="where" prefixOverrides="and"><if test="id>0">id=#{id}</if><if test="username!=null">and username=#{username}</if><if test="password!=null">and password=#{password}</if></trim>
</select>

4. set标签

set标签与where标签很相似,where用来替换查询SQL,而set是用于修改 sql 中,用来自动生成set部分的SQL语句。

set标签可以自动去除最后面的一个,

比如我们写一个能够修改账户名username,密码password,头像photo的动态 SQL,根据id进行修改。

方法声明:

// 使用动态SQL实现修改用户信息,包括账户名,密码,头像
int updateUser(UserInfo userInfo);

动态 SQL:

<update id="updateUser">update userinfo<set><if test="username!=null">username=#{username},</if><if test="password!=null">password=#{password},</if><if test="photo!=null">photo=#{photo}</if></set>where id=#{id}
</update>

单元测试代码:

@Test
void updateUser() {UserInfo userinfo = new UserInfo();userinfo.setId(3);// 修改密码为 456userinfo.setPassword("456");int result = userMapper.updateUser(userinfo);System.out.println("受影响行数 : " + result);
}

结果如下:
修改成功并且可以根据传入参数个数自动生成相应的修改 sql,以及可以自动去除最后的,

img

数据库验证:

img
以上<set>标签也可以使用 <trim prefix="set" suffixOverrides=",">替换,和以下写法效果是一样的。

<update id="updateUser">update userinfo<trim prefix="set" suffixOverrides=","><if test="username!=null">username=#{username},</if><if test="password!=null">password=#{password},</if><if test="photo!=null">photo=#{photo}</if></trim>where id=#{id}
</update>

5. foreach标签

对集合进行遍历可以使用foreach标签,常用的场景有批量删除功能。

  • collection:绑定方法参数中的集合,如 List,Set,Map或数组对象
  • item:遍历时的每一个对象
  • open:语句块开头的字符串
  • close:语句块结束的字符串
  • separator:每次遍历之间间隔的字符串

为了方便演示批量删除,我们随便插入几条数据到数据库:
img
方法声明:

// 使用动态 sql 批量删除元素
public int deleteIds(List<Integer> ids);

动态 SQL 语句:

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

单元测试代码:
删除数据库中id为 8 9 10 11 的用户。

@Test
void delByIds() {List<Integer> list = new ArrayList<Integer>() {{add(8);add(9);add(10);add(11);}};int result = userMapper.delByIds(list);System.out.println("受影响行数" + result);
}

结果如下:
img
验证数据库,成功生成了批量删除的 sql,这就是foreach标签的作用,它能够遍历集合。
img

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

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

相关文章

【C++进阶】多态

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

Unity实现2D游戏跟随摄像机(平滑移动)

文章目录 玩家角色脚本字段跟随逻辑 完整代码其他相关文章连接 玩家角色 首先创建一个可用的玩家角色&#xff0c;写好移动逻辑&#xff0c;如果要使用在Unity商店中购买的资源&#xff0c;可以点击Window菜单栏> Package Manager选项&#xff0c;来打开Package Manager窗口…

应急响应-Windows挖矿实战

0x00 主机表现 windows主机cpu拉满&#xff0c;主机卡顿&#xff0c;初步判断为中了挖矿病毒 0x00 处置 通过cpu拉满状态&#xff0c;定位初步的进程文件&#xff0c; 通过进程得到的文件上传沙箱&#xff0c;结果显示为恶意文件&#xff0c; 定位到文件夹&#xff0c; 存…

【C++ Core Guidelines解析】深入理解现代C++的特性和原理

文章目录 &#x1f468;‍⚖️《C Core Guidelines解析》的主要观点&#x1f468;‍&#x1f3eb;《C Core Guidelines解析》的主要内容&#x1f468;‍&#x1f4bb;作者介绍 &#x1f338;&#x1f338;&#x1f338;&#x1f337;&#x1f337;&#x1f337;&#x1f490;&a…

蚂蚁开源编程大模型,提高开发效率

据悉&#xff0c;日前蚂蚁集团首次开源了代码大模型 CodeFuse&#xff0c;而这是蚂蚁自研的代码生成专属大模型&#xff0c;可以根据开发者的输入提供智能建议和实时支持&#xff0c;帮助开发者自动生成代码、自动增加注释、自动生成测试用例、修复和优化代码等kslouitusrtdf。…

3D封装技术发展

长期以来&#xff0c;芯片制程微缩技术一直驱动着摩尔定律的延续。从1987年的1um制程到2015年的14nm制程&#xff0c;芯片制程迭代速度一直遵循摩尔定律的规律&#xff0c;即芯片上可以容纳的晶体管数目在大约每经过18个月到24个月便会增加一倍。但2015年以后&#xff0c;芯片制…

ffmpeg-android studio创建jni项目

一、创建native项目 1.1、选择Native C 1.2、命名项目名称 1.3、选择C标准 1.4、项目结构 1.5、app的build.gradle plugins {id com.android.application }android {compileSdk 32defaultConfig {applicationId "com.anniljing.ffmpegnative"minSdk 25targetSdk 32…

使用Vue + axios实现图片上传,轻松又简单

目录 一、Vue框架介绍 二、Axios 介绍 三、实现图片上传 四、Java接收前端图片 一、Vue框架介绍 Vue是一款流行的用于构建用户界面的开源JavaScript框架。它被设计用于简化Web应用程序的开发&#xff0c;特别是单页面应用程序。 Vue具有轻量级、灵活和易学的特点&#xf…

AI系统论文阅读:SmartMoE

提出稀疏架构是为了打破具有密集架构的DNN模型中模型大小和计算成本之间的连贯关系的——最著名的MoE。 MoE模型将传统训练模型中的layer换成了多个expert sub-networks&#xff0c;对每个输入&#xff0c;都有一层special gating network 来将其分配到最适合它的expert中&…

【C#实战】控制台游戏 勇士斗恶龙(3)——营救公主以及结束界面

君兮_的个人主页 即使走的再远&#xff0c;也勿忘启程时的初心 C/C 游戏开发 Hello,米娜桑们&#xff0c;这里是君兮_&#xff0c;最近开始正式的步入学习游戏开发的正轨&#xff0c;想要通过写博客的方式来分享自己学到的知识和经验&#xff0c;这就是开设本专栏的目的。希望…

中国电信研究院发布《5G+数字孪生赋能城市数字化应用研究报告》

9月5日&#xff0c;中国电信研究院战略发展研究所联合中关村智慧城市产业技术创新战略联盟在2023年中国国际服务贸易交易会数字孪生专题论坛正式对外发布《5G数字孪生赋能城市数字化应用研究报告》。 会上&#xff0c;中国电信研究院战略发展研究所副所长季鸿从数字中国…

【Spring Boot】JPA — JPA入门

JPA简介 1. JPA是什么 JPA是Sun官方提出的Java持久化规范&#xff0c;它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据&#xff0c;通过注解或者XML描述“对象-关系表”之间的映射关系&#xff0c;并将实体对象持久化到数据库中&#xff0c;极大地简…

centos7更新podman

实验环境&#xff1a;centos7.7.1908 1.安装podman并查看版本 yum install podman podman -v 当前podman版本信息是1.6.4 2.更新podman版本 通过查看资料显示centos 7 支持最高版本为 3.4.4&#xff0c;更新podman大致有以下四步&#xff1a; golang 安装(本次使用版本: 1.…

实时测试工具 Visual Studio 扩展 NCrunch 4.18 Crack

NCrunch Visual Studio 扩展 .NET 的终极实时测试工具 在编码时查看实时测试结果和内联指标。 下载v4.18 发布于 2023 年 7 月 17 日 跳过视频至&#xff1a; 代码覆盖率 指标 分布式处理 配置 发动机模式 Visual Studio 自动并发测试 NCrunch 是一个完全自动化的测试扩展&a…

[machine Learning]强化学习

强化学习和前面提到的几种预测模型都不一样,reinforcement learning更多时候使用在控制一些东西上,在算法的本质上很接近我们曾经学过的DFS求最短路径. 强化学习经常用在一些游戏ai的训练,以及一些比如火星登陆器,月球登陆器等等工程领域,强化学习的内容很简单,本质就是获取状…

网络编程套接字,Linux下实现echo服务器和客户端

目录 1、一些网络中的名词 1.1 IP地址 1.2 端口号port 1.3 "端口号" 和 "进程ID" 1.4 初始TCP协议 1.5 UDP协议 2、socket编程接口 2.1 socket 常见API 2.2 sockaddr结构 3、简单的网络程序 3.1 udp实现echo服务器和客户端 3.1.1 echo服务器实…

Arrays.copyOf 和System.arraycopy?深拷贝和浅拷贝?

Arrays.copyOf 和 System.arraycopy 1&#xff09;二者有何不同&#xff1f; System.arraycopy()方法 System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length); 需主动创建目标对象dest可定义起始元素&#xff0c;灵活拷贝元素比较重要的一点&…

华为三层交换机与路由器对接上网

华为三层交换机与路由器对接上网

昇腾Ascend TIK自定义算子开发教程(概念版)

一、参考资料 【2023 CANN训练营第一季】Ascend C算子开发入门&#xff08;中&#xff09; 二、重要说明 TIK2编程范式把算子核内的处理程序&#xff0c;分成多个流水任务&#xff0c;任务之间通过队列&#xff08;Queue&#xff09;进行通信和同步&#xff0c;并通过统一的…

目标检测笔记(十三): 使用YOLOv5-7.0版本对图像进行目标检测完整版(从自定义数据集到测试验证的完整流程))

文章目录 一、目标检测介绍二、YOLOv5介绍2.1 和以往版本的区别 三、代码获取3.1 视频代码介绍 四、环境搭建五、数据集准备5.1 数据集转换5.2 数据集验证 六、模型训练七、模型验证八、模型测试九、评价指标 一、目标检测介绍 目标检测&#xff08;Object Detection&#xff…