# MP **Repository Path**: xu_chuang/mp ## Basic Information - **Project Name**: MP - **Description**: mybatis第三方增强工具 mybatis plus - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2021-05-22 - **Last Updated**: 2021-05-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # MP(Mybatis Plus) #### 概述 mybatis plus是mybatis第三方增强框架, 只做增强不做改变,提高效率,简化开发。 #### 软件架构 bs架构 mybatis+springboot #### 环境搭建 1. 创建springboot项目 ​ ```xml org.springframework.boot spring-boot-starter-parent 2.3.11.RELEASE ``` 2. 导入mybatis plus依赖 ```xml com.baomidou mybatis-plus-bootstarter 3.4.3 ``` 3. 导入数据库依赖 ```xml mysql mysql-connector-java runtime ``` 4. 导入lombook(简化实体开发) ```xml org.projectlombok lombok true ``` #### 入门案例(环境搭建+查询 暂时忽略条件构造器Wrapper) 1. 导入mybatis plus依赖 2. 配置数据源 ```yml spring: datasource: driver-class-name: 驱动类权限定名 url: jdbc:mysql://ip:端口/dbname?characterEncoding=utf-8&serverTimezone=UTC username: 用户名 password: 密码 ``` 3. 创建表/实体 1. 表结构 ```sql 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) ); ``` b. 数据 ```sql 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'); ``` ​ ```java @Data public class User { private Long id; private String name; private Integer age; private String email; } ``` 4. mapper中定义接口实现 BaseMaapper<实体作为泛型> 5. 扫描Mapp接口 1. 方式1 : 在接口中添加@Mapper注解 2. 方式2 : 在启动类上添加@MapperScanner注解 ```java @SpringBootApplication @MapperScan("com.example.castday1.mapper") public class CastDay1Application { public static void main(String[] args) { SpringApplication.run(CastDay1Application.class); } } ``` 6. 编写代码进行测试 ```java /* @Test进行测试的时候 通过在测试类上配置的这里个注解 可将springbean生成在内存中 不用再单独启动spring容器了 当测试过程设计到ioc的时候 可以使用这里个注解 */ @RunWith(SpringRunner.class) @SpringBootTest ``` ​ #### 基础注解(插入 引入几个基础的注解) 1. 插入案例 2. @Tablename 表名和实体名不一样时,使用@Tablename注解将表和实体进行绑定 3. @TableId 将表的主键和实体属性进行绑定 4. @TableFeild 将表的非主键列和实体属性进行绑定 #### 排除表字段的三种方式 1.使用statis修饰字段 2.使用transient 修饰字段 3.使用@TableField注解修饰字段 3.1 给字段添加该注解 3.2 给注解添加exist属性 属性值为false ```java @Data @TableName("User") public class UserA { @TableId("id") private Long userId; @TableField("name") private String username; private Integer age; private String email; @TableField(exist = false) private String sex; //private transient String sex; //private static String sex; } ``` #### 添加sql日志 ```yml logging: level: root: warning 持久层包路径 : trace ``` #### 插入 #### 查询(select/find/retrieve) #### AbstractWrapper条件构造器 ##### 理解条件构造器的作用(根据字面意义理解即可) ​ 所谓的条件构造器就是用于构造sql语句查询条件的工具 ``` 比如 : 查询员工表中姓氏包含m,并且年龄在30-50之间的并且工资大于 5000的员工的信息。 该sql语句的条件为 ``` ```sql last_name like '%m%' and age between 30 and 50 and salary > 5000 ``` 对该sql语句的条件进行拆分 a. 列名 : lastname / age / salary 这一部分是直接填充的。 b. 数据 m / 30 / 50 / 5000 直接填充 c. sql中的运算符和关键字 由条件构造器提供。 ​ ##### 条件构造器案例 条件构造器中封装好了用于生成sql中的运算符/关键字的方法,而列名和数据则作为方法的参数进行传递,最终条件构造器会生成符合标准sql规范的sql语句的查询条件。 ​ 案例1 ​ 需求: 查询年龄大于20的用户信息。 ``` age>20 ``` ```java @Test public void queryInfoCastOne(){ QueryWrapper queryWrapper = new QueryWrapper(); //QueryWrapper queryWrapper1 = Wrappers.query(); // gt > lt < queryWrapper.gt("age",20); List userAList = userMapper.selectList(queryWrapper); userAList.forEach(System.out::println); } ``` ​ ​ 案例2 ​ 需求: 查询年龄在20到25之间 并且 姓名包含z的用户信息 ​ ```sql age between 20 and 50 and name like '%z%' ``` ```java public void queryInfoCastTwo(){ QueryWrapper queryWrapper = Wrappers.query(); queryWrapper.between("age",20,25); queryWrapper.like("name","z"); List userAList =userMapper.selectList(queryWrapper); userAList.forEach(System.out::println); } ``` ​ 案例3: 查询年龄在是 21 23 25 27 30 中或者邮箱不为空的用户信息 ​ ```java public void queryInfoCastThree(){ List userAList =userMapper.selectList( new QueryWrapper() .in("age",21,22,23,24,25) .or() .isNotNull("email") ); userAList.forEach(System.out::println); } ``` ​ ​ 案例4 : 查询年龄小于等于20的所有用户信息,并且根据降序排列 ```java public void queryInfoCastFour(){ List userAList = userMapper.selectList( new QueryWrapper() .le("age",20) .orderByDesc("id") ); userAList.forEach(System.out::println); } ``` 案例5: 查询年龄等于19或者名字为zs的用户信息 ```java public void queryInfoCastFive(){ Map map = new HashMap(); map.put("age",19); map.put("name","zs"); List userAList = userMapper.selectList( new QueryWrapper() /*.eq("age",19) .or() .eq("name","张三")*/ // and连接 .allEq(map) ); userAList.forEach(System.out::println); } ``` 案例6:查询年龄最大的用户信息 ```java // age = (select max(age) from user) //找不到满足需求的方法 // age in (select max(age) from user) // inSql notInSql @Test public void queryInfoCastSix(){ List userAList = userMapper.selectList( new QueryWrapper() .inSql("age","select max(age) from user") ); userAList.forEach(System.out::println); } ``` 案例7: 查询年龄最大的三个用户的信息 ```java public void queryInfoCastSeven(){ List userAList = userMapper.selectList( new QueryWrapper() .orderByDesc("age") //last 将sql语句拼接到最后 //安全性低 存在sql注入的风险 .last(" limit 0,3") ); userAList.forEach(System.out::println); } ``` 9. 了解apply 函数动态入参 提高代码分析能力,规避sql注入。 10. 自定义查询结果中的列 查询年龄最大的三个用户的的姓名 和 年龄 ​ ```java public void queryInfoCastSeven(){ List userAList = userMapper.selectList( new QueryWrapper() .orderByDesc("age") //last 将sql语句拼接到最后 //安全性低 存在sql注入的风险 .select("name as username","age") .last(" limit 0,3") //.select("name","age") ); userAList.forEach(System.out::println); } ``` ​ ##### 条件构造器中经常会出现condition ``` 该参数表示条件**是否**加入最后生成的sql中 往往用于动态sql的实现 根据年龄和名字查询用户信息 案例 根据姓名和邮箱模糊查询用户信息 ``` ```java public void dynamicSqlCast(){ String name = " "; String email = "xx"; userMapper.selectList(new QueryWrapper() .like(StringUtils.isNotBlank(name),"name",name) .like(StringUtils.isNotBlank(email),"email",email)); } ``` ##### 实体作为条件构造器方法 的参数。 ``` 1、实体中的非空字段会进行等于比较 2、如果存在其他条件,非空字段比较条件会和其他条件使用and连接 3、慎用 避免条件冲突或者重复 ``` ```java public void test(){ UserA userA = new UserA(); userA.setUserId(10001L); userA.setEmail("xxx@xxx.com"); userA.setAge(20); QueryWrapper queryWrapper = new QueryWrapper(userA); queryWrapper.like("name","z"); List userAList = userMapper.selectList(queryWrapper); userAList.forEach(System.out::println); } ``` ##### 其他条件构造器作为参数的方法 ​ 1、selectList 查询结果为一个集合 (以上条件构造器的案例均使用selectList) ​ 2、selectMaps 将查询结果以键值对的方式存入Map集合 再将map集合封装 到list集合 最终返回List list中每一个Map集合都是条查询结果 字段为key 只为value ​ 3、selectCount 返回查询结果中的数据条数 // 查询平均id大于200的各个年龄的用户数量和平均年龄 ​ ```java //// 查询平均id大于200的各个年龄的用户数量和年龄 // select count(*),age from user group by age having avg(id)>200 public void testSelectMap(){ QueryWrapper queryWrapper = new QueryWrapper(); queryWrapper .groupBy("age") .having("avg(id)>{0}",1) .select("count(*)","age"); List> mapList = userMapper.selectMaps(queryWrapper); mapList.forEach(System.out::println); } ``` ​ ​ ```java // 查询年龄为 19的 用户数量 @Test public void testSelectCount(){ int count = userMapper.selectCount( new QueryWrapper() .eq("age",19) ); System.out.println("查询到的数据的条数为==>"+count); } ``` 4、selectOne 返回实体对象作为查询结果 当且仅当查询结果只有一条数据的时候,可以选择使用 selectOne ​ ```java public void testSelectOne(){ UserA userA = userMapper.selectOne( new QueryWrapper() //.gt("id",1000) // 报错 因为此时查询结果不只一条 应该返回集合 .eq("id",1000) // 正确 查询结果只有一条 返回的是UserA对象 ); System.out.println(userA); } ``` ​ ##### Lambada条件构造器 ​ 了解 即可 Lambada条件构造器本质上就是MP对 构造器中列名的一种安全规范。 ​ ```java public void testLambada(){ // 获取LambdaQueryWrapper的方式 // 方式1 直接new LambdaQueryWrapper的对象 //LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper(); // 方式2 使用QueryWrapper 获取 LambadaQueryWrapper //LambdaQueryWrapper lambdaQueryWrapper = new QueryWrapper().lambda(); //方式3 通过Wrappers获取LambdaQueryWrapper LambdaQueryWrapper lambdaQueryWrapper = Wrappers.lambdaQuery(); lambdaQueryWrapper.like(UserA::getUsername,"zs"); List userAList = userMapper.selectList(lambdaQueryWrapper); userAList.forEach(System.out::println); } ``` 新增三部分测试类 1--实体作为条件构造器参数的情况 2--dao层curd以条件构造器作为参数的其他方法 queryList queryMaps queryCount queryOne queryObjs 3--LambdaQueryWapper条件构造器 根QueryWrapper条件构造器一样 都是用于构造查询条件 LambdaWrapper条件构造器 提供了列名的函数式编程写法 有效规避了 列名字符串 运行时异常的隐患 #### 自定义sql ​ 同mybatis写法 #### 分页查询 ##### 1-集成mybatis plus分页插件 ```java @Configuration public class MybatisConfig { // 最新版 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } } ``` ##### 2-创建Page对象 封装分页参数 ```java Page page = new Page(2,3); ``` ##### 3-调用selectPage或者selectMapsPage实现分页 ```java @Test public void demo1(){ Page page = new Page(2,3); IPage iPage = userMapper.selectPage(page,new QueryWrapper() .eq("age",19) ); System.out.println("数据条数==》"+iPage.getTotal()); System.out.println("总页数==》"+iPage.getPages()); System.out.println("当前页==>"+iPage.getCurrent()); System.out.println("页长==>"+iPage.getSize()); iPage.getRecords().forEach(System.out::println); } ``` ​ ```java public void demo2(){ //注意泛型 selectMapsPage 返回page page中使用list封装记录 list泛型为Map 没一个map集合都是一条记录 Page> page = new Page(1,4); userMapper.selectMapsPage(page,new QueryWrapper() .gt("age",10) .likeRight("name","z") ); System.out.println("数据条数==》"+page.getTotal()); System.out.println("总页数==》"+page.getPages()); System.out.println("当前页==>"+page.getCurrent()); System.out.println("页长==>"+page.getSize()); List> list = page.getRecords(); list.forEach(System.out::println); } ``` #### 更新 ​ ```java // 根据id删除 不需要条件构造器 只要在方法中传入实体对象即可 @Test public void test1(){ User user = new User(); user.setAge(100); user.setId(1001); int n = userMapper.updateById(user); System.out.println("受影响的数据的条数为 ===> "+n); } ``` ​ ```java // 根据姓名 修改年龄和邮箱信息 @Test public void test2(){ User user = new User(); user.setEmail("cxb_xuc@163.com"); user.setAge(20); // 第一个参数为实体的实例 实例中非空属性作为set后面修改的内容 // 第二个参数为条件构造器 作为 where后面的条件 int n = userMapper.update(user,Wrappers.update() .eq("age",19) ); System.out.println("受影响的数据的条数为 ===> "+n); } ``` ​ ```java // 根据姓名修改年龄和邮箱信息 @Test public void test3(){ // 第一个为null // 第二个参数为条件构造器 作为 where后面的条件 // 通过调用set方法 确定需要修改的列和值 int n = userMapper.update(null,Wrappers.update() .eq("age",20) .set("email","xx@xx.com") .set("name","xc") ); System.out.println("受影响的数据的条数为 ===> "+n); } ``` ​ ​ ```java // 引入LambdaUpdate public void test4(){ // 和QueryLambdaWrapper类似 为了提高代码的安全性 规避运行时列名报错的问题 int n = userMapper.update(null,Wrappers.lambdaUpdate() .eq(User::getAge,20) .set(User::getEmail,"xx@xx.com") .set(User::getName,"xc") ); System.out.println("受影响的数据的条数为 ===> "+n); } ``` ​ ```java //引入condition 实现动态修改 @Test public void test5(){ String email = " "; String name = " xc "; // 和QueryLambdaWrapper类似 为了提高代码的安全性 规避运行时列名报错的问题 int n = userMapper.update(null,Wrappers.lambdaUpdate() .eq(User::getAge,20) .set(email!=null&&(!email.trim().equals("")),User::getEmail,email) .set(StringUtils.isNotBlank(name),User::getName,name.trim()) ); System.out.println("受影响的数据的条数为 ===> "+n); } ``` ​ ```java //引入实体作为Wrapper 参数的情况 // 实例的非空字段会进行 等于比较 此处存在 bug // 比如 年龄 字段 定义为 int类型 默认为 0 不是空 不给年龄set值 也会参与比较 // 所以 建议将实体的基本类型 重构成 对应的包装类型 @Test public void test6(){ String email = " "; String name = " xc "; User user = new User(); user.setId(1005); // 和QueryLambdaWrapper类似 为了提高代码的安全性 规避运行时列名报错的问题 int n = userMapper.update(null,Wrappers.lambdaUpdate(user) .eq(User::getAge,20) .set(email!=null&&(!email.trim().equals("")),User::getEmail,email) .set(StringUtils.isNotBlank(name),User::getName,name.trim()) ); System.out.println("受影响的数据的条数为 ===> "+n); } ``` #### 删除 ```java // 根据id删除 一条数据 public void test1(){ int n = userMapper.deleteById(1001); System.out.println("受影响的数据条数 ==> "+n); } ``` ```java // 根据id删除多条数据 @Test public void test2(){ List list = new ArrayList(); list.add(1001); list.add(1002); list.add(1003); int n = userMapper.deleteBatchIds(list); System.out.println("受影响的数据条数 ==> "+n); } ``` ```java // 根据其他列进行删除 @Test public void test3(){ Map map = new HashMap(); map.put("name","xc"); map.put("age",20); int n = userMapper.deleteByMap(map); System.out.println("受影响的数据条数 ==> "+n); } ``` ​ ```java // 根据条件构造器进行删除 @Test public void test4(){ int n = userMapper.delete(Wrappers.query() .likeRight("name"," ") ); System.out.println("受影响的数据条数 ==> "+n); } ``` #### 主键生成策略 ``` public class User { @TableId(type = IdType.AUTO) /* IdType.AUTO 主键自增 表的主键 添加 auto_increment 自增长 如不不添加IdType.AUTO 实体的id为0或者空时 默认自增 实体id不为0或者空时 那么会按照输入的id进行插入 IdType.ASSiGN_ID 雪花算法生成的随机id IdType.ASSIGN_UUID 生成uuid作为表的主键 */ private int id; private String name; private int age; private String email; } ``` #### 通用service(对业务层代码的优化) ​ 1、定义service层接口 集成IService ```java public interface UserService extends IService { } ``` ​ 2、定义接口的实现类 实现接口的同时 集成 ServiceImpl类 ```java @Service public class UserServiceImpl extends ServiceImpl implements UserService { } ``` ​ 3、测试代码 ```java public class E_ServiceDemo { @Autowired private UserService userService; @Test public void test(){ User user = userService.getOne(new QueryWrapper() .eq("id",7) ); System.out.println(user); } } ``` #### 逻辑删除 ##### 概念 ​ 场景: 员工表(附表) 和 部门表(主表) ​ 操作 : 删除部门表数据的时候 怎么处理员工表数据? ​ on delete set null 外键列设置为空(找不到相关联的主表数据) ​ on delete cascade 删除相关联的附表数据(迷你版删库到跑路) ​ 逻辑删除用于解决类似问题。 ​ 所谓逻辑删除就是假删除,通过修改表的状态列来进行模拟删除操作 ​ 逻辑删除最终执行的是update方法 ##### 注意点 ​ 插入: 不受逻辑删除影响 ​ 修改: 忽略掉逻辑已删除的数据 ​ 查询: 忽略掉逻辑已删除的数据 ​ 简言之,在进行修改和查询的时候,会自动拼接条件对 已删除数据进行过滤 ##### 实现逻辑删除的操作流程 ###### 1. 全局配置 ​ 注意:汉字部分可能出现乱码 引发容器启动失败的问题 ```yml mybatis-plus: global-config: db-config: logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2) logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) ``` ​ ###### 2. @TableLogic注解 ​ 加在实体的逻辑删除字段 ```java @Data public class Student { private int id; private String name; private String sex; @TableLogic private int status; } ``` ###### 3. 测试代码 ​ ```java public class F_LogicDelete { @Autowired private StudentMapper studentMapper; @Test public void test(){ //UPDATE student SET status=1 WHERE id=? AND status=0 int n = studentMapper.deleteById(1); System.out.println(n); } @Test public void test2(){ //SELECT id,name,sex,status FROM student WHERE status=0 List list = studentMapper.selectList(null); list.forEach(System.out::println); } } ``` #### 自动填充 ​ 给表中字段自动填充提前配置好的数据,常常用于对日期列的字段填充。 ##### 1、注解填充字段 ​ ```java @Data @Accessors(chain = true) // set方法的链式调用 非必须 public class Student { private int id; private String name; private String sex; @TableLogic private int status; @Version private int version=1; // 插入填充 @TableField( fill = FieldFill.INSERT) private LocalDateTime createdate; // 修改填充 @TableField( fill = FieldFill.UPDATE) private LocalDateTime updatedate; } ``` ##### 2、自定义组件 ​ ```java public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.fillStrategy(metaObject, "createdate", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug) } @Override public void updateFill(MetaObject metaObject) { System.out.println(LocalDateTime.now()); this.strictUpdateFill(metaObject, "updatedate", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐) } } ``` ##### 3、测试代码 ​ ```java @RunWith(SpringRunner.class) @SpringBootTest public class H_FillTest { @Autowired private StudentMapper studentMapper; @Test public void test(){ studentMapper.insert(new Student() .setSex("M") .setName("zsf") ); } @Test public void test2(){ studentMapper.updateById(new Student() .setName("徐闯") .setId(9) .setSex("M") ); } } ``` ##### FAQ ​ mybatis-plus 中 实体属性驼峰式命名 对应 标准 xx_xx方式命名 #### 乐观锁 ##### 概念 ​ 当要更新一条记录的时候,希望(认为)这条记录没有被别人更新 ##### 实现方式 ​ 通过给表添加用于表示版本的列 作为数据是否已被别人更新的依据。 ​ 取出记录时,获取当前version ​ 更新时,带上这个version ​ 执行更新时, set version = newVersion where version = oldVersion ​ 如果version不对,就更新失败 ##### 具体操作 ###### 配置乐观锁插件 ​ OptimisticLockInnerInterceptor ```java @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } ``` ###### @Version ​ 实体类上给用于控制版本的字段添加@Version注解 ​ ```java @Data @Accessors(chain = true) // set方法的链式调用 非必须 public class Student { private int id; private String name; private String sex; @TableLogic private int status; @Version private int version; } ``` ###### 测试代码 ```java public void testOptimistic(){ studentMapper.updateById(new Student() .setName("徐闯") .setId(1) .setSex("M") ); } ``` ###### 执行结果 ​ ![1621944482122](C:\Users\A\AppData\Roaming\Typora\typora-user-images\1621944482122.png) ###### 注意点 ​ 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime ​ 整数类型下 newVersion = oldVersion + 1 ​ newVersion 会回写到 entity 中 ​ 仅支持 updateById(id) 与 update(entity, wrapper) 方法 ​ 在 update(entity, wrapper) 方法下, wrapper 不能复用!!! ##### faq ​ 聊聊乐观锁和悲观锁 ​ 乐观锁和悲观锁是线程并发操作下,保证事务一致性和隔离性的手段。 ​ 乐观锁认为要操作的数据没有被其他线程修改,真正进行写入操作时再进行判断,没问题正常修改,若存在问题,则响应问题给用户,有用户绝对接下来的操作。 ​ 悲观锁认为要操作的数据往往会被其他线程修改,在和数据库交互进行写入操作之前就已经通过java手段或数据库行级锁的手段提前进行加锁,防止数据被修改,悲观锁处理问题的方式具有独占性,容易造成其他线程阻塞,影响应用的响应速度。 ​ 读多写少的应用,建议用悲观锁,反之建议使用乐观锁。 #### 代码生成器 ##### 概述 ​ 对mybatis逆向工程的封装,根据特定配置生成对单表操作的dao/service/controller代码 ##### 优缺点 ​ 开发一时爽 维护火葬场 ​ 个人观点 : 小项目 可以尽情使用 大项目避免使用 后期维护会让极其搞心态(别问我怎么知道的) ##### 具体操作 ​ 添加代码生成器依赖 ``` com.baomidou mybatis-plus-generator 3.4.1 ``` ​ 添加模板引擎 ``` org.freemarker freemarker 2.3.31 ``` 代码生成器测试类https://gitee.com/xu_chuang/mp/blob/master/mpcast/cast-day2/src/main/java/com/castday2/castday2/test/E_TestAutoGenerate.java #### 性能优化 ​ 通过p6spy插件 生成sql语句及执行时间的日志。 ##### 1、引入p6spy的插件 ```xml p6spy p6spy 最新版本 ``` ##### 2、修改数据源的配置 ​ ```yaml spring: datasource: driver-class-name: com.p6spy.engine.spy.P6SpyDriver url: jdbc:p6spy:h2:mem:test ... ``` ##### 3、添加p6spy.properties配置文件 ​ 配置文件名字必须是spy.properties,添加在Resources目录下。 ​ https://gitee.com/xu_chuang/mp/blob/master/mpcast/cast-day2/src/main/resources/spy.properties ##### 4、打印结果如下 ​ ![1621942012475](C:\Users\A\AppData\Roaming\Typora\typora-user-images\1621942012475.png) #### 安全操作 ​ 配置BlockAttackInnerInterceptor 防止全表更新和全部删除 > ​ **`经测试,该操作不生效,官网没有过多阐述....** > > https://mybatis.plus/guide/interceptor-block-attack.html` ```java @Bean public BlockAttackInnerInterceptor myBlockAttackInnerInterceptor(){ return new BlockAttackInnerInterceptor(); } ``` #### 多租户 同一套程序下实现多用户数据的隔离 #### 统一返回格式封装 ##### 优点 ​ 1、代码更加优雅 ​ 2、简化前端开发 ​ 3、是一种开发趋势,越来越多的项目组进行开发时进行统一返回格式的封装 ##### 要求 ​ 统一返回格式的封装没有固定的要求,但是一般都会包含一下部分: ​ 1) 自定义状态码 规定不同的状态码 表示不同的含义 ​ 比如 200 正常状态 500 后台异常 403 没有操作权限 ​ 2)成功或者失败的布尔值 ​ 3) 返回消息 用于封装 成功/失败的信息 ​ 4) 响应数据 封装模型数据 ​ 比如 以下即为统一返回格式的对象响应到前端页面的数据 ``` { “code”:200, "success":true, "message":"操作成功", "data":{ "students":[ { "id":10, "name":"zs", "age":18, "sex":"男" }, { "id":10, "name":"zs", "age":18, "sex":"男" }, { "id":10, "name":"zs", "age":18, "sex":"男" } ] } } ``` ##### 操作 ​ 枚举 ```java // 枚举封装的是一组常量 所以不建议添加set方法(违反设计原则) @Getter @AllArgsConstructor @JsonFormat( shape = JsonFormat.Shape.OBJECT) public enum CodeEnum { OK(200,true,"成功"), ERROR(500,false,"失败"); private Integer code; private Boolean status; private String message; } ``` ​ 统一返回格式 ```java @Data @Accessors( chain = true) @AllArgsConstructor public class Result{ private Enum INFO; //状态码 //private Integer code; //是否成功的标识 //private Boolean success; //返回给前端的描述性消息 //private String message; //模型数据 private T data; // 提取冗余代码 二次封装 public static Result ok(Object o){ System.out.println(CodeEnum.OK); return new Result(CodeEnum.OK,o); //return new Result(OK,true,"成功",o); /* return new Result().setCode(200) .setData(o) .setMessage("成功") .setSuccess(true);*/ } public static Result error(){ return new Result(CodeEnum.ERROR,null); //return new Result(500,false,"失败",null); /* return new Result().setCode(500) .setData(null) .setMessage("失败") .setSuccess(false);*/ } } ``` ##### 验证 ```java @RestController @ResponseBody @RequestMapping("/user") public class ShowInfoController { @Autowired private UserService userService; @RequestMapping("/queryUser") public Result queryOne(){ User user = userService.getOne( new QueryWrapper() .eq("id",1) ); return Result.ok(user); } @RequestMapping("/queryUsers") public Result queryUsers(){ return Result.ok( userService.list() ); } } ``` ##### FAQ ###### 枚举概念 ​ 枚举的本质就是类。 ​ 如果一个类具有固定个数的实例,那么这个类就可以声明为一个枚举 ​ 反言之,所谓枚举就是具有固定个数实例的类。 ​ 比如 : 季节类 只有春夏秋冬四个季节 ​ 颜色类 颜色数量输固定的 一个颜色对应一个实例 ​ 方向类 东南西北 方向的个数时固定的 一个方方向对应一个实例 ​ 以上类 均可以声明为枚举 ​ 枚举项 : 枚举类中的每一个实例 均是一个枚举项 ###### 语法 ​ 定义枚举的关键字 enum ​ 所有的枚举默认继承java.lang.Enum 所以枚举项可以调用java.lang.Enum中的方法,因此枚举不能继承其他类(java类-单继承) ​ Enum类方法如下 ```java int compareTo(E e):比较两个枚举常量谁大谁小,其实比较的就是枚举常量在枚举类中声明的顺序,例如FRONT的下标为0,BEHIND下标为1,那么FRONT小于BEHIND; boolean equals(Object o):比较两个枚举常量是否相等; int hashCode():返回枚举常量的hashCode; String name():返回枚举常量的名字; int ordinal():返回枚举常量在枚举类中声明的序号,第一个枚举常量序号为0; String toString():把枚举常量转换成字符串; static T valueOf(Class enumType, String name):把字符串转换成枚举常量。 ``` ​ 枚举中的构造方法默认是私有的,因此枚举不能被其他类继承,并且枚举只能在本类中创建对象(枚举项) 此处可通过 **javap 字节码文件名字** 进行理解 ```java // 这是一个枚举 enum是定义枚举的关键字 public enum Color{ //这四个是枚举项 使用逗号隔开 最后添加分号 //枚举项的本质就是枚举类的对象 // 比如 枚举项 RED的实际写法为: // public static final Color RED = new Color(); RED,BLUE,GREEN,BLACK; } ``` ​ 枚举类中可以正常添加属性/方法及静态方法 ```java public enum Color{ //前三个枚举项是通过无参构造创建 //最后一个枚举项是通过有参构造创建 RED(),BLUE,GREEN,BLACK(8,"红色"); private Intege id; private String name; Color(){} Color(int id,String name){ this.id = id; this.name = name; } public String getName(){ return this.name; } } // 其他地方调用 Color.BLACK.getName() // 可以获取到字符串 : "红色" ``` 枚举类可以直接调用的特殊方法(可以通过javap 查看反编译后的class文件了解源码) ```java static Direction[] values():返回本类所有枚举常量; static Direction valueOf(String name):通过枚举常量的名字返回Direction常量,注意,这个方法与Enum类中的valueOf()方法的参数个数不同 ``` ​ ​