本笔记学习资源:狂神说Javahttps://space.bilibili.com/95256449?from=search&seid=4500591325352457207
大家多多支持,共同进步!!笔记中有错请各位不吝赐教。
文章目录
MyBatisPlus
了解
官网:https://mp.baomidou.com/
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
为什么学习MyBatisPlus
在MyBaits中,一些简单的SQL仍然需要手动编写,而使用了MyBatisPlus就可以帮我们省去简单的CRUD代码的编写过程。
快速开始
-
使用官方数据 建库,数据插入
-
spring-boot构建项目,MyBatisPlus等依赖导入
<!--新版--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1.tmp</version> </dependency> <!--旧版--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.5</version> </dependency>
-
数据库连接的相关配置
spring: application: name: mybatis_plus datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 170312 url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
-
编写User实体类
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; }
-
编写UserMapper接口
从这里开始,Mybatis与MyBatisPlus的区别开始显现出来
我们传统使用MyBatis的步骤是
- 编写Mapper接,定义方法
- 编写xxMapper.xml,通过SQL语句的编写实现接口中的方法
使用MyBatisPlus则就是简简单单一步
- Mapper接口继承
BaseMapper
并传入泛型,你甚至方法都不用定义,普通的CRUD代码就已经为你实现了。
-
测试使用
注意点:
- **在继承BaseMapper时需要传入 你所希望CRUD的对象类型 作为泛型参数。**例如上例中数据库中是User对象,所以我需要针对User创建一些简单的CRUD操作,只需要在UserMapper接口继承BaseMapper<User>。
- 不要忘了使用@Mapper,或者@MapperScan
以上快速使用就结束了,有几个问题需要思考
- 有哪些基本CRUD可以供我们使用?
- 使用BaseMapper中的方法时的Wrapper是什么?
通过简单浏览源码,和查看结构,简单的CRUD确实可以拿来即用,但是要想完全利用这个工具就需要了解
Wapper
这个类如何使用,基本上大部分的方法都需要这样一个参数,并且可看到泛型基本上每一个方法都使用到了,所以泛型参数一定的不能少的。
为了使我们能清楚看到MyBatisPlus自动生成的SQL我们可以进行日志配置,来输出生成的SQL语句
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
INSERT
先从插入开始,BaseMapper中只有一个方法与数据插入有关。insert(T t)
@Test
void insertUser() {
User user = new User();
user.setName("sakura");
user.setAge(20);
user.setEmail("843452233@qq.com");
int result = userMapper.insert(user);
System.out.println(result);
}
看的出来,我们并没有为主键Id 设置值,就直接进行了插入操作,结果如何呢?我们看看日志的输出
即时我们没有为主键设置内容,他也会为我们自动生成一个哦,并且能够保证这个主键是全局唯一的
这就涉及到一个相关的知识点——主键生成策略
与之紧密联系的就是分布式全局唯一ID生成策略,推荐阅读https://www.jianshu.com/p/9d7ebe37215e
文章中介绍主要用到的ID生成策略有以下几种
-
自增ID
-
UUID
-
雪花算法(SnowFlake)——Twitter
-
Redis生成
而这里使用的正是雪花算法,雪花算法是生成的是一个 64位的2进制整数,即8个字节的整数,也就对应我们使用的数据类型Long,而这64位数据也并非随意组合。
雪花算法(知识补充)
以下借助文章中的图文
1位标识符:始终是0,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。
41位时间戳:41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截 )得到的值,这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的。
10位机器标识码:可以部署在1024个节点,如果机器分机房(IDC)部署,这10位可以由 5位机房ID + 5位机器ID 组成。
12位序列:毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号
这样生成的id基本可以做到全球唯一。
了解完ID生成策略,我们回到主键生成策略,在主键id上使用@TableId(type=xxx)
来更改使用其他主键生成策略。默认是使用IdType.NONE
IdType
是一个枚举类,3.0.5版本提供了六种主键生成策略.
最新版本3.3.0 使用ASSIGN_ID
代替了ID_WORKER和ID_WORKER_STR,ASSIGN_UUID
代替了UUID
我们对这几种策略进行一一测试。
-
AUTO:数据库ID自增
这个策略在我们数据库没有勾选主键Id自增选项时,是无法使用的。
-
INPUT:用户自定义填充
这个就是你设置id是啥就是啥,但是需要程序员手动设置
后面三个只有在插入对象时id为空才会自动填充,若已经手动设置,是无法生效的
-
ID_WORKER(过时)替换使用ASSIGN_ID
这个就是使用的雪花算法,生成全球唯一ID,生成的ID是Long类型
-
UUID(过时)替换使用ASSIGN_UUID
通过UUID生成全球唯一ID
-
ID_WORKER_STR(过时)替换使用ASSIGN_ID
与ID_WORKER是一样的,它所生成的ID是String类型。
-
ASSIGN_ID(新版本更新)
生成ID是number类型或String类型
UPDATE
更新数据,BaseMapper提供了两个方法update()
和updateById()
在使用update方法时,需要我们传入一个条件Wrapper,如果填了null,会直接修改数据库中的所有数据,不要乱用噢
updateById(),也要求你的数据库中必须有ID这个字段,当然你在传入的User对象中不设置ID,在数据库中是无法匹配数据的,也就不能完成更新。
@Test
void updateUser() {
User user = new User();
user.setId(5L);
user.setName("sakura");
user.setAge(20);
user.setEmail("3424307473@qq.com");
int result = userMapper.updateById(user);
System.out.println(result);
}
现在我们就可以看到MyBatisPlus强大的地方,它可以通过我们传入的实体类对象,来判断我们要修改什么,进而为我们生成动态SQL。
字段自动填充
在数据库中必然有一些时间相关地字段,阿里巴巴开发手册中关于数据库就有要求:
必备字段:id、create_time、update_time,id前面我们提到了主键生成策略,而后面两个时间相关的字段也应该是由程序为我们自动生成,绝不是通过手动设置。这里就要用到自动填充
数据库修改(工作是禁止修改数据库,不推荐)
为createTime、updateTime两个字段设置默认值 CURREN_TIMESTAMP,并为updateTime字段勾选自动更新。
`create_time` datetime(6) DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间',
`update_time` datetime(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) COMMENT '修改时间',
当然这种情况是可用的,但是不推荐使用。
程序填充(重点)
首先我们恢复数据库字段到初始状态
`create_time` datetime(6) NOT NULL COMMENT '创建时间',
`update_time` datetime(6) NOT NULL COMMENT '修改时间',
然后我们要在实体类中做ORM映射 (这里推荐使用JDK8的新版时间类LocalDateTime)
private LocalDateTime createTime;
private LocalDateTime updateTime;
现在我们要对需要自动填充的字段使用@TableField
注解(这个注解简直是宝藏!)
源码中我们发现这个注解有一个fill
属性是用于设置字段自动填充策略
然后又能看到 有一个关于自动填充策略的枚举 FieldFill
,默认的填充策略是DEFAULT,进入FieldFill来看看还有哪些填充策略
- Default 默认不处理
- INSERT 插入时自动填充
- UPDATE 更新时自动填充
- INSERT_UPDATE 插入和更新时自动填充
这四个策略刚好满足我们的需求,createTime 只需要在插入时填充,updateTime在插入和更新时填充
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
修改完字段,按官方文档使用介绍我们还需要手动编写一个处理器并实现MetaObjectHandler接口,并重写insertFill、updateFill方法
基本框架:
@Slf4j
@Component // 一定要注册到容器
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
}
@Override
public void updateFill(MetaObject metaObject) {
}
}
在3.0.5版本中使用this.setFieldByName()
,来设置属性填充
setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject)
但是在更新到3.3.0+版本,这个方法过时了,采用this.strictInsertFill()
和strictUpdateFill()
strictInsertFill(MetaObject metaObject, String fieldName, Class<T> fieldType, Supplier<T> fieldVal)
strictUpdateFill(MetaObject metaObject, String fieldName, Class<T> fieldType, Object fieldVal)
明显后者定位属性要更加准确,要求我们设置自动填充属性的 属性名和类型
@Override
public void insertFill(MetaObject metaObject) {
log.info("insertFill start==>");
// 旧版本使用
// this.setFieldValByName("createTime",LocalDateTime.now(),metaObject);
// this.setFieldValByName("updateTime",LocalDateTime.now(),metaObject);
// 新版本使用
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
log.info("insertFill end <==");
}
@Override
public void