MybatisPlus概述及基本使用

318 阅读9分钟

1.MybatisPlus简介

MybatisPlus是全自动的ORM框架,按照开闭原则,在不影响Mybatis现有功能的情况下,实现了对Mybatis的功能增强,简单来说,MybatisPlusMybatis的增强工具,只做增强,不作改变,简化开发,提高效率

MybatisMybatisPlus 的区别: MyBatis

  • 所有SQL语句全部自己写
  • 手动解析实体关系映射转换为MyBatis内部对象注入容器
  • 不支持Lambda形式调用

MybatisPlus

  • 强大的条件构造器,满足各类使用需求
  • 内置的Mapper,通用的Service,少量配置即可实现单表大部分CRUD操作
  • 支持Lambda形式调用
  • 提供了基本的CRUD功能,连SQL语句都不需要编写
  • 自动解析实体关系映射转换为MyBatis内部对象注入容器

2.MybatisPlus快速开始

使用MybatisPlus之前先创建相关数据库及实体类:

数据库:

image-20231025095838953

实体类:

@Data
public class User {

    private Long id;

    private String username;

    private String password;

    private String phone;

    //详细信息
    private String info;

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

    // 账户余额
    private Integer balance;

    private LocalDateTime createTime;

    private LocalDateTime updateTime;
}

2.1 引入依赖

引入依赖:

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

由于这个starter包含对mybatis的自动装配,因此完全可以替换掉Mybatis的starter

2.2 定义Mapper

为了简化单表CRUD,MybatisPlus提供了一个基础的BaseMapper接口,其中已经实现了单表的CRUD

因此我们自定义的Mapper只要实现了这个BaseMapper,就无需自己实现单表CRUD了

创建UserMapper接口,让其继承BaseMapper

package com.qing.mp.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qing.mp.domain.po.User;

public interface UserMapper extends BaseMapper<User> {
}

2.3 CRUD操作

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

    //根据id查询用户
    @Test
    void testSelectById() {
        User user = userMapper.selectById(5L);
        System.out.println("user = " + user);
    }

    //修改用户数据
    @Test
    void testUpdateById() {
        User user = new User();
        user.setId(5L);
        user.setBalance(20000);
        userMapper.updateById(user);
    }
    //删除用户
    @Test
    void testDelete() {
        userMapper.deleteById(5L);
    }
}

3.MybatisPlus常见注解

在上述的快速入门案例中,我们仅仅引入了依赖,继承了BaseMapper就能使用MybatisPlus进行CRUD了,非常简单

但是问题来了: MybatisPlus如何知道我们要查询的是哪张表?表中有哪些字段呢?

UserMapper在继承BaseMapper的时候指定了一个泛型User实体类

Mybatisplus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息

MybatisPlus就是根据实体类的信息来推断出表的信息,从而生成SQL的。默认情况下:

  • MybatisPlus会把实体的类名驼峰转下划线作为表名
  • MybatisPlus会把实体的所有变量名驼峰转下划线作为表的字段名,并根据变量类型推断字段类型
  • MybatisPlus会把名为id的字段作为主键

但很多情况下,默认的实现与实际场景不符,因此MybatisPlus提供了一些注解便于我们声明表信息

3.1 @TableName

作用:用于标记实体类对应的数据库表名

使用@TableName注解时,需要在实体类上添加该注解,并指定对应的表名

示例:

@TableName("user")
public class User {
  private Long id;
  private String name;
  private Integer age;
}

3.2 @TableId

作用:用于标记实体类属性对应的数据库主键

使用@TableId注解时,可以指定主键的属性,例如主键的类型、是否为自增、是否为全局唯一标识符等

示例:

public class User {
  @TableId(type = IdType.AUTO)
  private Long id;
  
  private String userName;
  private Integer userAge;
}

3.3 @TableField

作用:用于标记实体类属性对应的数据库字段名

使用@TableField注解的常见场景:

  • 成员变量名与数据库字段不一致
  • 成员变量名以is开头,且是布尔值
  • 成员变量名与数据库关键字冲突,如order
  • 成员变量不是数据库字段

示例:

@TableName("user")
public class User {
    @TableId
    private Long id;
    private String name;
    private Integer age;
    @TableField("isMarried")
    private Boolean isMarried;
    @TableField("`order`")
    private Integer order;
    @TableField(exist = false)
    private String address;
}

4.MybatisPlus核心功能

在入门案例中都是以id为条件的简单CRUD,一些复杂条件的SQL语句就要用到一些更高级的功能才能实现

4.1 条件构造器

除了新增以外,修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以id作为where条件以外,还支持更加复杂的where条件

Wrapper是条件构造的抽象类,其下有很多默认实现,继承关系如图:

image-20231025164143782

4.1.1 QueryWrapper

无论是修改、删除、查询,都可以使用QueryWrapper来构建查询条件

示例:

1.查询出名字中带o的,存款大于等于1000元的人。代码如下:

@Test
void testQueryWrapper() {
    // 1.构建查询条件 where name like "%o%" AND balance >= 1000
    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.更新用户名为jack的用户的余额为2000,代码如下:

@Test
void testUpdateByQueryWrapper() {
    // 1.构建查询条件 where name = "Jack"
    QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "Jack");
    // 2.更新数据,user中非null字段都会作为set语句
    User user = new User();
    user.setBalance(2000);
    userMapper.update(user, wrapper);
}

4.1.2 UpdateWrapper

基于BaseMapper中的update方法更新时只能直接赋值,对于一些复杂的需求就难以实现。

例如:更新id为1,2,4的用户的余额,扣200,对应的SQL应该是:

UPDATE user SET balance = balance - 200 WHERE id in (1, 2, 4)

