🔥 本文由 程序喵正在路上 原创,CSDN首发!
💖 系列专栏:Springcloud微服务
🌠 首发时间:2024年6月4日
🦋 欢迎关注🖱点赞👍收藏🌟留言🐾
目录
资料下载
微服务
微服务是一种软件架构风格,它是以专注于单一职责的很多小型项目为基础,组合出复杂的大型应用。

MybatisPlus介绍


快速入门
入门案例
目标:
- 学会 MP 的基本用法
- 体会 MP 的无侵入和方便快捷的特点
需求:基于资料提供的项目,实现下列功能:
- 新增用户功能
- 根据 id 查询用户
- 根据 id 批量查询用户
- 根据 id 更新用户
- 根据 id 删除用户

具体步骤:
-
引入 MybatisPlus 的起步依赖
MyBatisPlus 官方提供了 starter,其中集成了 Mybatis 和 MybatisPlus 的所有功能,并且实现了自动装配效果。因此我们可以用 MybatisPlus 的 starter 代替 Mybatis 的 starter:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency>可将 UserMapper 里的方法和 UserMapper.xml 中的 SQL 语句删除,因为我们不再需要了
-
自定义 Mapper 继承 BaseMapper 接口
自定义的 Mapper 需要继承 MybatisPlus 提供的 BaseMapper 接口:
public interface UserMapper extends BaseMapper<User> { }

-
将测试类中调用的方法改为 MybatisPlus 自带的方法:


-
自行测试一下即可
常见注解
MyBatisPlus 通过扫描实体类,并基于反射获取实体类信息作为数据库表信息。
public class User {
private Long id; //用户id
private String username; //用户名
private String password; //密码
private String phone; //注册手机号
private String info; //详细信息
private Integer status; //使用状态(1正常 2冻结)
private Integer balance; //账户余额
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
MybatisPlus 是如何获取实现 CRUD 的数据库表信息的?
-
默认以类名驼峰转下划线作为表名
-
默认将名为 id 的字段作为主键
-
默认将变量名驼峰转下划线作为表的字段名

只要我们定义的类符合规则,MP 就能自动获取到对应的数据库表信息,从而帮我们生成 SQL 语句。如果不一样,我们就需要通过注解来指定。
MybatisPlus 中比较常用的几个注解如下:
@TableName:用来指定表名@TableId:用来指定表中的主键字段信息@TableField:用来指定表中的普通字段信息
IdType枚举:
AUTO:数据库自增长INPUT:通过set方法自行输入ASSIGN_ID:分配 ID,接口IdentifierGenerator的方法nextId来生成id,默认实现类为DefaultIdentifierGenerator雪花算法
使用 @TableField 的常见场景:
- 成员变量名与数据库字段名不一致
- 成员变量名以
is开头,且是布尔值 - 成员变量名与数据库关键字冲突
- 成员变量不是数据库字段
例子:
有一个用户表:

我们定义的实体类:
public class User {
private Long id;
private String name;
private Boolean isMarried;
private Integer order;
private String address;
}
加上注解后:
@TableName("tb_user")
public class User {
@TableId(value = "id", type = IdType.AUTO) //自增
private Long id;
@TableField("username") //不一致
private String name;
@TableField("is_married") //遇到布尔型且以 is开头的变量,MP会自动去掉 is,即为 married
private Boolean isMarried;
@TableField("'order'") //和 sql关键字一样
private Integer order;
@TableField(exist = false) //表中没有该字段
private String address;
}
将我们项目中定义的实体类修改一下:
public class User {
@TableId(type = IdType.AUTO) //不指定的话,默认为随机生成id,也就是第三种方式
private Long id; //用户id
private String username; //用户名
private String password; //密码
private String phone; //注册手机号
private String info; //详细信息
private Integer status; //使用状态(1正常 2冻结)
private Integer balance; //账户余额
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
常见配置
MyBatisPlus 的配置项继承了 MyBatis 原生配置和一些自己特有的配置。例如:
mybatis-plus:
type-aliases-package: com.itheima.mp.domain.po # 别名扫描包
mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,默认值
configuration:
map-upderscore-to-camel-case: true # 是否开启下换线和驼峰的映射
cache-enabled: false # 是否开启二级缓存
global-config:
db-config:
id-type: assign_id # id为雪花算法生成
update-strategy: not_null # 更新策略:只更新非空字段
别看上面代码这么多,其实除了第一个,其他都是默认的,可以不用配置,除非你要自己设置为别的值。至于其他的配置,需要用到的时候,可以百度搜索或者参考官方文档:使用配置 | MyBatis-Plus。
总结
MyBatisPlus 使用的基本流程是什么?
- 引入起步依赖
- 自定义
Mapper基础BaseMapper - 在实体类上添加注解声明表信息
- 在
application.yml中根据需要添加配置
核心功能
条件构造器
MyBatisPlus 支持各种复杂的 where 条件,可以满足日常开发的所有需求。在 MyBatisPlus 中,Wrapper 类是构建查询和更新条件的核心工具。
Wrapper 类及其子类:

BaseMapper 中很多方法都是支持用 Wrapper 对象作为参数的:

AbstractWrapper类:

QueryWrapper类:

UpdateWrapper类:

基于QueryWrapper的查询
需求:
-
查询出名字中带 o 的,存款大于等于 1000 元的人的 id、username、info、balance字段
sql 语句这样写:
select id,username,info,balance from user where username like ? and balance >= ?QueryWrapper 这样写:
@Test void testQueryWrapper() { //构建查询条件 QueryWrapper<User> wrapper = new QueryWrapper<User>() .select("id", "username", "info", "balance") .like("username", "o") .ge("balance", 1000); //查询 userMapper.selectList(wrapper); } -
更新用户名为 jack 的用户的余额为 2000
sql 语句这样写:
update user set balance = 2000 where (username = "jack")QueryWrapper 这样写:
@Test void testUpdateByQueryWrapper() { //准备要更新的数据 User user = new User(); user.setBalance(2000); //更新的条件 QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "jack"); //执行更新 userMapper.update(user, wrapper); }
基于UpdateWrapper的查询
-
需求:更新 id 为 1,2,4 的用户的余额,扣 200
sql 语句这样写:
update user set balance = balance - 200 where id in (1, 2, 4)UpdateWrapper 这样写:
@Test 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); }
LambdaQueryWrapper
一个基于 Lambda 表达式的查询条件构造器,它通过 Lambda 表达式来引用实体类的属性,从而避免了硬编码字段名。这种方式提高了代码的可读性和可维护性,尤其是在字段名可能发生变化的情况下。
我们用 LambdaQueryWrapper 改进 QueryWrapper 的第一个需求的代码,如下:
@Test
void testLambdaQueryWrapper() {
//构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.select(User::getId, User::getUsername, User::getInfo, User::getBalance)
.like(User::getUsername, "o")
.ge(User::getBalance, 1000);
//查询
userMapper.selectList(wrapper);
}
LambdaUpdateWrapper 的用法类似。
总结
条件构造器的用法:
QueryWrapper和LambdaQueryWrapper通常用来构建select、delete、update的where条件部分UpdateWrapper和LambdaUpdateWrapper通常只有在set语句比较特殊才使用- 尽量使用
LambdaQueryWrapper和LambdaUpdateWrapper,避免硬编码
自定义SQL
在演示 UpdateWrapper 的案例中,我们在代码中编写了更新的 SQL 语句。
@Test
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);
}
这种写法在某些企业也是不允许的,因为 SQL 语句最好都维护在持久层,而不是业务层。就当前案例来说,由于条件是 in 语句,只能将 SQL 写在 Mapper.xml 文件,利用 foreach 来生成动态 SQL,但是这实在是太麻烦了。假如查询条件更复杂,动态 SQL 的编写也会更加复杂。
所以,MybatisPlus 提供了自定义 SQL 功能,可以让我们利用 Wrapper 生成查询条件部分,再结合我们自己在 Mapper.xml 编写 SQL。
基本用法
改写上面那个例子:
-
基于
Wrapper构建where条件@Test void testCustomWrapper() { //准备查询条件 List<Long> ids = List.of(1L, 2L, 4L); LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().in(User::getId, ids); int amount = 200; //调用mapper的自定义方法,直接传递mapper userMapper.deductBalanceByIds(wrapper, amount); } -
在
mapper方法参数中用Param注解声明wrapper变量名称,必须是ew -
自定义
SQL,并使用Wrapper条件,注解或者xml文件皆可@Mapper public interface UserMapper extends BaseMapper<User> { @Update("update user set balance = balance - #{amount} ${ew.customSqlSegment}") void deductBalanceByIds(@Param("ew") LambdaQueryWrapper<User> wrapper, @Param("amount") int amount); }
多表查询
理论上来讲 MyBatisPlus 是不支持多表查询的,不过我们可以利用 Wrapper 中自定义条件结合自定义 SQL 来实现多表查询的效果。
例如,我们要查询出所有收货地址在北京的并且用户 id 在 1、2、4 之中的用户。
@Test
void testCustomJoinWrapper() {
//准备查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.in("u.id", List.of(1L, 2L, 4L))
.eq("a.city", "北京");
//调用mapper的自定义方法
List<User> users = userMapper.queryUserByWrapper(wrapper);
users.forEach(System.out::println);
}
@Select("select u.* from user u inner join address a on u.id = a.user_id ${ew.customSqlSegment}")
List<User> queryUserByWrapper(@Param("ew")QueryWrapper<User> wrapper);

Service接口
MybatisPlus 不仅提供了 BaseMapper,还提供了通用的 Service 接口及默认实现,封装了一些常用的 service 模板方法。
通用接口为 IService,默认实现为 ServiceImpl,其中封装的方法可以分为以下几类:
- save:新增
- remove:删除
- update:更新
- get:查询单个结果
- list:查询集合结果
- count:计数
- page:分页查询
CRUD
新增:

save是新增单个元素saveBatch是批量新增saveOrUpdate是根据 id 判断,如果数据存在就更新,不存在则新增saveOrUpdateBatch是批量的新增或修改
删除:

removeById:根据 id 删除removeByIds:根据 id 批量删除removeByMap:根据 Map 中的键值对为条件删除remove(Wrapper<T>):根据 Wrapper 条件删除
修改:

updateById:根据id修改update(Wrapper<T>):根据UpdateWrapper修改,Wrapper中包含set和where部分update(T,Wrapper<T>):按照T内的数据修改与Wrapper匹配到的数据updateBatchById:根据id批量修改
查询:

getById:根据id查询1条数据getOne(Wrapper<T>):根据Wrapper查询1条数据getBaseMapper:获取Service内的BaseMapper实现,某些时候需要直接调用Mapper内的自定义SQL时可以用这个方法获取到Mapper
批量查询:

listByIds:根据id批量查询list(Wrapper<T>):根据Wrapper条件查询多条数据list():查询所有
计数:

count():统计所有数量count(Wrapper<T>):统计符合Wrapper条件的数据数量
getBaseMapper:当我们在 service 中要调用 Mapper 中的自定义 SQL 时,就必须获取 service 对应的 Mapper,就可以通过这个方法。
基本用法
由于 Service 中经常需要定义与业务有关的自定义方法,因此我们不能直接使用 IService,而是自定义 Service 接口,然后继承 IService 以拓展方法。同时,让自定义的 Service 实现类继承 ServiceImpl,这样就不用自己实现 IService 中的接口了。

首先,定义 IUserService,继承 IService:
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;
public interface IUserService extends IService<User> {
}
然后,编写 UserServiceImpl 类,继承 ServiceImpl,实现 UserService:
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.mapper.UserMapper;
import com.itheima.mp.service.IUserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}
项目结构如下:

双击选中 IUserService 右键选择 Generate → \rightarrow → Test → \rightarrow → Ok 生成测试类:

调用 IService 的一些方法,测试是否成功:
import com.itheima.mp.domain.po.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.List;
@SpringBootTest
class IUserServiceTest {
@Autowired
private IUserService userService;
@Test
void testSaveUser() {
User user = new User();
user.setId(5L);
user.setUsername("Lucy");
user.setPassword("123");
user.setPhone("18688990011");
user.setBalance(200);
user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
userService.save(user);
}
@Test
void testQuery() {
List<User> users = userService.listByIds(List.of(1L, 2L, 4L));
users.forEach(System.out::println);
}
}

基于Restful风格实现接口
需求:基于 Restful 风格实现下面的接口:

首先,我们在项目中引入几个依赖:
<!--swagger-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.1.0</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
然后需要配置 swagger 信息:
knife4j:
enable: true
openapi:
title: 用户管理接口文档
description: "用户管理接口文档"
email: zhanghuyi@itcast.cn
concat: 嘻嘻
url: https://www.itcast.cn
version: v1.0.0
group:
default:
group-name: default
api-rule: package
api-rule-resources:
- com.itheima.mp.controller
然后,接口需要两个实体:
- UserFormDTO:代表新增时的用户表单
- UserVO:代表查询的返回结果
首先是 UserFormDTO:
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "用户表单实体")
public class UserFormDTO {
@ApiModelProperty("id")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("密码")
private String password;
@ApiModelProperty("注册手机号")
private String phone;
@ApiModelProperty("详细信息,JSON风格")
private String info;
@ApiModelProperty("账户余额")
private Integer balance;
}
然后是 UserVO:
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {
@ApiModelProperty("用户id")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("详细信息")
private String info;
@ApiModelProperty("使用状态(1正常 2冻结)")
private Integer status;
@ApiModelProperty("账户余额")
private Integer balance;
}
项目结构如下:

