MybatisPlus小白入门学习干货!!

前言
文章为MybatisPlus笔记,实战案例比较多,小白学习建议收藏练习~
微服务持续更新中…

Mybatis

非常流行的持久层框架,主要用来做数据库的增删改查。

MybatisPlus

对于 Mybatis 框架的增强和升级,但并非替代 Mybatis 。

下面,我们来做一个实战案例:
在这里插入图片描述

使用步骤:

1、引入 MybatisPlus 的起步依赖

MybatisPlus 官方提供了 starter ,其中继承了 Mybatis 和 MybatisPlus 的所有功能,并实现了自动装配效果。

依赖如下:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

2、定义 Mapper

自定义的 Mapper 继承 MybatisPlus 提供的 BaseMapper 接口。

如下图:

在这里插入图片描述

这里需要注意的是,BaseMapper 后指定的泛型为这个 Mapper 的实体类,这样才知道增删改查的是哪个实体。

如图是使用 Mybatis 手写的 Mapper:在这里插入图片描述

在这里插入图片描述

内容多且复杂,现在我们已经做好了 MybatisPlus 的准备操作,让我们使用 MybatisPlus 来简化代码编写吧。

首先删除原本编写好的 Mapper:
在这里插入图片描述
在这里插入图片描述

原本编写好的 Mapper 被删除后,测试类自然也会报错:

在这里插入图片描述

咱们用 MybatisPlus 来重写方法,打个 “.” ,就发现,idea 已经给我们提示出来了 BaseMapper 为我们提供好的方法,我们选择并使用即可。
在这里插入图片描述

重写后的测试类:

在这里插入图片描述

再来测试下功能:执行新增

在这里插入图片描述
在这里插入图片描述

可以发现是执行成功了,数据库表中也有我们刚才插入的新数据。剩下的删改查这里就不做演示了,同样也是可以正常使用的。

那么大家肯定很好奇为什么 MybatisPlus 可以这么方便呢,接下来来解释一下 MybatisPlus 的原理。

MybatisPlus原理

常用注解

MybatisPlus 通过扫描实体类,并基于反射获取实体类信息作为数据库表信息。这也就是上面在 Mapper 继承 BaseMapper 时强调的需要指定实体类泛型。通过反射,拿到字节码,就获得了实体类信息,然后就可以把实体类信息按照约定作为数据库表信息了。这里提到的约定如下:

•类名驼峰转下划线作为表名

•名为id的字段作为主键

•变量名驼峰转下划线作为表的字段名

符合约定的实体类就不需要我们自己配置,但如果不符合约定,就需要我们自定义配置了。在 MybatisPlus 中给我们提供了很多自定义配置的注解,常见的三个注解分别为:

@TableName:用来指定表名

@TableId:用来指定表中的主键字段信息

@TableField:用来指定表中的普通字段信息

具体使用方式如下(根据下方数据库表信息,添加合适的注解):
在这里插入图片描述
在这里插入图片描述

其中涉及到的注意事项:

IdType枚举:

•AUTO:数据库自增长

•INPUT:通过set方法自行输入

•ASSIGN_ID:分配 ID,接口IdentifierGenerator的方法nextId来生成id,默认实现类为DefaultIdentifierGenerator雪花算法

使用@TableField的常见场景:

•成员变量名与数据库字段名不一致

•成员变量名以is开头,且是布尔值

•成员变量名与数据库关键字冲突

•成员变量不是数据库字段

常用配置

在之前的案例中,我们配置好了 starter 后 MybatisPlus 就可以自动装配,直接就可以使用,同时, MybatisPlus 也支持很多常见配置

在这里插入图片描述

核心功能

条件构造器

MybatisPlus 支持各种复杂的 where 条件,可以满足日常开发的所有需求。
在这里插入图片描述
在这里插入图片描述

接下来我们来写几个案例:

在这里插入图片描述

图中展示的是正常的 SQL 语句,接下来利用 MybatisPlus 的条件构造器来构造这些语句。

需求1:

@Test
void testQueryMapper(){
    //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);
}

需求2:

@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);
}

在这里插入图片描述

@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);
}

自定义SQL

我们可以利用 MybatisPlus 的 wrapper 来构建复杂的 where 条件,然后自己定义 SQL 语句中剩下的部分。在之前的案例中,我们都是直接通过 MybatisPlus 来生成,相当于全自动,现在只有 where 条件由 MybatisPlus 生成,相当于半自动。为什么要这么做呢,接下来我们来看两个实际案例:
在这里插入图片描述