SET的赋值结果是基于字段现有值的,这个时候就要利用UpdateWrapper中的setSql功能了:

@Test
void testUpdateWrapper() {
    List<Long> ids = List.of(1L, 2L, 4L);
    // 1.生成SQL
    UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
            .setSql("balance = balance - 200") // SET balance = balance - 200
            .in("id", ids); // WHERE id in (1, 2, 4)
    // 2.更新,第一个参数可以给null,也就是不填更新字段和数据,而是基于UpdateWrapper中的setSQL来更新
    userMapper.update(null, wrapper);
}

4.1.3 LambdaQueryWrapper

无论是QueryWrapper还是UpdateWrapper在构造条件的时候都需要写死字段名称,这在编程规范中是不推荐的

因此MybatisPlus又提供了一套基于Lambda的Wrapper,包含两个:LambdaQueryWrapperLambdaUpdateWrapper

针对4.1.1中的例1可做如下调整:

@Test
void testLambdaQueryWrapper() {
    // 1.构建条件 WHERE username LIKE "%o%" AND balance >= 1000
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.lambda()
            .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);
}

4.2 自定义SQL

在上述代码中:

image-20231026160229079

这种写法在某些企业是不允许的,因为SQL语句最好都维护在持久层,而不是业务层。就当前案例来说,由于条件是in语句,只能将SQL写在Mapper.xml文件,利用foreach来生成动态SQL, 比较麻烦,假如查询条件更复杂,动态SQL的编写也会更加复杂

MybatisPlus提供了自定义SQL功能,可以让我们利用Wrapper生成查询条件,再结合mapper.xml编写SQL

对于这个案例,我们可以利用MybatisPlus的Wrapper来构建复杂的where语句,然后自定义sql语句中的剩下部分:

1.基于wrapper构建where条件

@Test
void testCustomWrapper() {
    // 1.准备自定义查询条件
    List<Long> ids = List.of(1L, 2L, 4L);
    QueryWrapper<User> wrapper = new QueryWrapper<User>().in(User::getId, ids);
    // 2.调用mapper的自定义方法,直接传递Wrapper
    userMapper.deductBalanceByIds(money, wrapper);
}

2.在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew

public interface UserMapper extends BaseMapper<User> {
   
    void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper);
}

3.自定义SQL,并使用wrapper条件

  • 直接在方法上面使用注解编写

    public interface UserMapper extends BaseMapper<User> {
        @Select("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}")
        void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper);
    }
    
  • 在UserXml.xml中编写

    <update id="updateBalanceByIds">
        UPDATE tb_user SET balance = balance - #{money} ${ew.customSqlSegment}
    </update>
    

4.3 IService接口

MybatisPlus不仅提供了BaseMapper,还提供了通用的Service接口及默认实现,封装了一些常用的service模板方法。 通用接口为IService,默认实现为ServiceImpl,其中封装的方法可以分为以下几类:

  • save:新增
  • remove:删除
  • update:更新
  • get:查询单个结果
  • list:查询集合结果
  • count:计数
  • page:分页查询

继承关系如下:

image-20231030100942335

4.3.1 IService接口的基本用法

首先,定义IUserService,继承IService

public interface IUserService extends IService<User> {
    // 拓展自定义方法
}

然后,编写UserServiceImpl类,继承ServiceImpl,实现UserService

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

4.3.2 IService接口实例应用

接下来,我们快速实现下面的实例接口:

编号接口请求方式请求路径请求参数返回值
1新增用户POST/users用户表单实体
2删除用户DELETE/users/{id}用户id
3根据id查询用户GET/users/{id}用户id用户VO
4根据id批量查询GET/users用户id集合用户VO集合
5根据id扣减余额PUT/users/{id}/deduction/{money}用户id、扣减金额

首先,接口需要两个实体:

  • UserFormDTO:代表新增时的用户表单
  • UserVO:代表查询的返回结果

UserFormDTO:

@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:

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

然后,按照Restful风格编写Controller接口方法:

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

    private final IUserService userService;

    @PostMapping
    @ApiOperation("新增用户")
    public void saveUser(@RequestBody UserFormDTO userFormDTO){
        // 1.转换DTO为PO
        User user = BeanUtil.copyProperties(userFormDTO, User.class);
        // 2.新增
        userService.save(user);
    }

    @DeleteMapping("/{id}")
    @ApiOperation("删除用户")
    public void removeUserById(@PathVariable("id") Long userId){
        userService.removeById(userId);
    }

    @GetMapping("/{id}")
    @ApiOperation("根据id查询用户")
    public UserVO queryUserById(@PathVariable("id") Long userId){
        // 1.查询用户
        User user = userService.getById(userId);
        // 2.处理vo
        return BeanUtil.copyProperties(user, UserVO.class);
    }

    @GetMapping
    @ApiOperation("根据id集合查询用户")
    public List<UserVO> queryUserByIds(@RequestParam("ids") List<Long> ids){
        // 1.查询用户
        List<User> users = userService.listByIds(ids);
        // 2.处理vo
        return BeanUtil.copyToList(users, UserVO.class);
    }
}

可以看到上述四个接口都直接在controller即可实现,无需编写任何service代码,非常方便

不过,一些带有业务逻辑的接口则需要在service中自定义实现了。例如第五个接口:根据id扣减用户余额

首先在UserController中定义一个方法:

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

然后是UserService接口:

public interface IUserService extends IService<User> {
    void deductBalance(Long id, Integer money);
}

最后是UserServiceImpl实现类:

@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.deductMoneyById(id, money);
    }
}

最后是mapper:

@Update("UPDATE user SET balance = balance - #{money} WHERE id = #{id}")
void deductMoneyById(@Param("id") Long id, @Param("money") Integer money);