找往期文章包括但不限于本期文章中不懂的知识点:
个人主页:我要学编程程(ಥ_ಥ)-CSDN博客
所属专栏:JavaEE
目录
LambdaQueryWrapper、LambdaUpdateWrapper
前言
初始JavaEE篇 —— Mybatis操作数据库(上)-CSDN博客
初始JavaEE篇 —— Mybatis操作数据库(下)-CSDN博客
通过前面的学习,我们已经知道了如何使用 Mybatis 来操作数据库了,相较于 JDBC 来说,Mybatis 的使用更加方便和容易上手,但是与我们今天学习的 Mybatis-plus 来说,Mybatis 操作数据库的方式还是比较繁琐的,因为需要我们自己去写SQL语句来实现具体的操作,而Mybatis-plus对于简单的查询,根本就不需要我们自己写SQL语句了,甚至连XML文件都无需生成了。
Mybatis-plus 快速上手
1、创建项目工程:
File -> New -> Project:
然后选择对应的项目配置:
接着,选择Lombok、MySQL driver依赖即可:
2、在pom文件中,添加Mybatis-plus 所需的依赖,刷新Maven:
SpringBoot 2.x:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.11</version>
</dependency>
SpringBoot 3.x:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.11</version>
</dependency>
3、在yml文件中,添加MySQ数据源配置,以及
# 配置数据库连接
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
4、编写对应的SQL脚本:
-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
-- 使用数据数据
USE mybatis_test;
-- 创建表[用户表]
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (
`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
`username` VARCHAR ( 127 ) NOT NULL,
`password` VARCHAR ( 127 ) NOT NULL,
`age` TINYINT ( 4 ) NOT NULL,
`gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-女 0-默认',
`phone` VARCHAR ( 15 ) DEFAULT NULL,
`delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now() ON UPDATE now(),
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
-- 添加用户信息
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );
5、编写实体类:
@Data
public class UserInfo {
private Integer id;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
6、编写mapper接口:
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
注意:这里只需要继承 BaseMapper<T> 这个接口即可。
7、编写测试方法:
@SpringBootTest
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void testSelectById() {
System.out.println(userInfoMapper.selectById(1));
}
}
8、运行测试方法,观察结果:
从最终结果来看,查询数据成功了,而我们并没有在mapper接口中去编写任何方法以及去实现,但最终却能成功执行,这就是 Mybatis-plus 的强大之处。
接下来,我们测试其他的方法是否可以成功执行。
插入:
删除:
修改:
以上这些基本的增删改查都是可以使用Mybatis-plus提供的方法。
学会了如何简单使用Mybatis-plus之后,我们接下来就需要学习更加复杂的操作。
Mybatis-plus 复杂操作
常用注解
@TableName
首先,我们来想一个问题:Mybatis-plus 是如何知道查询的是哪个表的呢?这就是通过前面继承的BaseMapper<T> 中泛型来决定的。但问题是数据库中的表是 user_info,而实体类却是UserInfo,两者对应的名称是不同的,这怎么找呢?Mybatis-plus 默认通过 驼峰 转 蛇形 的方式,将实体类转换为蛇形字段,从而去对应的数据库中查找,因此,UserInfo 转换后的结果就是 user_info,在数据库中成功找到该表,因此最终执行的操作可以对应上。但问题又来了:在实际开发中,并不是所有的程序员都会遵守 Mybatis-plus 的规定的,万一有人将 UserInfo 写成了 user_info 呢?该怎么办?Mybatis-plus 给我们提供了注解 @TableName,让我们手动指定数据库的表名,继而其会根据表名去对应的数据库中查找,如果找到的话,就会执行相应的操作;反之,则会抛出异常。
使用 @TableName 之前:
使用 @TableName 之后:
注意:在实际开发中,最好还是按照规范来,避免出现一些不可预测的问题。
@TableField
既然表名会出现上述情况,同样字段名称也是会出现上述情况的。(Mybatis-plus 也是按照BaseMapper中的泛型实体类的字段名去数据库对应的数据表中查询对应的字段名称)这里是使用 @TableField 注解来指定查找的字段名。
使用 @TableField 之前:
使用 @TableField 之后:
@TableId
当 数据库中主键名称 和 属性名 对应不上,不能使用 @TableField,而应该使用 @TableId。
如果我们在快速上手时, 去仔细观察 插入方法 对应在 数据库中的结果的话,会有一个奇怪的现象:我们并未指定 id 的值,正常来说是在原始的 id 上进行自增的,而数据库中对应的值,更像是一个自增的随机数。
这需要使用 @TableId 中的 type 属性来解决。我们可以 ctrl + 左键 点击 @TableId,去观察:
当我们修改 @TableId 的 type 的值为 Type.AUTO之后,再去插入数据,观察数据库:
注意:表中的数据是默认是按照 id升序 来排列的,而图中最前面两条数据的 id 是要比1小的,因此我们可以猜测出其是负数(可以去赋值id到画图板上看一下),如果有小伙伴遇到的数是正数的话,那么最终自增的值应该是证书的最大值+1,因为如果插入的数比数据库中原始的数据还要小的话,那么一直自增下去,总有一天,会到达该值,这时就会有冲突,因此,自增的值是从最大值开始自增的。
打印日志
为了方便学习与观察SQL语句的执行结果,我们可以配置SQL语句的执行日志。
# 配置 Mybatis-plus 日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
该日志和我们前面学习的Mybatis的日志只是最开始的前缀不一致,Mybatis 对应的前缀是 Mybatis,而 Mybatis-plus 对应的前缀是 Mybatis-plus。
条件构造器
前面学习了简单的CRUD,但在实际开发中肯定不止这些场景,还有一些复杂查询等操作,同样Mybatis-plus 也是提供了相应的支持。
MyBatis-Plus 提供了一套强大的条件构造器(Wrapper),用于构建复杂的数据库查询条件。Wrapper 类允许开发者以链式调用的方式构造查询条件,无需编写繁琐的 SQL 语句,从而提高开发效率并减少 SQL 注入的风险。
在 MyBatis-Plus 中,Wrapper 类是构建查询和更新条件(这里指的是where子句)的核心工具。以下是主要的 Wrapper 类及其功能:
AbstractWrapper:这是一个抽象基类,提供了所有 Wrapper 类共有的方法和属性。它定义了条件构造的基本逻辑,包括字段(column)、值(value)、操作符(condition)等。所有的 QueryWrapper、UpdateWrapper、LambdaQueryWrapper 和 LambdaUpdateWrapper 都继承自 AbstractWrapper。
QueryWrapper:专门用于构造查询条件,支持基本的等于、不等于、大于、小于等各种常见操作。它允许你以链式调用的方式添加多个查询条件,并且可以组合使用
and
和or
逻辑。UpdateWrapper:用于构造更新条件,可以在更新数据时指定条件。与 QueryWrapper 类似,它也支持链式调用和逻辑组合。使用 UpdateWrapper 可以在不创建实体对象的情况下,直接设置更新字段和条件。
LambdaQueryWrapper:这是一个基于 Lambda 表达式的查询条件构造器,它通过 Lambda 表达式来引用实体类的属性,从而避免了硬编码字段名。这种方式提高了代码的可读性和可维护性,尤其是在字段名可能发生变化的情况下。
LambdaUpdateWrapper:类似于 LambdaQueryWrapper,LambdaUpdateWrapper 是基于 Lambda 表达式的更新条件构造器。它允许你使用 Lambda 表达式来指定更新字段和条件,同样避免了硬编码字段名的问题。
接下来就学习具体如何使用:
QueryWrapper
QueryWrapper 并不只用于查询语句,只要是涉及到了where子句的地方,都可以使用,即删除、修改、查询都能使用。
查询:
SQL:
select id, username, password, age from user_info
where age = 18 and username like '%admin%';
测试代码:
@Test
void testSelect() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
// 构造SQL语句
queryWrapper.select("id", "username", "password", "age")
.eq("age", 18)
.like("username", "%admin%");
// 执行SQL语句
System.out.println(userInfoMapper.selectList(queryWrapper));
}
QueryWrapper的select方法需要传输两个参数,一个是Boolean类型,当其为true时,才会将后面的字段列表加入最终的SQL中,eq方法是equals的简写,用于设置单个字段的相等条件,like方法用于构建模糊查询条件。当SQL语句构建完成之后,最终需要执行,还得通过Mybatis-plus的提供的方法来调用。
注意:select方法、eq方法以及like方法传输的第一个参数都是对应数据库的字段名,而不是实体类的属性名。
更新:
SQL:
update user_info set delete_flag = 1 where age < 20;
测试代码:
@Test
void testUpdate2() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.lt("age", 20);
UserInfo userInfo = new UserInfo();
userInfo.setDeleteFlag(1);
userInfoMapper.update(userInfo, queryWrapper);
}
这里的 lt 是 less than 的缩写,用于设置单个字段的小于条件。le 是 less than or equals to 的缩写,用于设置单个字段的小于等于条件。ge 是 greater than or equals to 的缩写,用于设置单个字段的大于等于条件。gt 是 greater than 的缩写,用于设置单个字段的大于条件。ne 是 not equals 的缩写,用于设置单个字段的不相等条件。
删除:
SQL:
delete from user_info where age = 18;
测试代码:
@Test
void testDelete2() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("age", 18);
userInfoMapper.delete(queryWrapper);
}
UpdateWrapper
对于更新操作,可以直接使用 UpdateWrapper,这样不必创建实体类,从而直接设置更新字段和条件。
基础更新:
SQL:
update user_info set delete_flag = 0, age = 5 where id in (1,2,3);
测试代码:
@Test
void testUpdate3() {
UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.set("delete_flag", 0).set("age", 5).in("id", List.of(1, 2, 3));
userInfoMapper.update(updateWrapper);
}
set 用于设置更新语句中的 SET 字段。通过调用 set
方法,可以指定在更新操作中要修改的字段及其新值。in 用于设置单个字段的 IN 条件,即字段的值在给定的集合中。
基于SQL更新:
update user_info set age = age + 10 where id in (1,2,3);
测试代码:
@Test
void testUpdate4() {
UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.setSql("age = age + 10").in("id", List.of(1, 2, 3));
userInfoMapper.update(updateWrapper);
}
setSql 用于设置更新语句中的 SET 部分 SQL 包含 数据库字段。
注意:如果上述传递的列名错误的话,也会发生badsql异常,可以通过打印的SQL语句观察。
LambdaQueryWrapper、LambdaUpdateWrapper
QueryWrapper和UpdateWrapper存在一个问题:需要写死字段名,如果字段名发生变更,可能会
因为测试不到位酿成事故(因为字段名是String类型)。
LambdaQueryWrapper:这是一个基于 Lambda 表达式的查询条件构造器,它通过 Lambda 表达式来引用实体类的属性,从而避免了硬编码字段名。这种方式提高了代码的可读性和可维护性,尤其是在字段名可能发生变化的情况下。
LambdaUpdateWrapper:类似于 LambdaQueryWrapper,LambdaUpdateWrapper 是基于 Lambda 表达式的更新条件构造器。它允许你使用 Lambda 表达式来指定更新字段和条件,同样避免了硬编码字段名的问题。
还是直接来看代码:
@Test
void testSelect() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
// 构造SQL语句
queryWrapper.select("id", "username", "password", "age")
.eq("age", 18)
.like("username", "%admin%");
// 执行SQL语句
System.out.println(userInfoMapper.selectList(queryWrapper));
}
上面是使用QueryWrapper来编写的查询,而下面是使用LambdaQueryWrapper编写的查询:
@Test
void testSelect2() {
LambdaQueryWrapper<UserInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.select(UserInfo::getId,
UserInfo::getUsername,
UserInfo::getPassword,
UserInfo::getAge)
.eq(UserInfo::getAge, 18)
.like(UserInfo::getUsername, "%admin%");
userInfoMapper.selectList(lambdaQueryWrapper);
}
整体上来看对于字段参数都是采用Lambda表达式的写法来通过实体类获得属性,从而根据属性来推出对应字段名。 同样使用下面的方式来创建LambdaQueryWrapper:
@Test
void testSelect3() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
// 通过调用 Lambda 方法成功获取到了 LambdaQueryWrapper对象
LambdaQueryWrapper < UserInfo > lambda = queryWrapper.lambda();
lambda.select(UserInfo::getId,
UserInfo::getUsername,
UserInfo::getPassword,
UserInfo::getAge)
.eq(UserInfo::getAge, 18)
.like(UserInfo::getUsername, "%admin%");
userInfoMapper.selectList(queryWrapper);
}
同样LambdaUpdateWrapper的写法也是类似的。
@Test
void testUpdate5() {
// 同样可以直接new LambdaUpdateWrapper
// 也可以调用UpdateWrapper的lambda方法
UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.lambda()
.set(UserInfo::getDeleteFlag, 0)
.set(UserInfo::getAge, 10)
.in(UserInfo::getId, Arrays.asList(1, 2, 3));
userInfoMapper.update(updateWrapper);
}
自定义SQL
在实际开发中,Mybatis-plus提供的操作可能并不满足我们的实际需求,Mybatis-plus也提供了自定义SQL的功能,我们可以利用Wrapper构造查询条件,再结合Mapper编写SQL。
注意:Mybatis-plus支持自定义SQL的版本要 3.0.7 及其以上。
SQL:
select id, username, password, age from user_info where username = 'admin';
Mapper:
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
@Select("select id, username, password, age " +
"from user_info ${ew.customSqlSegment}")
// wrapper 拼接的是 where子句
List<UserInfo> selectUserInfo(@Param("ew") Wrapper<UserInfo> wrapper);
// List<UserInfo> selectUserInfo(@Param(Constants.WRAPPER) Wrapper<UserInfo> wrapper);
}
测试代码:
@Test
void selectUserInfo() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.eq(UserInfo::getUsername, "admin");
System.out.println(userInfoMapper.selectUserInfo(queryWrapper));
}
注意:
1、参数命名:在自定义SQL时,传递的Wrapper对象必须指定参数名为 "ew",且需要通过 @Param 注解来手动绑定参数名为 "ew",否则就会抛异常。
即使我们使用 Constants.WRAPPER 本质是还是绑定参数名为 "ew" 。这里的Constants要用maobidou包下的。
2、在SQL语句中,必须要使用 ${ew.customSqlSegment}来引用Wrapper对象生成的SQL片段。因为 Wrapper 对象生成的SQL片段存储在 customSqlSegment 属性中,因此引用它才能正确提取。
3、 虽然这里使用的是 ${},但是Mybatis-plus 对 Wrapper 条件 做了处理,并不会出现SQL注入的风险。
上述是使用注解的方式来自定义SQL,同样我们也可以使用XML的方式来自定义SQL。
在yml中配置XML文件的映射路径:
# 配置XML文件的映射路径
# 同样只是前缀发生了变化,从Mybatis变为了Mybatis-plus
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
Mapper:
List<UserInfo> selectUserInfo2(@Param(Constants.WRAPPER) Wrapper<UserInfo> wrapper);
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">
<!-- namespace 表示的该xml文件实现的接口类的全限定名称 -->
<mapper namespace="com.springboot.mybatisplusdemo.mapper.UserInfoMapper">
<select id="selectUserInfo2" resultType="com.springboot.mybatisplusdemo.model.UserInfo">
<!-- 同样这里只能使用 ${ew.customSqlSegment} -->
select * from user_info ${ew.customSqlSegment}
</select>
</mapper>
测试代码:
@Test
void selectUserInfo2() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.eq(UserInfo::getUsername, "admin");
System.out.println(userInfoMapper.selectUserInfo2(queryWrapper));
}
上述都是简单查询,下面来学习如何编写基于SQL进行更新。
SQL:
update user_info set age = age + 10 where id in (1, 2, 3);
Mapper:
@Update("update user_info set age = #{age} + 10 ${ew.customSqlSegment}")
Integer update(Integer age, @Param(Constants.WRAPPER) Wrapper<UserInfo> wrapper);
测试代码:
@Test
void update() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper
.lambda()
.in(UserInfo::getId, List.of(1, 2, 3));
System.out.println("受影响的行数: " + userInfoMapper.update(10, queryWrapper));
}
Wrapper 只是添加了where子句,至于其他部分还是和Mybatis一样去传参和接收参数。
好啦!本期 初始JavaEE篇 —— Mybatis-plus 操作数据库 的学习之旅 就到此结束啦!我们下一期再一起学习吧!