这个案例很眼熟吧,我们上面使用 MybatisPlus 写过:

在这里插入图片描述

这样写将原本复杂的 SQL 语句简化了,按理说是很方便啦,实际不是的,我们现在写的这些逻辑其实是业务逻辑,将来是在 Service 层去定义,相当于我们把 SQL 语句写在了实际业务代码当中了,这在很多企业的开发规范中是不允许的,此时就需要自定义 SQL 了。

UserMapperTest:

@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);
    //3.调用自定义的SQL方法
    userMapper.updateBalanceByIds(wrapper, amount);
}

UserMapper:

void updateBalanceByIds(@Param(Constants.WRAPPER) QueryWrapper<User> wrapper, @Param("amount") int amount);

UserMapper.xml:

<update id="updateBalanceByIds">
    update user set balance = balance - #{amount} ${ew.customSqlSegment}
</update>

Servive接口

上面介绍了使用 MybatisPlus 可以省去写 Mapper 增删改查的方法。MybatisPlus 同时也很贴心的为我们准备了 Service 接口,只要继承了它,一些基础的增删改查的 Service 代码,也不用写了。具体流程如下图:

在这里插入图片描述
在这里插入图片描述

下面我们来写一个案例:

IUserService:

package com.itheima.mp.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;

public interface IUserService extends IService<User> {

}

UserServiceImpl:

package com.itheima.mp.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.mapper.UserMapper;
import com.itheima.mp.service.IUserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

}

IUserServiceTest:

package com.itheima.mp.service;

import com.itheima.mp.domain.po.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.time.LocalDateTime;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class IUserServiceTest {

    @Autowired
    private IUserService userService;

    @Test
    void testSaveUser(){
        User user = new User();
        //user.setId(5L);
        user.setUsername("LiLei");
        user.setPassword("123");
        user.setPhone("18688990011");
        user.setBalance(200);
        user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        userService.save(user);
    }
}

介绍完了 Mapper 接口和 Service 接口,大家会发现,二者中有很多的方法都是重复的,从功能上是一致的,那么在实际业务开发中,到底该使用哪个接口中提供的方法呢,接下来我们来看一个案例:
在这里插入图片描述

首先导入相关依赖:

pom.xml:

<!--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>

application.yaml:

knife4j:
  enable: true
  openapi:
    title: 用户管理接口文档
    description: "用户管理接口文档"
    email: zhanghuyi@itcast.cn
    concat: 虎哥
    url: https://www.itcast.cn
    version: v1.0.0
    group:
      default:
        group-name: default
        api-rule: package
        api-rule-resources:
          - com.itheima.mp.controller

创建 DTO、VO:

UserFormDTO:

package com.itheima.mp.domain.dto;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel(description = "用户表单实体")
public class UserFormDTO {

    @ApiModelProperty("id")
    private Long id;

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("密码")
    private String password;

    @ApiModelProperty("注册手机号")
    private String phone;

    @ApiModelProperty("详细信息,JSON风格")
    private String info;

    @ApiModelProperty("账户余额")
    private Integer balance;
}

UserVO:

package com.itheima.mp.domain.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel(description = "用户VO实体")
public class UserVO {

    @ApiModelProperty("用户id")
    private Long id;

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("详细信息")
    private String info;

    @ApiModelProperty("使用状态(1正常 2冻结)")
    private Integer status;

    @ApiModelProperty("账户余额")
    private Integer balance;
}

UserController:

package com.itheima.mp.controller;

import cn.hutool.core.bean.BeanUtil;
import com.itheima.mp.domain.dto.UserFormDTO;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.vo.UserVO;
import com.itheima.mp.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Api(tags = "用户管理接口")
@RequestMapping("/users")
@RestController
@RequiredArgsConstructor
public class Usercontroller {

    private final IUserService userService;

    @ApiOperation("新增用户接口")
    @PostMapping
    public void saveUser(@RequestBody UserFormDTO userFormDTO){
        //1.把DTO拷贝到PO
        User user = BeanUtil.copyProperties(userFormDTO, User.class);
        //2.新增
        userService.save(user);
    }

