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

课程安排:

需求:基于课前资料提供的项目,实现下列功能:
(1)导入项目到IDEA

(2)执行SQL脚本
①启动SQL服务

②打开SQL命令行窗口,复制粘贴资料里的SQL脚本,执行

③在IDEA中打开数据库表


这里记得把项目中application.yaml中的数据库连接密码也改成自己的

(3)安装插件 - MyBatisx

点击左侧的小鸟可以快速切换到XML文档

使用步骤:
1. 引入MyBatisPlus的起步依赖 - pom.xml
MyBatisPlus官方提供了starter,其中集成了MyBatis和MyBatisPlus的所有功能,并且实现了自动装配效果。因此,我们可以使用MyBatisPlus的starter代替MyBatis的starter:
- <dependency>
- <groupId>com.baomidougroupId>
- <artifactId>mybatis-plus-boot-starterartifactId>
- <version>3.5.3.1version>
- dependency>
2. 定义Mapper - userMapper.java
自定义的Mapper继承MyBatisPlus提供的BaseMapper,注意要指定泛形
- package com.itheima.mp.mapper;
-
- import com.baomidou.mybatisplus.core.mapper.BaseMapper;
- import com.itheima.mp.domain.po.User;
- import org.apache.ibatis.annotations.Mapper;
-
-
-
- @Mapper // 不要忘记加上@Mapper注解
- public interface UserMapper extends BaseMapper
{ - // ... ...
- }

如果在测试时遇到下面的问题,把pom.xml中的lombok依赖版本改为1.18.30(因为JDK22对应的lombok版本为1.18.30)
java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field 'com.sun.tools.javac.tree.JCTree qualid'
- <dependency>
- <groupId>org.projectlombokgroupId>
- <artifactId>lombokartifactId>
- <version>1.18.30version>
- dependency>
3. 删除src/main/resources/mapper/UserMapper.xml里的内容如下

4. 把UserMapperTest.java改成如下,亲测全部测试通过
- package com.itheima.mp.mapper;
-
- 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 UserMapperTest {
-
- @Autowired
- private UserMapper userMapper;
-
- @Test
- void testInsert() {
- 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());
- userMapper.insert(user);
- }
-
- @Test
- void testSelectById() {
- User user = userMapper.selectById(5L);
- System.out.println("user = " + user);
- }
-
-
- @Test
- void testQueryByIds() {
- List
users = userMapper.selectBatchIds(List.of(1L, 2L, 3L, 4L)); - users.forEach(System.out::println);
- }
-
- @Test
- void testUpdateById() {
- User user = new User();
- user.setId(5L);
- user.setBalance(20000);
- userMapper.updateById(user);
- }
-
- @Test
- void testDeleteUser() {
- userMapper.deleteById(5L);
- }
- }
MyBatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库信息。

MyBatisPlus中比较常用的几个注解如下:

MyBatisPlus的配置项继承了MyBatis原生配置和一些自己特有的配置。例如:
①修改application.yaml
- spring:
- datasource:
- url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
- driver-class-name: com.mysql.cj.jdbc.Driver
- username: root
- password: 123456
- logging:
- level:
- com.itheima: debug
- pattern:
- dateformat: HH:mm:ss
- mybatis-plus:
- type-aliases-package: com.itheima.mp.domain.po
- global-config:
- db-config:
- id-type: auto
MyBatisPlus支持各种复杂的where条件,可以满足日常开发的所有需求。