最后,按照 Restful 风格编写 Controller 接口方法:
import cn.hutool.core.bean.BeanUtil;
import com.itheima.mp.domain.dto.UserFormDTO;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.vo.UserVO;
import com.itheima.mp.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/users")
@Api(tags = "用户管理接口")
@RequiredArgsConstructor //注入对象所需
public class UserController {
//spring不推荐使用@Autoweird这种方式注入对象,而是推荐使用构造器的方式
private final IUserService userService;
/**
* 新增用户
*
* @param userFormDTO
*/
@PostMapping
@ApiOperation("新增用户接口")
public void saveUser(@RequestBody UserFormDTO userFormDTO) {
//转换DTO为PO
User user = BeanUtil.copyProperties(userFormDTO, User.class);
userService.save(user);
}
/**
* 删除用户
*
* @param userId
*/
@DeleteMapping("/{id}")
@ApiOperation("删除用户接口")
public void deleteById(@ApiParam("用户id") @PathVariable("id") Long userId) {
userService.removeById(userId);
}
/**
* 根据id查询用户
*
* @param userId
* @return
*/
@GetMapping("/{id}")
@ApiOperation("根据id查询用户")
public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long userId) {
//查询
User user = userService.getById(userId);
return BeanUtil.copyProperties(user, UserVO.class);
}
/**
* 根据id集合查询用户
*
* @param ids
* @return
*/
@GetMapping
@ApiOperation("根据id集合查询用户")
public List<UserVO> queryUserByIds(@RequestParam("ids") List<Long> ids) {
List<User> users = userService.listByIds(ids);
return BeanUtil.copyToList(users, UserVO.class);
}
}
可以看到上述接口都直接在 controller 即可实现,无需编写任何 service 代码,非常方便。
不过,一些带有业务逻辑的接口则需要在 service 中自定义实现了,最后一个接口就是。
根据 id 扣减用户余额,这看起来是个简单的修改功能,只要修改用户余额即可。其实这个业务包含一些业务逻辑处理:
- 判断用户状态是否正常
- 判断用户余额是否充足
这些业务逻辑都要在 service 层来做,另外更新余额需要自定义 SQL,要在 mapper 中来实现。因此,我们除了要编写 controller 以外,具体的业务还要在 service 和 mapper 中编写,我们工作中遇到的接口也是很复杂的。
首先在 UserController 中定义一个方法:
/**
* 根据id扣减用户余额
*
* @param id
* @param money
*/
@PutMapping("/{id}/deduction/{money}")
@ApiOperation("扣减用户余额接口")
public void deductBalance(
@ApiParam("用户id") @PathVariable("id") Long id,
@ApiParam("扣减的金额") @PathVariable("money") Integer money
) {
userService.deductBalance(id, money);
}
然后是 UserService 接口:
/**
* 根据id扣减用户余额
*
* @param id
* @param money
*/
void deductBalance(Long id, Integer money);
UserServiceImpl 实现类:
/**
* 根据id扣减用户余额
*
* @param id
* @param money
*/
public void deductBalance(Long id, Integer money) {
// 1.查询用户
User user = getById(id);
// 2.校验用户状态
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常!");
}
// 3.校验余额是否充足
if (user.getBalance() < money) {
throw new RuntimeException("用户余额不足!");
}
// 4.扣减余额
baseMapper.deductBalance(id, money); //继承的ServiceImpl已经注入了mapper,直接使用即可
}
最后是 mapper:
@Update("update user set balance = balance - #{money} where id = #{id}")
void deductBalance(@Param("id") Long id, @Param("money") Integer money);
启动服务,浏览器访问 http://localhost:8080/doc.html 接口文档,自行测试一下。
Lambda
IService 中还提供了 Lambda 功能来简化我们的复杂查询及更新功能。我们通过两个案例来学习一下。
案例一:实现一个根据复杂条件查询用户的接口,查询条件如下:
- name:用户名关键字,可以为空
- status:用户状态,可以为空
- minBalance:最小余额,可以为空
- maxBalance:最大余额,可以为空
可以理解成一个用户的后台管理界面,管理员可以自己选择条件来筛选用户,因此上述条件不一定存在,需要做判断。
我们首先需要定义一个查询条件实体,用来接口前端传过来的数据,UserQuery 实体:
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery {
@ApiModelProperty("用户名关键字")
private String name;
@ApiModelProperty("用户状态:1-正常,2-冻结")
private Integer status;
@ApiModelProperty("余额最小值")
private Integer minBalance;
@ApiModelProperty("余额最大值")
private Integer maxBalance;
}

