MyBatis-Plus 实战教程二 核心功能(二)https://developer.aliyun.com/article/1391860
可以看到上述接口都直接在controller即可实现,无需编写任何service代码,非常方便。
不过,一些带有业务逻辑的接口则需要在service中自定义实现了。例如下面的需求:
- 根据id扣减用户余额
这看起来是个简单修改功能,只要修改用户余额即可。但这个业务包含一些业务逻辑处理:
- 判断用户状态是否正常
- 判断用户余额是否充足
这些业务逻辑都要在service层来做,另外更新余额需要自定义SQL,要在mapper中来实现。因此,我们除了要编写controller以外,具体的业务还要在service和mapper中编写。
首先在UserController中定义一个方法:
@PutMapping("{id}/deduction/{money}") @ApiOperation("扣减用户余额") public void deductBalance(@PathVariable("id") Long id, @PathVariable("money")Integer money){ userService.deductBalance(id, money); }
然后是UserService接口:
package com.onenewcode.mpdemo.service; import com.baomidou.mybatisplus.extension.service.IService; import com.onenewcode.mpdemo.domain.po.User; public interface UserService extends IService<User> { void deductBalance(Long id, Integer money); }
最后是UserServiceImpl实现类:
package com.onenewcode.mpdemo.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.onenewcode.mpdemo.domain.po.User; import com.onenewcode.mpdemo.mapper.UserMapper; import com.onenewcode.mpdemo.service.IUserService; import org.springframework.stereotype.Service; @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Override 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.deductMoneyById(id, money); } }
最后是mapper:
@Update("UPDATE user SET balance = balance - #{money} WHERE id = #{id}") void deductMoneyById(@Param("id") Long id, @Param("money") Integer money);
Lambda
IService中还提供了Lambda功能来简化我们的复杂查询及更新功能。我们通过两个案例来学习一下。
案例一:实现一个根据复杂条件查询用户的接口,查询条件如下:
- name:用户名关键字,可以为空
- status:用户状态,可以为空
- minBalance:最小余额,可以为空
- maxBalance:最大余额,可以为空
可以理解成一个用户的后台管理界面,管理员可以自己选择条件来筛选用户,因此上述条件不一定存在,需要做判断。
我们首先需要定义一个查询条件实体,UserQuery实体:
package com.onenewcode.mpdemo.domain.query; 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方法:
我们无需自己通过new的方式来创建Wrapper,而是直接调用lambdaQuery和lambdaUpdate方法:
基于Lambda查询:
@GetMapping("/list") @ApiOperation("根据id集合查询用户") public List<UserVO> queryUsers(UserQuery query){ // 1.组织条件 String username = query.getName(); Integer status = query.getStatus(); Integer minBalance = query.getMinBalance(); Integer maxBalance = query.getMaxBalance(); // 2.查询用户 List<User> users = userService.lambdaQuery() .like(username != null, User::getUsername, username) .eq(status != null, User::getStatus, status) .ge(minBalance != null, User::getBalance, minBalance) .le(maxBalance != null, User::getBalance, maxBalance) .list(); // 3.处理vo return BeanUtil.copyToList(users, UserVO.class); }
可以发现lambdaQuery方法中除了可以构建条件,还需要在链式编程的最后添加一个list(),这是在告诉MP我们的调用结果需要是一个list集合。这里不仅可以用list(),可选的方法有:
- .one():最多1个结果
- .list():返回集合结果
- .count():返回计数结果
MybatisPlus会根据链式编程的最后一个方法来判断最终的返回结果。
与lambdaQuery方法类似,IService中的lambdaUpdate方法可以非常方便的实现复杂更新业务。
例如下面的需求:
需求:改造根据id修改用户余额的接口,要求如下
- 如果扣减后余额为0,则将用户status修改为冻结状态(2)
也就是说我们在扣减用户余额时,需要对用户剩余余额做出判断,如果发现剩余余额为0,则应该将status修改为2,这就是说update语句的set部分是动态的。
实现如下:
@Override @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.扣减余额 update tb_user set balance = balance - ? int remainBalance = user.getBalance() - money; lambdaUpdate() .set(User::getBalance, remainBalance) // 更新余额 .set(remainBalance == 0, User::getStatus, 2) // 动态判断,是否更新status .eq(User::getId, id) .eq(User::getBalance, user.getBalance()) // 乐观锁 .update(); }
批量新增
IService中的批量新增功能使用起来非常方便,但有一点注意事项,我们先来测试一下。
首先我们测试逐条插入数据:
@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; }
执行结果如下:
可以看到速度非常慢。
然后再试试MybatisPlus的批处理:
@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)); }
仓库地址
https://github.com/onenewcode/mp-demo.git