【Springcloud微服务】MybatisPlus上篇

2024-06-05 01:20

本文主要是介绍【Springcloud微服务】MybatisPlus上篇,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

🔥 本文由 程序喵正在路上 原创,CSDN首发!
💖 系列专栏:Springcloud微服务
🌠 首发时间:2024年6月4日
🦋 欢迎关注🖱点赞👍收藏🌟留言🐾

目录

  • 资料下载
  • 微服务
  • MybatisPlus介绍
  • 快速入门
    • 入门案例
    • 常见注解
    • 常见配置
    • 总结
  • 核心功能
    • 条件构造器
      • 基于QueryWrapper的查询
      • 基于UpdateWrapper的查询
      • LambdaQueryWrapper
    • 自定义SQL
      • 基本用法
      • 多表查询
    • Service接口
      • CRUD
      • 基本用法
      • 基于Restful风格实现接口
      • Lambda
      • 批量新增

资料下载

点击此处进入下载资料

微服务

微服务是一种软件架构风格,它是以专注于单一职责的很多小型项目为基础,组合出复杂的大型应用。

在这里插入图片描述

MybatisPlus介绍

官网:https://baomidou.com/

在这里插入图片描述

在这里插入图片描述

快速入门

入门案例

目标:

  • 学会 MP 的基本用法
  • 体会 MP 的无侵入和方便快捷的特点

需求:基于资料提供的项目,实现下列功能:

  • 新增用户功能
  • 根据 id 查询用户
  • 根据 id 批量查询用户
  • 根据 id 更新用户
  • 根据 id 删除用户

在这里插入图片描述

具体步骤:

  1. 引入 MybatisPlus 的起步依赖

    MyBatisPlus 官方提供了 starter,其中集成了 MybatisMybatisPlus 的所有功能,并且实现了自动装配效果。因此我们可以用 MybatisPlusstarter 代替 Mybatisstarter

    <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version>
    </dependency>
    

    可将 UserMapper 里的方法和 UserMapper.xml 中的 SQL 语句删除,因为我们不再需要了

  2. 自定义 Mapper 继承 BaseMapper 接口

    自定义的 Mapper 需要继承 MybatisPlus 提供的 BaseMapper 接口:

    public interface UserMapper extends BaseMapper<User> {}
    

在这里插入图片描述

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

    在这里插入图片描述

    在这里插入图片描述

  2. 自行测试一下即可

常见注解

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

public class User {private Long id;                //用户idprivate 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 的常见场景:

  1. 成员变量名与数据库字段名不一致
  2. 成员变量名以 is 开头,且是布尔值
  3. 成员变量名与数据库关键字冲突
  4. 成员变量不是数据库字段

例子:

有一个用户表:

在这里插入图片描述

我们定义的实体类:

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,即为 marriedprivate 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;                //用户idprivate 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 使用的基本流程是什么?

  1. 引入起步依赖
  2. 自定义 Mapper 基础 BaseMapper
  3. 在实体类上添加注解声明表信息
  4. application.yml 中根据需要添加配置

核心功能

条件构造器

MyBatisPlus 支持各种复杂的 where 条件,可以满足日常开发的所有需求。在 MyBatisPlus 中,Wrapper 类是构建查询和更新条件的核心工具。

Wrapper 类及其子类:

在这里插入图片描述

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

在这里插入图片描述
AbstractWrapper类:

在这里插入图片描述

QueryWrapper类:

在这里插入图片描述
UpdateWrapper类:

在这里插入图片描述

基于QueryWrapper的查询

需求:

  1. 查询出名字中带 o 的,存款大于等于 1000 元的人的 idusernameinfobalance字段

    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);
    }
    
  2. 更新用户名为 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的查询

  1. 需求:更新 id1,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 的用法类似。

总结

条件构造器的用法:

  • QueryWrapperLambdaQueryWrapper 通常用来构建 selectdeleteupdatewhere 条件部分
  • UpdateWrapperLambdaUpdateWrapper 通常只有在 set 语句比较特殊才使用
  • 尽量使用 LambdaQueryWrapperLambdaUpdateWrapper,避免硬编码

自定义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

基本用法

