快速入门
简介
在项目开发中,Mybatis已经为我们简化了代码编写。
但是我们仍需要编写很多单表CURD语句,MybatisPlus可以进一步简化Mybatis。
MybatisPlus官方文档:https://www.baomidou.com/,感谢苞米豆和黑马程序员。
MybatisPlus无侵入的提供了代码生成、自动分页、逻辑删除、自动填充等功能。
需要注意,MybatisPlus提高了单表查询开发效率,复杂的多表查询还需要结合Mybatis配置文件使用。
引入依赖
MybatisPlus提供了starter,包含Mybatis以及MybatisPlus的相关依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
定义Mapper
为了简化单表CURD,MybatisPlus提供了BaseMapper接口。
BaseMapper实现了单表CURD,我们定义的Mapper接口只需要继承它:
package com.test.mp.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.mp.domain.po.User;
public interface UserMapper extends BaseMapper<User> {
}
这样,对于单表简单的CURD,我们不再需要编写SQL语句。
原理
MybatisPlus是如何判断我们查询的表和字段名呢?
MybatisPlus通过扫描实体类,基于反射获取实体类信息作为数据库表信息。
在定义Mapper继承BaseMapper<User>时,我们指定了泛型<User>。
这个泛型就是数据库对应的POJO,MybatisPlus就是根据这个泛型进行推断生成SQL:
- 把POJO的类名驼峰转下划线作为表名
- 把POJO所有变量名驼峰转下划线作为字段名,并根据变量类型推断字段类型
- 把名为id的字段作为主键
常用注解
@TableName
如果POJO类名不符合约定,与表明不一致。
可以使用@TableName注解指定表名。
例如,POJO类名为UserMapper,实际的数据库表名为tb_user:
package com.test.mp.domain.po;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("tb_user")
public class User {
private Long id;
private String username;
private String password;
private String phone;
private String info;
private Integer status;
private Integer balance;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
@TableId
如果POJO类里没有名为id的成员变量,或者主键不是id。
可以使用@TableId注解指定主键成员变量,它有2个属性:
- value:数据库主键字段名(成员变量名和数据库字段名不一致时使用)
- type:主键的策略(默认为雪花算法,常用IdType.NONE)
其中,常用的type有如下三种:
- AUTO:数据库自增长,即AUTO_INCREMENT。
- INPUT:通过set方法自行输入。
- ASSIGN_ID:分配ID,接口identifierGenerator的方法nextId来生成id,默认实现类为DefaultIdentifierGenerator雪花算法。
(通常情况下,我们按照MybatisPlus规范定义POJO和数据库字段,因此只需要指定@TableId(type=IdType.AUTO)
例如:
package com.test.mp.domain.po;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("tb_user")
public class User {
@TableId(value="uid", type=IdType.AUTO)
private Long uid;
private String username;
private String password;
private String phone;
private String info;
private Integer status;
private Integer balance;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
@TableField
和TableId的用途一样,只不过不指定主键。只指定成员变量和数据库字段名对应关系。
除此之外,有3种特殊情况也需要用到@TableField注解:
- 成员变量是Boolean类型且以is开头,如isMarried。MybatisPlus通过反射获取成员变量名时会去掉is,得到Married。
- 成员变量名和sql关键字冲突,如order。
- 成员变量不是数据库字段。
因此,一般不建议成员变量使用is开头或与sql关键字冲突。如果非要这么使用,需要用@TableField注解指定和数据库字段的映射关系。
例如:
package com.itheima.mp.domain.po;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
private Long id;
private String username;
private String password;
@TableField("`is_married`")
private boolean isMarried;
@TableField("`order`")
private Integer orderr;
@TableField(exist=false)
private Integer notInTable;
}
常见配置
MybatisPlus的配置项继承了Mybatis原生配置,并在此基础上新增自己的配置。如:
mybatis-plus:
type-aliases-package: com.test.mp.domain.pojo # 别名扫描包
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 # 更新策略:只更新非空字段
核心功能
MybatisPlus为我们提供的CURD是根据id为条件的,如果我们需要复杂条件的CURD,需要用到一些核心功能。
条件构造器
在MybatisPlus提供的BaseMapper接口按ctrl+f12,发现很多方法的参数有Wrapper类型:
选中代码中的Wrapper类名按ctrl+h显示所有子类,右击Wrapper选择Diagrams -> Show Diagrams Popup。
然后右击Wrapper选择Show Implementations查看继承关系图,这些Wrapper就是条件构造器:
QueryWrapper
QueryWrapper常用于构建select、delete、update的where条件部分。
我们以QueryWrapper为例,查询名字中带o的且存款大于等于1000元的用户的id、username、info和balance字段:
SELECT id, username, info, balance From user WHERE username LIKE ? AND balance >= ?;
@Test
void testQueryWrapper() {
// 1.构建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.select("id", "username", "info", "balance")
.like("username", "o")
.ge("balance", 1000);
// 2.查询
List<User> 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<User> wrapper = new QueryWrapper<User>()
.eq("username", "jack");
// 3.执行更新
userMapper.update(user, wrapper);
}
UpdateWrapper
UpdateWrapper通常只有在set语句比较特殊才使用。
我们以UpdateWrapper为例,更新id为1, 2, 4的用户的余额,扣200。
UPDATE user SET balance = balance - 200 WHERE id in (1, 2, 4);
@Test
void testUpdateByWrapper() {
// 1. 要更新的id集合
List<Long> ids = List.of(1L, 2L, 4L);
// 2. 设置set语句并构建更新条件
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance -200")
.in("id", ids);
// 3. 更新
userMapper.update(null, wrapper);
}
LambdaWrapper
LambdaWrapper包括:LambdaQueryWrapper和LambdaUpdateWrapper。
我们以LambdaQueryWrapper为例,前面使用QueryWrapper时,我们使用了字符串硬编码。
为了避免硬编码,MybatisPlus推荐使用LambdaQueryWrapper,通过lambda方法引用来调用:
@Test
void testLambdaQueryWrapper() {
// 1.构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.select(User::getId, User::getUsername, User::getInfo, User::getBalance)
.like(User::getUsername, "o")
.ge(User::getBalance, 1000);
// 2.查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
自定义SQL
在前面的UpdateWrapper中,我们将set更新的sql语句写在了业务层代码中,不符合实际开发低耦合的规范。
比较常见的用法是用MybatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分。
(对于多表操作,也可以用这种方法构建Where条件,然后编写剩余部分的SQL语句)
例如,我们将id在指定范围内的用户的余额扣减指定值:
-
基于Wrapper构建where条件:
@Test void testCustomSqlUpdate() { // 1.更新条件 List<Long> ids = List.of(1L, 2L, 4L); int amount = 200; // 2.定义条件 LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>() .in(User::getId, ids); // 3.调用自定义SQL方法 userMapper.updateBanlanceByIds(wrapper, amount); }
-
在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew:
// 注解声明的Wrapper的变量名称必须是ew,也可以使用常量值Constants.WRAPPER public interface UserMapper extends BaseMapper<User> { void updateBanlanceByIds(@Param(Constants.WRAPPER) LambdaQueryWrapper<User> wrapper, @Param("amount") int amount); }
-
自定义SQL,并使用传递下来的Wrapper条件:
<update id="updateBanlanceByIds"> UPDATE user SET balance = balance - #{amount} ${ew.customSqlSegment} </update>
Service接口
原理
MybatisPlus已经帮我们简化了单表CURD操作,但我们还需要写Service层代码。
为了进一步简化,MybatisPlus为我们提供了IService接口,这个接口提供了简单CURD的Service抽象方法。
我们定义的UserService接口可以继承IService,IService包含简单CURD的抽象方法。但是会出现一个问题。
IService接口非常多,UserServiceImpl在实现接口时需要实现全部方法,如下图所示:
为了解决这个问题,MybatisPlus又为我们提供了ServiceImpl实现Iservice所有的抽象方法,我们的UserServiceImpl只需要继承它即可:
基本用法
下面通过一个例子来演示IService:
-
定义IUserService接口继承IService接口,并指定泛型为POJO类:
package com.test.mp.service; import com.baomidou.mybatisplus.extension.service.IService; import com.test.mp.domain.po.User; public interface IUserService extends IService<User> { }
-
定义IUserServiceImpl类实现IUserService接口,然后继承ServiceImpl类并指定Mapper和POJO的泛型:
package com.test.mp.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.test.mp.domain.po.User; import com.test.mp.mapper.UserMapper; import com.test.mp.service.IUserService; import org.springframework.stereotype.Service; @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { }
-
测试,简单CURD无需编写Service和Mapper代码:
@SpringBootTest class IUserServiceTest { @Autowired private IUserService userService; @Test void testSaveUser() { User user = new User(); user.setUsername("Test"); user.setPassword("123456"); user.setPhone("18888888888"); user.setBalance(200); 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); } }
基础用法
下面以一个案例来演示MybatisPlus的强大的功能:实现对用户表的新增、删除、查询。
-
为了方便接口调试,我们引入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>
-
然后在Controller编写,对于简单的增、删、查,IService已经为我们封装好了相关方法。
我们不需要再编写Service和Mapper代码就可以实现这些功能:
@Api(tags = "用户管理接口") @RequestMapping("/users") @RestController @RequiredArgsConstructor public class UserController { // @RequiredArgsConstructor注解通过构造函数给userService赋值 private final IUserService userService; @ApiOperation("新增用户接口") @PostMapping public void saveUser(@RequestBody UserFormDTO userDTO) { User user = BeanUtil.copyProperties(userDTO, User.class); userService.save(user); } @ApiOperation("删除用户接口") @DeleteMapping("/{id}") public void deleteUser(@ApiParam("用户id") @PathVariable("id") Long id) { userService.removeById(id); } @ApiOperation("根据id查询用户接口") @GetMapping("/{id}") public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id) { User user = userService.getById(id); return BeanUtil.copyProperties(user, UserVO.class); } @ApiOperation("根据id批量查询用户接口") @GetMapping public List<UserVO> queryUserByIds(@ApiParam("用户id") @RequestParam("ids") List<Long> ids) { List<User> users = userService.listByIds(ids); return BeanUtil.copyToList(users, UserVO.class); } }
自定义用法
对于一些比较复杂的查询,还需要我们自定义Service和Mapper代码。
以这样一个案例为例:扣减用户余额。
-
由于不是常规查询,因此我们在Controller编写代码调用自定义的Service方法。
@Api(tags = "用户管理接口") @RequestMapping("/users") @RestController @RequiredArgsConstructor public class UserController { // @RequiredArgsConstructor注解通过构造函数给userService赋值 private final IUserService userService; @ApiOperation("扣减用户余额接口") @GetMapping("/{id}/deduction/{money}") public void deductMoneyById(@ApiParam("用户id") @PathVariable("id") Long id, @ApiParam("扣减的金额") @PathVariable("money") Integer money) { userService.deductBalance(id, money); } }
-
然后在ServiceImpl实现自定义的方法。首先判断是否合法,然后调用Mapper中自定义的方法:
@Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Override public void deductBalance(Long id, Integer money) { User user = getById(id); if (user == null || user.getStatus() == 2) { throw new RuntimeException("用户状态异常!"); } if (user.getBalance() < money) { throw new RuntimeException("用户余额不足!"); } baseMapper.deductBalance(id, money); } }
-
然后在Mapper中定义SQL语句,复杂的可以使用Wrapper,简单的可以使用注解或xml:
public interface UserMapper extends BaseMapper<User> { @Update("UPDATE user SET balance = balance - #{money} WHERE id = #{id}") void deductBalance(@Param("id") Long id, @Param("money") Integer money); }
Lambda查询
对于条件查询,以往我们需要写xml判断某个参数是否为空。MybatisPlus也为我们提供了简便用法Lambda以省略mapper编写。
现在,我们以这样一个案例为例:根据name、status、minBalance、maxBalance模糊查询用户。
-
在Controller编写代码调用Service层自定义方法:
@Api(tags = "用户管理接口") @RequestMapping("/users") @RestController @RequiredArgsConstructor public class UserController { private final IUserService userService; @ApiOperation("根据复杂条件查询用户接口") @GetMapping("/list") public List<UserVO> queryUsers(UserQuery query) { List<User> users = userService.queryUsers(query.getName(), query.getStatus(), query.getMinBalance(), query.getMaxBalance()); return BeanUtil.copyToList(users, UserVO.class); } }
-
在ServiceImpl定义方法,可以通过查询方法的链式调用进行查询。
对于like、eq、gt、lt等方法,如果传入三个参数,第一个参数为condition。
最后通过one、list、page、count等方法获取查询结果。
@Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Override public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) { return lambdaQuery() .like(name != null, User::getUsername, name) .eq(status != null, User::getStatus, status) .gt(minBalance != null, User::getBalance, minBalance) .lt(maxBalance != null, User::getBalance, maxBalance) .list(); } }
Lambda更新
我们以这样一个需求为例:根据id扣减用户余额,完成对用户状态、余额校验。如果扣减后余额为0,修改status为2。
前面我们已经完成了deductBalance业务流程,现在我们只需要修改service层对mapper调用为lambda即可。无需再写mapper代码。
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public void deductBalance(Long id, Integer money) {
User user = getById(id);
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常!");
}
if (user.getBalance() < money) {
throw new RuntimeException("用户余额不足!");
}
int remainBalance = user.getBalance() - money;
lambdaUpdate()
.set(User::getBalance, remainBalance)
.set(remainBalance == 0, User::getStatus, 2)
.eq(User::getId, id)
.update();
}
}
但是这里还存在一个问题,如果有多个线程执行这个方法。如:ThreadA(扣100)、ThreadB(扣100)。
若同时执行到remainBalance,最终得到的remainBlance相同。最终两个线程只有一个生效。
因此,这里需要加线程锁。可以是乐观锁,也可以是悲观锁。
我们这里采用乐观锁(先比较再更新),在执行update前先使用.eq(User::getBalance, user.getBalance())判断。
如果条件不成立,说明已经有其它线程对Balance进行修改。当前update就失败了。
@Service
@Transactional
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public void deductBalance(Long id, Integer money) {
User user = getById(id);
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常!");
}
if (user.getBalance() < money) {
throw new RuntimeException("用户余额不足!");
}
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();
}
}
拓展功能
代码生成
前面,我们根据数据库表定义POJO、Mapper接口、Service接口、ServiceImpl和Controller。
对于不同的数据库表,定义的模板文件是相同的,区别就在于POJO字段名和文件的接口名及类名。
MybatisPlus为我们提供了代码生成器,可以根据数据库表一键生成这些文件:
-
安装MyabtisPlus插件:
-
然后打开菜单栏->Other->Config Database配置数据库信息:
jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
-
然后再次打开菜单栏->Other->Code Generator:
-
点击右下角code generatro即可生成相应的文件:
静态工具
IService接口提供的方法都是非静态方法,不同Service之间相互调用可能会导致循环依赖。
MybatisPlus为我们提供了DB静态工具,其中的方法为静态方法,避免了循环依赖问题,DB调用时需要传入Class参数。
第一个案例:根据id查询用户并查询用户的所有地址。
@Override
public UserVO queryUserAndAddressById(Long id) {
User user = getById(id);
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常!");
}
List<Address> addresses = Db.lambdaQuery(Address.class)
.eq(Address::getUserId, id)
.list();
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
if (CollUtil.isNotEmpty(addresses)) {
userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
}
return userVO;
}
第二个案例:批量查询用户并查询用户的所有地址。
@Override
public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {
// 根据id查询user
List<User> users = listByIds(ids);
if (CollUtil.isEmpty(users)) {
return Collections.emptyList();
}
// 获取用户id集合
List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
// 根据id查询地址
List<Address> addresses = Db.lambdaQuery(Address.class)
.in(Address::getUserId, userIds)
.list();
// 转换地址为VO
List<AddressVO> addressVOList = BeanUtil.copyToList(addresses, AddressVO.class);
// 分类整理地址集合:相同用户的放入一个集合
Map<Long, List<AddressVO>> addressMap = new HashMap<>(0);
if (CollUtil.isNotEmpty(addressVOList)) {
addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
}
// 转换VO返回
List<UserVO> list = new ArrayList<>(users.size());
for (User user : users) {
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
list.add(userVO);
userVO.setAddresses(addressMap.get(user.getId()));
}
return list;
}
逻辑删除
日常生活中,我们在网站上执行删除操作,实际上是软删除。只是在数据库中将数据标记为删除,并不是真正的从数据库中删除。
具体实现方法是在表中添加一个字段标记数据是否被删除,当删除数据时把标记置为1,查询时只查询标记为0的数据。
例如:逻辑删除字段为deleted
查询操作:
SELECT * FROM user WHERE deleted = 0;
删除操作:
UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0;
MybatisPlus提供了逻辑删除功能,无需改变方法调用方式,而是在底层帮我们自动修改CURD语句。
我们需要做的就是在application.yaml文件中配置逻辑删除的字段名称和值即可。
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名,字段类型可以是boolean、integer
logic-delete-value: 1 # 逻辑删除值(默认为1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为0)
枚举处理器
如果POJO类中有一个枚举类型的成员变量,如:
@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;
}
}
@Data
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
private String phone;
private String info;
private Integer status;
private Integer balance;
private UserStatus status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
对于status成员变量,数据库字段的类型是INT,POJO的类型的enum。
Mybatis有一个TypeHandler接口,它可以实现数据库和Java类型的转换。但它不能将Enum类型和数据库的类型进行转换。
MybatisPlus提供了MybatisEnumTypeHandler处理器,以实现Enum类型和数据库类的转换,用法如下:
-
在application.yml配置:
mybatis-plus: configuration: default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
-
在enum类上加@EnumValue注解:
@Getter public enum UserStatus { NORMAL(1, "正常"), FREEZE(2, "冻结") ; @EnumValue private final int value; private final String desc; UserStatus(int value, String desc) { this.value = value; this.desc = desc; } }
此时,再次查询会默认返回枚举项,如:NORMAL、FREEZE。
如果想返回value或desc,可以在对应的成员变量上加@JsonValue注解。
JSON处理器
数据库字段有一个冷门的类型:json。在Java对应的POJO中我们一般用String成员变量映射这个字段。
Mybatis也为我们提供了自动从数据库的json转换为Java的字符串的功能。如:
但是在Java中String类型不能直接访问json成员,为了解决这个问题,我们可以根据Json定义一个实体类:
为了实现实体类和数据库json类型的转换,MybatisPlus提供了一个Json类型处理器。用法如下:
在对应的字段的@TableFirld注解上加入typeHandler = JacksonTypeHandler.class属性。
但是这样需要在所有json字段都加注解,非常繁琐。我们可以采用另一种方法:
在@TableName注解上加入autoResultMap = true属性。
插件功能
MybatisPlus提供的内置拦截器有:多租户插件、动态表名插件、分页插件、乐观锁插件、SQL性能规范插件等。
比较常用的插件就是分页插件,之前我们通过pageHelper实现分页,下面将介绍MybatisPlus分页插件的用法。
分页插件
首先,要在配置类中注册MybatisPlus的核心插件,同时添加分页插件:
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 初始化核心插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
然后编写分页查询代码:
@Test
void testPageQuery() {
// 1.分页查询,new Page()的两个参数分别是:页码、每页大小
Page<User> p = userService.page(new Page<>(2, 2));
// 2.总条数
System.out.println("total = " + p.getTotal());
// 3.总页数
System.out.println("pages = " + p.getPages());
// 4.数据
List<User> records = p.getRecords();
records.forEach(System.out::println);
}
也支持排序:
int pageNo = 1, pageSize = 5;
// 分页参数
Page<User> page = Page.of(pageNo, pageSize);
// 排序参数, 通过OrderItem来指定
page.addOrder(new OrderItem("balance", false));
userService.page(page);
通用分页实体
很多业务都有分页查询需求,因此我们可以为分页查询定义一个通用的实体PageQuery:
@Data
public class PageQuery {
private Integer pageNo;
private Integer pageSize;
private String sortBy;
private Boolean isAsc;
public <T> Page<T> toMpPage(OrderItem ... orders){
// 1.分页条件
Page<T> p = Page.of(pageNo, pageSize);
// 2.排序条件
// 2.1.先看前端有没有传排序字段
if (sortBy != null) {
p.addOrder(new OrderItem(sortBy, isAsc));
return p;
}
// 2.2.再看有没有手动指定排序字段
if(orders != null){
p.addOrder(orders);
}
return p;
}
public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc){
return this.toMpPage(new OrderItem(defaultSortBy, isAsc));
}
public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {
return toMpPage("create_time", false);
}
public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {
return toMpPage("update_time", false);
}
}
然后定义一个返回结果的通用实体PageDTO:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageDTO<V> {
private Long total;
private Long pages;
private List<V> list;
/**
* 返回空分页结果
* @param p MybatisPlus的分页结果
* @param <V> 目标VO类型
* @param <P> 原始PO类型
* @return VO的分页对象
*/
public static <V, P> PageDTO<V> empty(Page<P> p){
return new PageDTO<>(p.getTotal(), p.getPages(), Collections.emptyList());
}
/**
* 将MybatisPlus分页结果转为 VO分页结果
* @param p MybatisPlus的分页结果
* @param voClass 目标VO类型的字节码
* @param <V> 目标VO类型
* @param <P> 原始PO类型
* @return VO的分页对象
*/
public static <V, P> PageDTO<V> of(Page<P> p, Class<V> voClass) {
// 1.非空校验
List<P> records = p.getRecords();
if (records == null || records.size() <= 0) {
// 无数据,返回空结果
return empty(p);
}
// 2.数据转换
List<V> vos = BeanUtil.copyToList(records, voClass);
// 3.封装返回
return new PageDTO<>(p.getTotal(), p.getPages(), vos);
}
/**
* 将MybatisPlus分页结果转为 VO分页结果,允许用户自定义PO到VO的转换方式
* @param p MybatisPlus的分页结果
* @param convertor PO到VO的转换函数
* @param <V> 目标VO类型
* @param <P> 原始PO类型
* @return VO的分页对象
*/
public static <V, P> PageDTO<V> of(Page<P> p, Function<P, V> convertor) {
// 1.非空校验
List<P> records = p.getRecords();
if (records == null || records.size() <= 0) {
// 无数据,返回空结果
return empty(p);
}
// 2.数据转换
List<V> vos = records.stream().map(convertor).collect(Collectors.toList());
// 3.封装返回
return new PageDTO<>(p.getTotal(), p.getPages(), vos);
}
}
以后业务需要的分页查询和分页结果可以继承这两个通用实体。