案例1:基于QueryWrapper的查询
需求:
①查询出名字中带o的,存款大于等于1000元的人的id、username、info、balance字段
- SELECT id, username, info, balance
- FROM user
- WHERE username LIKE ? > AND balance >= ?
- // 基于QueryWrapper
- @Test
- void testQueryWrapper() {
- // 1. 构建查询条件
- QueryWrapper
wrapper = new QueryWrapper() - .select("id", "username", "info", "balance")
- .like("username", "o")
- .ge("balance", 1000);
- List
users = userMapper.selectList(wrapper); - users.forEach(System.out::println);
- }
- // 基于LambdaQueryWrapper
- @Test
- void testLambdaQueryWrapper() {
- // 1. 构建查询条件
- LambdaQueryWrapper
wrapper = new LambdaQueryWrapper() - .select(User::getId, User::getUsername, User::getInfo, User::getBalance)
- .like(User::getUsername, "o")
- .ge(User::getBalance, 1000);
- List
users = userMapper.selectList(wrapper); - users.forEach(System.out::println);
- }
②更新用户名为jack的用户的余额为2000
- UPDATE user
- SET balance = 2000
- WHERE username = "jack"
- @Test
- void testUpdateByQueryWrapper() {
- // 1. 要更新的数据
- User user = new User();
- user.setBalance(2000);
- // 2. 更新的条件
- QueryWrapper
wrapper = new QueryWrapper().eq("username", "jack"); - // 3. 执行更新
- userMapper.update(user, wrapper);
- }
案例2:基于UpdateWrapper的更新
需求:更新id为1,2,4的用户余额,扣200
- UPDATE user
- SET balance = balance - 200
- WHERE id in (1, 2, 4)
- @Test
- void testUpdateWrapper() {
- List
ids = List.of(1L, 2L, 4L); - UpdateWrapper
wrapper = new UpdateWrapper() - .setSql("balance = balance - 200")
- .in("id", ids);
- userMapper.update(null, wrapper);
- }
总结
条件构造器的用法:
我们可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分。
案例1:自定义SQL
需求:将id在指定范围内的用户(如1、2、4)的余额扣减指定值

①基于Wrapper构建where条件

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

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



使用示例:
①自定义service接口继承 IService接口
IUserService.java
- package com.itheima.mp.service;
-
- import com.baomidou.mybatisplus.extension.service.IService;
- import com.itheima.mp.domain.po.User;
-
- public interface IUserService extends IService
{ - }
②自定义Service实现类,实现自定义接口并继承 ServiceImpl类
UserServiceImpl.java
- package com.itheima.mp.service.impl;
-
- 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
implements IUserService { - }
IUserServiceTest.java
- package com.itheima.mp.service;
-
- 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;
-
- import static org.junit.jupiter.api.Assertions.*;
-
- @SpringBootTest
- class IUserServiceTest {
- @Autowired
- private IUserService userService;
-
- // 新增
- @Test
- void testSaveUser() {
- User user = new User();
- user.setUsername("Lily");
- user.setPassword("123");
- user.setPhone("18358995031");
- user.setBalance(20000);
- user.setInfo("{\"age\": 24, \"intro\": \"物理老师\", \"gender\": \"female\"}");
- user.setCreateTime(LocalDateTime.now());
- user.setUpdateTime(LocalDateTime.now());
- userService.save(user);
- }
-
- // 查询
- @Test
- void testQuery() {
- List
users = userService.listByIds((List.of(1L, 2L, 4L))); - users.forEach(System.out::println);
- }
-
- }
案例1:基于Restful风格实现下列接口
需求:基于Restful风格实现下面的接口:

文档地址:Docs
①在项目中引入依赖 - pom.xml
- <dependency>
- <groupId>com.github.xiaoymingroupId>
- <artifactId>knife4j-openapi2-spring-boot-starterartifactId>
- <version>4.1.0version>
- dependency>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-webartifactId>
- dependency>
②配置swagger信息 - application.yaml
- 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
③UserController.java
- package com.itheima.mp.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;
-
- @Api(tags = "用户管理接口")
- @RequestMapping("/users")
- @RestController
- @RequiredArgsConstructor
- public class UserController {
-
- private final IUserService userService;
-
- @ApiOperation("新增用户接口")
- @PostMapping
- public void saveUser(@RequestBody UserFormDTO userDTO) {
- // 1. 把DTO拷贝到PO(hutool)
- User user = BeanUtil.copyProperties(userDTO, User.class);
- // 新增
- userService.save(user);
- }
-
- @ApiOperation("删除用户接口")
- @DeleteMapping("{id}")
- public void deleteUserById(@ApiParam("用户id") @PathVariable("id") Long id) {
- userService.removeById(id);
- }
-
- @ApiOperation("根据id查询用户接口")
- @GetMapping("{id}")
- public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id) {
- // 1. 查询用户PO
- User user = userService.getById(id);
- // 2. 把PO拷贝到VO
- return BeanUtil.copyProperties(user, UserVO.class);
- }
-
- @ApiOperation("根据id批量查询用户接口")
- @GetMapping
- public List
queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List ids) { - // 1. 查询用户PO
- List
users = userService.listByIds(ids); - // 2. 把PO拷贝到VO
- return BeanUtil.copyToList(users, UserVO.class);
- }
-
- @ApiOperation("扣减用户余额接口")
- @PutMapping("/{id}/deduction/{money}")
- public void deductBalance(
- @ApiParam("用户id") @PathVariable("id") Long id,
- @ApiParam("扣减的金额") @PathVariable("money") Integer money) {
- userService.deductBalance(id, money);
- }
- }
④IUserService.java
- package com.itheima.mp.service;
-
- import com.baomidou.mybatisplus.extension.service.IService;
- import com.itheima.mp.domain.po.User;
-
- public interface IUserService extends IService
{ - void deductBalance(Long id, Integer money);
- }
⑤UserServiceImpl.java
- package com.itheima.mp.service.impl;
-
- 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
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.deductBalance(id, money);
- }
- }
⑥UserMapper.java
- package com.itheima.mp.mapper;
-
- import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
- import com.baomidou.mybatisplus.core.mapper.BaseMapper;
- import com.itheima.mp.domain.po.User;
- import org.apache.ibatis.annotations.Mapper;
- import org.apache.ibatis.annotations.Param;
- import org.apache.ibatis.annotations.Update;
-
- import java.util.List;
-
- @Mapper // 不要忘记加上@Mapper注解
- public interface UserMapper extends BaseMapper
{ -
- @Update("UPDATE tb_user SET balance = balance - #{money} WHERE id = #{id}")
- void deductBalance(@Param("ew") Long id, @Param("money") Integer money);
- }
http://localhost:8080/doc.html#/home

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

