文章目录
一、MyBatis-Plus入门
大家在日常开发中应该能发现,单表的CRUD功能代码重复度很高,也没有什么难度。而这部分代码量往往比较大,开发起来比较费时。因此,目前企业中都会使用一些组件来简化或省略单表的CRUD开发工作。目前在国内使用较多的一个组件就是MyBatis-Plus。MyBatis-Plus不仅仅可以简化单表操作,而且还对Mybatis的功能有很多的增强。可以让我们的开发更加的简单,高效。MyBatis-Plus和Mybatis是协作关系。
1.1、快速开始
1.引入依赖。MyBatis-Plus提供了starter,实现了自动Mybatis以及MyBatis-Plus的自动装配功能。
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
2.定义Mapper。
为了简化单表CRUD,MyBatis-Plus提供了一个基础的BaseMapper
接口,其中已经实现了单表的CRUD:
因此我们自定义的Mapper只要实现了这个 BaseMapper ,就无需自己实现单表CRUD了。定义一个User类,对应一张数据表。
让UserMapper接口继承BaseMapper,然后可以编写代码实现单表的CRUD。实际上就是利用MyBatis-Plus省去了以前需要频繁编写的mapper语句。
1.2、常见注解
在刚刚的入门案例中,我们仅仅引入了依赖,继承了BaseMapper就能使用MyBatis-Plus,非常简单。但是问题来了,MyBatis-Plus如何知道我们要查询的是哪张表?表中有哪些字段呢?UserMapper在继承BaseMapper的时候指定了一个泛型,泛型中的User就是与数据库对应的PO。MyBatis-Plus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息,从而生成SQL的。默认情况下:
- MyBatis-Plus会把PO实体的类名驼峰转下划线作为表名。
- MyBatis-Plus会把PO实体的所有变量名驼峰转下划线作为表的字段名,并根据变量类型推断字段类型。
- MyBatis-Plus会把名为id的字段作为主键。
但很多情况下,默认的实现与实际场景不符,因此MyBatis-Plus提供了一些注解便于我们声明表信息。
1.2.1、@TableName
- 描述:表名注解,标识实体类对应的表
- 使用位置:实体类
1.2.2、@TableId
- 描述:主键注解,标识实体类中的主键字段
- 使用位置:实体类的主键字段
比较常见的type有三种,对应主键id的生成策略:
AUTO
:利用数据库的id自增长INPUT
:手动生成idASSIGN_ID
:雪花算法生成Long类型的全局唯一id,这是默认的ID策略
1.2.3、@TableField
- 描述:普通字段注解
一般情况下我们并不需要给字段添加@TableField注解,一些特殊情况除外:
- 成员变量名与数据库字段名不一致
- 成员变量是以isXXX命名,按照JavaBean的规范,MyBatis-Plus识别字段时会把is去除,这就导致与数据库不符。
- 成员变量名与数据库一致,但是与数据库的关键字冲突。使用@TableField注解给字段名添加转义字符:``
- 成员变量不是数据库字段
除了上面介绍的三个常用配置,其他配置可以看官网:
1.3、常见配置
MyBatis-Plus 提供了丰富的配置选项,以满足不同用户的需求。这些配置中,一部分继承自 MyBatis 原生支持的配置,另一部分则是 MyBatis-Plus 特有的扩展配置。
二、核心功能
刚才的案例中都是以id为条件的简单CRUD,一些复杂条件的SQL语句就要用到一些更高级的功能了。
2.1、条件构造器
除了新增以外,修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以id作为where条件以外,还支持更加复杂的where条件。
参数中的Wrapper
就是条件构造的抽象类,其下有很多默认实现,继承关系如图:
Wrapper的子类AbstractWrapper
提供了where中包含的所有条件构造方法:
而QueryWrapper
在AbstractWrapper的基础上拓展了一个select方法,允许查询指定字段:
而UpdateWrapper
在AbstractWrapper的基础上拓展了一个set方法,允许指定SQL中的SET部分:
2.1.1、QueryWrapper
无论是修改、删除、查询,都可以使用QueryWrapper来构建查询条件。接下来看一些例子:
1、查询:查询出名字中带o的,存款大于等于1000元的人的id、username、info、balance。代码如下:
@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);
}
2.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);
}
2.1.3、LambdaQueryWrapper
无论是QueryWrapper还是UpdateWrapper在构造条件的时候都需要写死字段名称,会出现字符串魔法值。这在编程规范中显然是不推荐的。那怎么样才能不写字段名,又能知道字段名呢?其中一种办法是基于变量的gettter方法结合反射技术。因此我们只要将条件对应的字段的getter方法传递给Mybatis-Plus,它就能计算出对应的变量名了。而传递方法可以使用JDK8中的方法引用和Lambda表达式。因此MybatisPlus又提供了一套基于Lambda的Wrapper,包含两个:
LambdaQueryWrapper
LambdaUpdateWrapper
分别对应QueryWrapper和UpdateWrapper,改造2.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);
}
2.2、自定义SQL
在演示UpdateWrapper的案例中,我们在代码中编写了更新的SQL语句,这种写法在某些企业也是不允许的,因为SQL语句最好都维护在持久层,而不是业务层。就当前案例来说,由于条件是in语句,只能将SQL写在Mapper.xml文件,利用foreach来生成动态SQL。这实在是太麻烦了:
假如查询条件更复杂,动态SQL的编写也会更加复杂。所以,Mybatis-Plus提供了自定义SQL功能,可以让我们利用Wrapper生成查询条件,再结合Mapper.xml编写SQL。
@Test
void testCustomWrapper() {
// 1.准备自定义查询条件
List<Long> ids = List.of(1L, 2L, 4L);
QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);
// 2.调用mapper的自定义方法,直接传递Wrapper
userMapper.deductBalanceByIds(200, wrapper);
}
然后在UserMapper中自定义SQL,在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew。
package com.itheima.mp.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.mp.domain.po.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import org.apache.ibatis.annotations.Param;
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);
}
2.3、Service接口
Mybatis-Plus不仅提供了BaseMapper,还提供了通用的Service接口及默认实现,封装了一些常用的service模板方法,这样一些常用的service也不用自己写了。通用接口为IService
,默认实现为ServiceImpl
,其中封装的方法可以分为以下几类:
- save:新增
- remove:删除
- update:更新
- get:查询单个结果
- list:查询集合结果
- count:计数
- page:分页查询
2.3.1、基本用法
由于Service中经常需要定义与业务有关的自定义方法,因此我们不能直接使用IService,而是自定义Service接口,然后继承IService以拓展方法。同时,让自定义的Service实现类继承ServiceImpl,这样就不用自己实现IService中的方法了,如下图所示:
2.3.2、批量新增性能对比
1.for循环逐条新增:这种方式是最慢的,相当于每新增一条数据都需要网络请求数据库,而网络请求是耗费时间的。
2.MybatisPlus的默认批量新增:每1000条(因为每次请求数据库的数据包大小是有上限的)网络请求一次数据库新增数据。其实MybatisPlus的批处理是基于PrepareStatement的预编译模式,然后批量提交,最终在数据库执行时还是会有多条insert语句,逐条插入数据,还是比较耗时的。
3.修改参数后的批量新增:为了更快的执行批量新增,我们希望利用一条SQL语句新增多条数据,即:
为了实现这个效果,需要增加配置参数,修改项目中的application.yml文件,在jdbc的url后面添加参数&rewriteBatchedStatements=true
。再次测试插入10万条数据,可以发现速度有非常明显的提升。这时因为在ClientPreparedStatement的executeBatchInternal中,有判断rewriteBatchedStatements值是否为true并重写SQL的功能(将多条SQL语句重写为一条)。
三、拓展功能
除了上述基础功能外,还提供了很多扩展功能,后续有需要可以看: