1 快速入门
1.1 入门案例
-
需求:基于课前资料提供的项目,实现下列功能:
① 新增用户功能② 根据 id 查询用户③ 根据 id 批量查询用户④ 根据 id 更新用户⑤ 根据 id 删除用户 - 步骤
1.引入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>
2.定义Mapper
自定义的Mapper继承MybatisPlus提供的BaseMapper接口:
public interface UserMapper extends BaseMapper<User> {
// void saveUser(User user);
//
// void deleteUser(Long id);
//
// void updateUser(User user);
//
// User queryUserById(@Param("id") Long id);
//
// List<User> queryUserByIds(@Param("ids") List<Long> ids);
}

1.2 常见注解
MyBatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息。
MybatisPlus中比较常用的几个注解如下:
IdType枚举:
使用@TableField的常见场景:
例如有这样的表结构:

@TableName("tb_user")
public class User {
@TableId(value="id",type=IdType.AUTO)
private Long id;
@TableField("username")
private String name;
@TableField("is_married")
private Boolean isMarried;
@TableField("`order`")
private Integer order;
@TableField(exist = false)
private String address;
}
总结
MybatisPlus是如何获取实现CRUD的数据库表信息的?
MybatisPlus的常用注解有哪些?
IdType的常见类型有哪些?
使用@TableField的常见场景是?
1.3 常用配置
MyBatisPlus的配置项继承了MyBatis原生配置和一些自己特有的配置。例如:
mybatis-plus:
type-aliases-package: com.itheima.mp.domain.po # 别名扫描包
mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,默认值
configuration:
map-underscore-to-camel-case: true # 是否开启下划线和驼峰的映射
cache-enabled: false # 是否开启二级缓存
global-config:
db-config:
id-type: assign_id # id为雪花算法生成
update-strategy: not_null # 更新策略:只更新非空字段
1.4 总结
MyBatisPlus使用的基本流程是什么?
2 核心功能
2.1 条件构造器
- MyBatisPlus支持各种复杂的where条件,可以满足日常开发的所有需求。





Lambda是基于lambda语法
- 案例:基于QueryWrapper的查询
需求:
SELECT id,username,info,balance
FROM user
WHERE username LIKE ? AND balance >= ?
@Test
void testQueryWrapper(){
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.select("id","username","info","balance")
.like("username","o")
.ge("balance","1000");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
UPDATE user
SET balance = 2000
WHERE (username = "jack")
@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
UPDATE user
SET balance = balance - 200
WHERE id in (1, 2, 4)
@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);
}
- LambdaUpdateWrapper
@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");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
条件构造器的用法
- QueryWrapper和LambdaQueryWrapper通常用来构建select、delete、update的where条件部分
- UpdateWrapper和LambdaUpdateWrapper通常只有在set语句比较特殊才使用
- 尽量使用LambdaQueryWrapper和LambdaUpdateWrapper,避免硬编码
2.2 自定义SQL
我们可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分。
例如这个语句如果单纯用mp是很难写出来的
SELECT status, COUNT(id) AS total
FROM tb_user
<where>
<if test="name != null">AND username LIKE #{name}</if>
<if test="ids != null">
AND id IN
<foreach collection="ids" open="(" close=")" item="id" separator=",">
#{id}
</foreach>
</if>
</where>
GROUP BY status
解决步骤
List<Long> ids=List.of(1L,2L,4L);
Integer amount=200;
LambdaQueryWrapper<User> wrapper=new LambdaQueryWrapper<User>()
.in(User::getId,ids);
userMapper.updateBalanceById(wrapper,amount);
void updateBalanceById(@Param("ew") LambdaQueryWrapper<User> wrapper,@Param("amount") Integer amount);
<update id="updateBalanceById">
update user set balance=balance-#{amount} ${ew.customSqlSegment}
</update>
2.3 IService


- MP的Service接口使用流程是怎样的?
- 自定义Service接口继承IService接口
public interface UserService extends IService<User> {
}
- 自定义Service实现类,实现自定义接口并继承ServiceImpl类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
- test
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;
@SpringBootTest
public class IUSerServiceTest {
@Autowired
private UserService userService;
@Test
void testSaveUSer(){
User user = new User();
//user.setId(5L);
user.setUsername("LiLei");
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, 3L));
users.forEach(System.out::println);
}
}
案例:基于Restful风格实现下列接口
需求:基于Restful风格实现下面的接口:
| 编号 | 接口 | 请求方式 | 请求路径 | 请求参数 | 返回值 |
| 1 | 新增用户 | POST | /users | 用户表单实体 | 无 |
| 2 | 删除用户 | DELETE | /users/{id} | 用户id | 无 |
| 3 | 根据id查询用户 | GET | /users/{id} | 用户id | 用户VO |
| 4 | 根据id批量查询 | GET | /users | 用户id集合 | 用户VO集合 |
| 5 | 根据id扣减余额 | PUT | /users/{id}/deduction/{money} |
•
用户
id
•
扣减金额
| 无 |
- 加入web和swagger依赖
<!--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
- domain.dto.UserFormDTO
package com.itheima.mp.domain.dto;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
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;
}
- domin.vo.Uservo
package com.itheima.mp.domain.vo;
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;
}
- controller.UserContrller
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.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Api(tags = "用户管理接口")
@RequestMapping("/users")
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@ApiOperation("新增用户接口")
@PostMapping
public void saveUSer(@RequestBody UserFormDTO userFormDTO){
User user = BeanUtil.copyProperties(userFormDTO, User.class);
userService.save(user);
}
@ApiOperation("删除用户接口")
@DeleteMapping("/{id}")
public void deleteUserById(@ApiParam("id") @PathVariable Long id){
userService.removeById(id);
}
@ApiOperation("根据id查询用户接口")
@GetMapping("/{id}")
public UserVO queryUserById(@ApiParam("id") @PathVariable Long id){
User user = userService.getById(id);
return BeanUtil.copyProperties(user,UserVO.class);
}
@ApiOperation("根据id批量查询用户接口")
@GetMapping
public List<UserVO> queryUserByIds(@ApiParam("ids") @PathVariable List<Long> ids){
List<User> users = userService.listByIds(ids);
return BeanUtil.copyToList(users,UserVO.class);
}
}
自定义service方法
@ApiOperation("扣减用户余额接口")
@PutMapping("/{id}/deduction/{money}")
public void deductMoneyById(
@ApiParam("id") @PathVariable Long id,
@ApiParam("money")@PathVariable Integer money
){
userService.deductBalance(id,money);
}
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.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public void deductBalance(Long id, Integer money) {
User user = this.getById(id);
if(user==null||user.getStatus()==2){
throw new RuntimeException("用户状态异常");
}
if(user.getBalance()<money){
throw new RuntimeException("用户余额不足");
}
baseMapper.deductBalance(id,money);
}
}
@Update("update user set balance=balance-#{money} where id=#{id}")
void deductBalance(Long id, Integer money);
案例:IService的Lambda查询
需求:实现一个根据复杂条件查询用户的接口,查询条件如下:
@Override
public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
List<User> list = 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();
return list;
}
案例:IService的Lambda更新
需求:改造根据id修改用户余额的接口,要求如下
@Override
public void deductBalance(Long id, Integer money) {
User user = this.getById(id);
if(user==null||user.getStatus()==2){
throw new RuntimeException("用户状态异常");
}
if(user.getBalance()<money){
throw new RuntimeException("用户余额不足");
}
Integer remainBalance=user.getBalance()-money;
boolean update = lambdaUpdate()
.set(User::getBalance, remainBalance)
.set(remainBalance == 0, User::getStatus, 2)
.eq(User::getId, id)
.eq(User::getBalance, user.getBalance())
.update();
}
案例:IService批量新增
需求:批量插入10万条用户数据,并作出对比:
- 使用普通for循环插入
@Test
void testSaveOneByOne(){
Long begin=System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
userService.save(builderUser(i));
}
Long end=System.currentTimeMillis();
System.out.println("耗时:"+(end-begin));
}
- 使用iservice的批量插入,mp底层会对list集合进行预编译为sql语句,
-
@Test void testSaveBatch(){ List<User> list=new ArrayList<>(1000); Long begin=System.currentTimeMillis(); for (int i = 1; i <= 100000; i++) { list.add(builderUser(i)); if(i%1000==0){ userService.saveBatch(list); list.clear(); } } Long end=System.currentTimeMillis(); System.out.println("耗时:"+(end-begin)); }
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: Pengjixuan0524.
批处理方案:
3. 拓展功能
3.1 静态工具








案例:静态工具查询
需求:
@Override
public UserVO queryUserAndAddress(Long id) {
User user = this.lambdaQuery().eq(User::getId, id).one();
if(user==null||user.getStatus()==2){
throw new RuntimeException("用户不存在或被冻结");
}
List<Address> list = Db.lambdaQuery(Address.class).eq(Address::getUserId, id).list();
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
if(list!=null){
userVO.setAddressVOS(BeanUtil.copyToList(list, AddressVO.class));
}
return userVO;
}
当出现多个service方法相互调用的情况的时候,如果直接使用依赖注入,那么会出现循环依赖的的局面,这是可以使用Db工具类。
@Override
public List<UserVO> queryUserAndAddressByids(List<Long> ids) {
List<User> users = listByIds(ids);
if(CollUtil.isEmpty(users)){
return Collections.emptyList();
}
List<Long> listId = users.stream().map(User::getId).collect(Collectors.toList());
List<Address> address = Db.lambdaQuery(Address.class).in(Address::getUserId, ids).list();
List<AddressVO> addressVOS = BeanUtil.copyToList(address, AddressVO.class);
Map<Long, List<AddressVO>> addressMap=new HashMap<>(0);
if(CollUtil.isEmpty(addressMap)) {
addressMap = addressVOS.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
}
ArrayList<UserVO> userVOS = new ArrayList<>(users.size());
for (User user : users) {
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
userVO.setAddressVOS(addressMap.get(user.getId()));
userVOS.add(userVO);
}
return userVOS;
}
3.2 逻辑删除
逻辑删除就是基于代码逻辑模拟删除效果,但并不会真正删除数据。思路如下:
例如逻辑删除字段为deleted:
-
UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0
SELECT * FROM user WHERE deleted = 0
MybatisPlus提供了逻辑删除功能,无需改变方法调用的方式,而是在底层帮我们自动修改CRUD的语句。我们要做的就是在application.yaml文件中配置逻辑删除的字段名称和值即可:
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
global-config:
db-config:
logic-delete-field: deleted

注意:
逻辑删除本身也有自己的问题,比如:
因此,我不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。
3.3 枚举处理器
将User类 里面的用户状态字段由Integer类型转换为枚举类型,优点就是可读性加强。
但同样带来了一个问题,Java当中的枚举类型和数据库中中的类型相互转换的问题
解决方案:
- 第一步,为枚举类型中的value字段加一个@EnumValue注解,作用是可以和数据库中的类型相同,知道把谁写进数据库。
package com.itheima.mp.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import io.swagger.models.auth.In;
import lombok.Getter;
@Getter
public enum UserStatus {
NORMAL(1,"普通"),
FREEZE(2,"冻结"),
;
@EnumValue
private final Integer value;
private final String desc;
UserStatus(Integer value, String desc) {
this.value = value;
this.desc = desc;
}
}
- 第二步,配置yaml文件
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
注意,最后写进数据库的值是枚举的值,如果想改变值的表现形式,可以在特定的属性是加上@JSONValue。
3.4 JSON处理器
数据库中user表中有一个json类型的字段:

在Java中一般拿String类型与之对应,但当要用到info中的信息的时候,就会产生不便利,要想操作更方便应该定义一个实体类,来更json格式相对应。
操作步骤:
1. 定义一个实体类
2.在User类中的info字段加一个@TableField(typeHandler = JacksonTypeHandler.class),在类上加一个@TableName(value = "User",autoResultMap = true)。
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;
}
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 = "User",autoResultMap = true)
public class User {
/**
* 用户id
*/
@TableId(value = "id",type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 注册手机号
*/
private String phone;
/**
* 详细信息
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private UserInfo info;
/**
* 使用状态(1正常 2冻结)
*/
private UserStatus status;
/**
* 账户余额
*/
private Integer balance;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
4.插件功能
MyBatisPlus提供的内置拦截器有下面这些:
| 序号 | 拦截器 | 描述 |
| 1 | TenantLineInnerInterceptor | 多租户插件 |
| 2 | DynamicTableNameInnerInterceptor | 动态表名插件 |
| 3 | PaginationInnerInterceptor | 分页插件 |
| 4 | OptimisticLockerInnerInterceptor | 乐观锁插件 |
| 5 | IllegalSQLInnerInterceptor | SQL性能规范插件,检测并拦截垃圾SQL |
| 6 | BlockAttackInnerInterceptor | 防止全表更新和删除的插件 |
4.1 分页插件
首先,要在配置类中注册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 mybatisPlusInterceptor=new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor=new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setMaxLimit(1000L);
mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
return mybatisPlusInterceptor;
}
}
接着,就可以使用分页的API了:
@Test
public void testPageQuery(){
int pageNo=1,pageSize=2;
Page<User> page=Page.of(pageNo,pageSize);
page.addOrder(new OrderItem("balance",true));
Page<User> page1 = userService.page(page);
long total = page1.getTotal();
System.out.println("total: "+total);
long pages = page1.getPages();
System.out.println("pages: "+pages);
List<User> records = page1.getRecords();
records.forEach(System.out::println);
}
4.2 通用分页实体
需求:遵循下面的接口规范,编写一个UserController接口,实现User的分页查询

- UserQuery
package com.itheima.mp.domain.query;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@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;
}
- PageQuery
package com.itheima.mp.domain.query;
import lombok.Data;
@Data
public class PageQuery {
private Long pageNo;
private Long pageSize;
private String sortBy;
private Boolean isAsc;
}
- PageDto
package com.itheima.mp.domain.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageDto {
private Long Total;
private Long pages;
private List list;
}
- UserController
@ApiOperation("根据id批量查询用户接口")
@GetMapping("/page")
public PageDto queryUsersPage(UserQuery query){
return userService.queryUrersPage(query);
}
- UserServiceImpl
@Override
public PageDto queryUrersPage(UserQuery query) {
String name = query.getName();
Integer status = query.getStatus();
Page<User> page = Page.of(query.getPageNo(), query.getPageSize());
if(query.getSortBy()!=null){
page.addOrder(new OrderItem(query.getSortBy(),true));
}else{
page.addOrder(new OrderItem("update_time",false));
}
Page<User> page1 = lambdaQuery().like(name != null, User::getUsername, name)
.eq(status != null, User::getStatus, status)
.page(page);
PageDto pageDto=new PageDto();
pageDto.setTotal(page1.getTotal());
pageDto.setPages(page1.getPages());
List<User> records = page1.getRecords();
if(CollUtil.isEmpty(records)){
pageDto.setList(Collections.emptyList());
}else{
List<UserVO> userVOS = BeanUtil.copyToList(records, UserVO.class);
pageDto.setList(userVOS);
}
return pageDto;
}
1034

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