UserController.java
- import com.itheima.mp.query.UserQuery;
-
- // ... ...
- @ApiOperation("根据复杂条件查询用户接口")
- @GetMapping("/list")
- public List
queryUsers(UserQuery query) { - // 1. 查询用户PO
- List
users = userService.queryUsers( - query.getName(), query.getStatus(), query.getMinBalance(), query.getMaxBalance());
- // 2. 把PO拷贝到VO
- return BeanUtil.copyToList(users, UserVO.class);
- }
UserQuery.java
- package com.itheima.mp.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;
- }
IUserService.java
- package com.itheima.mp.service;
-
- import com.baomidou.mybatisplus.extension.service.IService;
- import com.itheima.mp.domain.po.User;
-
- import java.util.List;
-
- public interface IUserService extends IService
{ - List
queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance); - }
UserServiceImpl.java
- @Override
- public List
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();
-
- }
案例2:IService的Lambda更新
需求:改造根据id修改用户余额的接口,要求如下
①完成对用户状态校验
②完成对用户余额校验
③如果扣减后余额为0,则将用户status修改为冻结状态
UserServiceImpl.java
- @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. 扣减余额
- // baseMapper.deductBalance(id, money);
- 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();
- }
案例3:IService批量新增
需求:批量插入1万条用户数据,并作出对比:
- 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(LocalDateTime.now());
- return user;
- }
-
- @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));
- }
-
- @Test
- void testSaveBatch() {
- // 我们每次批量插入1000条件,插入100次,即10万条数据
- // 1. 准备一个容量为1000的集合
- List
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();
- }
- }
- long e = System.currentTimeMillis();
- System.out.println("耗时:" + (e - b));
- }
测试完记得删除测试数据

开启rewriteBatchedStatements=true参数 - application.yaml
- spring:
- datasource:
- url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
- driver-class-name: com.mysql.cj.jdbc.Driver
- username: root
- password: 123456


MyBatisPlus插件装不了的直接进官网安装:MyBatisPlus - IntelliJ IDEs Plugin | Marketplace
①测试连接

②代码生成

注:在AddressServiceImpl里加上@Service注解
效果:


案例1:静态工具查询
需求:
(1)改造根据id查询用户的接口,查询用户的同时,查询除用户对应的所有地址
出现service相互调用的情况。
①从Day01的资料里拷贝AddressVO.java到vo文件夹下,在UserVO.java中添加地址属性:
- @ApiModelProperty("用户的收货地址")
- private List
addresses;
②改写UserController.java下的 queryUserById方法
- @ApiOperation("根据id查询用户接口")
- @GetMapping("{id}")
- public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id) {
- return userService.queryUserAndAddressById(id);
- }
③IUserService.java
UserVO queryUserAndAddressById(Long id);
④UserServiceImpl.java
- import cn.hutool.core.bean.BeanUtil;
- import cn.hutool.core.collection.CollUtil;
- import com.baomidou.mybatisplus.extension.toolkit.Db;
-
- // ... ...
- @Override
- public UserVO queryUserAndAddressById(Long id) {
- // 1. 查询用户
- User user = getById(id);
- if(user == null || user.getStatus() == 2) {
- throw new RuntimeException("用户状态异常!");
- }
- // 2. 查询地址
- List 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;
- }
(2)改造根据id批量查询用户的接口,查询用户的同时,查询出用户对应的所有地址
①UserController.java
- @ApiOperation("根据id批量查询用户接口")
- @GetMapping
- public List
queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List ids) { - return userService.queryUserAndAddressByIds(ids);
- }
②IUserService.java
List queryUserAndAddressByIds(List ids) ;
③UserServiceImpl.java
- @Override
- public List
queryUserAndAddressByIds(List ids) { - // 1. 查询用户
- List
users = listByIds(ids); - if(CollUtil.isEmpty(users)) {
- return Collections.emptyList();
- }
- // 2. 查询地址
- // 2.1 获取用户id集合
- List
userIds = users.stream().map(User::getId).collect(Collectors.toList()); - // 2.2 根据用户id查询地址
- List addresses = Db.lambdaQuery(Address.class).in(Address::getUserId, userIds).list();
- // 2.3 转换地址vo
- List
addressVOList = BeanUtil.copyToList(addresses, AddressVO.class); - // 2.4 用户地址集合分组处理,相同用户的放入一个集合(组)中
- Map
> addressMap = new HashMap<>(0); - if(CollUtil.isNotEmpty(addressVOList)) {
- addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
- }
-
- // 3. 转VO返回
- List
list = new ArrayList<>(users.size()); - for (User user : users) {
- // 3.1 转换user的po为vo
- UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
- list.add(vo);
- // 3.2 转换地址vo
- vo.setAddresses(addressMap.get(user.getId()));
- }
- return list;
- }
逻辑删除就是基于代码逻辑模拟删除效果,但并不会真正删除数据。思路如下:
例如,逻辑删除字段为deleted:
UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0
SELECT * FROM user WHERE deleted = 0
MyBatisPlus提供了逻辑删除功能,无需改变方法调应的方式,而是在底层帮我们自动修改CRUD的语句。我们要做的就是在application.yaml文件中配置逻辑删除的字段名称和值即可:
- mybatis-plus:
- global-config:
- db-config:
- id-type: auto
- logic-delete-field: deleted # 全局逻辑删除的实体字段名,字段类型可以是boolean、integer
- logic-delete-value: 1 # 逻辑已删除值(默认为1)
- logic-not-delete-value: 0 # 逻辑未删除值(默认为0)
IAddressServiceTest.java
- package com.itheima.mp.service;
-
- import com.itheima.mp.domain.po.Address;
- import org.junit.jupiter.api.Test;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
-
- import static org.junit.jupiter.api.Assertions.*;
-
- @SpringBootTest
- class IAddressServiceTest {
-
- @Autowired
- private IAddressService addressService;
-
- @Test
- void testLogicDelete() {
- // 1. 删除
- addressService.removeById(59L);
- // 2. 查询
- Address address = addressService.getById(59L);
- System.out.println("address = " + address); // null
- }
- }
注意:逻辑删除本身也有自己的问题,比如:
因此,不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其他表的方法。
User类中有一个用户状态字段:

