MyBatis-Plus精讲 —— 从快速入门到项目实战

一、前言

之前我们在操作持久层的时候都是使用的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接口使用注解替代了。

相关推荐
库库林_沙琪马2 小时前
7、集成MyBatis
spring boot·mybatis
吃喝不愁霸王餐APP开发者2 小时前
霸王餐API文档自动化:Spring REST Docs与Asciidoctor多模块聚合
数据库·spring·自动化
BBB努力学习程序设计2 小时前
Java条件判断:程序的"决策大脑"
java
我是华为OD~HR~栗栗呀2 小时前
华为OD-C面经-23届学院哦
java·c++·python·华为od·华为·面试
Lear2 小时前
【MySQL】索引失效10大场景详解:如何避免索引失效提升查询性能
后端
Lear2 小时前
【Spring】事务失效场景详解:原理、问题与解决方案
后端
小马爱打代码2 小时前
Spring AI:文生图:调用通义万相 AI 大模型
java·人工智能·spring
摇滚侠2 小时前
2025最新 SpringCloud 教程,网关功能、创建网关,笔记51、笔记52
java·笔记·spring cloud
又是忙碌的一天2 小时前
Socket学习
java·学习·socket