文章目录
- 一.概念
- 1.1 简介
- 1.2 特性
- 二.快速入门
- 三.CRUD扩展
- 3.1 Insert插入
- 3.2 主键生成策略
- 3.3 Update更新
- 3.4 自动填充
- 3.5 乐观锁
- 3.6 查询操作
- 3.7 删除操作
- 3.8 性能分析插件(新版本的Mybatis-plus已将此插件移除)
- 3.9 条件构造器
- 3.10 代码生成器
一.概念
1.1 简介
Mybatisplus从字面上面理解时Mybatis的加强版,它可以大大节省我们大量的工作时间,所有的增删改查代码它都可以在mybatisplus中自动的完成,其本质上上Mybatis的一个增强工具,在Mybatis的基础上只做增强不做改变,为简化开发,提高效率而生。
1.2 特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
二.快速入门
使用官方提供的快速开始文档
- 创建数据库
mybatisplus
- 创建表
user
插入数据
DROP TABLE IF EXISTS user;
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)
);
DELETE FROM user;
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');
- 创建一个springboot项目
- 导入依赖
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
- 连接数据库
- 配置SpringBoot配置文件
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatisplus?userSSL=false&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
- 编写pojo类(与传统使用Mybatis一样)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class user {
private Long id;
private String name;
private Integer age;
private String email;
}
- 配置Dao层
使用mybatisplus之后只需要在对应的mapper上面继承基本的类BaseMapper即可(不需要像Mybatis那样繁琐)。到此所有的CRUD已经Mybatisplus帮我们自动编写完成十分方便(当然我们也可以在这里面定义自己的代码)。
@Repository //代表这个接口是Dao层的
public interface UserMapper extends BaseMapper<User> {
}
- 在启动类上配置mapper扫描(此时dao层就配置完毕了)
@MapperScan("com.chailong.getspring.mapper") //扫描mapper的文件夹
@SpringBootApplication
public class GetspringApplication {
public static void main(String[] args) {
SpringApplication.run(GetspringApplication.class, args);
}
- 在测试类中测试
@SpringBootTest
class GetspringApplicationTests {
@Autowired
private UserMapper userMapper; //注入usermapper的bean
@Test
void contextLoads() {
//seleclist的参数是一个wrapper(条件构造器)
List<user> userList=userMapper.selectList(null);//查询全部
for (user user1 : userList) {
System.out.println(user1);
}
}
}
运行结果:
8. 配置日志
在前面的操作过程中我们是具体看不到我们的sql语句的,为了能在控制台看到我们的sql语句,我们需要对日志进行配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
结果:
三.CRUD扩展
3.1 Insert插入
- 编写插入代码
@Test
void testInsert() {
user usernew=new user();
usernew.setName("chailong");
usernew.setAge(11);
usernew.setEmail("2012126846@qq.com");
int result=userMapper.insert(usernew);
System.out.println(result);
}
结果:(发现自动插入了id)
2. 问题
思考这里为什么会自动生成唯一ID?
这就要涉及到分布式系统唯一id生成中的雪花算法
3.2 主键生成策略
- 雪花算法
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。可以保证ID唯一性。
- 使用
@TableId
注解可以配置主键生成策略(下面是常见的策略)
默认策略是ID_WORKER全局唯一ID
@Data
@AllArgsConstructor
@NoArgsConstructor
public class user {
@TableId(type = IdType.ID_WORKER )
private Long id;
private String name;
private Integer age;
private String email;
}
3. 主键自增策略
- 在实体类字段上配置
@TableId(type=IdType.AUTO)
- 勾选Mysql数据库中ID自增选项
- 其它自增策略源码解释
public enum IdType {
AUTO(0), //数据库id自增
NONE(1),//未设置主键
INPUT(2),//手动输入(数据库里面自己填)
ID_WORKER(3),//默认全局唯一id
UUID(4),//全局唯一id
ID_WORKER_STR(5);//ID_worker的字符串表示法
}
3.3 Update更新
测试类代码
@Test
void testUpdate() {
user usernew=new user();
usernew.setId(11);
usernew.setName("jakiechai");
usernew.setAge(11);
usernew.setEmail("2012126846@qq.com");
int i=userMapper.updateById(usernew);
System.out.println(i);
}
结果:
可以发现mybatis-plus帮我们实现了自动动态配置,这就需要讲到mybatis-plus的自动填充技术,下面介绍自动填充技术。
3.4 自动填充
问题提出:在创建数据表中我们有些参数是不希望用户去手动填充的而是希望系统帮我们去自动填充(如表创建时间和表修改时间等)。阿里巴巴开发手册规定几乎所有数据表字段都必须要有一个gmt_create(创建时间)和gmt_modified(修改时间),而这两个参数是需要系统自动填充的;这就涉及到了自动填充技术。
- 数据库级别自动填充(实际生活中不建议这种方法)
在表中新增字段创建时间和修改时间设置默认值即可(修改表字段即可,注意pojo对应属性也要更改)
- 代码级别自动填充
- 删除数据库默认值
- 实体类属性上加入相关注解
在TableField接口中有一个fill函数用于属性值的自动填充
FieldFill fill() default FieldFill.DEFAULT;
FieldFill
的值表示填充的方法
public enum FieldFill {
DEFAULT, //默认不填充
INSERT, //插入时填充
UPDATE, //更新时填充
INSERT_UPDATE; //更新或插入时填充
}
然后在类属性上加入@TableField
注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class user {
@TableId(type = IdType.AUTO )
private int id;
private String name;
private Integer age;
private String email;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
- 编写处理器来处理注解
- 在主目录下建立一个Handler包,专门用来放SpringBoot项目的处理器,编写处理器代码
@Slf4j //使用日志
@Component //处理器加入到ioc容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
//实现插入时自动填充
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill.....");
//setFieldValByName(字段名,字段值,metaObject)
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
log.info("insert over");
}
//实现更新时自动填充
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill.....");
//setFieldValByName(字段名,字段值,metaObject)
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
log.info("update over");
}
}
- 测试类代码
@Test
void testInsert() {
user usernew=new user();
usernew.setName("menglei");
usernew.setAge(11);
usernew.setEmail("2012126846@qq.com");
int result=userMapper.insert(usernew);
System.out.println(result);
}
运行结果:数据库插入成功(字段也帮我们自动填充了)
3.5 乐观锁
- 简介
所谓的乐观锁,其实主要就是一种思想,因为乐观锁的操作过程中其实没有没有任何锁的参与,乐观锁只是和悲观锁相对,严格的说乐观锁不能称之为锁。所以要了解乐观锁的概念,通常与悲观锁对比起来看才更好理解,下面我们就通过乐观锁与悲观锁的对比来更好的理解乐观锁。乐观锁总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,只在更新的时候会判断一下在此期间别人有没有去更新这个数据。悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞,直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
- 乐观锁实现方式
- 取出记录时获取当前version
- 更新时带上这个version
- 执行更新时,set version=nerVersion where version =oldVersion
- 如果version不对,就更新失败
- 举例说明
假设两个线程A和B都在执行更新操作:
//A执行更新
update user set name="kuanshen",version=version+1
where id=2 and version =1
//B执行更新
update user set name="kuanshen",version=version+1
where id=2 and version =1
假设B先完成了更新,version被改变,此时A就会更新失败,这就是乐观锁实现线程安全的机制
4. 使用Mybatis-plus的乐观锁插件
- 给数据表中加入version字段,并设置默认值为1
- 实体类同步,并给属性上加入乐观锁注解
- 在Handler文件下编写乐观锁的组件
@Configuration //声明这是一个配置类
@EnableTransactionManagement //事务
public class MybatisplusConfig {
//注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
- 测试乐观锁
- 单线程情况
@Test
void testOptimisticLocker(){
//1查询用户信息
user usermy=userMapper.selectById(1);
//2.修改用户信息
usermy.setName("mike");
usermy.setAge(11);
usermy.setEmail("jake.com");
//3.更新用户信息
userMapper.updateById(usermy);
}
运行结果:更新成功version加1(单线程不存在现场安全问题,一定会更新成功)
- 多线程情况模拟
@Test
void testOptimisticLocker(){
//线程1(准备更新但是还没更新)
user usermy=userMapper.selectById(1);
usermy.setName("mike2");
usermy.setAge(11);
usermy.setEmail("jake.com");
//线程2(插队在线程1之前执行了更新)
user usermy2=userMapper.selectById(1);
usermy2.setName("mike1");
usermy2.setAge(11);
usermy2.setEmail("mary.com");
userMapper.updateById(usermy2);//线程2的更新操作
userMapper.updateById(usermy);//线程1的更新操作(如果没有乐观锁就会把线程2更新的值直接覆盖掉)
}
结果:乐观锁机制使得线程1没有更新
3.6 查询操作
- 根据Mybatis-plus提供的接口直接用即可(比如下面根据ID查询多个用户)
@Test
void testquery(){
System.out.println(userMapper.selectBatchIds(Arrays.asList(1,2,3)));
}
结果:
2. 分页查询
在以前的ssm项目中几乎使用的都是原始的limit分页方式或者使用pageHelper等第三方插件,Mybaits-plus也为开发者准备了分页插件,开发直接使用即可,十分方便
- 配置分页组件(在配置类中加入分页的Bean就行)
@Bean
public PaginationInterceptor pageInationInterceptor(){
return new PaginationInterceptor();
}
- 使用Page对象进行分页
@Test
void testPage(){
Page<user> page=new Page<>(2,5);//当前页第2页,每页5条数据
userMapper.selectPage(page,null);//null是给条件查询器参数(这里不使用)
page.getRecords().forEach(System.out::println);
}
结果:
3.7 删除操作
- 根据id删除用户
@Test
void testDeleteById(){
int result=userMapper.deleteById(1);
System.out.println(result);
}
结果:
2. 逻辑删除
物理删除:从数据库中直接删除
逻辑删除:并没有从数据库中删除而是通过一个变量让其不能使用(达到了所谓删除的目的),用于防止数据的丢失,相当于回收站的作用
- 增加一个删除标记字段在数据库中
- pojo增加属性并使用注解
@TableLogic
private int flage;
- 配置逻辑删除组件
@Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}
- 配置文件中配置逻辑删除
mybatis-plus:
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
- 测试逻辑删除
@Test
void logicdelet(){
userMapper.deleteById(2); //逻辑删除2号用户
}
结果:数据并没有删除(但flage变成了1即数据已经被逻辑删除了)
然后测试一下是否能查询到逻辑删除的数据:
@Test
void testquery(){
System.out.println(userMapper.selectById(2));
}
结果:查询不到
3.8 性能分析插件(新版本的Mybatis-plus已将此插件移除)
慢sql:慢SQL指的是MySQL慢查询,是运行时间超过long_query_time值的SQL。真实的慢SQL通常会伴随着大量的行扫描、临时文件排序或者频繁的磁盘flush,直接影响就是磁盘IO升高,让正常的SQL变成了慢SQL,大面积执行超时。
在实际操作中我们会经常遇到一些慢sql,如何找出这些慢sql我们的Mybatis-plus也提供了 相应的插件:
性能分析插件:
性能分析拦截器,用于输出每条SQL语句及其执行时间。SQL性能执行分析,开发环境使用,超过指定时间停止运行,有助于发现问题。
- 编写性能分析插件
//SQL执行效率插件
@Bean
@Profile({"dev","test"}) //设置 dev test 环境开启,保证我们的效率
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor=new PerformanceInterceptor();
performanceInterceptor.setMaxTime(1); //设置sql的最大执行时间为1ms
performanceInterceptor.setFormat(true);//是否开启格式化支持
return performanceInterceptor;
}
- 在spring配置文件中配置环境为dev或者test环境
spring:
profiles:
active: dev
- 测试插件
@Test
void testPage(){
Page<user> page=new Page<>(2,5);
userMapper.selectPage(page,null);
page.getRecords().forEach(System.out::println);
}
结果:sql运行时间为14ms超过了规定的时间所以执行失败
3.9 条件构造器
在前面使用的mybatis-plus提供的sql函数接口中,我们会发现我们把wrapper这个参数设置为了null,wrapper就是我们的条件构造器,用于写一些复杂的sql
- 使用wrapper进行复杂的查询操作:
@Test
void contextLoads(){
//查询name不为空的用户,并且邮箱不为空的用户,年龄>21的用户
QueryWrapper<user> wrapper=new QueryWrapper<user>(); //创建条件构造器
wrapper.isNotNull("name"); //name不为空的查询条件
wrapper.isNotNull("email"); //邮箱不为空
wrapper.ge("age",12); //年龄大于12
userMapper.selectList(wrapper).forEach(System.out::println);
}
结果:
@Test
void contextLoads(){ //查询名字为menglei的用户
//查询name不为空的用户,并且邮箱不为空的用户,年龄>21的用户
QueryWrapper<user> wrapper=new QueryWrapper<user>(); //创建条件构造器
wrapper.eq("name","menglei");
userMapper.selectOne(wrapper);
}
结果:
官网可以查看更多的使用方法
3.10 代码生成器
代码自动生成:AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
public static void main(String[] args) {
//构建一个代码自动生成器对象
AutoGenerator autoGenerator=new AutoGenerator();
//配置策略
//1全局配置
GlobalConfig gc=new GlobalConfig();
String property = System.getProperty("user.dir"); //获取用户目录
gc.setOutputDir(property+"/src/src/main/java");//代码文件的生成路径
gc.setAuthor("chailong"); //设置作者
gc.setFileOverride(false);//是否覆盖
gc.setServiceName("%sService");//去service的i前缀
gc.setIdType(IdType.ID_WORKER); //设置Id自增
gc.setDateType(DateType.ONLY_DATE);//设置日期类型
gc.setSwagger2(true);//设置swagger
autoGenerator.setGlobalConfig(gc);
//2. 设置数据源
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/mybatisplus");
dataSourceConfig.setUsername("root");
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
dataSourceConfig.setPassword("123456");
dataSourceConfig.setDbType(DbType.MYSQL);
autoGenerator.setDataSource(dataSourceConfig);
//3.设置包
PackageConfig packageConfig = new PackageConfig();
packageConfig.setModuleName("blog");
packageConfig.setParent("com.chailong.getspring");
packageConfig.setEntity("entity");
packageConfig.setMapper("mapper");
packageConfig.setService("service");
packageConfig.setController("controller");
autoGenerator.setPackageInfo(packageConfig);
//4.策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("user"); //设置映射的表名
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);//自动生成lombok
strategy.setLogicDeleteFieldName("flage"); //设置逻辑删除名字
//4.设置自动填充
TableFill create_time = new TableFill("create_time", FieldFill.INSERT_UPDATE);
TableFill update_time = new TableFill("update_time", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> arrayList = new ArrayList<>();
arrayList.add(create_time);
arrayList.add(update_time);
strategy.setTableFillList(arrayList);
//乐观锁
strategy.setVersionFieldName("version");
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true);
autoGenerator.setStrategy(strategy);
autoGenerator.execute();//执行
}
运行结果:
- 生成的新的java目录(原来的内容没有被覆盖)