通用枚举
在application.yaml中配置全局枚举处理器,实现类型转换:
- mybatis-plus:
- type-aliases-package: com.itheima.mp.domain.po
- configuration:
- default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
UserStatus.java
- package com.itheima.mp.enums;
-
- import com.baomidou.mybatisplus.annotation.EnumValue;
- import com.fasterxml.jackson.annotation.JsonValue;
- import lombok.Getter;
-
- @Getter
- public enum UserStatus {
- NORMAL(1, "正常"),
- FROZEN(2, "冻结"),
- ;
- @EnumValue
- private final int value;
- @JsonValue
- private final String desc;
-
- UserStatus(int value, String desc) {
- this.value = value;
- this.desc = desc;
- }
- }
User.java
- import com.itheima.mp.enums.UserStatus;
- /**
- * 账户余额
- */
- private UserStatus balance;
UserVO.java
- @ApiModelProperty("使用状态(1正常 2冻结)")
- private UserStatus status;
把用到账户状态值的地方改成枚举变量 - UserServiceImpl.java
- package com.itheima.mp.service.impl;
-
- import cn.hutool.core.bean.BeanUtil;
- import cn.hutool.core.collection.CollUtil;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import com.baomidou.mybatisplus.extension.toolkit.Db;
- import com.itheima.mp.domain.po.Address;
- import com.itheima.mp.domain.po.User;
- import com.itheima.mp.domain.vo.AddressVO;
- import com.itheima.mp.domain.vo.UserVO;
- import com.itheima.mp.enums.UserStatus;
- import com.itheima.mp.mapper.UserMapper;
- import com.itheima.mp.service.IUserService;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
-
- import java.util.*;
- import java.util.stream.Collectors;
-
- @Service
- public class UserServiceImpl extends ServiceImpl
implements IUserService { - @Override
- @Transactional
- public void deductBalance(Long id, Integer money) {
- // 1. 查询用户
- User user = getById(id);
- // 2. 校验用户状态
- if (user == null || user.getStatus() == UserStatus.FROZEN) {
- throw new RuntimeException("用户状态异常!");
- }
- // 3. 校验用户余额是否充足
- if (user.getBalance() < money) {
- throw new RuntimeException("用户余额不足!");
- }
- // 4. 扣减余额
- // baseMapper.deductBalance(id, money);
- int remainBalance = user.getBalance() - money;
- lambdaUpdate()
- .set(User::getBalance, remainBalance)
- .set(remainBalance == 0, User::getStatus, UserStatus.FROZEN)
- .eq(User::getId, id)
- .eq(User::getBalance, user.getBalance()) // 乐观锁
- .update();
- }
-
- @Override
- public List
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();
-
- }
-
- @Override
- public UserVO queryUserAndAddressById(Long id) {
- // 1. 查询用户
- User user = getById(id);
- if(user == null || user.getStatus() == UserStatus.FROZEN) {
- throw new RuntimeException("用户状态异常!");
- }
- // 2. 查询地址
- List 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;
- }
-
- @Override
- public List
queryUserAndAddressByIds(List ids) { - // 1. 查询用户
- List
users = listByIds(ids); - if(CollUtil.isEmpty(users)) {
- return Collections.emptyList();
- }
- // 2. 查询地址
- // 2.1 获取用户id集合
- List
userIds = users.stream().map(User::getId).collect(Collectors.toList()); - // 2.2 根据用户id查询地址
- List addresses = Db.lambdaQuery(Address.class).in(Address::getUserId, userIds).list();
- // 2.3 转换地址vo
- List
addressVOList = BeanUtil.copyToList(addresses, AddressVO.class); - // 2.4 用户地址集合分组处理,相同用户的放入一个集合(组)中
- Map
> addressMap = new HashMap<>(0); - if(CollUtil.isNotEmpty(addressVOList)) {
- addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
- }
-
- // 3. 转VO返回
- List
list = new ArrayList<>(users.size()); - for (User user : users) {
- // 3.1 转换user的po为vo
- UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
- list.add(vo);
- // 3.2 转换地址vo
- vo.setAddresses(addressMap.get(user.getId()));
- }
- return list;
- }
- }
总结:
如何实现PO类中的枚举类型变量与数据库字段的转换?
①给枚举中的与数据库对应value值添加@EnumValue注解
②在配置文件中配置统一的枚举处理器,实现类型转换。

