我是 ABin-阿斌:写一生代码,创一世佳话,筑一揽芳华。 如果小伙伴们觉得我的文章有点 feel ,那就点个赞再走哦。
一、什么是 MyBatis-plus
二、MyBatis-plus与MyBatis有和不同
2.1、不同之处
MyBatis:
- 简单易学: 没有任何的第三方依赖,比较轻量化。
- 灵活: SQL 语句只需要我们写在 xml 中即可,便于我们的统一化管理和维护。不会对我们的应用层或者 DB 的现有设计附加不必要的影响。
- 解除了我们SQL 与业务代码的耦合性: 通过提供的 DAL 层,将业务逻辑和数据访问逻辑进行一个有效的分离,从而提高了我们的一个可维护性。
- 提供了对象关系映射标签: 支持对象关系组建维护
- 提供了 xml 标签,支持编写动态 SQL: 我们可以直接编写动态 SQL
MyBatis-Plus:
- 无侵入: 只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小: 启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作, BaseMapper
- 强大的 CRUD 操作: 内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求, 简单的 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
三、快速入门:MyBatis-plus
3.1、创建数据表
CREATE TABLE user (
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (id) );
INSERT INTO user (id, name, age, email)
VALUES (1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
3.2、创建 SpringBoot 项目,导入相关依赖
注意:我们使用 MyBatis-plus 可以节省我们大量的代码,尽量不要同时导入 MyBatis 和 MyBatis-plus 的依赖,以免造成版本冲突。
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- mybatis-plus -->
<!-- mybatis-plus 是自己开发,并非官方的! -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
3.3、配置连接数据库信息
# MySQL数据配置
# mysql 5 驱动不同 com.mysql.jdbc.Driver
# mysql 8 驱动不同com.mysql.cj.jdbc.Driver、需要增加时区的配置:serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/test1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
3.4、相关类的创建
实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
Mapper接口
注意:别忘记在主启动类上去扫描我们的mapper包下的所有接口
代码:@MapperScan(“com.mangobin.mapper”)
/**
* @author ABin-阿斌
* @description
* @date 2021/4/1
*/
// 在对应的Mapper上面继承基本的类 BaseMapper
@Repository //该注解表示当前是一个持久层
public interface UserMapper extends BaseMapper<User> {
}
编写测试类
@SpringBootTest
@Slf4j
class MybatisPlus01ApplicationTests {
@Autowired
private UserMapper userMapper;
/**
* 数据查询测试
*/
@Test
void contextLoads() {
//由于我们Mapper 接口继承了 BaseMapper,它里面会帮我们完成最基本的CRUD操作
//而不需要我们去编写 SQL,除非有一些多表或复杂的业务时才需要我们自己去定义
//查询所有用户数据
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
测试结果
四、配置日志信息
当我们执行程序的时候我们可以清晰的看到打印 SQL 语句的日志信息,这个在我们实际开发找 Bug 排错起到了很大的作用。
#日志配置
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
配置完成后的效果展示:
五、CRUD的具体操作
5.1、插入操作(Insert)
/**
* 数据插入测试
*/
@Test
public void testInsert() {
User user = new User();
user.setName("里西西");
user.setAge(3);
user.setEmail("2121212@qq.com");
int result = userMapper.insert(user);
//打印插入信息,查是否插入成功
log.info("插入的条数为:" + result);
//查看 Id 自增注解是否有效
log.info("Id自增展示:" + user);
}
5.2、更新操作
/**
* 数据更新测试
*/
@Test
public void testUpdate() {
User user = new User();
user.setId(1378265857087610884L);
user.setName("栈北北");
user.setAge(22);
user.setEmail("6666212@qq.com");
int result = userMapper.updateById(user);
//打印插入信息,查是否插入成功
log.info("修改的条数为:" + result);
}
注意事项:
5.3、查询操作
/**
* 根据 Id 查询数据
*/
@Test
public void testSelectById() {
User user = userMapper.selectById(3L);
log.info("查询到的数据为:" + user);
}
/**
* 查询所有数据
*/
@Test
public void testAll() {
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 3, 5));
users.forEach(System.out::println);
}
/**
* 使用 Map 进行条件查询
*/
@Test
public void testSelectByMap() {
HashMap<String, Object> map = new HashMap(16);
map.put("name", "张三");
map.put("age", 22);
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}
5.4、分页查询操作
在实际开发当中不管我们做的是什么项目,分页查询功能是我们不可避免要做的。常见的分页做法有: 原始的 limit 进行分页、pageHelper 第三方插件。当然,在 Mybatis-plus 当中也有它特有的分页功能。
实现思路:
- 第一步:我们注入MyBatis-plus 分页插件
/**
* 分页插件
*
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
- 第二步:编写测试类
@Test
public void testPage() {
/**
* 参数一:当前页
* 参数二:页面大小
*/
Page<User> page = new Page<>(1, 3);
page.getRecords().forEach(System.out::println);
userMapper.selectPage(page, null);
System.out.println(page.getTotal());
}
5.5、删除操作
/**
* 测试删除操作
*/
@Test
public void testDeleteById() {
userMapper.deleteById(4L);
}
/**
* 通过 Id 进行删除
*/
@Test
public void testDeleteBatchIds() {
userMapper.deleteBatchIds(Arrays.asList(
1L,
2L,
6L));
}
/**
* 通过 Map 集合进行删除
*/
@Test
public void testDeleteByMap() {
HashMap<String, Object> map = new HashMap<>(16);
map.put("name", "小东东");
map.put("age", 22);
userMapper.deleteByMap(map);
}
5.6、逻辑删除
什么是逻辑删除
- 一般我们删除从某种意义上可以分为两种:一种叫【物理删除】,一种叫【逻辑删除】。
- 物理删除:我们一般指的是从数据库中直接删除,可直观看见的。
- 逻辑删除:我们一般指的是:在数据库中我们并没有发现数据变动,而是通过一个变量来让它失效。比如:deleted = 0 或 deleted = 1
有什么作用
- 不管我们做什么项目都会有一个后台管理系统,该系统一般用于我们处理数据和用户信息数据。那么,这么多的数据难免会有人开发人员进行删除,防止误删丢失有效数据,我们管理人员【权限配置】可以看到是删除该记录的人员是那个,从而有效进行恢复。
如何使用
- 第一步:我们在 User 表中加入 deleted 字段用来做标识
注意:加入该字段时,设置默认值为 0(表示未执行) 1(表示:已执行逻辑删除)
- 第二步:在实体类中加入该属性和注解
/**
* 逻辑删除标识
*/
@TableLogic
private Integer deleted;
- 第三步:注入逻辑删除组件
/**
* 逻辑删除
*/
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
- 第四步:在配置文件中加入【逻辑删除配置】
# 配置逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
- 第五步:测试
/**
* 测试删除操作
*/
@Test
public void testDeleteById() {
userMapper.deleteById(4L);
}
/**
* 通过 Id 进行删除
*/
@Test
public void testDeleteBatchIds() {
userMapper.deleteBatchIds(Arrays.asList(
1L,
2L,
6L));
}
查询测试
/**
* 查询所有数据
*/
@Test
public void testAll() {
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 3, 5));
users.forEach(System.out::println);
}
六、主键生成策略
什么是组件生成策略:具体可看这篇文章:什么是组件生成策略
雪花算法:(snowflake)
- Snowflake是 Twitter 开源的分布式 ID 生成算法,结果是一个 long 型的 ID。其核心思想是: 使用 41bit 作为毫秒数,10bit 作为机器的 ID(5个 bit 是数据中心,5个 bit 的机器ID),12bit 作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位永远是0,可以保证几乎全球唯一性。
6.1、配置主键自增
- 第一步:将数据表id字段设置成自增
- 第二步:我们在实体类字段上加上注解
具体源码解析:
public enum IdType {
AUTO(0), // 数据库id自增
NONE(1), // 未设置主键
INPUT(2), // 手动输入
ID_WORKER(3), // 默认的全局唯一id
UUID(4), // 全局唯一id uuid
ID_WORKER_STR(5); //ID_WORKER 字符串表示法
}
七、配置MySQL自动填充时间信息
在我们实际开发当中,一般表中的时间字段是不需要我们手动去做修改,都是由自动化来进行完成。
7.1、通过数据库的方式:
- 第一步:在User 表中添加时间字段,且为自动更新
- 将 create_time、update_time,类型选 datetime ,然后默认值设置【CURRENT_TIMESTAMP】
第二步:在实体类中添加上这两个属性
private Date createTime;
private Date updateTime;
第三步:我们再次测试插入操作,看看是否有变化
7.2、使用代码的方式:
第一步:我们去除刚刚在数据库中默认值的配置
第二步:在实体类属性上加上相应的注解
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 修改时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
第三步:编写处理器来对该注解进行处理
/**
* @author ABin-阿斌
* @description 时间自动更新处理器
* @date 2021/4/3
*/
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 输入插入时的时间填充
*
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("数据正在插入中.......");
/**
* @param fieldName Java bean属性名称
* @param fieldVal java bean 属性值
* @param metaObject 元对象参数
*/
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}
/**
* 数据修改时的数据填充
*
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("数据正在修改中......");
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
第四步:测试我们的插入与更新,查看是否生效
八、乐观锁与悲观锁
8.1 什么是乐观锁与悲观锁
- 具体可以通过这篇文章去了解:JavaGuide的乐观锁与悲观锁详解
8.2、如何使用乐观锁
具体的实现思路:
- 取出记录时,获取当前 version(版本号)
- 更新时,带上这个 version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果 version 不对,就更新失败
乐观锁:1、先查询,获得版本号 version = 1
-- A
update user set name = "张三", version = version + 1
where id = 2 and version = 1
-- B 线程抢先完成,这个时候 version = 2,会导致 A 修改失败!
update user set name = "张三", version = version + 1
where id = 2 and version = 1
第一步:我们在数据库中添加 version(版本号)字段
第二步:在实体类中添加【版本号】属性和注解
/**
* 版本号:乐观锁
*/
@Version
private Integer version;
第三步:注入乐观锁的插件
/**
* @author ABin-阿斌
* @description Mybatis-Plus配置类
* @date 2021/4/3
*/
@MapperScan("com.mangobin.mapper")
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {
/**
* 注入乐观锁插件
*
* @return
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
第四步:编写乐观锁测试类
/**
* 测试乐观锁的版本号机制是否生效
*/
@Test
public void testOptimisticLocker() {
User user = userMapper.selectById(3L);
user.setName("李四");
user.setAge(22);
userMapper.updateById(user);
}
/**
* 乐观锁失效:多线程并发下的修改操作
*/
@Test
public void testConcurrentModification() {
User user = userMapper.selectById(3L);
user.setName("马到成功");
user.setAge(22);
User user2 = userMapper.selectById(3L);
user2.setAge(25);
user2.setName("青云直上");
userMapper.updateById(user2);
userMapper.updateById(user);
}
SQL性能分析插件
性能分析插件是什么
-
在我们日常开发工作当中,避免不了查看当前程序所执行的 SQL 语句,便于程序员排忧解难。
-
MyBatis-plus 提供了两种方式,用于输出每条 SQL 语句及其执行时间,针对执行较长时间的 SQL 可以停止运行,有助于发现问题。
-
注意: 这两种方式只适用于开发环境和测试环境,不建议生产环境使用,因为在分析SQL时会增大我们的一个性能消耗。
如何使用
- 第一步:导入插件
/**
* SQL执行效率插件
* @Profile设置: dev test 环境开启,保证我们的效率
*/
@Bean
@Profile({"dev", "test"})
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(1000);
// ms设置sql执行的最大时间,如果超过了则不 执行
performanceInterceptor.setFormat(true);
// 是否格式化代码
return performanceInterceptor;
}
- 第二步:设置环境为dev,可以在yml如下配置,或者直接测试类中也可以设置。
# 配置开发环境:dev
spring.profiles.active=dev
- 第三步:编写测试类,查看插件是否生效
/**
* 数据查询测试
*/
@Test
void contextLoads() {
//由于我们Mapper 接口继承了 BaseMapper,它里面会帮我们完成最基本的CRUD操作
//而不需要我们去编写 SQL,除非有一些多表或复杂的业务时才需要我们自己去定义
//查询所有用户数据
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
十、条件构造器
什么是条件构造器
- 条件构造器可谓是MP的一大巨作了,大大提高了我们的开发效率。
- 接口方法的参数中,会出现各种 Wrapper,比如 queryWrapper、updateWrapper 等。Wrapper 的作用就是用于定义各种各样的条件(where)。所以不管是查询、更新、删除都会用到 Wrapper。
如何使用
- 代码演示:这里只举例比较常用的几种,更多的演示可以参考【官方文档】
package com.mangobin;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.mangobin.entity.User;
import com.mangobin.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
/**
* @author ABin-阿斌
* @description
* @date 2021/4/5
*/
@SpringBootTest
@Slf4j
public class WrapperTest {
@Autowired
private UserMapper userMapper;
/**
* isNotNull(不为空)、ge(大于等于)、 eq(等于) 的具体使用
* 查询用户名、邮箱不为空的用户,且年龄 >= 12的
*/
@Test
void contextLoads() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
// userQueryWrapper.isNotNull("name")
// .isNotNull("age")
// .ge("age", 12);
// List<User> users = userMapper.selectList(userQueryWrapper);
// users.forEach(System.out::println);
userQueryWrapper.eq("name", "青云直上");
userMapper.selectOne(userQueryWrapper);
System.out.println(userQueryWrapper);
}
/**
* Between(区间范围)、Like(模糊查询:左右)、OrderBy(排序)
*/
@Test
void test02() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
//区间条件
// userQueryWrapper.between("age", 20, 30);
// userMapper.selectCount(userQueryWrapper);
//模糊查询
/* userQueryWrapper.notLike("name", "e")
.likeRight("email", "t");
List<Map<String, Object>> maps = userMapper.selectMaps(userQueryWrapper);
maps.forEach(System.out::println);*/
//通过子查询进行查询
userQueryWrapper.inSql("id", "select id from user where id <= 3");
List<User> users = userMapper.selectList(userQueryWrapper);
users.forEach(System.out::println);
//排序
//通过 Id 进行升序查询
// userQueryWrapper.orderByAsc("id");
// List<User> users = userMapper.selectList(userQueryWrapper);
// users.forEach(System.out::println);
}
}