MyBatisPlus详解(三)lambdaQuery、lambdaUpdate、批量新增、代码生成、Db静态工具、逻辑删除
文章目录
- 前言
- 2 核心功能
- 2.3 Service接口
- 2.3.3 Lambda
- 2.3.3.1 lambdaQuery
- 2.3.3.2 lambdaUpdate
- 2.3.4 批量新增
- 3 扩展功能
- 3.1 代码生成
- 3.2 静态工具
- 3.2.1 基本用法
- 3.2.2 代码实例
- 3.3 逻辑删除
前言
MyBatisPlus详解系列文章:
MyBatisPlus详解(一)项目搭建、@TableName、@TableId、@TableField注解与常见配置
MyBatisPlus详解(二)条件构造器Wrapper、自定义SQL、Service接口
2 核心功能
2.3 Service接口
2.3.3 Lambda
IService接口中还提供了Lambda功能来简化复杂查询及更新功能。
2.3.3.1 lambdaQuery
例如,要实现一个根据复杂条件查询用户信息的接口,接口文档如下:
接口 请求方式 请求路径 请求参数 返回值 根据条件查询用户列表 GET /user/list UserQuery List 查询条件UserQuery的字段如下:
- username:用户名关键字,可以为空
- status:用户状态,可以为空
- minBalance:最小余额,可以为空
- maxBalance:最大余额,可以为空
上述条件有可能为空,因此在查询时需要做判断。
首先定义一个查询实体类UserQuery:
// com.star.learning.pojo.UserQuery @Data public class UserQuery { private String username; private Integer status; private Integer minBalance; private Integer maxBalance; }
接着在UserController类中编写一个list()方法:
// com.star.learning.controller.UserController @GetMapping("/list") public List list(UserQuery userQuery) { // 1.组织条件 String username = userQuery.getUsername(); Integer status = userQuery.getStatus(); Integer minBalance = userQuery.getMinBalance(); Integer maxBalance = userQuery.getMaxBalance(); System.out.println("根据条件查询用户列表 => " + userQuery); LambdaQueryWrapper wrapper = new QueryWrapper().lambda() .like(username != null, User::getUsername, username) .eq(status != null, User::getStatus, status) .ge(minBalance != null, User::getBalance, minBalance) .le(maxBalance != null, User::getBalance, maxBalance); // 2.查询用户 List users = userService.list(wrapper); System.out.println("查询结果 => " + userQuery); return users; }
在上述代码进行组织条件时,使用了username != null这样的判断语句,它的效果就和Mapper文件的标签一样,只有当条件成立时才会添加这个查询条件,从而实现动态查询。
调用/user/list?username=o&minBalance=500,控制台打印信息如下:
根据条件查询用户列表 => UserQuery(username=o, status=null, minBalance=500, maxBalance=null) ==> Preparing: SELECT id,username,password,phone,info,status,balance,create_time,update_time FROM t_user WHERE (username LIKE ? AND balance >= ?) ==> Parameters: %o%(String), 500(Integer) [User(id=3, username=Hope, password=123, phone=13900112222, info={"age": 25, "intro": "上进青年", "gender": "male"}, status=1, balance=19800, createTime=2024-04-21T10:13:35, updateTime=2024-04-21T18:48:28)]
可见,在上述SQL语句中只有username和minBalance是查询条件,其余两个字段均没有作为查询条件。
上述代码还可以继续简化,我们无需通过new的方式来创建Wrapper,而是直接调用lambdaQuery方法:
// com.star.learning.controller.UserController @GetMapping("/list") public List list(UserQuery userQuery) { // 1.组织条件 String username = userQuery.getUsername(); Integer status = userQuery.getStatus(); Integer minBalance = userQuery.getMinBalance(); Integer maxBalance = userQuery.getMaxBalance(); System.out.println("根据条件查询用户列表 => " + userQuery); // 2.查询用户 List users = userService.lambdaQuery() .like(username != null, User::getUsername, username) .eq(status != null, User::getStatus, status) .ge(minBalance != null, User::getBalance, minBalance) .le(maxBalance != null, User::getBalance, maxBalance) .list(); System.out.println("查询结果 => " + users); return users; }
可以发现,lambdaQuery方法中除了可以构建条件,还可以在链式编程的最后添加一个list(),告诉MP调用结果需要是一个List集合。
再次调用/user/list?username=o&minBalance=500接口,执行结果是一致的。
MybatisPlus会根据链式编程的最后一个方法来判断最终的返回结果,除了使用list()返回集合结果,还可以使用one()返回1个结果,使用count()返回计数结果。
2.3.3.2 lambdaUpdate
与lambdaQuery()方法类似,IService中的lambdaUpdate()方法可以非常方便的实现复杂更新业务。
例如有这样一个需求:根据id修改用户余额时,如果扣减后余额为0,则将用户status修改为冻结状态(2)。
也就是说,在扣减用户余额时,需要对用户剩余余额做出判断,如果发现剩余余额为0,则应该将status修改为2,这就是说update语句的set部分是动态的。
修改UserServiceImpl实现类中的deductBalance()方法:
// com.star.learning.service.impl.UserServiceImpl @Override public void deductBalance(Long userId, Integer money) { // 1.查询用户 User user = getById(userId); System.out.println(user); // 2.判断用户状态 if (user == null || user.getStatus() == 2) { throw new RuntimeException("用户状态异常"); } // 3.判断用户余额 if (user.getBalance()
调用/user/4/deduction/400接口,控制台打印信息如下:
扣减id=4的用户的余额400 ==> Preparing: SELECT id,username,password,phone,info,status,balance,create_time,update_time FROM t_user WHERE id=? ==> Parameters: 4(Long) Preparing: UPDATE t_user SET balance=?,status=? WHERE (id = ? AND balance = ?) ==> Parameters: 0(Integer), 2(Integer), 4(Long), 400(Integer) long b = System.currentTimeMillis(); for (int i = 1; i userService.save(buildUser(i)); } long e = System.currentTimeMillis(); System.out.println("耗时:" + (e - b)); } private User buildUser(int i) { User user = new User(); user.setUsername("user_" + i); user.setPassword("123"); user.setPhone("" + (18688190000L + i)); user.setBalance(2000); user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}"); return user; } List userList.add(buildUser(i)); // 每1000条插入一次数据 if(i % 1000 == 0) { userService.saveBatch(userList); userList.clear(); } } long e = System.currentTimeMillis(); System.out.println("耗时:" + (e - b)); } // 利用Db实现根据ID查询 User user = Db.getById(1L, User.class); System.out.println(user); System.out.println("=========="); // 利用Db实现复杂条件查询 List= ?) ==> Parameters: %o%(String), 1000(Integer) Preparing: UPDATE t_user SET balance=? WHERE (username = ?) ==> Parameters: 2000(Integer), Rose(String) // ... /** * 收货地址列表 */ @TableField(exist = false) private List // User user = userService.getById(userId); // System.out.println("根据id查询用户 = " + user); // 基于自定义Service方法查询 User user = userService.queryUserAndAddressById(userId); System.out.println("根据id查询用户及其收货地址信息 => " + user); return user; }
- 3)在IService接口中定义queryUserAndAddressById()方法,并在UserServiceImpl类中具体实现:
// com.star.learning.service.IUserService User queryUserAndAddressById(Long userId);
// com.star.learning.service.impl.UserServiceImpl @Override public User queryUserAndAddressById(Long userId) { // 1.查询用户 User user = getById(userId); if (user == null) { return null; } // 2.使用Db来查询收货地址 List addressList = Db.lambdaQuery(Address.class) .eq(Address::getUserId, userId) .list(); // 3.处理返回 user.setAddressList(addressList); return user; }
- 4)调用/user/1接口,控制台打印信息如下:
==> Preparing: SELECT id,username,password,phone,info,status,balance,create_time,update_time FROM t_user WHERE id=? ==> Parameters: 1(Long) Preparing: SELECT id,user_id,province,city,town,mobile,street,contact,is_default,notes,deleted FROM t_address WHERE (user_id = ?) ==> Parameters: 1(Long) User(id=1, username=Jack, password=123, phone=13900112224, info={"age": 20, "intro": "佛系青年", "gender": "male"}, status=1, balance=1600, createTime=2024-04-22T19:40:36, updateTime=2024-04-22T19:40:36, addressList=[Address(id=2, userId=1, province=北京, city=北京, town=朝阳区, mobile=13700221122, street=修正大厦, contact=Jack, isDefault=false, notes=null, deleted=false), Address(id=3, userId=1, province=上海, city=上海, town=浦东新区, mobile=13301212233, street=航头镇航头路, contact=Jack, isDefault=true, notes=null, deleted=false)])
可见,在查询地址时,采用了Db类的静态方法,避免了注入AddressService,从而减少了循环依赖的风险。
3.3 逻辑删除
对于一些比较重要的数据,往往会采用逻辑删除的方案,即在表中添加一个字段标记数据是否被删除,当删除数据时把标记置为true,当查询时过滤掉标记为true的数据。
但是一旦采用逻辑删除,查询和删除逻辑就会变得更加复杂。为此,MybatisPlus添加了对逻辑删除的支持。
但是要注意,只有MybatisPlus生成的SQL语句才支持自动的逻辑删除,自定义SQL需要自己手动处理逻辑删除。
例如,在t_address表及其对应的实体Address类中有一个逻辑删除字段deleted,默认值为0:
要开启MyBatisPlus的逻辑删除功能,需要在application.yml中配置逻辑删除字段:
# src/main/resources/application.yaml mybatis-plus: global-config: db-config: logic-delete-field: deleted # 全局逻辑删除的实体字段名 logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
接下来编写测试代码:
@Test public void testDeleteByLogic() { addressService.removeById(1); }
执行以上单元测试,控制台打印信息如下:
==> Preparing: UPDATE t_address SET deleted=1 WHERE id=? AND deleted=0 ==> Parameters: 1(Integer) List Preparing: SELECT id,user_id,province,city,town,mobile,street,contact,is_default,notes,deleted FROM t_address WHERE deleted=0 ==> Parameters:
- 4)调用/user/1接口,控制台打印信息如下:
- 3)在IService接口中定义queryUserAndAddressById()方法,并在UserServiceImpl类中具体实现: