✏️作者:银河罐头
📋系列专栏:JavaEE
🌲“种一棵树最好的时间是十年前,其次是现在”
目录
- MyBatis 是什么?
- 第⼀个MyBatis查询
- 创建数据库和表
- 添加MyBatis框架支持
- 设置 MyBatis 配置信息
- 添加业务代码
- 查询操作
- 单表查询
- ${paramName} 和 #{paramName}
- sql 注入问题
- like 查询
- 返回字典映射:resultMap
- 多表查询
- ⼀对⼀的表映射
- ⼀对多的表映射
- 单表增、删、改操作
- 修改
- 删除
- 添加
- 复杂情况:动态SQL使用
- if 标签
- trim 标签
- where 标签
- set 标签
- foreach 标签
MyBatis 是什么?
简单来说 MyBatis 是更简单完成程序和数据库交互的⼯具,也就是更简单的操作和读取数据库⼯具。
MyBatis 基于 JDBC.
MyBatis 也是⼀个 ORM 框架,ORM(Object Relational Mapping),即对象关系映射。
数据库表(table)–> 类(class)
记录(record,⾏数据)–> 对象(object)
字段(field) --> 对象的属性(attribute)
⼀般的 ORM 框架,会将数据库模型的每张表都映射为⼀个 Java 类。 也就是说使⽤ MyBatis 可以像操作对象⼀样来操作数据库中的表。
第⼀个MyBatis查询
创建数据库和表
使⽤ MyBatis 的⽅式来读取⽤户表中的所有⽤户,
具体 sql 语句如下:
-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;
-- 使用数据数据
use mycnblog;
-- 创建表[用户表]
drop table if exists userinfo;
create table userinfo(
id int primary key auto_increment,
username varchar(100) not null,
password varchar(32) not null,
photo varchar(500) default '',
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
`state` int default 1
) default charset 'utf8mb4';
-- 创建文章表
drop table if exists articleinfo;
create table articleinfo(
id int primary key auto_increment,
title varchar(100) not null,
content text not null,
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
uid int not null,
rcount int not null default 1,
`state` int default 1
)default charset 'utf8mb4';
-- 创建视频表
drop table if exists videoinfo;
create table videoinfo(
vid int primary key,
`title` varchar(250),
`url` varchar(1000),
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
uid int
)default charset 'utf8mb4';
-- 添加一个用户信息
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);
-- 文章添加测试数据
insert into articleinfo(title,content,uid)
values('Java','Java正文',1);
-- 添加视频
insert into videoinfo(vid,title,url,uid) values(1,'java title','http://www.baidu.com',1);
mysql> select database();//查看当前指令在操纵哪个数据库
+------------+
| database() |
+------------+
| mycnblog |
+------------+
1 row in set (0.00 sec)
添加MyBatis框架支持
新建项目多添加 2 个依赖: MyBatis Framework 和 MySQL Driver.
第一次创建好了 MyBatis 项目之后,启动会报错。
报错原因是还没有说明要连接的数据库 url 是?用户名?密码?。
设置 MyBatis 配置信息
1)设置数据库连接的相关信息:
application.properties 配置文件里设置:
spring.datasource.url= jdbc:mysql://localhost:3306/mycnblog?characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
2)MyBatis xml 文件的保存路径和 xml 的命名模式
#设置 MyBatis
mybatis.mapper-locations=classpath:/mybatis/*Mapper.xml
添加业务代码
1.interface: 让其他层可以注入使用的接口
2.xml: 具体实现 sql ( interface 的"实现")
1)添加实体类
@Data
public class UserEntity {
private Integer id;
private String username;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private Integer state;
}
2)添加 mapper 接口
@Mapper
public interface UserMapper {
List<UserEntity> getAll();
}
3)添加 UserMapper.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.UserMapper">
<select id="getAll" resultType="com.example.demo.mapper.UserMapper">
select * from userinfo
</select>
</mapper>
4)添加 service
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public List<UserEntity> getAll(){
return userMapper.getAll();
}
}
5)添加 controller
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/getall")
public List<UserEntity> getAll(){
return userService.getAll();
}
}
再插入一条数据测试:
mysql> INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES
-> (2, 'zhangsan', 'zhangsan', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);
Query OK, 1 row affected (0.00 sec)
查询操作
单表查询
实现⼀下根据⽤户 id 查询⽤户信息的功能。
@Mapper
public interface UserMapper {
//根据 id 查询某个用户信息
UserEntity getUserById(@Param("id") Integer id);
}
<select id="getUserById" resultType="com.example.demo.entity.UserEntity">
select * from userinfo where id = ${id};
</select>
${}:字符直接替换。是MyBatis 在处理 ${} 时,就是把 ${} 替换成变量的值。
写完这个 getUserById() 这个方法,我想验证这个方法的功能,可以像之前一样在 浏览器地址栏输入 url 返回数据验证(Controller -> Service -> Mapper 层层调用)。除此之外,还可以通过更加简单的"单元测试"来验证功能。
关于单元测试如何使用可以看我另一篇博客。
链接:
- 如果 @Param 里面的参数和形参不一样,能成功查询到吗?
通过单元测试验证,结果报错。
select * from userinfo where id = ${uid};
只需要把 UserMapper.xml 里 ${} 里的 “id” 改成 “uid” 就行了。
${paramName} 和 #{paramName}
- MyBatis 获取动态参数有 2 种方式。
- ${paramName} 直接替换
验证:打印 MyBatis 执行的 sql
#打印 MyBatis 执行的 sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#因为打印 MyBatis 执行的 sql 日志级别是 debug,而默认级别是 info,所以要修改日志的默认级别为 debug
logging.level.com.example.demo=debug
再次运行单元测试,
确实是直接替换。
2.#{paramName} 占位符模式
UserMapper.xml :
<select id="getUserById" resultType="com.example.demo.entity.UserEntity">
select * from userinfo where id = #{uid}
</select>
再次启动单元测试:
预执行。
可以排除 sql 注入的问题。
这里传入的 是 Integer 类型的数据。还看不出 ${paramName} 和 #{paramName} 的区别。
- 下面通过传入 String 类型数据来观察这二者的差别。
@Mapper
public interface UserMapper {
//根据 username 查询某个用户信息
UserEntity getUserByUsername(@Param("username") String username);
}
UserMapper.xml :
<select id="getUserByUsername" resultType="com.example.demo.entity.UserEntity">
select * from userinfo where username = #{username}
</select>
@SpringBootTest
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void getUserByUsername() {
UserEntity user = userMapper.getUserByUsername("zhangsan");
System.out.println(user);
}
}
把 # 改成 $ ,再次运行单元测试,会发生什么?
select * from userinfo where username = ${username}
报错了,“没有找到 zhangsan 这一列”。
观察发现,idea 的 报错信息和 MySQL 报错信息是一样的。
${paramName} 直接替换的方式,就直接换成了 zhangsan , 没有加 ’ ’ , 所以报错。如果传入参数是 int 类型就不会有这种问题。
- 那我手动给它再最外面加上 ’ ',能运行成功吗?
select * from userinfo where username = '${username}'
单元测试通过了。
==这是 ${paramName} 和 #{paramName} 的第一个区别:
${paramName} 是直接替换,而#{paramName} 是 占位符 预处理的。
第二个区别是 sql 注入的问题。==
sql 注入:用了一个不真实的用户名和密码查询到了数据。
@Mapper
public interface UserMapper {
//登录方法
UserEntity login(UserEntity user);//对象传参
}
<select id="login" resultType="com.example.demo.entity.UserEntity">
select * from userinfo where username = '${username}' and password = '${password}'
</select>
@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);
}
把 password 改成 “admin2”, 再次运行,查到的是 空 user.
sql 注入问题
- ${} sql 注入问题
String password = "' or 1='1";
把 password 改成这个,再次运行:
在 UserMapper.xml 中:
把 ${} 改成 #{}
select * from userinfo where username=#{username} and password=#{password}
${} 把 “’ or 1='1” 理解成是 sql 指令,不安全有 sql 注入问题。
#{} 是预执行占位符,不管是多么奇怪的像 “’ or 1='1” 这样都理解成是 字符串。
所以 表里只有一条数据时,select * from userinfo where username = ‘ u s e r n a m e ′ a n d p a s s w o r d = ′ {username}' and password = ' username′andpassword=′{password}’ 可以查询到(sql 注入)。
这样看来,那么 ${} 就一无是处了吗?
不是!
- 以排序举例:
List<UserEntity> getAllByIdOrder(@Param("ord") String ord);
<select id="getAllByIdOrder" resultType="com.example.demo.entity.UserEntity">
select * from userinfo order by id ${ord};
</select>
@Test
void getAllByIdOrder() {
List<UserEntity> list = userMapper.getAllByIdOrder("desc");
System.out.println(list.size());
}
单元测试通过。
把 $ 换成 #:
select * from userinfo order by id #{ord};
再次运行 单元测试:
#{} 把 sql 语句解析成:
select * from userinfo order by id 'desc';
所以报错!
使⽤ ${sort} 可以实现排序查询,⽽使⽤ #{sort} 就不能实现排序查询了,因为当使⽤ #{sort} 查询时, 如果传递的值为 String 则会加单引号,就会导致 sql 错误。
小结:因为 ${} 存在 sql 注入的 问题,所以要慎用,如果是排序这种特殊的场景适合用 ${}, 但是要保证你传入的参数是可以被枚举的,比如{asc, desc}.
like 查询
//根据用户名进行模糊查询
List<UserEntity> getListByName(@Param("username") String username);
<select id="getListByName" resultType="com.example.demo.entity.UserEntity">
select * from userinfo where username like '%#{username}%'
</select>
@Test
void getListByName() {
String username = "zhang";
List<UserEntity> list = userMapper.getListByName(username);
list.stream().forEach(System.out::println);
}
执行单元测试,报错!!
原因是,#{} 进行占位符替换会把 zhangsan 这个 字符串最外层加引号 ’ ’
select * from userinfo where name like '%#{username}%'
#{}=>
select * from userinfo where name like '%'zhangsan'%'
可以考虑使⽤ mysql 的内置函数 concat() 来处理。
mysql> select concat('%','zhang','%');
+-------------------------+
| concat('%','zhang','%') |
+-------------------------+
| %zhang% |
+-------------------------+
1 row in set (0.00 sec)
mysql> select concat('%','zhang','%','xxx','ddd');
+-------------------------------------+
| concat('%','zhang','%','xxx','ddd') |
+-------------------------------------+
| %zhang%xxxddd |
+-------------------------------------+
1 row in set (0.00 sec)
修改 UserMapper.xml :
<select id="getListByName" resultType="com.example.demo.entity.UserEntity">
select * from userinfo where username like concat('%',#{username},'%')
</select>
单元测试通过。
“%张” 和 “%张%” ,索引会失效;
“张%”,索引不会失效。
总结:%开头的索引就会失效。
返回字典映射:resultMap
resultMap 使⽤场景:
-
字段名称和程序中的属性名不同的情况,可使⽤ resultMap 配置映射;
-
⼀对⼀和⼀对多关系可以使⽤ resultMap 映射并查询数据。
举例:
如果我 实体类里写的是 pwd 字段而不是 数据库里的 password,
private String pwd;
此时 再次执行 getListByName() 单元测试
pwd = null, 数据库表里已经查到 一条数据,但是和 实例类 的 pwd 字段没有成功映射。
<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>
<result property="updatetime" column="updatetime"></result>
</resultMap>
再次运行 getListByName() 单元测试:
- 如果我不想使用 resultMap 这种方式,还有别的办法实现 password -> pwd 的映射吗?
查询数据的时候 把 password 重命名为 pwd.
<select id="getListByName" resultType="com.example.demo.entity.UserEntity">
select id, username, password as pwd from userinfo where username like concat('%',#{username},'%')
</select>
多表查询
⼀对⼀的表映射
一篇文章对应一个作者。
举例:实现 文章表详情页的查询。
显示文章详情页时,要显示作者姓名,但是文章表里只有 uid,没有 username 这个字段,而userinfo 表里才有 username 这个字段。
所以要通过多表联查。
VO(View Object):视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。 VO,就是一切给 View 提供数据的对象。
@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;
}
@Data
public class ArticleInfoVO extends ArticleInfo {
private String username;
}
@Mapper
public interface ArticleMapper {
//查询文章详情
ArticleInfoVO getDetail(@Param("id") Integer id);
}
<?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
private ArticleMapper articleMapper;
@Test
void getDetail() {
ArticleInfoVO articleInfoVO = articleMapper.getDetail(1);
System.out.println(articleInfoVO);
}
}
没有打印父类 ArticleInfo 的属性。
查看 class 文件,发现是注解 lombok.Data 的问题。
我们可以 自己重写 toString() 方法把父类 ArticleInfo 的属性也给打印了。
@Data
public class ArticleInfoVO extends ArticleInfo {
private String username;
@Override
public String toString() {
return "ArticleInfoVO{" +
"username='" + username + '\'' +
"} " + super.toString();
}
}
自己写了 toString() 之后,lombok 写的 toString() 就无效了。
⼀对多的表映射
⼀个⽤户对应多篇⽂章案例:
@Mapper
public interface ArticleMapper {
List<ArticleInfoVO> getListByUid(@Param("uid") Integer uid);
}
<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);//多线程
}
单表增、删、改操作
修改
//修改密码
int updatePassword(@Param("id")Integer id,
@Param("password") String password,
@Param("newPassword") String newPassword);
<update id="updatePassword">
<!-- select 有 2 个参数, 而 update 只有 id 这一个参数-->
update userinfo set password = #{newPassword}
where id = #{id} and password = #{password}
</update>
@Test
void updatePassword() {
int result = userMapper.updatePassword(1,"admin","123456");
System.out.println("修改: " + result);
}
单元测试通过。
mysql> select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime | updatetime | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
| 1 | admin | admin | | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 | 1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
1 row in set (0.00 sec)
mysql> select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime | updatetime | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
| 1 | admin | 123456 | | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 | 1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
1 row in set (0.00 sec)
数据库里也确实把 password 修改了。
之前说过 单元测试有一个 优点是 “不污染数据库”,不过这里为了验证修改功能污染了数据库!
可以通过加注解 @Transactional 的方式来达到"不污染数据库"的效果。
@Transactional//事务
@Test
void updatePassword() {
int result = userMapper.updatePassword(1,"123456","abcdefg");
System.out.println("修改: " + result);
}
执行单元测试之初会开启一个事务,执行完毕会执行 rollback 回滚操作,所以就没有污染数据库。
mysql> select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime | updatetime | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
| 1 | admin | 123456 | | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 | 1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
1 row in set (0.00 sec)
能够成功修改密码,同时数据库密码没有被修改。
删除
//删除用户
int deleteById(@Param("id") Integer id);
<delete id="deleteById">
delete from userinfo where id = #{id}
</delete>
@Transactional
@Test
void deleteById() {
int result = userMapper.deleteById(1);
System.out.println("删除: " + result);
}
添加
- 返回影响行数
//添加用户
int addUser(UserEntity user);
<insert id="addUser">
insert into userinfo(username,password) values(#{username}, #{password});
</insert>
@Test
void addUser() {
UserEntity user = new UserEntity();
user.setUsername("zhangsan");
user.setPassword("123456");
int result = userMapper.addUser(user);
System.out.println("添加: " + result);
}
mysql> select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime | updatetime | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
| 1 | admin | 123456 | | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 | 1 |
| 2 | zhangsan | 123456 | | 2023-05-29 20:10:14 | 2023-05-29 20:10:14 | 1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
2 rows in set (0.00 sec)
这样实现了能看到影响的行数。
如果我想要看到 新添加 的用户的 id?
- 返回影响行数和 id
int addUserGetId(UserEntity user);
<insert id="addUserGetId" useGeneratedKeys="true" keyProperty="id">
<!-- 是否要生成 key 属性-->
insert into userinfo(username,password) values(#{username}, #{password})
</insert>
@Test
void addUserGetId() {
UserEntity user = new UserEntity();
user.setUsername("lisi");
user.setPassword("123456");
int result = userMapper.addUserGetId(user);
System.out.println("添加结果: " + result);
System.out.println("id: " + user.getId());
}
mysql> select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime | updatetime | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
| 1 | admin | 123456 | | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 | 1 |
| 2 | zhangsan | 123456 | | 2023-05-29 20:10:14 | 2023-05-29 20:10:14 | 1 |
| 3 | lisi | 123456 | | 2023-05-29 20:27:53 | 2023-05-29 20:27:53 | 1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
3 rows in set (0.00 sec)
复杂情况:动态SQL使用
if 标签
动态 sql 是Mybatis的强大特性之⼀,能够完成不同条件下不同的 sql 拼接。
//`state` int(11) DEFAULT '1',
mysql> insert into userinfo(username, password, state) values('wangwu','123456',null);
Query OK, 1 row affected (0.00 sec)
mysql> insert into userinfo(username, password) values('wangwu2','123456');
Query OK, 1 row affected (0.00 sec)
mysql> select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime | updatetime | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
| 1 | admin | 123456 | | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 | 1 |
| 2 | zhangsan | 123456 | | 2023-05-29 20:10:14 | 2023-05-29 20:10:14 | 1 |
| 3 | lisi | 123456 | | 2023-05-29 20:27:53 | 2023-05-29 20:27:53 | 1 |
| 4 | wangwu | 123456 | | 2023-05-30 14:43:23 | 2023-05-30 14:43:23 | NULL |
| 5 | wangwu2 | 123456 | | 2023-05-30 14:44:30 | 2023-05-30 14:44:30 | 1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
5 rows in set (0.00 sec)
如果在添加⽤户的时候有不确定的字段传⼊,程序应该 如何实现呢?
比如 userinfo 表里的 photo 字段,用户可能输入也可能不输入。
这个时候就需要使⽤动态标签来判断了. 如添加的时候性别 photo 为⾮必填字段.
@Mapper
public interface UserMapper {
int addUser2(UserEntity user);
}
<insert id="addUser2">
insert into userinfo(username,password
<if test="photo != null and photo != ''">
,photo
</if>
) values(#{username}, #{pwd}
<if test="photo != null and photo != ''">
,#{photo}
</if>
)
</insert>
传 username, password 2 个参数:
@Transactional
@Test
void addUser2() {
String username = "liliu";
String password = "123456";
UserEntity user = new UserEntity();
user.setUsername(username);
user.setPwd(password);
int result = userMapper.addUser2(user);
System.out.println("添加: " + result);
}
传 username, password, photo 3 个参数:
@Transactional
@Test
void addUser2() {
String username = "liliu";
String password = "123456";
UserEntity user = new UserEntity();
user.setUsername(username);
user.setPwd(password);
user.setPhoto("cat.png");
int result = userMapper.addUser2(user);
System.out.println("添加: " + result);
}
注意 test 中的 photo,是传入对象中的属性,不是数据库字段。
- 为了证明这一点,把 UserEntity 里的属性 photo 改成 img:
@Data
public class UserEntity {
private Integer id;
private String username;
private String pwd;
private String img;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private Integer state;
}
<insert id="addUser2">
insert into userinfo(username,password
<if test="img != null and img != ''">
,photo
</if>
) values(#{username}, #{pwd}
<if test="img != null and img != ''">
,#{img}
</if>
)
</insert>
@Transactional
@Test
void addUser2() {
String username = "liliu";
String password = "123456";
UserEntity user = new UserEntity();
user.setUsername(username);
user.setPwd(password);
user.setImg("cat.png");
int result = userMapper.addUser2(user);
System.out.println("添加: " + result);
}
传 2 个参数和 传 3 个参数 都能通过单元测试。
trim 标签
如果所有字段都是⾮必填项,就考虑使⽤ 标签结合标签,对多个字段都采取动态⽣成的⽅式。
多个 组织的语句都以 , 结尾,在最后拼接好的字符串还会以 , 结尾,会基于 suffixOverrides 配置去掉最后⼀个 ,
int addUser3(UserEntity user);
<insert id="addUser3">
insert into userinfo
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username != null and username != ''">
username,
</if>
<if test="pwd != null and pwd != ''">
password,
</if>
<if test="img != null and img != ''">
photo
</if>
</trim>
<trim prefix="values(" suffix=")" suffixOverrides=",">
<if test="username != null and username != ''">
#{username},
</if>
<if test="pwd != null and pwd != ''">
#{pwd},
</if>
<if test="img != null and img != ''">
#{img},
</if>
</trim>
</insert>
@Test
void addUser3() {
String username = "liliu";
String password = "123456";
UserEntity user = new UserEntity();
user.setUsername(username);
user.setPwd(password);
int result = userMapper.addUser3(user);
System.out.println("添加: " + result);
}
mysql> select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime | updatetime | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
| 1 | admin | 123456 | | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 | 1 |
| 2 | zhangsan | 123456 | | 2023-05-29 20:10:14 | 2023-05-29 20:10:14 | 1 |
| 3 | lisi | 123456 | | 2023-05-29 20:27:53 | 2023-05-29 20:27:53 | 1 |
| 4 | wangwu | 123456 | | 2023-05-30 14:43:23 | 2023-05-30 14:43:23 | NULL |
| 5 | wangwu2 | 123456 | | 2023-05-30 14:44:30 | 2023-05-30 14:44:30 | 1 |
| 13 | liliu | 123456 | | 2023-05-30 15:57:37 | 2023-05-30 15:57:37 | 1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
6 rows in set (0.00 sec)
(MySQL 5.7 版本) 事务回滚会导致自增主键 不连续
where 标签
根据 id 或 title 查询文章
List<ArticleInfoVO> getListByIdOrTitle(@Param("id") Integer id, @Param("title") String title);
通过 if 标签和 trim 标签这样写:
<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>
但是这样写的话,如果 id 和 title 2 个参数都不传的话,这个 sql 语句就会报错。
MyBatis 中多个非必传参数的解决方案:
1.可以加个条件"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>
2.trim prefix=“where”
<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 标签中有代码,才会有前缀 prefix 和后缀。
id 和 title 都不传:
@Test
void getListByIdOrTitle() {
List<ArticleInfoVO> list = articleMapper.getListByIdOrTitle(null,null);
System.out.println(list.size());
}
传 id = 1:
@Test
void getListByIdOrTitle() {
List<ArticleInfoVO> list = articleMapper.getListByIdOrTitle(1,null);
System.out.println(list.size());
}
trim 标签的这 2 种解决方案都可行,不过有点麻烦。
3.where 标签
<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>
id 和 title 都不传:
@Test
void getListByIdOrTitle() {
List<ArticleInfoVO> list = articleMapper.getListByIdOrTitle(null,null);
System.out.println(list.size());
}
传 title like “s”:
@Test
void getListByIdOrTitle() {
List<ArticleInfoVO> list = articleMapper.getListByIdOrTitle(null,"s");
System.out.println(list.size());
}
以上 where 标签也可以使⽤ trim prefix = “where” prefixOverrides = "and"替换。
where 标签自动帮你去除掉最前面的 and 关键字,但是 where 标签不会帮你去除掉后面的 and 关键字。
<select id="getListByIdOrTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">
select * from articleinfo
<where>
<if test="id != null and id > 0">
id = #{id} and
</if>
<if test="title != null and title != ''">
title like concat('%',#{title},'%')
</if>
</where>
</select>
只传 id 参数:
@Test
void getListByIdOrTitle() {
List<ArticleInfoVO> list = articleMapper.getListByIdOrTitle(1,null);
System.out.println(list.size());
}
果然报错!
set 标签
int updateById(UserEntity user);
<update id="updateById" parameterType="com.example.demo.entity.UserEntity">
update userinfo
<set>
<if test="username">
username = #{username},
</if>
<if test="pwd">
password = #{pwd},
</if>
</set>
where id = #{id}
</update>
@Test
void updateById() {
String username = "zhaoliu";
String pwd = "zhaoliu";
UserEntity user = new UserEntity();
user.setUsername(username);
user.setPwd(pwd);
user.setId(5);
int result = userMapper.updateById(user);
System.out.println(result);
}
但是要求至少有一个参数不为 null.
mysql> update userinfo where id = 1;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'where id = 1' at line 1
如果所有参数都是 null, 就会报错.
foreach 标签
对集合进⾏遍历时可以使⽤该标签。
标签有如下属性:
collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象
item:遍历时的每⼀个对象
open:语句块开头的字符串
close:语句块结束的字符串
separator:每次遍历之间间隔的字符串
举例:根据多个文章 id 来删除文章。
int deleteByIds(List<Integer> ids);
<delete id="deleteByIds">
delete from articleinfo where id in
<foreach collection="list" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</delete>
@Transactional
@Test
void deleteByIds() {
List<Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3);
int result = articleMapper.deleteByIds(list);
System.out.println(result);
}