介绍:

基于mybatis的升级,两者是合作关系效率更高,可以做到引入不对mb起冲突,可以快速实现单表的CRUD。

MP的使用文档:https://baomidou.com

使用 MybatisPlus 的基本步骤:

① 引入 MybatisPlus 依赖,代替 Mybatis 依赖

1
2
3
4
5
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>

② 定义 Mapper 接口并继承 BaseMapper

1
2
3
public interface UserMapper extends BaseMapper<User> {

}

③ 在实体类上添加注解声明表信息

④ 在application.yml中根据需要添加配置

常用注解:

通过扫描实体类基于反射获取实体类信息作为数据库信息

  • 约定大于配置
  • 类名驼峰转下划线作为表名
  • 变量名驼峰转下划线作为字段名

注解:

  • @TableName 指定表名
  • @Tableld 指定主键 还可以指定Type属性:AUTO数据库自增长 INPUT自己输入 ASSIG_ID采用雪花算法生成id一个long类型的20位数据
  • @TableField 指定字段名

使用@TableField的常见场景:

  • 成员变量名与数据库字段名不一致
  • 成员变量名以is开头,且是布尔值
  • 成员变量名与数据库关键字冲突
  • 成员变量不是数据库字段 这时候需要属性名为:exist = bollean

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@TableName("tb_user")
public class User {
@TableId(value = "id", type = IdType.AUTO)
private Long id;

@TableField("username")
private String name;

@TableField("is_married")
private Boolean isMarried;

@TableField("`order`")
private Integer order;

private String address;
}

常用配置

示例:

1
2
3
4
5
6
7
8
9
10
mybatis-plus:
type-aliases-package: com.itheima.mp.domain.po # 别名扫描包
mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,默认值
configuration:
map-underscore-to-camel-case: true # 是否开启下划线和驼峰的映射
cache-enabled: false # 是否开启二级缓存
global-config:
db-config:
id-type: assign_id # id为雪花算法生成
update-strategy: not_null # 更新策略:只更新非空字段

核心功能:

条件构造器:

用途用来构造where语句

总结:

条件构造器的用法:

  • QueryWrapper和LambdaQueryWrapper通常用来构建select、delete、update的where条件部分
  • UpdateWrapper和LambdaUpdateWrapper通常只有在set语句比较特殊才使用
  • 尽量使用LambdaQueryWrapper和LambdaUpdateWrapper,避免硬编码

案例:

基于Querywrapper的更新和查询

更新名为jack的用户余额为200

1
2
3
4
5
6
7
8
9
void testUpdateByQueryWrapper() {
// 1.要更新的数据
User user = new User();
user.setBalance(2000);
// 2.更新的条件
QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "jack");
// 3.执行更新
userMapper.update(user, wrapper);
}

基于Updatewrapper的更新

更新id为1,2,4的用户,余额减少200

1
2
3
4
5
6
7
void testUpdateWrapper() {
List<Long> ids = List.of(1L, 2L, 4L);
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance - 200")
.in("id", ids);
userMapper.update(null, wrapper);
}

基于lambda的更新,(防止硬编码)推荐!

查询id,username,信息,工资,当员工姓名有o并且工资等于1000

1
2
3
4
5
6
7
8
9
10
void testLambdaQueryWrapper() {
// 1.构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.select(User::getId, User::getUsername, User::getInfo, User::getBalance)
.like(User::getUsername, "o")
.ge(User::getBalance, 1000);
// 2.查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}

自定义SQL

用处:

  1. 为了防止sql语句基本写在业务层中导致mapper层无用
  2. 可以利用MP的wrapper来构造复杂的where条件,然后自己定义SQL语句剩下的部分

代码示例:

① 基于 Wrapper 构建 where 条件

1
2
3
4
5
6
List<Long> ids = List.of(1L, 2L, 4L);
int amount = 200;
// 1.构建条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().in(User::getId, ids);
// 2.自定义SQL方法调用
userMapper.updateBalanceByIds(wrapper, amount);

② 在 mapper 方法参数中用 Param 注解声明 wrapper 变量名称,必须是 ew

1
void updateBalanceByIds(@Param("ew") LambdaQueryWrapper<User> wrapper, @Param("amount") int amount);

③ 自定义 SQL,并使用 Wrapper 条件

1
2
3
<update id="updateBalanceByIds">
UPDATE tb_user SET balance = balance - #{amount} ${ew.customSqlSegment}
</update>

Service接口

使用流程:

  • 自定义Service接口继承IService接口
1
2
public interface IUserService
extends IService<User> {}
  • 自定义Service实现类,实现自定义接口并继承ServiceImpl类
1
2
3
4
public class UserServiceImpl
extends ServiceImpl<UserMapper, User>
implements IUserService {
}

IService (Interface)

  • getOne(Wrapper)
  • saveBatch(List)

UserService (Interface)

  • listByName(String)

ServiceImpl (Class)

  • getOne(Wrapper)
  • saveBatch(List)

UserServiceImpl (Class)

  • listByName(String)

关系说明(基于图示箭头)

  1. UserService 继承/扩展了 IService
  2. ServiceImpl 实现了 IService 接口。
  3. UserServiceImpl 继承了 ServiceImpl 类。
  4. UserServiceImpl 实现了 UserService 接口。

实现复杂业务接口

在一些需要先提前校验的时候,我们需要先写好判断条件,再基于mp的CRUD功能进行对数据的修改

IService的Lambd的实现方法

用来构造复杂查询

案例:

需求: 实现一个根据复杂条件查询用户的接口,查询条件如下:

  • name: 用户名关键字,可以为空
  • status: 用户状态,可以为空
  • minBalance: 最小余额,可以为空
  • maxBalance: 最大余额,可以为空
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="queryUsers" resultType="com.itheima.mp.domain.po.User">
SELECT *
FROM tb_user
<where>
<if test="name != null">
AND username LIKE #{name}
</if>
<if test="status != null">
AND `status` = #{status}
</if>
<if test="minBalance != null and maxBalance != null">
AND balance BETWEEN #{minBalance} AND #{maxBalance}
</if>
</where>
</select>

该代码是一个 MyBatis 的动态 SQL 查询语句,用于根据条件动态构建 SELECT 语句。

1
2
3
4
5
6
7
8
public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
return lambdaQuery()
.like(name != null, User::getUsername, name)
.eq(status != null, User::getStatus, status)
.ge(minBalance != null, User::getBalance, minBalance)
.le(maxBalance != null, User::getBalance, maxBalance)
.list();
}

该代码使用了 MyBatis-Plus 的 LambdaQueryWrapper(通过 lambdaQuery() 获取),实现了动态 SQL 查询逻辑,与上一张图片中的 XML 写法功能等价。

批处理:

IService批量新增

需求:批量插入10万条用户数据,并作出对比:

  • 普通for循环插入
  • IService的批量插入
  • 开启rewriteBatchedStatements=true参数

批处理方案:

  • 普通for循环逐条插入速度极差,不推荐
  • MP的批量新增,基于预编译的批处理,性能不错
  • 配置jdbc参数,开rewriteBatchedStatements,性能最好
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void testSaveBatch() {
// 我们每次批量插入1000条件,插入100次即10万条数据
// 1.准备一个容量为1000的集合
List<User> list = new ArrayList<>(1000);
long b = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
// 2.添加一个user
list.add(buildUser(i));
// 3.每1000条批量插入一次
if (i % 1000 == 0) {
userService.saveBatch(list);
// 4.清空集合,准备下一批数据
list.clear();
}
}
}

拓展功能

代码生成器:

用来快速构建一个控制层,业务层,实体类,持久层的开发框架,让用户不需要进行重复性构造框架

DB静态工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public UserVO queryUserAndAddressById(Long id) {
// 1.查询用户
User user = getById(id);
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常!");
}
// 2.查询地址
List<Address> addresses = Db.lambdaQuery(Address.class).eq(Address::getUserId, id).list();
// 3.封装VO
// 3.1.转User的PO为VO
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
// 3.2.转地址VO
if (CollUtil.isNotEmpty(addresses)) {
userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
}
return userVO;
}

该代码展示了如何根据 ID 查询用户及其关联的地址信息,并将实体对象(PO)转换为视图对象(VO)返回。其中使用了 Db.lambdaQuery 进行关联表查询,以及 Hutool 工具包中的 BeanUtilCollUtil 进行对象属性拷贝和集合判空处理。

逻辑删除:

逻辑删除就是基于代码逻辑模拟删除效果,但并不会真正删除数据。思路如下:

  • 在表中添加一个字段标记数据是否被删除
  • 当删除数据时把标记置为 1
  • 查询时只查询标记为 0 的数据

例如逻辑删除字段为 deleted:

  • 删除操作:
1
UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0
  • 查询操作:
1
SELECT * FROM user WHERE deleted = 0

MybatisPlus 提供了逻辑删除功能,无需改变方法调用的方式,而是在底层帮我们自动修改 CRUD 的语句。我们要做的就是在 application.yaml 文件中配置逻辑删除的字段名称和值即可:

1
2
3
4
5
6
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名,字段类型可以是boolean、integer
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

注意 逻辑删除本身也有自己的问题,比如:

  • 会导致数据库表垃圾数据越来越多,影响查询效率
  • SQL 中全都需要对逻辑删除字段做判断,影响查询效率

因此,不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。

枚举处理器:

使用方法:

在 application.yml 中配置全局枚举处理器:

1
2
3
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

配置用于指定 MyBatis-Plus 处理枚举类型的默认策略,这里使用了 MP 自带的 MybatisEnumTypeHandler

插件功能

分页插件

感觉还是pageHelper好用啊!!!!

分页插件

首先,要在配置类中注册 MyBatisPlus 的核心插件,同时添加分页插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class MybatisConfig {

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 1.初始化核心插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 2.添加分页插件
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
pageInterceptor.setMaxLimit(1000L); // 设置分页上限
interceptor.addInnerInterceptor(pageInterceptor);
return interceptor;
}
}

这段代码展示了如何在 Spring Boot 项目中通过 Java 配置类来注册 MyBatis-Plus 的分页拦截器。

接着,就可以使用分页的 API 了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
void testPageQuery() {
// 1.查询
int pageNo = 1, pageSize = 5;
// 1.1.分页参数
Page<User> page = Page.of(pageNo, pageSize);
// 1.2.排序参数,通过OrderItem来指定
page.addOrder(new OrderItem("balance", false));
// 1.3.分页查询
Page<User> p = userService.page(page);
// 2.总条数
System.out.println("total = " + p.getTotal());
// 3.总页数
System.out.println("pages = " + p.getPages());
// 4.分页数据
List<User> records = p.getRecords();
records.forEach(System.out::println);
}

这段代码展示了如何使用 MyBatis-Plus 提供的 Page 对象进行分页查询、设置排序规则以及获取分页结果(如总记录数、总页数和当前页的数据列表)。