在 UserController 中定义一个 controller 方法:
/**
* 根据复杂条件查询用户
*
* @param query
* @return
*/
@GetMapping("/list")
@ApiOperation("根据复杂条件查询用户接口")
public List<UserVO> queryUsers(UserQuery query) {
// 1.查询用户PO
List<User> users = userService.queryUsers(query.getName(), query.getStatus(), query.getMinBalance(), query.getMaxBalance());
// 2.把PO拷贝到VO
return BeanUtil.copyToList(users, UserVO.class);
}
在 IUserService 接口中定义方法:
/**
* 根据复杂条件查询用户
*
* @param name
* @param status
* @param minBalance
* @param maxBalance
* @return
*/
List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance);
在 UserServiceImpl 中实现方法:
Service 中对 LambdaQueryWrapper 和 LambdaUpdateWrapper 的用法进一步做了简化。我们无需自己通过 new 的方式来创建 Wrapper,而是直接调用 lambdaQuery 和 lambdaUpdate 方法即可实现。
/**
* 根据复杂条件查询用户
*
* @param name
* @param status
* @param minBalance
* @param maxBalance
* @return
*/
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();
}
可以发现 lambdaQuery 方法中除了可以构建条件,还需要在链式编程的最后添加一个 list(),这是在告诉 MP 我们的调用结果需要是一个 list 集合。这里不仅可以用 list(),可选的方法有:
.one():最多1个结果.list():返回集合结果.count():返回计数结果
MybatisPlus 会根据链式编程的最后一个方法来判断最终的返回结果。
测试一下:

与 lambdaQuery 方法类似,IService 中的 lambdaUpdate 方法可以非常方便的实现复杂更新业务。
案例二:改造根据 id 修改用户余额的接口,要求如下
- 如果扣减后余额为 0,则将用户 status 修改为冻结状态(2)
/**
* 根据id扣减用户余额
*
* @param id
* @param money
*/
@Transactional
public void deductBalance(Long id, Integer money) {
// 1.查询用户
User user = getById(id);
// 2.校验用户状态
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常!");
}
// 3.校验余额是否充足
if (user.getBalance() < money) {
throw new RuntimeException("用户余额不足!");
}
// 4.扣减余额
int remainBalance = user.getBalance() - money;
lambdaUpdate()
.set(User::getBalance, remainBalance)
.set(remainBalance == 0, User::getStatus, 2)
.eq(User::getId, id)
.eq(User::getBalance, user.getBalance()) //乐观锁,防止到更新这一步前,有另外的进程也同时在请求扣减用于余额导致余额不对
.update();
}
批量新增
需求:如果我们往数据库中批量插入 10 万条用户数据,并对以下三种方式作出对比:
- 使用普通 for 循环插入
- 使用 IService 的批量插入
- 配置 MySQL 的 rewriteBatchedStatements=true 参数
方式一:
@Test
void testSaveOneByOne() {
long b = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
userService.save(buildUser(i));
}
long e = System.currentTimeMillis();
System.out.println("耗时:" + (e - b));
}
private User buildUser(int i) {
User user = new User();
user.setUsername("user_" + i);
user.setPassword("123");
user.setPhone("" + (18688190000L + i));
user.setBalance(2000);
user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(user.getCreateTime());
return user;
}

耗时 3 分钟多,可以看到速度非常慢。
方式二:
@Test
void testSaveBatch() {
// 准备10万条数据
List<User> list = new ArrayList<>(1000);
long b = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
list.add(buildUser(i));
// 每1000条批量插入一次
if (i % 1000 == 0) {
userService.saveBatch(list);
list.clear();
}
}
long e = System.currentTimeMillis();
System.out.println("耗时:" + (e - b));
}
将前面插入的数据删除后,我们进行方式二的测试:

耗时 31 秒,性能还可以。
方式三:
MySQL 的客户端连接参数中有这样的一个参数:rewriteBatchedStatements。顾名思义,就是重写批处理的 statement 语句。参考文档:https://dev.mysql.com/doc/connector-j/en/connector-j-connp-props-performance-extensions.html

配置该参数后,可以将 MybatisPlus 批处理的 SQL 语言合并成一句,从而大大提升性能。
在配置文件中,数据库的 url 后面加上 &rewriteBatchedStatements=true 即可:


现在,使用 MybatisPlus 的批处理只耗时 8 秒钟,性能极大提升,而且随着数据量的增大效果会越明显。
2165

被折叠的 条评论
为什么被折叠?



