【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

相关文章

Java五子棋之坐标校正

上篇针对了Java项目中的解构思维,在这篇内容中我们不妨从整体项目中拆解拿出一个非常重要的五子棋逻辑实现:坐标校正,我们如何使漫无目的鼠标点击变得有序化和可控化呢? 目录 一、从鼠标监听到获取坐标 1.MouseListener和MouseAdapter 2.mousePressed方法 二、坐标校正的具体实现方法 1.关于fillOval方法 2.坐标获取 3.坐标转换 4.坐

Spring Cloud:构建分布式系统的利器

引言 在当今的云计算和微服务架构时代,构建高效、可靠的分布式系统成为软件开发的重要任务。Spring Cloud 提供了一套完整的解决方案,帮助开发者快速构建分布式系统中的一些常见模式(例如配置管理、服务发现、断路器等)。本文将探讨 Spring Cloud 的定义、核心组件、应用场景以及未来的发展趋势。 什么是 Spring Cloud Spring Cloud 是一个基于 Spring

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

java8的新特性之一(Java Lambda表达式)

1:Java8的新特性 Lambda 表达式: 允许以更简洁的方式表示匿名函数(或称为闭包)。可以将Lambda表达式作为参数传递给方法或赋值给函数式接口类型的变量。 Stream API: 提供了一种处理集合数据的流式处理方式,支持函数式编程风格。 允许以声明性方式处理数据集合(如List、Set等)。提供了一系列操作,如map、filter、reduce等,以支持复杂的查询和转

Java面试八股之怎么通过Java程序判断JVM是32位还是64位

怎么通过Java程序判断JVM是32位还是64位 可以通过Java程序内部检查系统属性来判断当前运行的JVM是32位还是64位。以下是一个简单的方法: public class JvmBitCheck {public static void main(String[] args) {String arch = System.getProperty("os.arch");String dataM

详细分析Springmvc中的@ModelAttribute基本知识(附Demo)

目录 前言1. 注解用法1.1 方法参数1.2 方法1.3 类 2. 注解场景2.1 表单参数2.2 AJAX请求2.3 文件上传 3. 实战4. 总结 前言 将请求参数绑定到模型对象上,或者在请求处理之前添加模型属性 可以在方法参数、方法或者类上使用 一般适用这几种场景: 表单处理:通过 @ModelAttribute 将表单数据绑定到模型对象上预处理逻辑:在请求处理之前

eclipse运行springboot项目,找不到主类

解决办法尝试了很多种,下载sts压缩包行不通。最后解决办法如图: help--->Eclipse Marketplace--->Popular--->找到Spring Tools 3---->Installed。

JAVA读取MongoDB中的二进制图片并显示在页面上

1:Jsp页面: <td><img src="${ctx}/mongoImg/show"></td> 2:xml配置: <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001

Java面试题:通过实例说明内连接、左外连接和右外连接的区别

在 SQL 中,连接(JOIN)用于在多个表之间组合行。最常用的连接类型是内连接(INNER JOIN)、左外连接(LEFT OUTER JOIN)和右外连接(RIGHT OUTER JOIN)。它们的主要区别在于它们如何处理表之间的匹配和不匹配行。下面是每种连接的详细说明和示例。 表示例 假设有两个表:Customers 和 Orders。 Customers CustomerIDCus

22.手绘Spring DI运行时序图

1.依赖注入发生的时间 当Spring loC容器完成了 Bean定义资源的定位、载入和解析注册以后,loC容器中已经管理类Bean 定义的相关数据,但是此时loC容器还没有对所管理的Bean进行依赖注入,依赖注入在以下两种情况 发生: 、用户第一次调用getBean()方法时,loC容器触发依赖注入。 、当用户在配置文件中将<bean>元素配置了 lazy-init二false属性,即让