①userInfo.java
- package com.itheima.mp.domain.po;
-
- import lombok.AllArgsConstructor;
- import lombok.Data;
- import lombok.NoArgsConstructor;
-
- @Data
- @NoArgsConstructor
- @AllArgsConstructor(staticName = "of")
- public class UserInfo {
- private Integer age;
- private String intro;
- private String gender;
- }
②User.java
- package com.itheima.mp.domain.po;
-
- import com.baomidou.mybatisplus.annotation.IdType;
- import com.baomidou.mybatisplus.annotation.TableField;
- import com.baomidou.mybatisplus.annotation.TableId;
- import com.baomidou.mybatisplus.annotation.TableName;
- import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
- import com.itheima.mp.enums.UserStatus;
- import lombok.Data;
-
- import java.time.LocalDateTime;
-
- @Data
- @TableName(value = "tb_user", autoResultMap = true)
- public class User {
-
- // ... ...
-
- /**
- * 详细信息
- */
- @TableField(typeHandler = JacksonTypeHandler.class)
- private UserInfo info;
-
- // ... ...
- }
③设置用户信息改成
- // user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
- user.setInfo(UserInfo.of(24,"英文老师","female"));
④UserVO.java
- @ApiModelProperty("详细信息")
- private UserInfo info;
⑤UserFormDTO.java
- @ApiModelProperty("详细信息,JSON风格")
- private UserInfo info;
如果有出现下面的问题,把UserMapper.xml里的SQL语句改成如下,删除parameterType="com.itheima.mp.domain.po.User"
- <insert id="saveUser">
- INSERT INTO `tb_user` (`id`, `username`, `password`, `phone`, `info`, `balance`)
- VALUES (#{id}, #{username}, #{password}, #{phone}, #{info}, #{balance});
- insert>

MyBatisPlus提供的内置拦截器有下面这些:
| 拦截器 | 描述 |
| TenantLineInnerInterceptor | 多租户插件 |
| DynamicTableNameInnerInterceptor | 动态表名插件 |
| PaginationInnerInterceptor | 分页插件 |
| OptimisticLockerInnerInterceptor | 乐观锁插件 |
| IllegalSQLInnerInterceptor | SQL性能规范插件,检测并拦截垃圾SQL |
| BlockAttackInnerInterceptor | 防止全表更新和删除的插件 |
①在配置类中注册MyBatisPlus的核心插件,同时添加分页插件:
- package com.itheima.mp.config;
-
- import com.baomidou.mybatisplus.annotation.DbType;
- import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
- import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
-
- @Configuration
- public class MyBatisConfig {
- @Bean
- public MybatisPlusInterceptor mybatisPlusInterceptor() {
- MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
- // 1. 创建分页插件
- PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
- paginationInnerInterceptor.setMaxLimit(1000L); // 设置分页上限
- // 2. 添加分页插件
- interceptor.addInnerInterceptor(paginationInnerInterceptor);
- return interceptor;
- }
- }
②使用分页的API
IUserServiceTest.java
- @Test
- void testPageQuery() {
- int pageNo = 1, pageSize = 2;
- // 1. 准备分页条件
- // 1.1 分页条件
- Page
page = Page.of(pageNo, pageSize); - // 1.2 排序条件
- page.addOrder(new OrderItem("balance", true));
- page.addOrder(new OrderItem("id", true));
-
- // 2. 分页查询
- Page
p = userService.page(page); -
- // 3. 解析
- long total = p.getTotal();
- System.out.println("total = " + total);
- long pages = p.getPages();
- System.out.println("pages = " + pages);
- List
users = p.getRecords(); - users.forEach(System.out::println);
- }
案例1:简单分页查询案例
需求:遵循下面的接口规范,编写一个UserController接口,实现User的分页查询

①PageQuery.java
- package com.itheima.mp.query;
-
- import io.swagger.annotations.ApiModel;
- import io.swagger.annotations.ApiModelProperty;
- import lombok.Data;
-
- @Data
- @ApiModel(description = "分页查询实体")
- public class PageQuery {
- @ApiModelProperty("页码")
- private Integer pageNo;
- @ApiModelProperty("页大小")
- private Integer pageSize;
- @ApiModelProperty("排序字段")
- private String sortBy;
- @ApiModelProperty("是否升序")
- private Boolean isAsc;
- }
②UserQuery继承PageQuery
- package com.itheima.mp.query;
-
- import io.swagger.annotations.ApiModel;
- import io.swagger.annotations.ApiModelProperty;
- import lombok.Data;
- import lombok.EqualsAndHashCode;
-
- @EqualsAndHashCode(callSuper = true)
- @Data
- @ApiModel(description = "用户查询条件实体")
- public class UserQuery extends PageQuery{
- @ApiModelProperty("用户名关键字")
- private String name;
- @ApiModelProperty("用户状态:1-正常,2-冻结")
- private Integer status;
- @ApiModelProperty("余额最小值")
- private Integer minBalance;
- @ApiModelProperty("余额最大值")
- private Integer maxBalance;
- }
③PageDTO.java
- package com.itheima.mp.domain.dto;
-
- import io.swagger.annotations.ApiModel;
- import io.swagger.annotations.ApiModelProperty;
- import lombok.Data;
-
- import java.util.List;
-
- @Data
- @ApiModel(description = "分页结果")
- public class PageDTO
{ - @ApiModelProperty("总条数")
- private Long total;
- @ApiModelProperty("总页数")
- private Long pages;
- @ApiModelProperty("集合")
- private List
list; - }
④UserController.java
- @ApiOperation("根据复杂条件分页查询用户接口")
- @GetMapping("/page")
- public PageDTO
queryUsersPage(UserQuery query) { - return userService.queryUsersPage(query);
- }
⑤IUserService.java
PageDTO queryUsersPage(UserQuery query);
⑥UserSeerviceImpl.java
- package com.itheima.mp.service.impl;
-
- import cn.hutool.core.bean.BeanUtil;
- import cn.hutool.core.collection.CollUtil;
- import cn.hutool.core.util.StrUtil;
- import com.baomidou.mybatisplus.core.metadata.OrderItem;
- import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import com.baomidou.mybatisplus.extension.toolkit.Db;
- import com.itheima.mp.domain.dto.PageDTO;
- import com.itheima.mp.domain.po.Address;
- import com.itheima.mp.domain.po.User;
- import com.itheima.mp.domain.vo.AddressVO;
- import com.itheima.mp.domain.vo.UserVO;
- import com.itheima.mp.enums.UserStatus;
- import com.itheima.mp.mapper.UserMapper;
- import com.itheima.mp.query.UserQuery;
- import com.itheima.mp.service.IUserService;
- import org.springframework.stereotype.Service;
-
-
- import java.util.*;
- import java.util.stream.Collectors;
-
- @Service
- public class UserServiceImpl extends ServiceImpl
implements IUserService { - // ... ...
- @Override
- public PageDTO
queryUsersPage(UserQuery query) { - String name = query.getName();
- Integer status = query.getStatus();
- Integer minBalance = query.getMinBalance();
- Integer maxBalance = query.getMaxBalance();
- // 1. 构建查询条件
- // 1.1 分页条件
- Page
page = Page.of(query.getPageNo(), query.getPageSize()); - // 1.2 排序条件
- if(StrUtil.isNotBlank(query.getSortBy())) {
- // 排序字段不为空,按照排序字段排序
- page.addOrder(new OrderItem(query.getSortBy(), query.getIsAsc()));
- } else {
- // 排序字段为空,默认按照更新时间排序
- page.addOrder(new OrderItem("update_time", false));
- }
- // 2. 分页查询
- Page
p = 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)
- .page(page);
- // 3. 封装VO结果
- PageDTO
dto = new PageDTO<>(); - // 3.1 总条数
- dto.setTotal(p.getTotal());
- // 3.2 总页数
- dto.setPages(p.getPages());
- // 3.3 当前页数据
- List
records = p.getRecords(); - if(CollUtil.isEmpty(records)) {
- dto.setList(Collections.emptyList());
- return dto;
- }
- // 3.4 拷贝user的VO
- dto.setList(BeanUtil.copyToList(records, UserVO.class));
- // 4. 返回
- return dto;
- }
- }
案例2:通用分页实体
需求:
①PageQuery.java
- package com.itheima.mp.query;
-
- import cn.hutool.core.util.StrUtil;
- import com.baomidou.mybatisplus.core.metadata.OrderItem;
- import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
- import com.itheima.mp.domain.po.User;
- import io.swagger.annotations.ApiModel;
- import io.swagger.annotations.ApiModelProperty;
- import lombok.Data;
-
- @Data
- @ApiModel(description = "分页查询实体")
- public class PageQuery {
- @ApiModelProperty("页码")
- private Integer pageNo = 1;
- @ApiModelProperty("页大小")
- private Integer pageSize = 5;
- @ApiModelProperty("排序字段")
- private String sortBy;
- @ApiModelProperty("是否升序")
- private Boolean isAsc = true;
-
- public
Page toMpPage(OrderItem ... items) { - // 1 分页条件
- Page
page = Page.of(pageNo, pageSize); - // 2 排序条件
- if(StrUtil.isNotBlank(sortBy)) {
- // 排序字段不为空,按照排序字段排序
- page.addOrder(new OrderItem(sortBy, isAsc));
- } else if(items != null) {
- // 排序字段为空,默认排序
- page.addOrder(items);
- }
- return page;
- }
-
- public
Page toMpPage(String defaultSortBy, Boolean defaultAsc) { - return toMpPage(new OrderItem(defaultSortBy, defaultAsc));
- }
-
- public
Page toMpPageDefaultSortByCreateTime() { - return toMpPage(new OrderItem("create_time", false));
- }
-
- public
Page toMpPageDefaultSortByUpdateTime() { - return toMpPage(new OrderItem("update_time", false));
- }
- }
②PageDTO.java
- package com.itheima.mp.domain.dto;
-
- import cn.hutool.core.bean.BeanUtil;
- import cn.hutool.core.collection.CollUtil;
- import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
- import com.itheima.mp.domain.po.User;
- import com.itheima.mp.domain.vo.UserVO;
- import io.swagger.annotations.ApiModel;
- import io.swagger.annotations.ApiModelProperty;
- import lombok.Data;
-
- import java.util.Collections;
- import java.util.List;
- import java.util.function.Function;
- import java.util.stream.Collectors;
-
- @Data
- @ApiModel(description = "分页结果")
- public class PageDTO
{ - @ApiModelProperty("总条数")
- private Long total;
- @ApiModelProperty("总页数")
- private Long pages;
- @ApiModelProperty("集合")
- private List
list; -
- public static
PageDTO of(Page p, Class clazz) { - PageDTO
dto = new PageDTO<>(); - // 1 总条数
- dto.setTotal(p.getTotal());
- // 2 总页数
- dto.setPages(p.getPages());
- // 3 当前页数据
- List
records = p.getRecords(); - if(CollUtil.isEmpty(records)) {
- dto.setList(Collections.emptyList());
- return dto;
- }
- // 4 拷贝user的VO
- dto.setList(BeanUtil.copyToList(records, clazz));
- // 5. 返回
- return dto;
- }
-
- public static
PageDTO of(Page p, Function convertor) { - PageDTO
dto = new PageDTO<>(); - // 1 总条数
- dto.setTotal(p.getTotal());
- // 2 总页数
- dto.setPages(p.getPages());
- // 3 当前页数据
- List
records = p.getRecords(); - if(CollUtil.isEmpty(records)) {
- dto.setList(Collections.emptyList());
- return dto;
- }
- // 4 拷贝user的VO
- dto.setList(records.stream().map(convertor).collect(Collectors.toList()));
- // 5. 返回
- return dto;
- }
- }
③UserServiceImpl.java
- package com.itheima.mp.service.impl;
-
- import cn.hutool.core.bean.BeanUtil;
- import cn.hutool.core.collection.CollUtil;
- import cn.hutool.core.util.StrUtil;
- import com.baomidou.mybatisplus.core.metadata.OrderItem;
- import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import com.baomidou.mybatisplus.extension.toolkit.Db;
- import com.itheima.mp.domain.dto.PageDTO;
- import com.itheima.mp.domain.po.Address;
- import com.itheima.mp.domain.po.User;
- import com.itheima.mp.domain.vo.AddressVO;
- import com.itheima.mp.domain.vo.UserVO;
- import com.itheima.mp.enums.UserStatus;
- import com.itheima.mp.mapper.UserMapper;
- import com.itheima.mp.query.UserQuery;
- import com.itheima.mp.service.IUserService;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
-
- import java.util.*;
- import java.util.stream.Collectors;
-
- @Service
- public class UserServiceImpl extends ServiceImpl
implements IUserService { -
- @Override
- public List
queryUserAndAddressByIds(List ids) { - // 1. 查询用户
- List
users = listByIds(ids); - if(CollUtil.isEmpty(users)) {
- return Collections.emptyList();
- }
- // 2. 查询地址
- // 2.1 获取用户id集合
- List
userIds = users.stream().map(User::getId).collect(Collectors.toList()); - // 2.2 根据用户id查询地址
- List addresses = Db.lambdaQuery(Address.class).in(Address::getUserId, userIds).list();
- // 2.3 转换地址vo
- List
addressVOList = BeanUtil.copyToList(addresses, AddressVO.class); - // 2.4 用户地址集合分组处理,相同用户的放入一个集合(组)中
- Map
> addressMap = new HashMap<>(0); - if(CollUtil.isNotEmpty(addressVOList)) {
- addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
- }
-
- // 3. 转VO返回
- List
list = new ArrayList<>(users.size()); - for (User user : users) {
- // 3.1 转换user的po为vo
- UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
- list.add(vo);
- // 3.2 转换地址vo
- vo.setAddresses(addressMap.get(user.getId()));
- }
- return list;
- }
-
- @Override
- public PageDTO
queryUsersPage(UserQuery query) { - String name = query.getName();
- Integer status = query.getStatus();
- Integer minBalance = query.getMinBalance();
- Integer maxBalance = query.getMaxBalance();
- // 1. 构建查询条件
- Page
page = query.toMpPageDefaultSortByUpdateTime(); - // 2. 分页查询
- Page
p = 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)
- .page(page);
- // 3. 封装VO结果
- // return PageDTO.of(p, UserVO.class);
- // return PageDTO.of(p, user -> BeanUtil.copyProperties(user, UserVO.class));
- return PageDTO.of(p, user -> {
- // 1. 拷贝基础属性
- UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
- // 2. 处理特殊逻辑
- vo.setUsername(vo.getUsername().substring(0, vo.getUsername().length() - 2) + "**");
- return vo;
- });
- }
- }