改写上面那个例子:

  1. 基于 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的自定义方法,直接传递mapperuserMapper.deductBalanceByIds(wrapper, amount);
    }
    
  2. mapper 方法参数中用 Param 注解声明 wrapper 变量名称,必须是 ew

  3. 自定义 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 来实现多表查询的效果。

例如,我们要查询出所有收货地址在北京的并且用户 id1、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 {@Autowiredprivate IUserService userService;@Testvoid 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);}@Testvoid 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: trueopenapi:title: 用户管理接口文档description: "用户管理接口文档"email: zhanghuyi@itcast.cnconcat: 嘻嘻url: https://www.itcast.cnversion: v1.0.0group:default:group-name: defaultapi-rule: packageapi-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为POUser 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.查询用户POList<User> users = userService.queryUsers(query.getName(), query.getStatus(), query.getMinBalance(), query.getMaxBalance());// 2.把PO拷贝到VOreturn 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 万条用户数据,并对以下三种方式作出对比:

  1. 使用普通 for 循环插入
  2. 使用 IService 的批量插入
  3. 配置 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 秒钟,性能极大提升,而且随着数据量的增大效果会越明显。

这篇关于【Springcloud微服务】MybatisPlus上篇的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1031676

相关文章

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Spring Cloud LoadBalancer 负载均衡详解

《SpringCloudLoadBalancer负载均衡详解》本文介绍了如何在SpringCloud中使用SpringCloudLoadBalancer实现客户端负载均衡,并详细讲解了轮询策略和... 目录1. 在 idea 上运行多个服务2. 问题引入3. 负载均衡4. Spring Cloud Load

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

在 Spring Boot 中使用 @Autowired和 @Bean注解的示例详解

《在SpringBoot中使用@Autowired和@Bean注解的示例详解》本文通过一个示例演示了如何在SpringBoot中使用@Autowired和@Bean注解进行依赖注入和Bean... 目录在 Spring Boot 中使用 @Autowired 和 @Bean 注解示例背景1. 定义 Stud

如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解

《如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解》:本文主要介绍如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别的相关资料,描述了如何使用海康威视设备网络SD... 目录前言开发流程问题和解决方案dll库加载不到的问题老旧版本sdk不兼容的问题关键实现流程总结前言作为

SpringBoot中使用 ThreadLocal 进行多线程上下文管理及注意事项小结

《SpringBoot中使用ThreadLocal进行多线程上下文管理及注意事项小结》本文详细介绍了ThreadLocal的原理、使用场景和示例代码,并在SpringBoot中使用ThreadLo... 目录前言技术积累1.什么是 ThreadLocal2. ThreadLocal 的原理2.1 线程隔离2

springboot将lib和jar分离的操作方法

《springboot将lib和jar分离的操作方法》本文介绍了如何通过优化pom.xml配置来减小SpringBoot项目的jar包大小,主要通过使用spring-boot-maven-plugin... 遇到一个问题,就是每次maven package或者maven install后target中的ja

Java中八大包装类举例详解(通俗易懂)

《Java中八大包装类举例详解(通俗易懂)》:本文主要介绍Java中的包装类,包括它们的作用、特点、用途以及如何进行装箱和拆箱,包装类还提供了许多实用方法,如转换、获取基本类型值、比较和类型检测,... 目录一、包装类(Wrapper Class)1、简要介绍2、包装类特点3、包装类用途二、装箱和拆箱1、装

TP-Link PDDNS服将于务6月30日正式停运:用户需转向第三方DDNS服务

《TP-LinkPDDNS服将于务6月30日正式停运:用户需转向第三方DDNS服务》近期,路由器制造巨头普联(TP-Link)在用户群体中引发了一系列重要变动,上个月,公司发出了一则通知,明确要求所... 路由器厂商普联(TP-Link)上个月发布公告要求所有用户必须完成实名认证后才能继续使用普联提供的 D

如何利用Java获取当天的开始和结束时间

《如何利用Java获取当天的开始和结束时间》:本文主要介绍如何使用Java8的LocalDate和LocalDateTime类获取指定日期的开始和结束时间,展示了如何通过这些类进行日期和时间的处... 目录前言1. Java日期时间API概述2. 获取当天的开始和结束时间代码解析运行结果3. 总结前言在J