MyBatisPlus
通过扫描实体类,并基于反射获取实体类信息作为数据库信息
- 类名驼峰转下划线作为表名
- 为id的字段作为主键
- 变量名驼峰转下划线作为表的字段名
遵守这些约定MyBatisPlus就会自动生成字段,方便我们快速实现
一、快速入门
- 起步依赖
MyBatisPlus官方提供了starter,其中集成了Mybatis和MybatisPlus的所有功能,并且实现了自动装配效果。
因此我们可以用MybatisPlus的starter代替Mybatis的starter:
<!--mybatisplus-->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.2</version>
</dependency>
- 定义Mapper
自定义的Mapper继承Mybatis提供的BaseMapper接口,注意这里
泛型需要指定为我们操作的类
public interface UserMapper extends BaseMapper<User> {
}
这里面继承了很多方法,方便我们使用
二、常见注解
常见注解如下
-
@TableName:用来
指定表名
- 用法:类名和表名不一致
-
@Tableld:用来
指定表中的主键字段信息
-
用法:主键id和数据库中不一致
-
type字段 设置主键的策略(这里需要指定不然默认雪花算法自动生成)
-
-
@TableField:用来
指定表中的普通字段信息
-
用法:普通字段和数据库中不一致
-
注意
- 字段为
is开头的类型
,必须要指定
否则mybatis会自动舍去掉is - 关键字冲突的必须要加上``
- 数据库中不存在的字段需要标记
- 字段为
-
三、常见配置
MyBatisPlus的配置项继承了MyBatis原生配置和一些自己特有的配置。
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
//构建查询条件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
//要更新的数据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
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
解决硬编码问题,推荐使用
//构建查询条件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中实现进行
/*** 新增用户* @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的批处理,让性能达到最好
@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功能:
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
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
注解
@TableLogic
private Integer deleted;
7.3.2、注意
逻辑删除本身也有自己的问题,比如:
- 会导致数据库表垃圾数据越来越多,影响查询效率.
- SQL中全都需要对逻辑删除字段做判断,影响查询效率
因此,我不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表
的办法。
7.4、枚举处理器
解决了繁琐的配置,让 mybatis 优雅的使用枚举属性!
- 定义枚举类
要让MybatisPlus
处理枚举与数据库类型自动转换,我们必须告诉MybatisPlus
,枚举中的哪个字段的值作为数据库值。 MybatisPlus
提供了@EnumValue
注解来标记枚举属性:
@JsonValue
注解表示标记JSON序列化时展示的字段
7.5、JSON处理器
数据库的user表中有一个info
字段,是JSON类型:
{"age": 20, "intro": "佛系青年", "gender": "male"}
而目前User
实体类中却是String
类型:
这样一来,我们要读取info中的属性时就非常不方便。如果要方便获取,info的类型最好是一个Map
或者实体类
因此MybatisPlus提供了很多特殊类型字段的类型处理器,解决特殊字段类型与数据库类型转换的问题。例如处理JSON就可以使用JacksonTypeHandler
处理器。
方法
- 首先,我们定义一个单独实体类来与info字段的属性匹配:
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、分页插件
在项目中新建一个配置类:
@Configuration
@MapperScan("scan.your.mapper.package")
public class MybatisPlusConfig {/*** 添加分页插件*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {//初始化核心插件MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();//添加分页插件PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);pageInterceptor.setMaxLimit(1000L);//设置分页上线interceptor.addInnerInterceptor(pageInterceptor);//如果配置多个插件,切记分页最后添加//interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 如果有多数据源可以不配具体类型 否则都建议配上具体的DbTypereturn interceptor;}
}
编写一个分页查询的测试:
@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);
}