自己做的网站打开超慢/b2b免费发布平台
文章目录
- ` 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
使用示例
//增@Testvoid 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);}//查@Testvoid testQueryByIds() {List<User> users = userMapper.selectBatchIds(List.of(1L, 2L, 3L, 4L));users.forEach(System.out::println);}//改@Testvoid testUpdateById() {User user = new User();user.setId(5L);user.setBalance(20000);userMapper.updateById(user);}//查@Testvoid testSelectById() {User user = userMapper.selectById(5L);System.out.println("user = " + user);} //删@Testvoid 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
条件构造器
@Testvoid 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
) -
…
@Testvoid 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);}
@Testvoid 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
)- …
@Testvoid testQueryWrapper2() {User user = new User();UpdateWrapper<User> wrapper = new UpdateWrapper<User>().set("balance", 2000).eq("username", "jack");int update = userMapper.update(user, wrapper);}
@Testvoid 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
示例
@Testvoid 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 底层是 Functionnew Function<Long, User>() {@Overridepublic Long apply(User user) {return user.getId()}} .select(user -> user.getId(), user -> user.getUsername(), user -> user.getInfo(), user -> user.getBalance());
MyBatisPlus
自定义 SQL
很多公司数据库代码不能写在业务层。所以我们可以写好复杂条件,然后去数据库层拼接
@Testvoid 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> {//这里条件必须叫 ewvoid 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 {@Autowiredprivate IUserService userservice;@Testvoid 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);}@Testvoid 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("新增用户接口")@PostMappingpublic 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. 查询用户 POUser user = userService.getById(id);// 2. 把 PO 拷贝到 VOreturn BeanUtil.copyProperties(user, UserVO.class);}@ApiOperation("根据id批量查询用户接口")@GetMappingpublic List<UserVO> queryUserById(@ApiParam("用户id的集合") @RequestParam("ids") List<Long> ids) {// 1. 查询用户 POList<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*/@Overridepublic 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
@Overridepublic 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>WHEREid = #{id}AND balance = #{user.balance} <!-- 乐观锁:基于旧值校验 -->
</update>
LambdaUpdate
改造
@Override@Transactionalpublic 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 次查询。效率最低,不推荐
@Testprivate 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;}@Testvoid 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
再逐条执行。效率还是差一点
@Testprivate 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;}@Testvoid 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));}@Testvoid testSaveBatch() {// 我们每次批量插入 1000 条数据, 插入 100此机 10万条数据List<User> list = new ArrayList<>(1000);long b = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {// 2.添加一个 userlist.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=truedriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: Ting123321
- 代码
@Testprivate 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;}@Testvoid 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));}@Testvoid testSaveBatch() {// 我们每次批量插入 1000 条数据, 插入 100此机 10万条数据List<User> list = new ArrayList<>(1000);long b = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {// 2.添加一个 userlist.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
层
@Overridepublic 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为VOUserVO userVO = BeanUtil.copyProperties(user, UserVO.class);// 3.2.转地址VOif(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
层
@Overridepublic 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.转换地址VOList<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为VOUserVO vo = BeanUtil.copyProperties(user, UserVO.class);list.add(vo);// 3.2.转换Address的PO为VOvo.setAddresses(addressMap.get(user.getId()));}return list;}
MP
逻辑删除
或者直接在字段上面标注 @TableLogic
注解
- 配置逻辑删除字段
mybatis-plus:mapper-locations: classpath*:mapper/*.xmlglobal-config:db-config:id-type: autologic-delete-field: deleted #配置逻辑删除字段
- 正常操作就进行逻辑删除
@SpringBootTest
class IAddressServiceTest {@Autowiredprivate IAddressService addressService;@Testvoid 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, "异常");@EnumValueprivate 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, "异常");@EnumValueprivate final int value;@JsonValueprivate 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;
}
- 测试
@Testvoid 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 {@Beanpublic 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;}
}
- 开始分页查询
@Testvoid 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*/@Overridepublic 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 的VOList<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 的VOdto.setList(records.stream().map(convertor).collect(Collectors.toList()));// 4.返回return dto;}}
UserServiceImpl
@Overridepublic 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;});}