文章目录
- 前言
- [2 核心功能](#2 核心功能)
-
- [2.1 条件构造器](#2.1 条件构造器)
-
- [2.1.1 Wrapper](#2.1.1 Wrapper)
- [2.1.2 QueryWrapper](#2.1.2 QueryWrapper)
- [2.1.3 UpdateWrapper](#2.1.3 UpdateWrapper)
- [2.1.4 LambdaQueryWrapper](#2.1.4 LambdaQueryWrapper)
- [2.2 自定义SQL](#2.2 自定义SQL)
-
- [2.2.1 基本用法](#2.2.1 基本用法)
- [2.2.2 多表关联](#2.2.2 多表关联)
- [2.3 Service接口](#2.3 Service接口)
-
- [2.3.1 IService](#2.3.1 IService)
-
- [2.3.1.1 save](#2.3.1.1 save)
- [2.3.1.2 remove](#2.3.1.2 remove)
- [2.3.1.3 update](#2.3.1.3 update)
- [2.3.1.4 get](#2.3.1.4 get)
- [2.3.1.5 list](#2.3.1.5 list)
- [2.3.1.6 count](#2.3.1.6 count)
- [2.3.1.7 page](#2.3.1.7 page)
- [2.3.2 基本用法](#2.3.2 基本用法)
前言
MyBatisPlus详解系列文章:
MyBatisPlus详解(一)项目搭建、@TableName、@TableId、@TableField注解与常见配置
2 核心功能
2.1 条件构造器
2.1.1 Wrapper
在BaseMapper接口提供的相关方法中,除了以id作为where条件,还支持更加复杂的where条件,即条件构造器Wrapper:
Wrapper是条件构造器的抽象类,其下有很多默认实现,继承关系如图:
Wrapper的子类AbstractWrapper提供了where中包含的所有条件构造方法:
而QueryWrapper在AbstractWrapper的基础上拓展了一个select
方法,允许指定查询字段:
而UpdateWrapper在AbstractWrapper的基础上拓展了一个set
方法,允许指定SQL中的SET部分:
2.1.2 QueryWrapper
使用QueryWrapper构建查询条件,例如:
select id, username, info, balance from t_user where id = 3 and username like '%o%' and balance >= 1000
java
@Test
public void testQueryWrapper() {
// select id, username, info, balance from t_user
// where id = 3 and username like '%o%' and balance >= 1000
QueryWrapper<User> wrapper = new QueryWrapper<User>()
// 设置需要查询的字段
.select("id", "username", "info", "balance")
// id = 3
.eq("id", 3)
// like '%o%'
.like("username", "o")
// balance >= 1000
.ge("balance", 1000);
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
执行以上单元测试结果如下:
==> Preparing: SELECT id,username,info,balance FROM t_user WHERE (id = ? AND username LIKE ? AND balance >= ?)
==> Parameters: 3(Integer), %o%(String), 1000(Integer)
<== Total: 1
User(id=3, username=Hope, password=null, phone=null, info={"age": 25, "intro": "上进青年", "gender": "male"}, status=null, balance=20000, createTime=null, updateTime=null)
update t_user set balance = 2000 where username = 'Jack'
java
@Test
void testUpdateByQueryWrapper() {
// update t_user set balance = 2000 where username = 'Jack'
QueryWrapper<User> wrapper = new QueryWrapper<User>()
// username = 'Jack'
.eq("username", "Jack");
// 2.更新数据
User user = new User();
user.setBalance(2000);
userMapper.update(user, wrapper);
}
执行以上单元测试结果如下:
==> Preparing: UPDATE t_user SET balance=? WHERE (username = ?)
==> Parameters: 2000(Integer), Jack(String)
<== Updates: 0
2.1.3 UpdateWrapper
如上面的单元测试testUpdateByQueryWrapper()
所示,update()
方法更新时只能直接赋值,对于一些复杂的需求就难以实现,例如:
sql
UPDATE t_user SET balance = balance - 200 WHERE id in (1, 2, 4);
SET语句的赋值结果是基于字段现有值的,这个时候就要利用UpdateWrapper中的setSql()
方法了:
java
@Test
void testUpdateWrapper() {
List<Long> ids = Arrays.asList(1L, 2L, 4L);
// 1.生成SQL
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
// SET balance = balance - 200
.setSql("balance = balance - 200")
// WHERE id in (1, 2, 4)
.in("id", ids);
// 2.更新
// 注意第一个参数为null,则会基于UpdateWrapper中的setSQL来更新
userMapper.update(null, wrapper);
}
执行以上单元测试结果如下:
==> Preparing: UPDATE t_user SET balance = balance - 200 WHERE (id IN (?,?,?))
==> Parameters: 1(Long), 2(Long), 4(Long)
<== Updates: 2
2.1.4 LambdaQueryWrapper
无论是QueryWrapper还是UpdateWrapper,在构造条件的时候都需要写死字段名称,会出现字符串魔法值。这在编程规范中显然是不推荐的。
而要不写字段名,又能知道字段名,其中一种办法是基于变量的gettter
方法结合反射技术。为此,MybatisPlus又提供了一套基于Lambda的Wrapper,包含两个:LambdaQueryWrapper和LambdaUpdateWrapper。
java
@Test
public void testLambdaQueryWrapper() {
// select id, username, info, balance from t_user
// where id = 3 and username like '%o%' and balance >= 1000
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
// 设置需要查询的字段
.select(User::getId, User::getUsername, User::getInfo, User::getBalance)
// id = 3
.eq(User::getId, 3)
// username like '%o%'
.like(User::getUsername, "o")
// balance >= 1000
.ge(User::getBalance, 1000);
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
执行该单元测试,可以得到和上面的testQueryWrapper()
一样的结果,但这种写法不需要将字段名写死。未来如果需要修改字段名,也不需要这里的逻辑代码。
2.2 自定义SQL
在单元测试testUpdateWrapper()
,编写了这样一行代码:.setSql("balance = balance - 200")
,这种写法其实也是不好的,因为SQL语句最好都维护在持久层,而不是业务层。
为此,MyBatis提供了自定义SQL功能,即利用Wrapper构建复杂的where条件,然后自己定义SQL语句剩余的部分。
也就是说,UPDATE t_user SET balance = balance - 200
自己定义,WHERE id in (1, 2, 4)
利用Wrapper构建。
2.2.1 基本用法
例如单元测试testUpdateWrapper()
可以这样修改:
java
@Test
public void testUpdateWrapper2() {
List<Long> ids = Arrays.asList(1L, 2L, 4L);
// 1.生成SQL
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
// SET balance = balance - 200
// .setSql("balance = balance - 200")
// WHERE id in (1, 2, 4)
.in("id", ids);
// 2.更新
// userMapper.update(null, wrapper);
// 改为调用自定义的Mapper方法,直接传递Wrapper
userMapper.deductBalanceByIds(200, wrapper);
}
然后在UserMapper中定义deductBalanceByIds()
方法:
java
// com.star.learning.mapper.UserMapper
@Update("UPDATE t_user SET balance = balance - #{money} ${uw.customSqlSegment}")
void deductBalanceByIds(@Param("money") int money, @Param("uw") UpdateWrapper<User> wrapper);
执行修改后的单元测试,执行结果和原来的是一样的。
2.2.2 多表关联
理论上来讲MyBatisPlus是不支持多表查询的,不过仍然可以利用Wrapper构建复杂where条件结合自定义SQL来实现多表查询的效果。
例如,要查询出所有收货地址在北京并且用户id在1、2、4之中的用户,其SQL语句为:
sql
SELECT * FROM t_user u
INNER JOIN t_address a ON a.user_id = u.id
WHERE u.id IN (1,2,4)
AND a.city = '北京'
编写单元测试,利用Wrapper构建复杂where条件,并调用自定义的Mapper方法:
java
@Test
public void testMultiTableQuery() {
List<Long> ids = Arrays.asList(1L, 2L, 4L);
// 1.利用Wrapper构建复杂where条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.in("u.id", ids)
.eq("a.city", "北京");
// 2.调用自定义的Mapper方法
User user = userMapper.queryUserByIdAndCity(wrapper);
System.out.println(user);
}
java
// com.star.learning.mapper.UserMapper
@Select("SELECT * FROM t_user u INNER JOIN t_address a ON a.user_id = u.id ${ew.customSqlSegment}")
User queryUserByIdAndCity(@Param("ew") QueryWrapper<User> wrapper);
执行以上单元测试,结果如下:
==> Preparing: SELECT * FROM t_user u INNER JOIN t_address a ON a.user_id = u.id WHERE (u.id IN (?,?,?) AND a.city = ?)
==> Parameters: 1(Long), 2(Long), 4(Long), 北京(String)
<== Total: 1
User(id=2, username=Rose, password=123, phone=13900112223, info={"age": 19, "intro": "青涩少女", "gender": "female"}, status=1, balance=200, createTime=2024-04-21T10:13:35, updateTime=2024-04-21T16:10:20)
2.3 Service接口
MybatisPlus不仅提供了BaseMapper接口,还提供了通用的Service接口及默认实现ServiceImpl,封装了一些常用的service模板方法。
2.3.1 IService
2.3.1.1 save
save
:插入一条记录saveBatch
:批量插入多条记录saveOrUpdate
:记录存在则更新记录,否则插入一条新记录saveOrUpdateBatch
:记录存在则批量修改,否则批量插入
2.3.1.2 remove
remove
:根据条件删除removeById
:根据ID删除removeByIds
:根据ID批量删除removeByMap
:根据Map中的键值对为条件删除removeBatchByIds
:批量删除(jdbc批量提交)
2.3.1.3 update
update(Wrapper<T>)
:根据UpdateWrapper修改,Wrapper中包含set和where部分update(T,Wrapper<T>)
:按照T内的数据修改与Wrapper匹配到的数据updateById
:根据id修改updateBatchById
:根据id批量修改
2.3.1.4 get
getById
:根据id查询getOne(Wrapper<T>)
:根据Wrapper查询1条记录getBaseMapper
:获取对应实体的BaseMappergetMap(Wrapper<T>)
:根据Wrapper查询1条记录,封装为Map
2.3.1.5 list
list()
:查询所有list(Wrapper<T>)
:根据Wrapper条件查询列表listByIds()
:根据ID集合查询列表
2.3.1.6 count
count()
:查询总记录数count(Wrapper<T>)
:根据Wrapper条件查询总记录数
2.3.1.7 page
page(IPage)
:无条件翻页查询page(IPage, Wrapper<T>)
:根据Wrapper条件翻页查询
2.3.2 基本用法
创建一个IUserService接口,继承IService接口以拓展方法;同时,自定义UserServiceImpl实现类,继承ServiceImpl实现类,并实现IUserService接口。
java
// com.star.learning.service.IUserService
public interface IUserService extends IService<User> {
}
java
// com.star.learning.service.impl.UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}
这样配置之后,导入IUserService接口,即可以使用IService接口中定义的各种方法。
接下来,实现下面这个接口:
接口 | 请求方式 | 请求路径 | 请求参数 | 返回值 |
---|---|---|---|---|
新增用户 | POST | /user/add | User | 无 |
首先,导入相关依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
然后创建一个UserController类,并编写一个addUser()
方法:
java
// com.star.learning.controller.UserController
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
@PostMapping("/add")
public void addUser(@RequestBody User user) {
boolean save = userService.save(user);
System.out.println("新增用户结果 => " + save);
}
}
最后进行功能测试,调用/user/add
接口,查看控制台打印信息:
==> Preparing: INSERT INTO t_user ( username, password, phone, info, balance ) VALUES ( ?, ?, ?, ?, ? )
==> Parameters: Jim(String), 123456(String), 999(String), {"age": 20, "intro": "佛系青年", "gender": "male"}(String), 500(Integer)
<== Updates: 1
新增用户结果 => true
接下来再来实现3个接口:
接口 | 请求方式 | 请求路径 | 请求参数 | 返回值 |
---|---|---|---|---|
根据id查询用户 | GET | /user/{id} | id | User |
根据id集合批量查询用户 | GET | /user/{id} | id | List<User> |
删除用户 | DELETE | /user/{id} | id | 无 |
其代码如下:
java
// com.star.learning.controller.UserController
@GetMapping("/{id}")
public User getById(@PathVariable("id") Long userId) {
User user = userService.getById(userId);
System.out.println("根据id查询用户 => " + user);
return user;
}
@GetMapping
public List<User> queryUserByIds(@RequestParam("ids") List<Long> ids){
List<User> users = userService.listByIds(ids);
System.out.println("根据id集合查询用户列表 => " + users);
return users;
}
@DeleteMapping("/{id}")
public void removeUserById(@PathVariable("id") Long userId){
boolean remove = userService.removeById(userId);
System.out.println("根据id删除用户 => " + remove);
}
调用这3个接口,查看控制台打印信息:
// 根据id查询用户
==> Preparing: SELECT id,username,password,phone,info,status,balance,create_time,update_time FROM t_user WHERE id=?
==> Parameters: 2(Long)
<== Total: 1
根据id查询用户 => User(id=2, username=Rose, password=123, phone=13900112223, info={"age": 19, "intro": "青涩少女", "gender": "female"}, status=1, balance=200, createTime=2024-04-21T10:13:35, updateTime=2024-04-21T16:10:20)
// 根据id集合批量查询用户
==> Preparing: SELECT id,username,password,phone,info,status,balance,create_time,update_time FROM t_user WHERE id IN ( ? , ? , ? )
==> Parameters: 1(Long), 2(Long), 3(Long)
<== Total: 2
根据id集合查询用户列表 => [User(id=2, username=Rose, password=123, phone=13900112223, info={"age": 19, "intro": "青涩少女", "gender": "female"}, status=1, balance=200, createTime=2024-04-21T10:13:35, updateTime=2024-04-21T16:10:20), User(id=3, username=Hope, password=123, phone=13900112222, info={"age": 25, "intro": "上进青年", "gender": "male"}, status=1, balance=20000, createTime=2024-04-21T10:13:35, updateTime=2024-04-21T11:12:48)]
// 根据id删除用户
==> Preparing: DELETE FROM t_user WHERE id=?
==> Parameters: 2(Long)
<== Updates: 1
根据id删除用户 => true
可以看到,上述4个接口都直接在Controller类中即可实现,无需编写任何Service代码,非常方便。
不过,一些带有业务逻辑的接口则需要在Service中自定义实现了。例如下面这个接口:
接口 | 请求方式 | 请求路径 | 请求参数 | 返回值 |
---|---|---|---|---|
根据id扣减用户余额 | PUT | {id}/deduction/{money} | id,money | 无 |
java
// com.star.learning.controller.UserController
@PutMapping("{id}/deduction/{money}")
public void deductBalance(@PathVariable("id") Long id, @PathVariable("money")Integer money){
System.out.println("扣减id=" + id + "的用户的余额" + money);
userService.deductBalance(id, money);
}
java
// com.star.learning.service.IUserService
public interface IUserService extends IService<User> {
void deductBalance(Long userId, Integer money);
}
java
// com.star.learning.service.impl.UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
private UserMapper userMapper;
@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() < money) {
throw new RuntimeException("用户余额不足");
}
// 4.扣减余额
// 2.2节自定义SQL创建了deductBalanceByIds方法可以直接使用
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.eq("id", userId);
userMapper.deductBalanceByIds(money, wrapper);
}
}
调用/user/3/deduction/200
接口,查看控制台打印信息:
扣减id=3的用户的余额200
==> Preparing: SELECT id,username,password,phone,info,status,balance,create_time,update_time FROM t_user WHERE id=?
==> Parameters: 3(Long)
<== Total: 1
User(id=3, username=Hope, password=123, phone=13900112222, info={"age": 25, "intro": "上进青年", "gender": "male"}, status=1, balance=20000, createTime=2024-04-21T10:13:35, updateTime=2024-04-21T11:12:48)
==> Preparing: UPDATE t_user SET balance = balance - ? WHERE (id = ?)
==> Parameters: 200(Integer), 3(Long)
<== Updates: 1
...
本节完,更多内容请查阅分类专栏:MyBatisPlus详解
感兴趣的读者还可以查阅我的另外几个专栏: