一文读懂:MybatisPlus从入门到进阶
快速入门
简介
在项目开发中,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 {@Autowiredprivate IUserService userService;@Testvoid 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);}@Testvoid 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("新增用户接口")@PostMappingpublic 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批量查询用户接口")@GetMappingpublic 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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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查询userList<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();// 转换地址为VOList<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、integerlogic-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, "冻结");@EnumValueprivate 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 {@Beanpublic 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);}
}
以后业务需要的分页查询和分页结果可以继承这两个通用实体。
相关文章:

一文读懂:MybatisPlus从入门到进阶
快速入门 简介 在项目开发中,Mybatis已经为我们简化了代码编写。 但是我们仍需要编写很多单表CURD语句,MybatisPlus可以进一步简化Mybatis。 MybatisPlus官方文档:https://www.baomidou.com/,感谢苞米豆和黑马程序员。 Mybat…...

C语言--------指针(1)
0.指针&指针变量 32位平台,指针变量是4个字节(32bit/84)--------x86 64位平台,指针变量是8个字节(64bit/88)--------x64 编号指针地址;我们平常讲的p是指针就是说p是一个指针变量; ************只要…...
Vite 下一代的前端工具链,前端开发与构建工具
一、Vite 简介 官方中文网站:Vite | 下一代的前端工具链 官方定义: Vite,下一代的前端工具链,为开发提供极速响应。 Vue3.4版本,Vue新版本使用Vite构建、开发、调试、编译。 Vite的优势 极速的服务启动 使用原生…...

【SpringBoot】FreeMarker视图渲染
目录 一、FreeMarker 简介 1.1 什么是FreeMarker? 1.2 Freemarker模板组成部分 1.3 为什么要使用FreeMarker 二、Springboot集成FreeMarker 2.1 配置 2.2 数据类型 2.2.1 字符串 2.2.2 数值 2.2.3 布尔值 2.2.4 日期 2.3 常见指令 2.3.2 assign 2.3…...

巴尔加瓦算法图解:算法运用。
树 如果能将用户名插入到数组的正确位置就好了,这样就无需在插入后再排序。为此,有人设计了一种名为二叉查找树(binary search tree)的数据结构。 每个node的children 都不大于两个。对于其中的每个节点,左子节点的值都比它小,…...

Docker的镜像和容器的区别
1 Docker镜像 假设Linux内核是第0层,那么无论怎么运行Docker,它都是运行于内核层之上的。这个Docker镜像,是一个只读的镜像,位于第1层,它不能被修改或不能保存状态。 一个Docker镜像可以构建于另一个Docker镜像之上&…...

忘记 RAG:拥抱Agent设计,让 ChatGPT 更智能更贴近实际
RAG(检索增强生成)设计模式通常用于开发特定数据领域的基于实际情况的ChatGPT。 然而,重点主要是改进检索工具的效率,如嵌入式搜索、混合搜索和微调嵌入,而不是智能搜索。 这篇文章介绍了一种新的方法,灵感…...

利用路由懒加载和CDN分发策略,对Vue项目进行性能优化
目录 一、Vue项目 二、路由懒加载 三、CDN分发策略 四、如何对Vue项目进行性能优化 一、Vue项目 Vue是一种用于构建用户界面的JavaScript框架,它是一种渐进式框架,可以用于构建单页应用(SPA)和多页应用。Vue具有简单易学、灵…...

【Scala】1. 变量和数据类型
1. 变量和数据类型 1.1 for begining —— hello world 新建hello.scala文件,注意object名字与文件名一致。 object hello { def main(args:Array[String]): Unit { println("hello world!") } }运行后打印结果如下: hello world!Pr…...

何时以及如何选择制动电阻
制动电阻的选择是优化变频器应用的关键因素 制动电阻器在变频器中是如何工作的? 制动电阻器在 VFD 应用中的工作原理是将电机减速到驱动器设定的精确速度。它们对于电机的快速减速特别有用。制动电阻还可以将任何多余的能量馈入 VFD,以提升直流母线上的…...

消息中间件:Puslar、Kafka、RabbigMQ、ActiveMQ
消息队列 消息队列:它主要用来暂存生产者生产的消息,供后续其他消费者来消费。 它的功能主要有两个: 暂存(存储)队列(有序:先进先出 从目前互联网应用中使用消息队列的场景来看,…...

Rust开发WASM,浏览器运行WASM
首先需要安装wasm-pack cargo install wasm-pack 使用cargo创建工程 cargo new --lib mywasm 编辑Cargo.toml文件,修改lib的类型为cdylib,并且添加依赖wasm-bindgen [package] name "mywasm" version "0.1.0" edition "…...

Vue3编写简单的App组件(二)
一、Vue3页面渲染基本流程 1、入口文件 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><link rel"icon" href"/favicon.ico"><meta name"viewport" content"widthde…...

java Servlet 云平台教学系统myeclipse定制开发SQLServer数据库网页模式java编程jdbc
一、源码特点 JSP 云平台教学系统是一套完善的web设计系统,对理解JSP java编程开发语言有帮助 系统采用serlvet dao bean,系统具有完整的源代码和数据库 ,系统主要采用B/S模式开发。开发 环境为TOMCAT7.0,Myeclipse8.5开发,数据…...
QT初始程序
#include "widget.h"#include <QApplication>int main(int argc, char *argv[]){QApplication a(argc, argv);Widget w;w.show();return a.exec();} 解释: Qt系统提供的类头文件没有.h后缀Qt一个类对应一个头文件,类名和头文件名一致QA…...

ubuntu22.04@laptop OpenCV Get Started: 001_reading_displaying_write_image
ubuntu22.04laptop OpenCV Get Started: 001_reading_displaying_write_image 1. 源由2. Read/Display/Write应用Demo2.1 C应用Demo2.2 Python应用Demo 3. 过程分析3.1 导入OpenCV库3.2 读取图像文件3.3 显示图像3.4 保存图像文件 4. 总结5. 参考资料 1. 源由 读、写、显示图像…...

51单片机之LED灯模块篇
御风以翔 破浪以飏 🎥个人主页 🔥个人专栏 目录 点亮一盏LED灯 LED的组成原理 LED的硬件模型 点亮一盏LED灯的程序设计 LED灯闪烁 LED流水灯 独立按键控制LED灯亮灭 独立按键的组成原理 独立按键的硬件模型 独立按键控制LED灯状态 按键的抖动 独立按键…...

springboo冬奥会科普平台源码和论文
随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息。为了迎合时代需求,优化管理效率,各种各样的管理平台应运而生,各行各业相继进入信息管理时代…...

改进神经网络
Improve NN 文章目录 Improve NNtrain/dev/test setBias/Variancebasic recipeRegularizationLogistic RegressionNeural networkother ways optimization problemNormalizing inputsvanishing/exploding gradientsweight initializegradient checkNumerical approximationgrad…...

HarmonyOS 开发学习笔记
HarmonyOS 开发学习笔记 一、开发准备1.1、了解ArkTs语言1.2、TypeScript语法1.2.1、变量声明1.2.2、条件控制1.2.3、函数1.2.4、类和接口1.2.5、模块开发 1.3、快速入门 二、ArkUI组件2.1、Image组件2.2、Text文本显示组件2.3、TextInput文本输入框组件2.4、Button按钮组件2.5…...

centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...

srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...

python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...