MybatisPlus核心知识点
主要学习内容
- 配置日志输出
- 插入数据及雪花算法
- 主键策略
- 自动填充
- 乐观锁(version)
- 分页查询
- 逻辑删除
- 性能分析插件
- 条件查询器Wrapper
- 代码自动生成器
- 数据安全保护
前置资料
本次学习使用到的的数据库SQL脚本
CREATE DATABASE `mybatis-plus` CHARSET utf8; USE `mybatis-plus`; DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` BIGINT(20) NOT NULL COMMENT '主键ID', `name` VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名', `age` INT NULL DEFAULT 0 COMMENT '年龄', `email` VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱', PRIMARY KEY(id) )ENGINE INNODB CHARSET utf8; INSERT INTO USER (id ,NAME, age, email) VALUES (1, 'Jone', 18, 'test@baomidou.com'), (2, 'Jack', 20, 'abc@baomidou.com'), (3, 'Tom', 19, 'cccc@baomidou.com'), (4, 'Sandy', 28, 'xiao@baomidou.com'), (5, 'Billie', 24, 'meimei@baomidou.com');
与Mybatis-Plus相关的jar依赖
<!-- 添加mybatis-plus依赖 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency>
<!-- Mybatis-Plus代码自动生成器 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.4.1</version> </dependency>
<!-- 添加 模板引擎 依赖,MyBatis-Plus 支持 Velocity(默认) --> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.1</version> </dependency> <!-- 数据库驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- 引入swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>
1、配置日志输出
只需要在springboot核心配置文件中配置下面一行代码即可
# 配置日志输出
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
2、自动填充——三步(注意:填充原理是直接给entity的属性设置值,而不是数据库表中的字段!!!)
1、编写一个类实现MetaObjectHandler接口,并重写该接口的两个方法insertFill() 和 updateFill()
1 package com.lzp.intercept; 2 3 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; 4 import lombok.extern.slf4j.Slf4j; 5 import org.apache.ibatis.reflection.MetaObject; 6 import org.springframework.stereotype.Component; 7 import java.util.Date; 8 9 /** 10 * @Author LZP 11 * @Date 2021/7/13 14:29 12 * @Version 1.0 13 * 14 * 填充原理是直接给entity的属性设置值!!! 15 */ 16 @Slf4j 17 @Component 18 public class MyMetaObjectHandler implements MetaObjectHandler { 19 @Override 20 public void insertFill(MetaObject metaObject) { 21 log.info("start insert fill ...."); 22 Date cur = new Date(); 23 this.setFieldValByName("createTime", cur, metaObject); 24 this.setFieldValByName("updateTime", cur, metaObject); 25 } 26 27 @Override 28 public void updateFill(MetaObject metaObject) { 29 log.info("start update fill ...."); 30 Date cur = new Date(); 31 this.setFieldValByName("updateTime", cur, metaObject); 32 } 33 }
2、在实体类指定要填充的字段上面加上@TableField这个注解,并指明要填充的类型(即在什么操作下才去填充这个字段)
3、测试
1)测试前数据
2)执行测试代码
1 // 自动填充 2 @Test 3 void autoFull() { 4 User user = new User(); 5 user.setId(1414828510853300227L); 6 user.setName("赵敏"); 7 user.setAge(40); 8 user.setEmail("zm@qq.com"); 9 userMapper.updateById(user); 10 }
3)测试后数据
3、乐观锁(OptimisticLockerInnerInterceptor)
乐观锁配置需要两步
1)配置插件
spring.xml方式
<bean class="com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor" id="optimisticLockerInnerInterceptor"/> <bean id="mybatisPlusInterceptor" class="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor"> <property name="interceptors"> <list> <ref bean="optimisticLockerInnerInterceptor"/> </list> </property> </bean>
spring boot注解方式
/**
* 旧版
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
/**
* 新版
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
2)先在数据表中添加version字段,然后在对应的实体类中也添加version字段,最后在实体类的字段上加上 @Version
注解
3)测试
测试前,查看一下数据库表user
执行测试代码
1 // 乐观锁 2 @Test 3 void optimisticLock() { 4 // 需要在数据库表中添加一个version字段 5 6 /* 7 模拟乐观锁:当一个用户在操作某一行时,在这中间由于网络延迟等原因,让 8 另外一个线程插队了,另外一个线程执行完后,当前线程才执行。这种情况下 9 我们为了避免多线程安全问题,则不允许当前线程再继续对当前行数据操作, 10 即当前线程的操作失效 11 */ 12 // 线程1 13 // 注意:这里一定要先查询一下即将要修改的用户(不查的话一开始就拿不到版本号,也就不能通过版本号来作为条件判断是否有线程插队) 14 User user = userMapper.selectById(1414828510853300227L); 15 user.setAge(41); 16 17 // 中间来了另外一个线程2 18 User user2 = userMapper.selectById(1414828510853300227L); 19 user2.setName("赵敏"); 20 userMapper.updateById(user2); 21 22 // 如果没有乐观锁,值就会被覆盖 23 userMapper.updateById(user); 24 }
运行结果:
数据库表
控制台
4、分页查询
1)第一步:配置分页拦截器组件
2)直接使用Page对象测试即可
1 // 分页 2 @Test 3 void limitPage() { 4 // 构造器 ==> public Page(long current, long size) 5 // current:当前页 6 // size:页面大小 7 Page<User> page = new Page<>(1, 5); 8 IPage<User> userIPage = userMapper.selectPage(page, null); 9 userIPage.getRecords().forEach(System.out::println); 10 }
3)测试效果
5、逻辑删除
定义
物理删除:从数据库中直接移除
逻辑删除:在数据库中没有被移除,而是通过一个变量让它失效(即正常的查询查询不到)
应用场景
管理员可以查看已经被删除的记录!防止数据的丢失,类似于回收站
实现步骤
1)在数据库表中增加一个deleted字段
修改表
添加新字段
效果如下:
2)在实体类中增加deleted属性
// 逻辑删除 @TableLogic private Integer deleted;
3)配置逻辑删除组件
// 配置逻辑删除组件 @Bean public ISqlInjector sqlInjector() { return new LogicSqlInjector(); }
4)在springboot全局配置文件中配置逻辑删除
# 配置逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
5)测试
删除前
执行逻辑删除代码
// 逻辑删除 @Test void logicDeleted() { userMapper.deleteById(1L); }
删除后
数据库表
6、性能分析插件
配置PerformanceInterceptor拦截器
1 /** 2 * SQL执行效率插件 3 */ 4 @Bean 5 @Profile({"dev", "test"}) // 设置dev、test环境开启,保证我们的效率 6 public PerformanceInterceptor performanceInterceptor() { 7 PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); 8 // 设置sql执行的最大时间,如果超过了则不执行,单位ms 9 performanceInterceptor.setMaxTime(1000); 10 // 是否开启SQL格式化支持 11 performanceInterceptor.setFormat(true); 12 return performanceInterceptor; 13 }
7、条件查询器Wrapper
直接使用BaseMapper里面提供的方法即可(用带Wrapper参数的方法),这里用的是selectList()方法,选用的是QueryWrapper
@Test void conditionQueryWrapper() { // Wrapper以下有两个常用的实现类:QueryWrapper 和 UpdateWrapper QueryWrapper<User> queryWrapper = new QueryWrapper<>(); // 年龄大于等于20 queryWrapper.ge("age", 20); // 姓名中不包含字母a queryWrapper.notLike("name", "a"); // 创建时间为空 queryWrapper.isNull("create_time"); List<User> users = userMapper.selectList(queryWrapper); users.forEach(System.out::println); }
测试结果
1)控制台输出
8、代码自动生成器
模板代码如下:
package com.lzp; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.InjectionConfig; import com.baomidou.mybatisplus.generator.config.*; import com.baomidou.mybatisplus.generator.config.po.LikeTable; import com.baomidou.mybatisplus.generator.config.po.TableFill; import com.baomidou.mybatisplus.generator.config.po.TableInfo; import com.baomidou.mybatisplus.generator.config.rules.DateType; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import java.util.ArrayList; import java.util.List; @SpringBootTest class MybatisPlusNetshopApplicationTests { // 代码生成器模板 @Test void contextLoads() { // 构造一个AutoGenerator AutoGenerator autoGenerator = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); gc.setOutputDir(projectPath + "/src/main/java"); gc.setAuthor("LZP"); gc.setOpen(false); // 是否覆盖 gc.setFileOverride(true); // 去Service的I前缀 gc.setServiceName("%sService"); gc.setDateType(DateType.ONLY_DATE); gc.setSwagger2(true); autoGenerator.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dataSourceConfig = new DataSourceConfig(); dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/netshop?useUnicode=true&useSSL=false&characterEncoding=utf-8"); dataSourceConfig.setDriverName("com.mysql.jdbc.Driver"); dataSourceConfig.setUsername("root"); dataSourceConfig.setPassword("123"); dataSourceConfig.setDbType(DbType.MYSQL); autoGenerator.setDataSource(dataSourceConfig); // 包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName("netshop"); pc.setParent("com.lzp"); pc.setController("controller"); pc.setService("service"); pc.setMapper("mapper"); pc.setEntity("pojo"); autoGenerator.setPackageInfo(pc); // 配置模板 目的:删除在/src/main/java目录下的mapper里生成的xml目录 TemplateConfig templateConfig = new TemplateConfig(); // 配置自定义输出模板 //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别 // templateConfig.setEntity("templates/entity2.java"); // templateConfig.setService(); // templateConfig.setController(); templateConfig.setXml(null); autoGenerator.setTemplate(templateConfig); /* 需求: 将mapper文件放入到我们的resources资源路径下,方便资源统一管理 */ // 自定义配置 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; // 如果模板引擎是 freemarker // String templatePath = "/templates/mapper.xml.ftl"; // 如果模板引擎是 velocity String templatePath = "/templates/mapper.xml.vm"; // 自定义输出配置 List<FileOutConfig> focList = new ArrayList<>(); // 自定义配置会被优先输出 focList.add(new FileOutConfig(templatePath) { @Override public String outputFile(TableInfo tableInfo) { // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!! return projectPath + "/src/main/resources/mapper/" + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); cfg.setFileOutConfigList(focList); autoGenerator.setCfg(cfg); // 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true); // 逻辑删除 strategy.setLogicDeleteFieldName("deleted"); // 乐观锁 strategy.setVersionFieldName("version"); // 自动填充 TableFill gmtCreate = new TableFill("create_time", FieldFill.INSERT); TableFill gmtUpdate = new TableFill("update_time", FieldFill.INSERT_UPDATE); List<TableFill> list = new ArrayList<>(); list.add(gmtCreate); list.add(gmtUpdate); strategy.setTableFillList(list); // 在Controller控制器上加上@RestController注解 strategy.setRestControllerStyle(true); /* @RequestMapping注解中的url格式设置 ==> 默认是驼峰命名 /user/userAdd 开启连字符 ==> strategy.setControllerMappingHyphenStyle(true); /user/user_add */ // 按前缀生成表 strategy.setLikeTable(new LikeTable("tb_")); // 设置表替换前缀 strategy.setTablePrefix("tb_"); autoGenerator.setStrategy(strategy); // 执行 autoGenerator.execute(); } }
生成后的项目目录结构
9、数据安全保护
所有配置文件展示
application.yaml
application-dev.yaml
application-pro.yaml
具体流程
该功能为了保护数据库配置及数据安全,在一定的程度上控制开发人员流动导致敏感信息泄露。
⚪ 3.3.2 开始支持
依赖:
<!-- Mybatis-Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.2</version> </dependency>
⚪ 配置安全
YML 配置:
# 数据源配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: mpw:DdGxMC2nD0jM0yiwbRwu68yUztZr4VQB4+e3z8LEfgaxJ67RvXC92lLRFV2LiXpge2RHvyfhxt/kPSUcFByIbIvVpmGgWWCMDcx1iwnOJw1+N6eTkPoVYKmc9sMA2h+P
username: mpw:ruFTzIubVYmKmWGFwGCK2g==
password: mpw:elqQmbc0TpnxeJVfYU/dPA==
密钥加密:
// 获取一个16位的随机 AES 密钥 String randomKey = AES.generateRandomKey(); System.out.println("随机生成的一个16位AES密钥:" + randomKey); // 加密 String url = AES.encrypt("jdbc:mysql://localhost:3306/mybatis-plus?useSSL=true&useUnicode=true&characterEncoding=utf-8", randomKey); String username = AES.encrypt("root", randomKey); String password = AES.encrypt("123", randomKey); System.out.println("url对应的密文:" + url); System.out.println("username对应的密文:" + username); System.out.println("password对应的密文:" + password);
如何使用:
1、将项目打成jar包
2、打开jar包所在目录,并通过jar命令启动项目(要注意的是,现在我们项目已经打包,但是配置文件用的还是开发环境的配置文件dev,所以我们需要在命令行中加上:--spring.profiles.active=pro 来切换为生产环境)
而且,我们还需要通过密钥解密启动项目,因为在生产环境下的数据库的url、用户名、密码都是加密之后的,要想连接数据库则必须要有密钥才行
①没有通过密钥启动
详细信息
②加上密钥之后再启动,项目正常启动