    @ApiOperation("删除用户接口")
    @DeleteMapping("/{id}")
    public void deleteUserById(@ApiParam("用户id") @PathVariable("id") Long id){
        userService.removeById(id);
    }

    @ApiOperation("根据id查询用户接口")
    @GetMapping("/{id}")
    public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id){
        //1.查询用户PO
        User user = userService.getById(id);
        //2.把PO拷贝到VO
        return BeanUtil.copyProperties(user, UserVO.class);
    }

    @ApiOperation("根据id批量查询用户接口")
    @GetMapping
    public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids){
        //1.查询用户PO
        List<User> users = userService.listByIds(ids);
        //2.把PO拷贝到VO
        return BeanUtil.copyToList(users, UserVO.class);
    }


    @ApiOperation("扣减用户余额接口")
    @PutMapping("/{id}/deduction/{money}")
    public void deductBalance(@ApiParam("用户id") @PathVariable("id") Long id,
                              @ApiParam("扣减的金额") @PathVariable("money") Integer money){
        userService.deductBalance(id, money);
    }
}

IUserService:

package com.itheima.mp.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;

public interface IUserService extends IService<User> {

    void deductBalance(Long id, Integer money);
}

UserServiceImpl:

package com.itheima.mp.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.mapper.UserMapper;
import com.itheima.mp.service.IUserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    public void deductBalance(Long id, Integer money) {
        //1.查询用户
        User user = 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);
    }
}

UserMapper:

package com.itheima.mp.mapper;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.itheima.mp.domain.po.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;

import java.util.List;

public interface UserMapper extends BaseMapper<User> {

    void updateBalanceByIds(@Param(Constants.WRAPPER) QueryWrapper<User> wrapper, @Param("amount") int amount);

    @Update("update user set balance = balance - #{money} where id = #{id}")
    void deductBalance(@Param("id") Long id, @Param("money") Integer money);
}

通过这个案例可以发现,只有当业务逻辑相对复杂的时候,需要自己写一些业务,而 MybatisPlus 只提供一些基础的增删改查,这个时候就需要去自定义 service 方法了,并且在里面编写相应的业务代码,Mapper 同理,当出现 BaseMapper 提供的方法不足以满足我们的需求时,就需要我们去自定义了。

接下来,我们来看一下 IService 中提供的跟 Lambda 有关的几个方法,我们通过一个案例来看,案例需求如下:
在这里插入图片描述

UserController:

@ApiOperation("根据复杂条件查询用户接口")
@GetMapping("/list")
public List<UserVO> queryUsers(UserQuery userQuery){
    //1.查询用户PO
    List<User> users = userService.queryUsers(userQuery.getName(), userQuery.getStatus(), userQuery.getMinBalance(), userQuery.getMaxBalance());
    //2.把PO拷贝到VO
    return BeanUtil.copyToList(users, UserVO.class);
}

IUserService:

List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance);

UserServiceImpl:

@Override
    public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {

//        List<User> users = lambdaQuery()
//                .like(name != null, User::getUsername, name)
//                .eq(status != null, User::getStatus, status)
//                .ge(minBalance != null, User::getBalance, minBalance)
//                .le(maxBalance != null, User::getBalance, maxBalance)
//                .list();
//        return users;

        return lambdaQuery()
                .like(name != null, User::getUsername, name)
                .eq(status != null, User::getStatus, status)
                .ge(minBalance != null, User::getBalance, minBalance)
                .le(maxBalance != null, User::getBalance, maxBalance)
                .list();
    }

再看一个案例,需求如下:
在这里插入图片描述

该案例之前做过,所以进行改造即可。

UserServiceImpl:

@Override
@Transactional
public void deductBalance(Long id, Integer money) {
    //1.查询用户
    User user = getById(id);
    //2.校验用户状态
    if (user == null || user.getStatus() == 2){
        throw new RuntimeException("用户状态异常!");
    }
    //3.校验余额是否充足
    if (user.getBalance() < money){
        throw new RuntimeException("用户余额不足!");
    }
    //4.扣减余额
    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();
}

void deductBalance(Long id, Integer money) {
//1.查询用户
User user = getById(id);
//2.校验用户状态
if (user == null || user.getStatus() == 2){
throw new RuntimeException(“用户状态异常!”);
}
//3.校验余额是否充足
if (user.getBalance() < money){
throw new RuntimeException(“用户余额不足!”);
}
//4.扣减余额
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();
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值