Mybatis-plus
简介:
Mybatis-plus(简称MP)是基于MyBatis框架基础上开发的增强型启动工具,宗旨简化开发提高效率
官网:MyBatis-Plus (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 操作智能分析阻断,也可自定义拦截规则,预防误操作
Mybatis-plus快速入门
-
新创建一个模块,增加依赖,只需要增加mysql的依赖即可
-
到项目中导入相对应的坐标(需要用到的坐标都导入进来)
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.31</version> <scope>runtime</scope> </dependency> <!--mybatis_plus依赖--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency> <!--lombok依赖--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.15</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
-
查看maven依赖,在这里我们可以看见,我们的mybatis-plus中已经帮我们导入了mybatis的依赖了,所以我们不需要在导入了。
-
编写yml配置文件
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource #Spring Boot 默认是不注入这些属性值的,需要自己绑定 #druid 数据源专有配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入 #如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
-
编写与数据库相关的实体类(这里使用的是Lombok插件)
@Data @NoArgsConstructor @AllArgsConstructor public class User { private Integer id; private String username; private String password; }
-
编写我们的数据层接口
/** * @author: anle * @date: 2023/06/04 11:36 **/ @Mapper public interface UserDao extends BaseMapper<User> { }
我们要使用mybatis-plus简化开发就必须继承这里BaseMapper<泛型>然后我们的数据层就写好了,我们就可以进行测试类。
-
编写测试类
@SpringBootTest class MybatisPlus01StartApplicationTests { @Autowired private UserDao userDao;//注入数据层接口 @Test void testGetAll() { //调用mybatis-plus中封装好的方法 List<User> users = userDao.selectList(null); System.out.println(users); } }
在这里我们的入门案列就完成了,在这里我们可以看到,跟我们之前springboot整合mybatis的步骤几乎差不都一样,不一样的地方就是我们不用在手动的去编写sql语句了,直接继承BaseMapper<泛型>就可以了,在测试和使用的时候去调用里面的方法就行了。
这些都是mybatis-puls封装的方法,真正的做到简化开发。
标准的CRUD开发
在我们没有使用mybatis-plus之前我们对应的需要自己写很多操作
现在我们只需要调用里面的方法就行。
增加操作
-
我们只需要在测试类中,调用MP对应的方法就行
@Test /*增加用户*/ void testSave(){ User user = new User(); user.setId(66); user.setUsername("李白"); user.setPassword("666"); //调用增加方法 userDao.insert(user); }
删除操作
@Test
/*删除用户*/
void testDelete(){
//根据id进行删除
userDao.deleteById(66);
}
修改操作
- 在这里我们只需要将需要改的值,设置一下,然后直接传递给下面的方法中就行
@Test
/**
* 根据id修改用户
* 需要修改什么直接传递就行
*/
void testUpdate(){
User user = new User();
user.setId(1);
user.setUsername("李四");
//根据id修改,可以传递一个实体类对象
userDao.updateById(user);
}
查询操作
@Test
/*根据id查询*/
void testGetById() {
User user = userDao.selectById(1);
System.out.println(user);
}
@Test
/*查询全部*/
void testGetAll() {
List<User> users = userDao.selectList(null);
System.out.println(users);
}
分页查询
我们在测试类里面编写分页方法进行测试
@Test
/**
* Page:MP内置对象,用来做分页查询
* 参数1:表示查询第几页的数据
* 参数2:表示参数1中有几条数据
*/
void testGetPage(){
//创建IPage对象
IPage Page = new Page(1,2);
userDao.selectPage(Page,null);
System.out.println("当前页面值:"+Page.getCurrent());
System.out.println("每页显示数:"+Page.getSize());
System.out.println("一共多少页:"+Page.getPages());
System.out.println("一共多少条数据:"+Page.getTotal());
System.out.println("数据:"+Page.getRecords());
}
我们看一下啊运行结果
可以看出这里打印出的数据不正确,其实到这里我们的分页还没有结束,我们还需要配置mybatis-plus的拦截器。
拦截器配置
-
我们创建一共config软件包在主启动类目录下,在config中编写我们的mybatis-plus的拦截器。
/** * @author: anle * @date: 2023/06/04 13:07 **/ @Configuration public class MPConfig { //定制mybatis-plus拦截器 @Bean public MybatisPlusInterceptor interceptor(){ //1、定义mybatis-plus拦截器 MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); //2、增加具体(分页)拦截器 mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return mybatisPlusInterceptor; } }
-
我们在到测试类中进行测试,看打印出的结果。
好,打完收工!
mybatis-plus日志
只需要在yml文件中增加配置就行
#开启MP日志(输出到控制台)
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
我们看下控制台,一般我们在调试的时候使用
– - - —在这里我们可以很明显看出mybatis-plus真的太方便了。
条件查询
在我们之前方法中设置为null的那个属性就是用来根据我们的条件进行查询的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5rBWUERC-1686036275782)(C:\Users\A’n’le\AppData\Roaming\Typora\typora-user-images\image-20230604144043385.png)]
条件查询的方式一
@Test
/*按条件查询*/
void testGetAllUser(){
//方式一
QueryWrapper qw = new QueryWrapper();
查询id小于4的
qw.lt("id",4);
List list = userDao.selectList(qw);
System.out.println(list);
}
条件查询方式二
@Test
/*按条件查询*/
void testGetAllUser(){
//方式二:lambda格式条件查询
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.lambda().lt(User::getId,"4");
List list = userDao.selectList(qw);
System.out.println(list);
}
条件查询方式三
@Test
/*按条件查询*/
void testGetAllUser(){
//方式三:lambda格式条件查询
LambdaQueryWrapper<User> uqw = new LambdaQueryWrapper<>();
uqw.lt(User::getId,"4");
List list = userDao.selectList(uqw);
System.out.println(list);
}
下面是更具不同的条件查询
LambdaQueryWrapper<User> uqw = new LambdaQueryWrapper<>();
//id:1到4之间 默认 . 表现并且
//uqw.lt(User::getId,"4").gt(User::getId,"1");
//id:小于2大于4 or()表示或
uqw.lt(User::getId,"2").or().gt(User::getId,"4");
List list = userDao.selectList(uqw);
System.out.println(list);
条件查询中的—null值处理
在日常生活中我们会遇到多种条件进行查询,都是有些情况下我们只需要输入一个条件就行了,有些条件直接为空
我们来解决一下这个问题
我们重新创建一个实体类并继承之前的实体类
/**
* @author: anle
* @date: 2023/06/04 14:59
**/
@Data
public class UserQuery extends User{
private Integer id2;
}
我们在测试类中进行编写
@Test
void textUser(){
UserQuery uq = new UserQuery();
uq.setId(1);
uq.setId2(5);
//null值判断
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//先判定第一个参数是否为true,如果为true连接当前条件
//链式编程
lqw.lt(null != uq.getId2(),User::getId,uq.getId2())
.gt(null != uq.getId(),User::getId,uq.getId());
List<User> users = userDao.selectList(lqw);
System.out.println(users);
}
在这里我们可以看出我们要判断空值,只需要我们在前面加上非空判断的参数就可以了,简化了我们之前用if判断的复杂性。
查询投影
主要是我们可以根据我们的实体类里面的属性来进行查询,我们想查询那些属性只需要设置对应的查询即可
测试
-
查询结果包含类中单一属性
//查询投影:格式一 LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); lqw.select(User::getUsername); // 只查询我们的姓名 List<User> users1 = userDao.selectList(lqw); System.out.println(users1);
// 格式二 QueryWrapper<User> lqw = new QueryWrapper<User>(); lqw.select("username"); List<User> users1 = userDao.selectList(lqw); System.out.println(users1);
-
查询结果包含类中未定义的属性
//查询统计有多少条数据 QueryWrapper<User> lqw = new QueryWrapper<User>(); lqw.select("count(*) as count,password"); //分组统计 lqw.groupBy("password"); List<Map<String, Object>> users = userDao.selectMaps(lqw); System.out.println(users);
结果:
查询条件的设置
-
用户登入(eq匹配)
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>(); //eq等同于 = lambdaQueryWrapper.eq(User::getUsername,"李四") .eq(User::getPassword,"11111"); User users = userDao.selectOne(lambdaQueryWrapper); System.out.println(users);
-
购物设定价格区间、户籍设定年龄区间(le ge 配置 或者 between匹配)
//范围查询 : between 前面写小的值 LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); lqw.between(User::getId,"2","5"); List<User> users = userDao.selectList(lqw); System.out.println(users);
-
查询信息,搜索新闻(非全文本索引:like匹配)
//模糊查询 : like LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); lqw.like(User::getUsername,"小"); List<User> users = userDao.selectList(lqw); System.out.println(users);
-
更多的条件查询配置在官网有详细说明和测试。
- 官网:
https://www.baomidou.com/pages/10c804/#abstractwrapper
- 官网:
映射匹配兼容性
-
问题一:表字段与编码属性设计不同
-
使用@TableFieid注解
-
作用:设置当前属性对应的数据库表中的字段关系
-
范例:
@TableField(value = "pwd") private String passw
-
属性:
- value:设置数据库字段名称
-
-
问题二:编码中增加了数据库未定义的属性
-
使用@TableFieid注解
-
作用:设置当前属性对应的数据库表中的字段关系
-
范例:
@TableField(exist = false) private Integer gender;
-
属性:
- exist:设置属性在数据库表中是否存在,默认为true。此属性无法与value合并使用。
-
-
问题三:采用默认的查询开放了更多的字段查看权限,比如我们的密码这些比较私密的属性。
-
使用@TableFieid注解
-
作用:设置当前属性对应的数据库表中的字段关系
-
范例:
@TableField(value = "pwd",select = false) private String password;
-
属性:
- select:设置属性是否参与查询,此属性与select()映射匹配不冲突
-
-
问题四:表名与编码开发设计不同步
-
使用@TableName注解
-
作用:设置当前类对应数据库表关系
-
范例:
@TableName("tb_user") public class User { private Integer id; private String username; @TableField(value = "pwd",select = false) private String password; @TableField(exist = false) private Integer gender; }
-
属性:
- value:设置数据库表名
-
-
大家可以根据业务需求来选择使用,大家也可以到官网进行查看。
- 官网:(https://www.baomidou.com/pages/10c804/#kotlin持久化对象定义最佳实践)
id生成策略
在我们的日常需求不同的表应用不同的id生成策略
日志:自增(1.2.3.4)
购物订单:特殊规则(fq123545324)
…等等
我们可以自己定义生成id的类型
我们只需要在我们的实体类中的id属性增加上以下注解:
-
@TableId
-
作用:设置当前类中主键属性的生成策略
-
范例:
@TableId(type = IdType.ASSIGN_ID) private Integer id;
-
相关属性:
- value:设置数据库主键名称
- type:设置主键属性生成的策略,值参照IdType枚举值。
我们来说以下,上面的方法主要是什么:
- AUTO(0):使用数据库id自增策略控制id生成
- NONE(1):不设置id生成策略
- INOPUT(2):用户手动输入id(注意这里需要将数据库的id自增关闭)
- ASSIGN_ID(3):雪花算法生成id(可以兼容数值型与字符串型),注意我们的数据库如果id的类型是int类型要改成bigint类型
- ASSIGN_UUID(4):以UUID生成算法作为id生成策略
雪花算法生成内容:
全局策略配置
-
在设置自动生成id的时候每一个实体类都需要加注解,在这里我们只需要在配置文件中配置全局配置
global-config: db-config: id-type: assign_id
-
在我们设置表的时候大多数表名都是以:tb_ 开头所以我们没一个实体类都要去配置表名,我们直接在配置文件中设置全局配置。
global-config: db-config: id-type: assign_id # 配置表名的前缀 table-prefix: tb_
多条数据删除
在我们日常需求中会出现这种情况,比如我们的购物车需要一次性删除多条数据,这时候我们就需要用到多条数据删除,也可以多条数据进行查询。
-
多条数据删除
@Test void testDelete() { List<Long> list = new ArrayList<>(); list.add(1665717565657149441L); list.add(1665714164340789249L); userDao.deleteBatchIds(list); }
-
多条数据查询
list.add(4L); list.add(5L); userDao.selectBatchIds(list);
逻辑删除
例如:论坛帖子的删除、修改、恢复等。通过逻辑删除,可以避免物理删除记录后造成的数据丢失,以及对索引、外键等关系的影响。
- 删除操作业务问题:业务从数据库中丢掉
- 逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中
我们一个如何做那?
-
在数据库中增加逻辑删除标记字段
-
在实体类中添加对应的字段,并设置当前字段为逻辑删除标记字段
//增加逻辑删除字段,标记当前记录是否为删除 //value:表示不删除时默认值 //delval:表示删除时默认值 @TableLogic(value ="0",delval ="1") private Integer deleted;
-
测试:
这里运用的是修改的sql语句而不是我们的删除语句,如果将某个数据逻辑删除以后,我们MP中查询是查不出来的,要想查出所有的数据可以通过自己手写的sql;
-
配置逻辑字段,在yml文件中
#增加逻辑删除字段,标记当前记录是否为删除 logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0
乐观锁
乐观锁的业务需求:
商品库存扣减:在高并发场景下,多个用户同时对同一件商品进行购买,此时需要使用乐观锁来避免超卖的情况。
订单状态更新:在多人同时对同一订单进行操作时,使用乐观锁可以避免订单状态修改错误的问题。
网络请求重试:在网络请求超时、网络异常等情况下,使用乐观锁可以避免出现重复提交的问题。
乐观锁可以处理小型的高并发访问
如何使用乐观锁
-
先在数据库中增加锁标记字段
-
在实体类中增加对应的字段,并设定该字段为逻辑删除标记字段给
@Version private Integer version;
-
配置乐观锁的拦截器实现锁机制对应的动态sql语句拼装
@Configuration public class MPConfig { //定制mybatis-plus拦截器 @Bean public MybatisPlusInterceptor interceptor(){ //1、定义mybatis-plus拦截器 MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); //2、增加乐观锁拦截器 mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return mybatisPlusInterceptor; } }
-
使用乐观锁机制在修改前必须先获取到对应数据的verion方可正常进行
User user = new User(); //1、通过要修改的数据根据id将当前数据查询出来 userDao.selectById(4L); //2、在将要修改的属性逐一修改设置进去 user.setUsername("java666"); userDao.updateById(user);
代码生成器
-
导入代码生成器的依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.3</version> </dependency>
-
创建代码生成器对象
public class Generator { public static void main(String[] args){ AutoGenerator autoGenerator = new AutoGenerator(); autoGenerator.execute(); } }
-
数据源相关配置:读取数据源中信息,根据数据库表结构生成代码
public class Generator { public static void main(String[] args){ AutoGenerator autoGenerator = new AutoGenerator(); DataSourceConfig dataSourceConfig =new DataSourceConfig(); dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC"); dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver"); dataSourceConfig.setUsername("root"); dataSourceConfig.setPassword("123456"); autoGenerator.setDataSource(dataSourceConfig); autoGenerator.execute(); } }
-
设置我们所需要的配置信息
public class CodeGenerator { public static void main(String[] args) { //1.获取代码生成器的对象 AutoGenerator autoGenerator = new AutoGenerator() //设置数据库相关配置 DataSourceConfig dataSource = new DataSourceConfig(); dataSource.setDriverName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC"); dataSource.setUsername("root"); dataSource.setPassword("123456"); autoGenerator.setDataSource(dataSource); //设置全局配置 GlobalConfig globalConfig = new GlobalConfig(); globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java"); //设置代码生成位置 globalConfig.setOpen(false); //设置生成完毕后是否打开生成代码所在的目录 globalConfig.setAuthor("anle"); //设置作者 globalConfig.setFileOverride(true); //设置是否覆盖原始生成的文件 globalConfig.setMapperName("%sDao"); //设置数据层接口名,%s为占位符,指代模块名称 globalConfig.setIdType(IdType.ASSIGN_ID); //设置Id生成策略 autoGenerator.setGlobalConfig(globalConfig); //设置包名相关配置 PackageConfig packageInfo = new PackageConfig(); packageInfo.setParent("com.xxx"); //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径 packageInfo.setEntity("domain"); //设置实体类包名 packageInfo.setMapper("dao"); //设置数据层包名 autoGenerator.setPackageInfo(packageInfo); //策略设置 StrategyConfig strategyConfig = new StrategyConfig(); strategyConfig.setInclude("tbl_user"); //设置当前参与生成的表名,参数为可变参数 strategyConfig.setTablePrefix("tbl_"); //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名 例如: User = tbl_user - tbl_ strategyConfig.setRestControllerStyle(true); //设置是否启用Rest风格 strategyConfig.setVersionFieldName("version"); //设置乐观锁字段名 strategyConfig.setLogicDeleteFieldName("deleted"); //设置逻辑删除字段名 strategyConfig.setEntityLombokModel(true); //设置是否启用lombok autoGenerator.setStrategy(strategyConfig); //2.执行生成操作 autoGenerator.execute(); } }
到这里我们的mybatis-plus基本的使用就完成了
完结!
m.xxx"); //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径
packageInfo.setEntity(“domain”); //设置实体类包名
packageInfo.setMapper(“dao”); //设置数据层包名
autoGenerator.setPackageInfo(packageInfo);
//策略设置
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setInclude("tbl_user"); //设置当前参与生成的表名,参数为可变参数
strategyConfig.setTablePrefix("tbl_"); //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名 例如: User = tbl_user - tbl_
strategyConfig.setRestControllerStyle(true); //设置是否启用Rest风格
strategyConfig.setVersionFieldName("version"); //设置乐观锁字段名
strategyConfig.setLogicDeleteFieldName("deleted"); //设置逻辑删除字段名
strategyConfig.setEntityLombokModel(true); //设置是否启用lombok
autoGenerator.setStrategy(strategyConfig);
//2.执行生成操作
autoGenerator.execute();
}
}
**到这里我们的mybatis-plus基本的使用就完成了**
完结!