MyBatisPlus
通过扫描实体类,并基于反射获取实体类信息作为数据库信息
- 类名驼峰转下划线作为表名
- 为id的字段作为主键
- 变量名驼峰转下划线作为表的字段名
遵守这些约定MyBatisPlus就会自动生成字段,方便我们快速实现
一、快速入门
- 起步依赖
MyBatisPlus官方提供了starter,其中集成了Mybatis和MybatisPlus的所有功能,并且实现了自动装配效果。
因此我们可以用MybatisPlus的starter代替Mybatis的starter:
xml
<!--mybatisplus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
- 定义Mapper
自定义的Mapper继承Mybatis提供的BaseMapper接口,注意这里
泛型需要指定为我们操作的类
java
public interface UserMapper extends BaseMapper<User> {
}
这里面继承了很多方法,方便我们使用
二、常见注解
常见注解如下
-
@TableName:用来
指定表名
- 用法:类名和表名不一致
-
@Tableld:用来
指定表中的主键字段信息
-
用法:主键id和数据库中不一致
-
type字段 设置主键的策略(这里需要指定不然默认雪花算法自动生成)
-
-
@TableField:用来
指定表中的普通字段信息
-
用法:普通字段和数据库中不一致
-
注意
- 字段为
is开头的类型
,必须要指定
否则mybatis会自动舍去掉is - 关键字冲突的必须要加上``
- 数据库中不存在的字段需要标记
- 字段为
-
三、常见配置
MyBatisPlus的配置项继承了MyBatis原生配置和一些自己特有的配置。
yaml
mybatis-plus:
#别名扫描包
type-aliases-package:
#xml文件地址,默认值
mapper-locations:
configuration:
map-underscore-to-camel-case: true #是否开启下划线和驼峰的映射
cache-enabled: false #是否开启二级缓存
global-config:
db-config:
id-type: assign_id #id为雪花算法生成
update-strategy: not_null #更新策略:只更新非空字段
四、条件构造器
MyBatisPlus支持各种复杂的where条件,可以满足日常开发的所有需求。
QueryWrapper
继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件
例子:查出名字带o,且存款大于等于1000的id,username,info
java
//构建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.select("id","username","info","balance")
.like("username","o")
.ge("balance",1000);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
例子:更新用户名为jack的余额为2000
java
//要更新的数据
User user = new User();
user.setBalance(2000);
//构建更新条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.eq("username","jack");
userMapper.update(user,wrapper);
UpdateWrapper
继承自
AbstractWrapper
,自身的内部属性entity
也用于生成 where 条件
例子:更新id为1,2,4的用户的余额,扣200
java
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);
LambdaWrapper
解决硬编码问题,推荐使用
java
//构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.select(User::getId,User::getUsername,User::getInfo,User::getBalance)
.like(User::getUsername,"o")
.ge(User::getBalance,1000);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
五、自定义SQL
我们可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分。
- 基于Wrapper构建where条件
- 在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew
- 自定义SQL并使用Wrapper条件
六、Service接口
MybatisPlus也提供了大量的service接口以便我们自己来使用
- 根据上图,我们可以看到,我们继承它给我们提供的Impl和我们自己实现的接口,注意红色框中需要指定泛型
userService
userServiceImpl
6.1、普通实现
那么我们简单的业务逻辑就可以直接在Controller中实现进行
java
/**
* 新增用户
* @param userFormDTO
*/
@PostMapping
@ApiOperation("新增用户 ")
public void addUser(@RequestBody UserFormDTO userFormDTO){
//拷贝
User User = new User();
BeanUtils.copyProperties(userFormDTO,User);
//执行新增
userService.save(User);
}
@DeleteMapping("/{id}")
@ApiOperation("删除用户 ")
public void deleteUser(@ApiParam("用户id") @PathVariable Long id){
//执行删除
userService.removeById(id);
}
当业务逻辑比较复杂的时候,我们就需要自己写。
当baseMapper提供的方法也不足以满足我们的需求,也需要自己写
6.2、lambda
查询
如果我们需要实现一个复杂的条件,例如下面这样
按照xml中我们就应该写成下面这种
而我们可以直接在serviceImpl中直接使用lambda快速构建
更新
这里必须要加update否则语句不会执行
6.3、批处理新增
如果在使用mysql数据库的时候,遇到批处理,MySQL的客户端连接参数中有这样的一个参数:rewriteBatchedStatements
。
修改项目中的application.yml文件,在jdbc的url后面添加参数&rewriteBatchedStatements=true
:
然后使用mybatis的批处理,让性能达到最好
java
@Test
void testSaveBatch() {
// 准备10万条数据
List<User> list = new ArrayList<>(1000);
long b = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
list.add(buildUser(i));
// 每1000条批量插入一次
if (i % 1000 == 0)
//预编译sql,提升速度
userService.saveBatch(list);
list.clear();
}
}
long e = System.currentTimeMillis();
System.out.println("耗时:" + (e - b));
}
七、扩展功能
7.1、代码生成器
这里没有使用官方给我们提供的,我们直接使用插件
在Idea顶部菜单中,找到other
,选择Config Database
配置数据库地址
在弹出的窗口中填写数据库连接的基本信息:
jdbc:mysql://localhost:3306/xx?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
然后再次点击Idea顶部菜单中的other,然后选择Code Generator
:
然后就会自动生成我们需要的代码
7.2、静态工具
有的时候Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:Db
,其中的一些静态方法与IService
中方法签名基本一致,也可以帮助我们实现CRUD功能:
java
List<Address> addresses = Db.lambdaQuery(Address.class).eq(Address::getId, id).list();
7.3、逻辑删除
逻辑删除
就是基于代码逻辑模拟删除效果,但并不会真正删除数据。思路如下:
- 在表中添加一个字段标记数据是否被删除
- 当删除数据时把标记置为1
- 查询时只查询标记为0的数据
增删改查那么我们都需要加上判断是否该数据处于删除状态
那么mybatisplus给我们提供了方法
只对自动注入的 sql 起效:
- 插入: 不作限制
- 查找: 追加 where 条件过滤掉已删除数据,如果使用 wrapper.entity 生成的 where 条件也会自动追加该字段
- 更新: 追加 where 条件防止更新到已删除数据,如果使用 wrapper.entity 生成的 where 条件也会自动追加该字段
- 删除: 转变为 更新
7.3.1、使用方法
- 例: application.yml
yaml
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
- 实体类字段上加上
@TableLogic
注解
java
@TableLogic
private Integer deleted;
7.3.2、注意
逻辑删除本身也有自己的问题,比如:
- 会导致数据库表垃圾数据越来越多,影响查询效率.
- SQL中全都需要对逻辑删除字段做判断,影响查询效率
因此,我不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表
的办法。
7.4、枚举处理器
解决了繁琐的配置,让 mybatis 优雅的使用枚举属性!
- 定义枚举类
要让MybatisPlus
处理枚举与数据库类型自动转换,我们必须告诉MybatisPlus
,枚举中的哪个字段的值作为数据库值。 MybatisPlus
提供了@EnumValue
注解来标记枚举属性:
@JsonValue
注解表示标记JSON序列化时展示的字段
7.5、JSON处理器
数据库的user表中有一个info
字段,是JSON类型:
JSON
{"age": 20, "intro": "佛系青年", "gender": "male"}
而目前User
实体类中却是String
类型:
这样一来,我们要读取info中的属性时就非常不方便。如果要方便获取,info的类型最好是一个Map
或者实体类
因此MybatisPlus提供了很多特殊类型字段的类型处理器,解决特殊字段类型与数据库类型转换的问题。例如处理JSON就可以使用JacksonTypeHandler
处理器。
方法
- 首先,我们定义一个单独实体类来与info字段的属性匹配:
Java
package com.itheima.mp.domain.po;
import lombok.Data;
@Data
public class UserInfo {
private Integer age;
private String intro;
private String gender;
}
- 接下来,将User类的info字段修改为UserInfo类型,并声明类型处理器:
八、插件功能
8.1、分页插件
在项目中新建一个配置类:
java
@Configuration
@MapperScan("scan.your.mapper.package")
public class MybatisPlusConfig {
/**
* 添加分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//初始化核心插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页插件
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
pageInterceptor.setMaxLimit(1000L);//设置分页上线
interceptor.addInnerInterceptor(pageInterceptor);//如果配置多个插件,切记分页最后添加
//interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 如果有多数据源可以不配具体类型 否则都建议配上具体的DbType
return interceptor;
}
}
编写一个分页查询的测试:
Java
@Test
void testPageQuery() {
// 1.分页查询,new Page()的两个参数分别是:页码、每页大小
int pageNo =1,pageSize = 2;
Page<User> page = Page.of(pageNo, pageSize);
//设置排序条件
page.addOrder(new OrderItem("balance",true));
//2.分页查询
Page<User> p = userService.page(page);
// 总条数
System.out.println("total = " + p.getTotal());
// 总页数
System.out.println("pages = " + p.getPages());
// 数据
List<User> records = p.getRecords();
records.forEach(System.out::println);
}