一、前言
之前我们在操作持久层的时候都是使用的MyBatis框架,这个框架有很多优点,比如可以统一操作流程,还擅长多表操作,但是我们会发现它的操作是比较复杂的,尤其对于我们最常用的单表操作,很多时候只是一个queryById都需要通过**"接口 - Mapper - 注解sql语句"** 来实现,这对开发效率有很大影响,毕竟在项目中的多数对持久层的操作都是单表的,所以这里我们引入MyBatis-Plus来解决这个问题,MyBatis-Plus是对于MyBatis的增强,也就是包含了MyBatis的一切,所以是无侵入的,我们可以只在单表操作时简化步骤使用MyBatis-Plus,然后多表操作时也可以依旧使用mybatis。
二、快速入门
由于是演示持久层操作的框架,所以肯定先需要创建一个表,这里我们创建如下表,id主键自增。

然后我们还是按照MyBatis的格式,准备一个Mapper:
java
public interface UserMapper extends BaseMapper<User> {
}
这里我们的Mapper继承了BaseMapper<>类,这个类上面需要提供操作的pojo,这里也就是User类。
BaseMapper是MyBatis提供的类,这个类中含有很多方法,包括了绝大多数常用单表操作,使用时直接调用里面的方法即可。

对于这些常用的方法,我们创建一个测试类来进行测试,在使用这些常用的增删查改时,我们可以发现,其实对于持久层操作的步骤就只有最后一行,并且不再需要自己在Mapper中用注解写sql语句了,当然xml文件也不需要了,我们仅仅只需要调用BaseMapper中的方法就能实现这些效果了。
java
@SpringBootTest
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void testInsert() {
User user = new User();
//user.setId(5L);
user.setUsername("Lucy");
user.setPassword("123");
user.setPhone("18688990011");
user.setBalance(200);
user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
userMapper.insert(user);
}
@Test
void testSelectById() {
User user = userMapper.selectById(5L);
System.out.println("user = " + user);
}
@Test
void testQueryByIds() {
List<User> users = userMapper.selectBatchIds(List.of(1L, 2L, 3L, 4L));
users.forEach(System.out::println);
}
@Test
void testUpdateById() {
User user = new User();
user.setId(5L);
user.setBalance(20000);
userMapper.updateById(user);
}
@Test
void testDeleteUser() {
userMapper.deleteById(5L);
}
}
三、条件构造器
条件构造器赋予了MyBatis-Plus更多应对复杂业务的能力。通过将条件封装到Wrapper中,就能直接替代以前需要在xml文件中写的<where>和<if>。
1.普通构造器
比如下面,我们就使用了条件构造器构造了一个条件:
1.只查询目标user的id、username、info、balance
2.模糊查询username中带有o的所有目标user
2.查询balance大于1000的目标user
综上,我们要查找的是username中带o的、余额大于1000的用户的id、用户名、信息、余额。
java
/**
* 测试用mp条件构造器查询集合
*/
@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);
}
最终,我们将条件对象赋给方法,得到的集合就是我们要找的user集合了。
同样,更新操作也是一样的:我们这里要更新用户名为jack的用户(条件),要将他的余额设置为2000。
java
/**
* 测试用mp条件构造器为指定的查询目标更新字段
*/
@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);
}
而如果想更新用户,让id为1,2,4的用户的余额减200:
我们依旧可以使用条件构造器,将set的sql语句直接写出来,然后接上条件ids。
最后将条件传到方法中去。
java
/**
* 测试用mp条件构造器更新
*/
@Test
void testUpdateWrapper(){
List<Long> ids = List.of(1L,2L,4L);
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance - 200")
.in("id",ids);
userMapper.update(null,wrapper);
}
2.Lambda构造器
我们发现,前面不管是"username"还是"id",这些都属于硬编码,很容易打错,并且不易后期维护,所以这里我们引入另一种构造器,把这些硬编码解耦。
注意:这个构造器相较于普通的条件构造器,在语法上和效果上都是没有改变的,仅仅是解耦了:
java
/**
* 测试用mp条件构造器(Lambda)查询集合 推荐使用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语句
由于在处理较复杂的业务的时候,我们需要MyBatis-Plus对于where/if部分条件的简化,同时我们又希望自己写前面部分的sql语句,这个时候就可以使用自定义sql语句,本质上是sql语句的拼接。
java
/**
* 测试自定义SQL
*/
@Test
void testCustomSqlUpdate(){
//1.更新条件
List<Long> ids = List.of(1L,2L,4L);
int amount = 200;
//2.定义条件
QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id",ids);
//调用自定义SQL方法
userMapper.updateBalanceByIds(wrapper,amount);
}
一定要明确一点,我们传参数是为了在xml映射文件中拼接sql语句,所以我们会将条件部分封装到一个注解中(可以理解为转化成了一个sql语句的字符串,然后放入ew的文本域),将参数封装到一个注解中(amount文本域),然后在xml文件中拿出来拼接。
Mapper中我们将自定义这个业务方法,这里需要使用两个注解,第一个是标记条件,第二个是标记参数。
java
public interface UserMapper extends BaseMapper<User> {
void updateBalanceByIds(@Param(Constants.WRAPPER) QueryWrapper<User> wrapper, @Param("amount") int amount);
}
在这里将sql语句片段进行拼接,也可以理解为获取文本域中的sql语句
XML
<?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">
update user set balance = balance -#{amount} ${ew.customSqlSegment}
</update>
</mapper>
五、IService接口
1.概念和使用
这个也是MyBatis-Plus的核心功能,刚刚我们都是基于持久层和Service层的操作,也就是在Serviece层中调用更加简便的Mapper对象方法。这样已经比较方便了,但是我们刚刚是没有引入Controller的,那有没有一种方法,能够直接在Controller中直接调用更加简便的ServiceImpl类对象方法呢?有,我们将引入IService接口。
使用这个接口的前提是:
1.接口必须继承IService接口。
2.实现类必须继承ServiceImpl<UserMapper, User>
这是有原因的:
首先明确四个概念:
IService接口、ServiceImpl类:这俩是MP提供的。
Service接口、实现类:这俩是我们自己写的,以前使用Mybatis的时候就会写的。
接下来我们再来回答这三个问题:
1.为什么接口必须继承IService接口?
因为以往我们的实现类会实现Service接口,然后实现Service接口上我们自己写的方法。
现在,我们想使用现成的、MyBatis-Plus提供的简易持久层操作方法,那当然需要在Service接口上继承MyBatis-Plus提供的接口,然后在实现类中进行实现。
2.为什么都有接口了还需要再继承一个ServiceImpl类?
因为如果我们不继承这个类,而选择直接实现Service接口,我们会发现,我们必须实现所有的IService的方法,这显然是不符合实际开发需求的,所以这里我们选择继承一个IServiece的实现类------ ServiceImpl类,这样我们自己的实现类中就有来自IServiece中的方法了,并且我们可以选择性使用。
3.为啥都继承了ServiceImpl类了还需要实现Service接口?
因为我们除了使用简易的持久层操作方法,我们也有可能遇到复杂的业务,这个时候还是得我们自己写,甚至还需要用xml去写(如果是多表),那么我们就要自己去编写接口方法和实现方法了(相当于回到MyBatis了)。
最后,直观看看具体是怎样继承和实现的:
java
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}
java
public interface IUserService extends IService<User> {
}
2.项目实战
(1)Controller
这里我们就要实际使用试试了,所以首先要补出Controller层:前面四个都是直接使用mp提供的简易方法,最后一个是要靠我们自己定义的Service层方法。
java
@RestController
@RequestMapping("/users")
@Slf4j
@Api(tags = "用户管理接口")
@RequiredArgsConstructor
public class UserController {
private final IUserService userService;
@ApiOperation("新增用户")
@PostMapping
public void saveUser(@RequestBody UserFormDTO userDTO){
//BeanUtils.copyProperties(userDTO,User.class);
//用的是Hutool工具的BeanUtil,相比spring的,这个可以用反射返回一个对象,不需要提前new一个了
User user = BeanUtil.copyProperties(userDTO, User.class);
userService.save(user);
}
@ApiOperation("删除用户")
@DeleteMapping("/{id}")
public void deleteUserById(@ApiParam("用户id") @PathVariable Long id){
userService.removeById(id);
}
@ApiOperation("根据id查询用户")
@GetMapping("/{id}")
public UserVO queryUserById(@ApiParam("用户id") @PathVariable Long id){
User user = userService.getById(id);
return BeanUtil.copyProperties(user,UserVO.class);
}
@ApiOperation("根据id批量查询用户")
@GetMapping
public List<UserVO> queryUserById(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids){
List<User> users = userService.listByIds(ids);
return BeanUtil.copyToList(users,UserVO.class);
}
@ApiOperation("扣减用户余额")
@PutMapping("/{id}/deduction/{money}")
public void deductMoneyById(@ApiParam("用户id") @PathVariable("id") Long id,
@ApiParam("扣减的金额") @PathVariable("money") Integer money
){
userService.deductBalance(id,money);
}
@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);
}
}
(2)Service层
然后是Service层:
接口:
java
public interface IUserService extends IService<User> {
void deductBalance(Long id, Integer money);
List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance);
}
**实现类:**可以注意一下这里怎么用mp实现对<if>的替代的
java
@Service
@Transactional
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public void deductBalance(Long id, Integer money) {
//1.查询用户
User user = this.getById(id);
//2.校验用户状态
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常");
}
//3.校验余额是否充足
if (user.getBalance() < money) {
throw new RuntimeException("用户余额不足");
}
//4.扣减余额
//baseMapper.deductBalance(id, money);
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();
}
@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, name)
.list();
}
}
(3)持久层
Mapper接口:
之前我们这里是用了wrapper对象的,这里不用的原因是where条件太简单了,没必要用wrapper来封装,自己直接写上去就是了。
java
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);
}
xml映射文件:
无,在Mapper接口使用注解替代了。