MyBatisPlus 学习笔记
文章目录
- ` MyBatisPlus` 快速入门
- 第一步:引入 `MyBaitsPlus` 起步依赖
- 第二步:自定义的 `Mapper` 继承 `BaseMapper` 接口
- 新增相关
- 修改相关
- 删除相关
- 查询相关
- `Mp` 使用示例
- `MyBaitsPlus` 常见注解
- `MP` 实体类与数据库信息约定
- `Mp` 实体类与数据库信息约定不符合解决方法
- `MP` 自定义常见配置
- `MyBatisPlus` 条件构造器
- `AbstractWrapper` 公共方法
- 比较条件
- 模糊查询
- 范围条件
- 逻辑条件
- 注意事项
- 高级特性
- `QueryWrapper` 特有方法
- **`UpdateWrapper`** 特有方法
- `LambdaWrapper` 示例
- `MyBatisPlus` 自定义 `SQL`
- `MyBatisPlus` 的 `IService` 接口
- `IService` 接口使用
- 第一步:`Service` 接口实现 `IService<T>`
- 第二步:`Service` 实现类继承 `ServiceImpl<M extends BaseMapper<T>, T>`
- 第三步:编写 `SQL`
- `IService` 开发基础业务接口
- `UserController` 层
- `USerService` 接口
- `UserService` 实现类
- `UserMapper` 层
- `IService` 的 `Lambda` 查询与更改
- **常用条件方法**
- `Iservice` 中的 `LambdaQuery`
- `Iservice` 中的 `LambdaUpdate`
- `IService` 批量新增
- 普通 `for` 方案
- `Mp` 批量处理方案
- `Mp` 批量处理配合 `rewriteBatchedStatements=true`
- 新增数据
- 删除数据
- 修改数据
- 查询数据
- 统计与分页
- 链式操作(高级)
- `MyBatisPlus` 拓展功能
- `MP` 代码生成器
- 安装插件
- 使用
- `Mp` 静态工具
- 示例一
- 示例二
- `MP` 逻辑删除
- `Mp` 枚举处理器
- `Mp` 的 `Json` 处理器
- `Mp` 插件功能
- `Mp` 分页插件基本使用
- `Mp` 通用分页查询
- `MP` 通用分页实体改造
MyBatisPlus
快速入门
第一步:引入 MyBaitsPlus
起步依赖
第二步:自定义的 Mapper
继承 BaseMapper
接口
BaseMapper
接口提前定义好了一大堆增删改查的方法注意:泛型为对应实体类
新增相关
insert (T entity)
INSERT INTO {表名}
({字段1}, {字段2}, ...)
VALUES (#{entity.属性1}, #{entity.属性2}, ...)
修改相关
updateById(T entity)
UPDATE {表名}
SET {字段1}=#{entity.属性1}, {字段2}=#{entity.属性2}, ...
WHERE id = #{entity.id}
update(T entity, Wrapper<T> wrapper)
\
UPDATE {表名}
SET {字段1}=#{entity.属性1}, {字段2}=#{entity.属性2}, ...
WHERE {wrapper动态生成的条件}
删除相关
deleteById(Serializable id)
DELETE FROM {表名} WHERE id = #{id}
deleteByMap(Map<String, Object> map)
DELETE FROM {表名}
WHERE {map中的键值对条件}
-- 例如:name = 'John' AND age = 25
deleteBatchIds(Collection<?> ids)
DELETE FROM {表名}
WHERE id IN (id1, id2, id3...)
查询相关
selectById(Serializable id)
SELECT * FROM {表名} WHERE id = #{id}
selectBatchIds(Collection<?> ids)
SELECT * FROM {表名}
WHERE id IN (id1, id2, id3...)
selectOne(Wrapper<T> wrapper)
SELECT * FROM {表名}
WHERE {wrapper动态生成的条件}
LIMIT 1
selectList(Wrapper<T> wrapper)
SELECT * FROM {表名}
WHERE {wrapper动态生成的条件}
Mp
使用示例
//增
@Test
void testInsert() {
User user = new User();
//user.setId(5L);
user.setUsername("ErGouZi");
user.setPassword("123");
user.setPhone("18688990013");
user.setBalance(200);
user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
userMapper.insert(user);
}
//查
@Test
void testQueryByIds() {
List<User> 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 testSelectById() {
User user = userMapper.selectById(5L);
System.out.println("user = " + user);
}
//删
@Test
void testDeleteUser() {
userMapper.deleteById(5L);
}
MyBaitsPlus
常见注解
MP
实体类与数据库信息约定
Mp
实体类与数据库信息约定不符合解决方法
实体类表名字段和数据库约定不符
- 加上对应的
@TableName
,@TableId
,TbaleField
主键自增长问题
没用
type
默认采用雪花算法数据库配置了
AUTO_INCREMENT
,实体类必须标注@TableId(type = IdType.AUTO)
数据库未配置
AUTO_INCREMENT
,可以选择INPUT
或者ASSIGN_ID
实体类
is
开头问题
- 实体类
isMarried
,数据库is_Married
符合映射规则。但是Java
中会忽略is
所以还是需要手动加上@TableField
字段名和数据库关键字冲突问题
- 比如下面的
order
。和数据库关键字冲突,所以笔记加上 ```飘号。实体类有,数据库没有的字段问题
- 使用
@TableField(exist = false)
MP
自定义常见配置
全局 id 配置配了,我们字段可以不用配置
type = ....
图中除了
type-aliases-package
其他都是默认值
MyBatisPlus
条件构造器
@Test
void testQueryWrapper() {
// 1.构建查询条件
QueryWrapper<User> warpper = new QueryWrapper<User>()
.select("id", "username", "info", "balance")
.like("username", "o")
.ge("balance", 1000);
// 2.查询
userMapper.selectList(warpper);
}
AbstractWrapper
公共方法
比较条件
eq(R column, Object val)
→column = val
ne(R column, Object val)
→column ≠ val
gt(R column, Object val)
→column > val
ge(R column, Object val)
→column ≥ val
lt(R column, Object val)
→column < val
le(R column, Object val)
→column ≤ val
模糊查询
like(R column, Object val)
→column LIKE '%val%'
notLike(R column, Object val)
→column NOT LIKE '%val%'
likeLeft(R column, Object val)
→column LIKE '%val
(左模糊)likeRight(R column, Object val)
→column LIKE 'val%'
(右模糊)
范围条件
between(R column, Object val1, Object val2)
→BETWEEN val1 AND val2
notBetween(R column, Object val1, Object val2)
→NOT BETWEEN
in(R column, Object... values)
→in(...)
逻辑条件
and()
→AND
连接多个条件or()
→OR
连接多个条件nested()
→ 嵌套条件
注意事项
- 链式调用:通过
.
连接多个条件(默认AND
逻辑) - 防 SQL 注入:优先使用
LambdaQueryWrapper
避免字段名硬编码 - 性能优化:避免在循环中频繁创建 Wrapper 对象
- 空值处理:
eq(null, val)
会忽略条件,需手动判空
高级特性
- 实体绑定:
setEntity(T entity)
自动从实体类提取条件 - 字段过滤:
columnsSqlInjectFilter()
防止非法字段注入 - Lambda 支持:
LambdaQueryWrapper
提供类型安全的字段引用(如User::getName
)
QueryWrapper
特有方法
-
select(String... columns)
→SELECT column1, column2
(指定返回字段) -
allEq(Map<R, V> params)
→ 批量等值匹配(如column1=val1 AND column2=val2
) -
…
@Test
void testQueryWrapper() {
// 1.构建查询条件
QueryWrapper<User> warpper = new QueryWrapper<User>()
.select("id", "username", "info", "balance")
.like("username", "o")
.ge("balance", 1000);
// 2.查询
List<User> users = userMapper.selectList(warpper);
users.forEach(System.out::println);
}
@Test
void testUpdateByQueryWrapper2() {
// 1. 要更新的信息
User user = new User();
user.setBalance(2000);
// 2. 更新的条件
QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "jack");
// 3. 执行更行
int update = userMapper.update(user, wrapper);
}
UpdateWrapper
特有方法
set(String column, Object value)
→SET column = value
setSql(String sql)
→ 直接拼接 SET 语句(如SET balance = balance + 100
)- …
@Test
void testQueryWrapper2() {
User user = new User();
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.set("balance", 2000)
.eq("username", "jack");
int update = userMapper.update(user, wrapper);
}
@Test
void testUpdateWrapper() {
User user = new User();
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance - 200")
.in("id", 1, 2, 3);
userMapper.update(user, wrapper);
}
LambdaWrapper
示例
@Test
void testLambdaQueryWrapper() {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>()
.select(User::getId, User::getUsername, User::getInfo, User::getBalance)
.like(User::getUsername, "o")
.ge(User::getBalance, 1000);
}
.select(User::getId, User::getUsername, User::getInfo, User::getBalance)
转为常规 Lambda 底层是 Function
new Function<Long, User>() {
@Override
public Long apply(User user) {
return user.getId()
}
}
.select(user -> user.getId(), user -> user.getUsername(), user -> user.getInfo(), user -> user.getBalance());
MyBatisPlus
自定义 SQL
很多公司数据库代码不能写在业务层。所以我们可以写好复杂条件,然后去数据库层拼接
@Test
void testCustomSqlUpdate() {
// 1.更新条件
List<Long> ids = List.of(1L, 2L, 4L);
int amount = 200;
// 2.定义条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.in("id", ids);
// 3.调用自定义SQL方法
userMapper.updateBalanceByIds(wrapper, amount);
}
public interface UserMapper extends BaseMapper<User> {
//这里条件必须叫 ew
void updateBalanceByIds(@Param("ew") QueryWrapper<User> wrapper,@Param("amount") int amount);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mp.mapper.UserMapper">
<update id="updateBalanceByIds">
<!-- ${ew.customSQlSegment} 固定写法 拼接条件 这里是 in(...)-->
<!-- UPDATE tb_user SET balance = balance - ? WHERE (id IN (?,?,?))-->
UPDATE tb_user SET balance = balance - #{amount} ${ew.customSQlSegment}
</update>
</mapper>
MyBatisPlus
的 IService
接口
IService
接口使用
我们自己的接口需要继承
Iservice
接口,我们自己的实现类需要继承ServiceImpl
第一步:Service
接口实现 IService<T>
//IService<T> T 是实体类 entity
public interface IUserService extends IService<User> {
}
第二步:Service
实现类继承 ServiceImpl<M extends BaseMapper<T>, T>
//ServiceImpl<Mapper的类型, 实体类entity>
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService{
}
第三步:编写 SQL
@SpringBootTest
class IUserServiceTest {
@Autowired
private IUserService userservice;
@Test
void testSaveUser() {
User user = new User();
//user.setId(5L);
user.setUsername("LiLei");
user.setPassword("123");
user.setPhone("18688990013");
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, 4L));
users.forEach(System.out::println);
}
}
IService
开发基础业务接口
UserController
层
@Api(tags = "用户管理接口")
@RequestMapping("/users")
@RestController
//对一开始需要初始化的变量去做构造函数。配合注解
@RequiredArgsConstructor
public class UserController {
//final 必须要初始化。这里建议在构造函数初始化, 自动注入
private final IUserService userService;
@ApiOperation("新增用户接口")
@PostMapping
public void saveUser(@RequestBody UserFormDTO userDTO) {
// 1. 把DTO拷贝到PO 从哪里拷贝到哪里
User user = BeanUtil.copyProperties(userDTO, User.class);
// 2. 新增
userService.save(user);
}
@ApiOperation("根据id删除用户接口")
@DeleteMapping("{id}")
public void deleteUserById(@ApiParam("用户id") @PathVariable("id") Integer id) {
userService.removeById(id);
}
@ApiOperation("根据id查询用户接口")
@GetMapping("{id}")
public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Integer id) {
// 1. 查询用户 PO
User user = userService.getById(id);
// 2. 把 PO 拷贝到 VO
return BeanUtil.copyProperties(user, UserVO.class);
}
@ApiOperation("根据id批量查询用户接口")
@GetMapping
public List<UserVO> queryUserById(@ApiParam("用户id的集合") @RequestParam("ids") List<Long> ids) {
// 1. 查询用户 PO
List<User> users = userService.listByIds(ids);
// 2. 把 PO 拷贝到 VO
// 把原始list 里面的对象拷贝到新的list 里面 转为别的类型
return BeanUtil.copyToList(users, UserVO.class);
}
@ApiOperation("扣减用户余额接口")
@DeleteMapping("{id}/deduction/{money}")
public void deductBalance(
@ApiParam("用户id") @PathVariable("id") Integer id,
@ApiParam("扣减的金额") @PathVariable("money") Integer money) {
userService.deductMoneyById(id, money);
}
}
USerService
接口
public interface IUserService extends IService<User> {
/**
* 扣减用户余额
* @param id
* @param money
*/
void deductMoneyById(Integer id, Integer money);
}
UserService
实现类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService{
/**
* 扣减用户余额
* @param id
* @param money
*/
@Override
public void deductMoneyById(Integer 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("余额不足");
}
// Service 实现类可直接用 baseMapper 调用 Mapper 方法,无需手动注入。 因为 extends ServiceImpl
// 4.扣减余额 update tb_user set balance = balance - ? where id = ?
baseMapper.deductBalanceById(id, money);
}
}
UserMapper
层
public interface UserMapper extends BaseMapper<User> {
@Update("update tb_user set balance = balance - #{money} where id = #{id}")
void deductBalanceById(@Param("id") Integer id, @Param("money") Integer money);
}
IService
的 Lambda
查询与更改
常用条件方法
方法 | SQL 等价 | 说明 | 示例 |
---|---|---|---|
eq() | column = value | 等于 | .eq(User::getStatus, 1) |
ne() | column <> value | 不等于 | .ne(User::getRole, "admin") |
gt() | column > value | 大于 | .gt(User::getAge, 18) |
ge() | column >= value | 大于等于 | .ge(User::getScore, 60) |
lt() | column < value | 小于 | .lt(User::getBalance, 1000) |
le() | column <= value | 小于等于 | .le(User::getBalance, maxBalance) |
like() | column LIKE '%value%' | 模糊匹配 | .like(User::getName, "Tom") |
between() | column BETWEEN min AND max | 范围查询 | .between(User::getAge, 20, 30) |
in() | column IN (v1, v2...) | 包含在集合中 | .in(User::getId, idsList) |
Iservice
中的 LambdaQuery
动态查询
- 原始
MyBatis
MyBaitsPlus
改造
//就是动态SQL
@Override
public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
List<User> users = lambdaQuery().like(name != null, User::getUsername, name)
//第一个参数条件,第二个参数要查询的字段 username,第三个参数要传进来匹配的值 username 的值
.like(name != null, User::getUsername, name)
.eq(status != null, User::getStatus, status)
.between(
minBalance != null && maxBalance != null, // 强制参数共存
User::getBalance,
minBalance,
maxBalance
)
.list();
//- .one():最多1个结果
//- .list():返回集合结果
//- .count():返回计数结果
return users;
}
Iservice
中的 LambdaUpdate
- 传统
xml
<update id="updateUserBalanceAndStatus">
UPDATE tb_user
<set>
balance = #{remainBalance},
<if test="remainBalance == 0">
status = 2,
</if>
</set>
WHERE
id = #{id}
AND balance = #{user.balance} <!-- 乐观锁:基于旧值校验 -->
</update>
LambdaUpdate
改造
@Override
@Transactional
public void deductMoneyById(Integer 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("余额不足");
}
// Service 实现类可直接用 baseMapper 调用 Mapper 方法,无需手动注入。 因为 extends ServiceImpl
// 4.扣减余额 update tb_user set balance = balance - ? where id = ?
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()) //乐观锁 用户的 balance = 查到的 balance 乐观锁校验
.update();
}
IService
批量新增
普通 for
方案
这种方案会发起 10000 次网络请求,
mysql
内部有执行 10000 次查询。效率最低,不推荐
@Test
private User buildUser(int i) {
User user = new User();
user.setUsername("user_" + i);
user.setPassword("123");
user.setPhone("" + (186889900L + 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 = 0; i < 1000; i++) {
userservice.save(buildUser(i));
}
long e = System.currentTimeMillis();
System.out.println("耗时:" + (e - b));
}
Mp
批量处理方案
这里我们每
1000
条数据就发送一次网络请求过去。然后MySQL
再逐条执行。效率还是差一点
@Test
private User buildUser(int i) {
User user = new User();
user.setUsername("user_" + i);
user.setPassword("123");
user.setPhone("" + (186889900L + 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 = 0; i < 10000; i++) {
userservice.save(buildUser(i));
}
long e = System.currentTimeMillis();
System.out.println("耗时:" + (e - b));
}
@Test
void testSaveBatch() {
// 我们每次批量插入 1000 条数据, 插入 100此机 10万条数据
List<User> list = new ArrayList<>(1000);
long b = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
// 2.添加一个 user
list.add(buildUser(i));
// 3.每1000条批量插入一次 1000条数据之后打包到 mysql 。也就是 1000条数据只用一次网络请求
if (i % 1000 == 0) {
userservice.saveBatch(list);
// 4.清空集合 准备下一批数据
list.clear();
}
}
long e = System.currentTimeMillis();
System.out.println("耗时:" + (e - b));
}
Mp
批量处理配合 rewriteBatchedStatements=true
这里我们每
1000
条数据就发送一次网络请求过去。然后将多态SQL
合并未一条MySQL
只执行一次。效率最高INSERT INTO user ( username, password, phone, info, balance, create_time, update_time ) VALUES (user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01), (user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01), (user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01), (user_4, 123, 18688190004, "", 2000, 2023-07-01, 2023-07-01);
- 配置文件
yaml
//配置rewriteBatchedStatements=true
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: Ting123321
- 代码
@Test
private User buildUser(int i) {
User user = new User();
user.setUsername("user_" + i);
user.setPassword("123");
user.setPhone("" + (186889900L + 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 = 0; i < 10000; i++) {
userservice.save(buildUser(i));
}
long e = System.currentTimeMillis();
System.out.println("耗时:" + (e - b));
}
@Test
void testSaveBatch() {
// 我们每次批量插入 1000 条数据, 插入 100此机 10万条数据
List<User> list = new ArrayList<>(1000);
long b = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
// 2.添加一个 user
list.add(buildUser(i));
// 3.每1000条批量插入一次 1000条数据之后打包到 mysql 。也就是 1000条数据只用一次网络请求
if (i % 1000 == 0) {
userservice.saveBatch(list);
// 4.清空集合 准备下一批数据
list.clear();
}
}
long e = System.currentTimeMillis();
System.out.println("耗时:" + (e - b));
}
新增数据
save(T)
: 新增单条数据 → 返回操作是否成功 (boolean
)saveBatch(Collection<T>)
: 批量新增 → 返回是否成功saveBatch(Collection<T>, int batchSize)
: 指定批次大小的批量新增
删除数据
removeById(Serializable id)
: 根据 ID 删除单条数据removeByMap(Map条件)
: 根据 Map 条件删除removeByIds(Collection<?> ids)
: 批量删除(根据 ID 集合)removeBatchByIds(Collection<?>, int batchSize)
: 指定批次大小的批量删除
修改数据
updateById(T)
: 根据 ID 修改单条数据 → 返回是否成功saveOrUpdate(T)
: 存在则更新,否则新增(单条)saveOrUpdateBatch(Collection<T>)
: 批量新增或更新
查询数据
getById(Serializable id)
: 根据 ID 查询单条 → 返回实体 (T
)getOne(Wrapper<T>条件)
: 根据条件查询单条 → 返回实体listByIds(Collection<?> ids)
: 根据 ID 集合批量查询 → 返回列表 (List<T>
)listByMap(Map条件)
: 根据 Map 条件查询 → 返回列表
统计与分页
count()
: 统计总数据量 → 返回long
count(Wrapper<T>条件)
: 根据条件统计 → 返回long
page()
: 分页查询(需配合分页参数使用)
链式操作(高级)
lambdaQuery()
: 链式 Lambda 条件查询(如lambdaQuery().eq(...).list()
)lambdaUpdate()
: 链式 Lambda 条件更新(如lambdaUpdate().set(...).eq(...)
)
MyBatisPlus
拓展功能
MP
代码生成器
在使用
MybatisPlus
以后,基础的Mapper
、Service
、PO
代码相对固定,重复编写也比较麻烦。因此MybatisPlus
官方提供了代码生成器根据数据库表结构生成PO
、Mapper
、Service
等相关代码。只不过代码生成器同样要编码使用,也很麻烦。
安装插件
使用
- 首先需要配置配置数据库地址
- 然后选择
Code Generator
,填写信息
Mp
静态工具
示例一
需求:改造根据
id
用户查询的接口,查询用户的同时返回用户收货地址列表
- 收货地址
VO
@Data
@ApiModel(description = "收货地址VO")
public class AddressVO{
@ApiModelProperty("id")
private Long id;
@ApiModelProperty("用户ID")
private Long userId;
@ApiModelProperty("省")
private String province;
@ApiModelProperty("市")
private String city;
@ApiModelProperty("县/区")
private String town;
@ApiModelProperty("手机")
private String mobile;
@ApiModelProperty("详细地址")
private String street;
@ApiModelProperty("联系人")
private String contact;
@ApiModelProperty("是否是默认 1默认 0否")
private Boolean isDefault;
@ApiModelProperty("备注")
private String notes;
}
- 用户
VO
- 注意添加一个地址属性
List<AddressVO> addresses
- 注意添加一个地址属性
@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;
//对多关系。
@ApiModelProperty("用户的收获地址")
private List<AddressVO> addresses;
}
UserController
层
@ApiOperation("根据id查询用户接口")
@GetMapping("{id}")
public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Integer id) {
return userService.queryUserAndAddressById(id);
}
UserServiceImpl
层
@Override
public UserVO queryUserAndAddressById(Integer id) {
// 1.查询用户
User user = getById(id);
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常!");
}
// 2.查询地址
List<Address> 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)) {
List<AddressVO> addressVos = BeanUtil.copyToList(addresses, AddressVO.class);
userVO.setAddresses(addressVos);
}
return userVO;
}
示例二
根据
id
批量查询用户,并查询出用户对应的所有地址
- 收货地址
VO
@Data
@ApiModel(description = "收货地址VO")
public class AddressVO{
@ApiModelProperty("id")
private Long id;
@ApiModelProperty("用户ID")
private Long userId;
@ApiModelProperty("省")
private String province;
@ApiModelProperty("市")
private String city;
@ApiModelProperty("县/区")
private String town;
@ApiModelProperty("手机")
private String mobile;
@ApiModelProperty("详细地址")
private String street;
@ApiModelProperty("联系人")
private String contact;
@ApiModelProperty("是否是默认 1默认 0否")
private Boolean isDefault;
@ApiModelProperty("备注")
private String notes;
}
- 用户
VO
- 注意添加一个地址属性
List<AddressVO> addresses
- 注意添加一个地址属性
@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;
//对多关系。
@ApiModelProperty("用户的收获地址")
private List<AddressVO> addresses;
}
UserController
层
@ApiOperation("根据id查询用户接口")
@GetMapping("{id}")
public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Integer id) {
return userService.queryUserAndAddressById(id);
}
UserServiceImpl
层
@Override
public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {
// 1.查询用户
List<User> users = listByIds(ids);
if(CollUtil.isEmpty(users)) {
return Collections.emptyList();
}
// 2.查询地址
// 2.1.获取用户id集合
List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
// 2.2.根据用户id查询地址
List<Address> addresses = Db.lambdaQuery(Address.class)
.in(Address::getUserId, userIds)
.list();
// 2.3.转换地址VO
List<AddressVO> addressVOList = BeanUtil.copyToList(addresses, AddressVO.class);
// 2.4.梳理地址集合, 分类整理, 相同用户的放入一个集合中
Map<Long, List<AddressVO>> addressMap = new HashMap<>(0);
if (CollUtil.isNotEmpty(addressVOList)) {
addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
}
// 3.转换VO返回
List<UserVO> 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.转换Address的PO为VO
vo.setAddresses(addressMap.get(user.getId()));
}
return list;
}
MP
逻辑删除
或者直接在字段上面标注 @TableLogic
注解
- 配置逻辑删除字段
mybatis-plus:
mapper-locations: classpath*:mapper/*.xml
global-config:
db-config:
id-type: auto
logic-delete-field: deleted #配置逻辑删除字段
- 正常操作就进行逻辑删除
@SpringBootTest
class IAddressServiceTest {
@Autowired
private IAddressService addressService;
@Test
void testLogicDelete() {
// 1. 删除
addressService.removeById(59L);
// 2. 查询
Address adress = addressService.getById(59L);
System.out.println("address = " + adress);
}
}
Mp
枚举处理器
像
status
这种字段我们一般会定义一个枚举,做业务判断的时候就可以直接基于枚举做比较。但是我们数据库采用的是int
类型,对应的PO
也是Integer
。因此业务操作时必须手动把枚举
与Integer
转换,非常麻烦。因此,MybatisPlus
提供了一个处理枚举的类型转换器,可以帮我们把枚举类型与数据库类型自动转换。@EnumValue
表示把哪个字段往数据库写
- 定义枚举类
@Getter
public enum UserStatus {
NORMAL(1, "正常"),
FREEZE(2, "冻结")
;
private final int value;
private final String desc;
UserStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
}
- 修改
User POJO
枚举字段为枚举类型
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {
@ApiModelProperty("用户id")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("详细信息")
private String info;
//这里改为枚举类型
@ApiModelProperty("使用状态(1正常 2冻结)")
private UserStatus status;
@ApiModelProperty("账户余额")
private Integer balance;
@ApiModelProperty("用户的收获地址")
private List<AddressVO> addresses;
}
- 枚举类中用
EnumValue
标记哪个字段的值作为数据库的值
public enum UserStatus {
NORMAL(1, "正常"),
FROZEN(2, "异常");
@EnumValue
private final int value;
private final String desc;
UserStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
}
- 配置枚举处理器
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
- 枚举类用
@JsonValue
标记JSON
序列化时展示的字段- 如果没标记默认用枚举值
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;
}
}
Mp
的 Json
处理器
我们数据库
User
表中有一个info
字段,是JSON
类型吗,而对应User
实体类的字段确实String
类型,这样一来info
属性要转来转去很麻烦。所以可以用Mp
中的JacksonTypeHandler
处理器自动处理
- 定义一个单独的实体类和
Info
字段匹配
@Data
@NoArgsConstructor
//生成一个静态工厂方法调用全参构造
@AllArgsConstructor(staticName = "of")
public class UserInfo {
private Integer age;
private String intro;
private String gender;
}
- 在
User
实体类修改字段类型为UserInfo
类型,并声明类型处理器,同时声明自动映射
@Data
//声明自动映射
@TableName(value = "tb_user", autoResultMap = true)
public class User {
/**
* 用户id
*/
@TableId(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;
}
- 测试
@Test
void testInsert() {
User user = new User();
//user.setId(5L);
user.setUsername("ErGouZi");
user.setPassword("123");
user.setPhone("18688990013");
user.setBalance(200);
//这里正常插入数据就行,自动转为 JSON 类型
user.setInfo(UserInfo.of(24, "英文老师", "female"));
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
userMapper.insert(user);
}
Mp
插件功能
Mp
分页插件基本使用
- 新建配置类,配置分页插件
@Configuration
public class MyBatisConfig {
@Bean
public MybatisPlusInterceptor myPlusInterceptor() {
// 初始化核心插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 1.创建分页插件
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 1.1配置分页上限
paginationInnerInterceptor.setMaxLimit(1000L);
// 2.添加分页插件
interceptor.addInnerInterceptor(paginationInnerInterceptor);
//如果要添加其他插件,就先创建出来然后add进去就行
return interceptor;
}
}
- 开始分页查询
@Test
void testPageQuery() {
int pageNo = 1, pageSize = 2;
// 1.准备分页条件
Page<User> page = Page.of(pageNo, pageSize);
// 1.1. 添加排序条件:按照 balance 升序排序,相同再按照 id 升序排序
page.addOrder(new OrderItem("balance", true));
page.addOrder(new OrderItem("id", true));
// 2.开始分页查询
Page<User> p = userservice.page(page);
// 3.解析
long total = p.getTotal();
System.out.println("总记录数:" + total);
long pages = p.getPages();
System.out.println("总页数:" + pages);
List<User> records = p.getRecords();
records.forEach(System.out::println);
}
Mp
通用分页查询
UserQuery
:分页查询条件的实体,包含分页、排序参数、过滤条件PageDTO
:分页结果实体,包含总条数、总页数、当前页数据UserVO
:用户页面视图实体
- 创建
PageQuery
分页条件类,并让UserQuery
继承pageNo
:页码pageSize
:每页数据条数sortBy
:排序字段isAsc
:是否升序
其中缺少的仅仅是分页条件,而分页条件不仅仅用户分页查询需要,以后其它业务也都有分页查询的需求。因此建议将分页查询条件单独定义为一个
PageQuery
实体
@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {
@ApiModelProperty("页码")
private Long pageNo;
@ApiModelProperty("页码")
private Long pageSize;
@ApiModelProperty("排序字段")
private String sortBy;
@ApiModelProperty("是否升序")
private Boolean isAsc;
}
@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;
}
UserVO
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {
@ApiModelProperty("用户id")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("详细信息")
private UserInfo info;
@ApiModelProperty("使用状态(1正常 2冻结)")
private UserStatus status;
@ApiModelProperty("账户余额")
private Integer balance;
@ApiModelProperty("用户的收获地址")
private List<AddressVO> addresses;
}
- 分页实体
PageDTO
@Data
@ApiModel(description = "分页结果")
public class PageDTO<T> {
@ApiModelProperty("总条数")
private Long total;
@ApiModelProperty("总页数")
private Long pages;
@ApiModelProperty("集合")
private List<T> list;
}
Controller
层
@ApiOperation("根据复杂条件分页查询接口")
@GetMapping("/page")
public PageDTO<UserVO> queryUsersPage(UserQuery query) {
return userService.queryUserPage(query);
}
UserServiceImpl
层
/**
* 根据复杂条件分页查询接口
* @param query
* @return
*/
@Override
public PageDTO<UserVO> queryUserPage(UserQuery query) {
String name = query.getName();
Integer status = query.getStatus();
// 1.构建查询条件
// 1.1.分页条件
Page<User> page = Page.of(query.getPageNum(), query.getPageSize());
if (StrUtil.isNotBlank(query.getSortBy())) {
//不为空
page.addOrder(new OrderItem(query.getSortBy(), query.getIsAsc()));
} else {
//为空, 默认按照更新时间排序
page.addOrder(new OrderItem("update_time", false));
}
// 2.分页查询
lambdaQuery()
.like(name != null, User::getUsername, name)
.eq(status != null, User::getStatus, status)
.page(page);
// 3.封装VO结果
PageDTO<UserVO> dto = new PageDTO<>();
// 3.1. 设置总条数
dto.setTotal(page.getTotal());
// 3.2. 设置总页数
dto.setPages(page.getPages());
// 3.3. 设置当前页数据
List<User> records = page.getRecords();
if (CollUtil.isEmpty(records)) {
dto.setList(Collections.emptyList());
return dto;
}
// 3.4 拷贝 user 的VO
List<UserVO> vos = BeanUtil.copyToList(records, UserVO.class);
dto.setList(vos);
// 4.返回
return dto;
}
MP
通用分页实体改造
PageQuery
@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {
@ApiModelProperty("页码")
private Integer pageNum = 1;
@ApiModelProperty("每页条数")
private Integer pageSize = 5;
@ApiModelProperty("排序字段")
private String sortBy;
@ApiModelProperty("是否升序")
private Boolean isAsc = true;
public <T> Page<T> toMpPage(OrderItem ... items) {
// 1.分页条件
Page<T> page = Page.of(pageNum, pageSize);
// 2.排序条件
if (StrUtil.isNotBlank(sortBy)) {
//不为空
page.addOrder(new OrderItem(sortBy, isAsc));
} else if(items != null){
//为空, 默认按照更新时间排序
page.addOrder(items);
}
return page;
}
public <T> Page<T> toMoPage(String defaultSortBy, Boolean defaultAsc) {
return toMpPage(new OrderItem("create_time", defaultAsc));
}
public <T> Page<T> toMoPageDefaultSortByCreateTime() {
return toMpPage(new OrderItem("create_time", false));
}
public <T> Page<T> toMoPageDefaultSortByUpdateTime() {
return toMpPage(new OrderItem("update_time", false));
}
}
PageDTO
@Data
@ApiModel(description = "分页结果")
public class PageDTO<T> {
@ApiModelProperty("总条数")
private Long total;
@ApiModelProperty("总页数")
private Long pages;
@ApiModelProperty("集合")
private List<T> list;
public static <PO, VO> PageDTO<VO> of(Page<PO> p, Function<PO, VO> convertor) {
PageDTO<VO> dto = new PageDTO<>();
// 3.1. 设置总条数
dto.setTotal(p.getTotal());
// 3.2. 设置总页数
dto.setPages(p.getPages());
// 3.3. 设置当前页数据
List<PO> 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()));
// 4.返回
return dto;
}
}
UserServiceImpl
@Override
public PageDTO<UserVO> queryUserPage(UserQuery query) {
String name = query.getName();
Integer status = query.getStatus();
// 1.构建查询条件
Page<User> page = query.toMoPageDefaultSortByUpdateTime();
// 2.分页查询
Page<User> p = lambdaQuery()
.like(name != null, User::getUsername, name)
.eq(status != null, User::getStatus, status)
.page(page);
// 3.封装 VO 结果
return PageDTO.of(p, user -> {
// 1.拷贝基础属性
UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
// 2.处理特殊逻辑
vo.setUsername(user.getUsername().substring(0, vo.getUsername().length() -2) + "**");
return vo;
});
}