课程内容
-
MyBatis-Plus简介
-
CRUD接口
-
常用注解
-
条件构造器
-
扩展功能
学习目标
- 能够基于MyBatis-Plus进行单表CRUD操作
- 掌握MyBatis-Plus的注解使用
- 掌握MyBatis-Plus的条件构造器使用
- 掌握id生成策略控制
0. 快速体验
MyBatis-Plus是MyBatis框架的一个增强工具,可以简化持久层代码开发。
接下来,先快速体验一下使用MyBatis-Plus开发持久层,代码是多么的简单。
将课程资料中的mp-demo工程导入IDEA中,如下:
注意:配套的数据库和表结构也需要创建出来,执行下面脚本创建:
CREATE DATABASE mybaitsplus_db DEFAULT CHARACTER SET utf8 ; USE mybaitsplus_db; CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号' , `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名' , `password` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码' , `age` int(3) NOT NULL COMMENT '龄年' , `tel` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '电话' , PRIMARY KEY (`id`) );
直接运行单元测试,验证是否正常执行:
通过执行单元测试方法,可以确认代码是可以正常执行的。
接下来可以对比一下原始的mybatis开发持久层代码和MyBatis-Plus开发持久层代码:
-
原始mybatis开发持久层
-
MyBatis-Plus开发持久层
@Mapper public interface UserMapper extends BaseMapper<User> { }
可以看到,使用MyBatis-Plus开发持久层非常简单,只需要继承BaseMapper就可以了,这是因为框架已经提供了常用的单表CRUD方法,如下:
只需要继承 BaseMapper,就可以实现单表 CRUD 操作,无须自己再编写SQL。
1. MyBatis-Plus简介
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
官网:MyBatis-Plus
MyBatis-Plus特性:
• 无侵入:只做增强不做改变,不会对现有工程产生影响
• 强大的 CRUD 操作:内置通用 Mapper,少量配置即可实现单表CRUD 操作
• 支持 Lambda:编写查询条件无需担心字段写错
• 支持主键自动生成
• 内置分页插件
• ……
开发方式:
• 单独使用 MyBatis-Plus
• 基于 Spring 使用 MyBatis-Plus
• 基于 SpringBoot 使用 MyBatis-Plus(最常用)
maven坐标:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency>
通过maven的依赖传递特性,可以看到MyBatis-Plus底层还是基于mybatis进行数据库操作的:
2. CRUD接口
本节来学习MP框架提供的常用CRUD方法,这些方法都是定义在BaseMapper接口中的。
学习MP,主要就是需要学习BaseMapper接口中的方法,开发过程中会经常使用到这些方法来操作数据库。
2.1 开发环境搭建
①:创建maven工程,并配置相关基础信息
②:配置pom文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.0</version> <relativePath/> </parent> <groupId>com.itheima</groupId> <artifactId>mp-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
③:配置数据源(application.yml)
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC username: root password: root
④:编写 SpringBoot 启动类
package com.itheima; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class MybatisPlusApplication { public static void main(String[] args) { SpringApplication.run(MybatisPlusApplication.class, args); } }
⑤:创建实体类与表结构(类名与表名对应,属性名与字段名对应)
package com.itheima.domain; import lombok.Data; @Data public class User { private Long id; private String name; private String password; private Integer age; private String tel; }
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号' , `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名' , `password` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码' , `age` int(3) NOT NULL COMMENT '龄年' , `tel` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '电话' , PRIMARY KEY (`id`) )
⑥:定义Mapper接口,继承 BaseMapper
package com.itheima.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.itheima.domain.User; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface UserMapper extends BaseMapper<User> { }
2.2 新增操作
新增操作可以调用BaseMapper中提供的insert方法:
/** * 插入一条记录 * * @param entity 实体对象 */ int insert(T entity);
单元测试方法:
/** * 测试Mapper接口的insert方法,可以插入一条数据 */ @Test void testInsert(){ User user = new User(); user.setName("黑马程序员"); user.setPassword("123456"); user.setAge(12); user.setTel("13812345678"); user.setOnline("1"); userMapper.insert(user); }
为了方便观察MP框架执行的数据库操作,可以在application.yml中配置MP的日志,将sql输出到控制台:
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启mp日志,将sql输出到控制台
控制台输出:
2.3 删除操作
删除操作可以调用BaseMapper中提供的deleteById和deleteBatchIds方法:
/** * 根据 ID 删除 * * @param id 主键ID */ int deleteById(Serializable id);
/** * 删除(根据ID 批量删除) * * @param idList 主键ID列表(不能为 null 以及 empty) */ int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
单元测试方法:
/** * 测试Mapper接口的delete方法,可以删除数据 */ @Test void testDelete(){ //根据id删除数据 int i = userMapper.deleteById(1624945956237721601L); System.out.println(i); //根据id批量删除数据 int batchIds = userMapper.deleteBatchIds(Arrays.asList(1625061142848167937L, 1625061824372146177L)); System.out.println(batchIds); }
控制台输出:
2.4 修改操作
修改操作可以调用BaseMapper中提供的updateById方法:
/** * 根据 ID 修改 * * @param entity 实体对象 */ int updateById(@Param(Constants.ENTITY) T entity);
单元测试方法:
/** * 测试Mapper接口的update方法,根据id修改数据 */ @Test void testUpdate(){ User user = new User(); user.setId(2625307405933957121L); user.setName("传智教育"); int i = userMapper.updateById(user); System.out.println(i); }
控制台输出:
2.5 查询操作
查询操作可以调用BaseMapper中提供的如下方法:
/** * 根据 ID 查询 * * @param id 主键ID */ T selectById(Serializable id);
/** * 查询(根据ID 批量查询) * * @param idList 主键ID列表(不能为 null 以及 empty) */ List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
/** * 根据 entity 条件,查询全部记录(并翻页) * * @param page 分页查询条件(可以为 RowBounds.DEFAULT) * @param queryWrapper 实体对象封装操作类(可以为 null) */ <E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
注意:如果要实现分页功能,需要配置MP框架的分页拦截器:
package com.itheima.config; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MPConfiguration { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); //添加分页拦截器,实现分页查询功能 mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return mybatisPlusInterceptor; } }
单元测试方法:
/** * 测试Mapper接口的select方法,可以查询数据 */ @Test void testSelect(){ //根据id查询数据 User user = userMapper.selectById(1625307405933957121L); System.out.println(user); //根据id批量查询数据 List<User> users = userMapper.selectBatchIds(Arrays.asList(1625307405933957121L, 1625310776384380930L)); System.out.println(users); //分页查询条件对象 Page<User> page = new Page<>(1,5); //分页查询 userMapper.selectPage(page,null); System.out.println(page); //总记录数 long total = page.getTotal(); //分页结果数据 List<User> records = page.getRecords(); }
控制台输出:
注:可以通过debug方式观察程序的运行过程。
2.6 问题思考
-
MP 是如何知道要操作 user 表的?
-
如果将 user 表改名为 t_user 还能正常操作吗?
-
如果将 user 表中的 password 字段改名为 pwd,还能正常操作吗?
3. 常用注解
3.1 @TableName注解
通过@TableName注解可以映射实体类和表的对应关系。
-
名称:@TableName
-
类型:类注解
-
位置:模型类上
-
作用:设置当前类对应与数据库表关系
-
范例:
@TableName("t_user") //当前实体类对应的表为t_user public class User { private Long id; }
注:如果类名和表名一致,MP可以自动进行映射,此时 @TableName 注解可以省略
如果类名使用驼峰命名法命名,表名使用对应的下划线分隔命名,MP可以自动进行映射,此时 @TableName 注解可以省略,如下例子:
3.2 @TableField注解
通过@TableField注解可以映射实体类的属性和表字段的对应关系。
-
名称:@TableField
-
类型:属性注解
-
位置:模型类属性上
-
作用:设置当前属性对应的数据库表中的字段关系
-
相关属性:
value:设置数据库表字段名称
exist:设置属性在数据库表字段中是否存在,默认为true
-
范例:
public class User { @TableField(value="pwd") //当前属性对应的字段为pwd private String password; @TableField(exist = false) //当前属性在表中没有对应的字段 private String online; }
如果属性名和字段名一致,MP可以自动进行映射,此时 @TableField 注解可以省略
如果属性名使用驼峰命名法命名,字段名使用对应的下划线分隔命名,MP可以自动进行映射,此时 @TableField 注解可以省略。
3.3 @TableId注解
通过@TableId注解可以映射实体类的属性和表主键字段的对应关系,还可以设置主键的生成策略。
-
名称:@TableId
-
类型:属性注解
-
位置:模型类中用于表示主键的属性上
-
作用:映射类中属性和表中主键对应关系,设置主键的生成策略
-
相关属性:
value:设置数据库主键字段名称,如果属性名和字段名一致,可以省略此属性
type:设置主键属性的生成策略,值参照IdType枚举值
-
范例:
public class User { @TableId(type = IdType.AUTO) //当前id属性和表的主键字段id对应,并且设置主键生成策略为AUTO private Long id; }
主键生成策略,在MP框架的IdType枚举中定义:
主键生成策略:
-
AUTO(0):使用数据库id自增策略控制id生成
-
NONE(1):不设置id生成策略
-
INPUT(2):用户手工输入id
-
ASSIGN_ID(3):雪花算法生成id(可兼容数值型与字符串型)
-
ASSIGN_UUID(4):以UUID生成算法作为id生成策略
雪花算法(SnowFlake):是Twitter公司推出的专门针对分布式ID的解决方案
结构:符号位+时间戳+工作进程位+序列号位,一个64bit的整数,8字节,正好为一个long类型数据
- 为了简化开发,可以在application.yml中配置全局的主键生成策略:
mybatis-plus: global-config: db-config: id-type: assign_id #全局设置主键生成策略
注:配置了全局的主键生成策略,在实体类中就无须再配置主键生成策略了。
4. 条件构造器
4.1 条件构造器介绍
通过条件构造器(Wrapper),可以控制最终生成的 SQL 语句的条件部分,如下:
-
select from table where order by
-
update table set where
-
delete from table where
通过条件构造器,就可以控制上面SQL语句中位置的SQL片段,在项目开发过程中经常使用到。
BaseMapper中的很多方法,都需要条件构造器作为参数,如下:
条件构造器的顶级父类为Wrapper:
条件构造器的继承关系如下:
使用比较多的条件构造器:
-
QueryWrapper
-
LambdaQueryWrapper
-
UpdateWrapper
-
LambdaUpdateWrapper
4.2 QueryWrapper
通过 QueryWrapper 条件构造器,可以控制最终生成的查询、删除类的SQL语句。
SQL结构:
-
select ___ from table where ___ order by ___
-
delete from table where ___
BaseMapper中的如下方法可以传入QueryWrapper对象:
示例代码:
注:使用QueryWrapper设置条件时,是通过字符串指定字段名,如果字段名有误,在编译阶段无法发现错误,在程序运行阶段会抛出异常,如下:
4.3 LambdaQueryWrapper
LambdaQueryWrapper的作用和QueryWrapper相同,都是控制最终生成的查询、删除类的SQL语句。不同点在于语法层面。QueryWrapper是通过字段名来设置条件,LambdaQueryWrapper是通过Lambda语法来设置条件,可以做到在编译期就能够发现错误。
示例代码:
项目开发中建议使用 LambdaQueryWrapper 来代替 QueryWrapper。
4.4 UpdateWrapper
通过 UpdateWrapper 条件构造器,可以控制最终生成的更新类的SQL语句。
SQL结构:
-
update table set ___ where ___
BaseMapper中的如下方法可以传入UpdateWrapper对象:
示例代码:
使用 BaseMapper 的 update 方法设置 set 条件时,既可以通过第一个参数(实体对象)设置,也可以通过第二个参数(UpdateWrapper)设置。
注:使用UpdateWrapper设置条件时,是通过字符串指定字段名,如果字段名有误,在编译阶段无法发现错误,在程序运行阶段会抛出异常。
4.5 LambdaUpdateWrapper
LambdaUpdateWrapper的作用和UpdateWrapper相同,都是控制最终生成的更新类的SQL语句。不同点在于语法层面。UpdateWrapper是通过字段名来设置条件,LambdaUpdateWrapper是通过Lambda语法来设置条件,可以做到在编译期就能够发现错误。
示例代码:
项目开发中建议使用 LambdaUpdateWrapper 来代替 UpdateWrapper。
扩展:在使用条件构造器进行条件构造时,可以使用链式编程风格,如下:
//条件构造器 QueryWrapper<User> queryWrapper = new QueryWrapper<User>(); //链式编程风格构造条件 queryWrapper .select("id", "name") .gt("age", 20) .eq(tel != null, "tel", tel) .like("name", "itcast") .orderByAsc("id");
4.6 问题思考
已经使用了MP,还能够使用 mybatis 进行 SQL 定义吗?
注意:MP只是对 mybatis 框架进行增强,不会改变 mybatis 框架的使用方法。也就是说原来怎么使用 mybatis 的,现在还可以按照原来的方式使用即可,如下:
为了进一步增强 mybatis 的功能,我们还可以在 Mapper 中声明方法时,方法的参数使用MP提供的Wrapper条件构造器对象,例如:
上面Mapper中声明的findByCondition方法,参数为 Wrapper类型,即条件构造器对象。并且通过@Param注解修饰,为其指定了别名为ew,这样就可以在SQL语句中通过 ${ew.customSqlSegment} 来获取到对应的SQL片段,这个SQL片段就是通过当前Wrapper对象解析成的。
注意,customSqlSegment为固定写法,底层会调用Wrapper对象的getCustomSqlSegment方法来获取对应的SQL片段。下面是Wrapper类的部分源码:
在单元测试方法中测试findByCondition方法:
5. 扩展功能
5.1 逻辑删除
删除数据库中的数据,可以通过物理删除,也可以通过逻辑删除。
-
物理删除 指的是直接将数据从数据库中删除,执行的是delete语句
-
逻辑删除 指的是修改数据的某个字段,使其表示为已删除状态,执行的是update语句
如下是逻辑删除的效果,在表中增加deleted字段,标识数据是否被删除(例如:1表示删除,0表示未删除)。
注意:对于重要的、后期可能需要恢复的数据,可以考虑使用逻辑删除
由于在项目开发过程中经常会使用到逻辑删除,所以MP框架已经对逻辑删除提供了实现,我们直接使用即可。
具体使用步骤如下:
第一步:在表中添加逻辑删除字段,用于标识数据的删除状态
第二步:在application.yml中配置逻辑删除相关配置项
第三步:在实体类中加入逻辑删除属性(和表中的逻辑删除字段对应),并加入 @TableLogic 注解
加入逻辑删除后,再次调用BaseMapper的删除方法和查询方法,发出的SQL语句已经发生了变化。
删除数据时发出的SQL为update语句,将deleted字段的值改为1,表示当前数据被删除了。
查询数据时发出的SQL语句自动追加上查询条件 deleted=0,表示查询的数据是未删除的数据。
5.2 MP对于Service层的支持
MP 框架除了可以简化持久层代码开发,还为 Service 层提供了业务接口和实现类,可以简化 Service 层的开发。
MP提供的业务接口:
MP提供的业务层实现类:
使用步骤:
① 业务接口继承 MP 提供的 IService 接口
/** * 用户模块的Service接口 * * @Author itcast * @Create 2023/2/21 **/ public interface UserService extends IService<User> { }
② 业务实现类继承 MP 提供的 ServiceImpl 类
/** * 用户模块的业务实现 * * @Author itcast * @Create 2023/2/21 **/ @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { }
对于单表操作,可以直接调用MP提供的Service中的方法即可。如果MP的Service中提供的方法不能满足要求,可以自己扩展方法,例如:
-
在业务接口中声明方法:
-
在业务实现类中实现方法:
可以看到,在业务实现类中可以调用 ServiceImpl 中的 getBaseMapper 方法获得对应的mapper对象,下面是ServiceImpl的源码:
可以看到,在ServiceImpl类中通过 @AutoWired 注解已经将Mapper对象注入,所以在子类中调用getBaseMapper方法就可以获取到Spring容器中的Mapper对象了。
5.3 代码生成器
MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。
安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 mybatisx 搜索并安装,如下所示:
代码生成器的操作步骤:
① 在IDEA的DataBase窗口中配置数据源
② 通过代码生成器提供的菜单项生成代码
选中表,然后点右键,在弹出的菜单中点击 MybatisX-Generator,此时会弹出如下窗口:
- module path:指定代码生成到哪个模块中
- base-package:指定代码生成的包结构
- ignore table prefix:忽略表名前缀,例如表名为t_user,此时指定当前输入框为t_,则生成的实体类名就是User,否则生成的实体类名为TUser
- ignore field prefix:忽略字段名前缀,例如字段名为f_name,此时指定当前输入框为f_,则生成的属性名就是name,否则生成的属性名为fName
点击Next进入下个窗口:
点击Finish,此时就可以生成代码:
通过代码生成器,就可以根据表结构,反向生成对应的实体类、Mapper、Service等,可以简化开发,提供开发效率。