[
  {
    "path": ".gitignore",
    "content": "HELP.md\n/target/\n.mvn/\nmvnw\nmvnw.cmd\n\n### STS ###\n.apt_generated\n.classpath\n.factorypath\n.project\n.settings\n.springBeans\n.sts4-cache\n\n### IntelliJ IDEA ###\n.idea\n*.iws\n*.iml\n*.ipr\n\n### NetBeans ###\n/nbproject/private/\n/nbbuild/\n/dist/\n/nbdist/\n/.nb-gradle/\n/build/\n\n### VS Code ###\n.vscode/\n/log/\n.gradle\ngradle\ngradlew\ngradlew.bat\n*.log\n"
  },
  {
    "path": "README.md",
    "content": "# MyBatis-Plus-Example\n\nSpring Boot 整合 MyBatis Plus 实例，持续更新！！！\n\n## 代码生成器\n\n[请点击此处的传送门](https://github.com/fengwenyi/mybatis-plus-code-generator)\n\n## 不再维护\n\n作者尽力有限，此项目不再维护，后续会全部迁移到这里\n\n[demo-spring-boot-mybatis-plus](https://github.com/fengwenyi/spring-boot-demo/tree/main/demo-spring-boot-mybatis-plus)\n\n## 三方框架版本总览\n\n| 名称 | 版本 | 更新时间 |\n| --- | --- | --- |\n| Spring Boot | 2.4.2 |  2021.02.02 |\n| MyBatis-Plus | 3.4.2 |  2021.02.02 |\n| JavaLib | 2.1.1 |  2021.07.06 |\n| api-result | 2.3.1 |  2021.02.02 |\n| swagger | 2.9.2 |  2019.08.27 |\n\n## 项目版本标识说明\n\nBUILD\n开发版本：用于标识该版本正在构建或者开发中。\n\nSNAPSHOT\n预览版本：开发已经完成，开始进入测试阶段。\n\nRELEASE\n稳定版本：已发布到中央仓库。\n\n## 数据库设计\n\n### 商品类别表-category\n| 字段 | 类型 | 说明 |\n| --- | --- | --- |\n| id | bigint(20) | 主键ID |\n| name | varchar(50) | 类别名称 |\n\n### 商品表-goods\n| 字段 | 类型 | 说明 |\n| --- | --- | --- |\n| id | bigint(20) | 主键ID |\n| name | varchar(255) | 商品名称 |\n| category_id | bigint(20) | 类别ID |\n| stock_num | bigint(20) | 库存数量 |\n| price | decimal(9, 4) | 单价 |\n| flag | tinyint(1) | 上下架。0：下架；1：上架。默认0。 |\n| delete_status | tinyint(1) | 逻辑删除状态。0：正常；1：删除。默认0。 |\n| create_time | datetime | 创建时间 |\n| update_time | datetime | 更新时间 |\n| create_by | varchar(64) | 创建人 |\n| update_by | varchar(64) | 修改人 |\n| version | int(11) | 版本。默认：0。 |\n\n## 常见问题\n\n### 1、LocalDateTime无法使用的问题\n\n#### 描述\n\n用代码生成器生成的时间类型默认为 `LocalDateTime`, 但是在项目中使用报错。今天在项目上遇到了这个问题，什么原因呢？\n\n#### 分析\n\n首先要明确，如果你的项目都和示例项目配置、依赖、版本都一样，那说明本身是没有问题的。肯定是其他问题导致的。\n\n经过排查，项目引入了 `druid`, 版本比较老，所以无法转换导致的。\n\n#### 解决\n\n升级 `druid` 版本\n"
  },
  {
    "path": "doc/00-mybatis-plus知识点.md",
    "content": "# MyBatis-Plus知识点\n\n## Spring Boot整合MyBatis-Plus\n\n第一步：pom.xml引入MyBatis-Plus依赖，注意，不需要再引入MyBatis的包，因为我这里使用Spring Boot搭建的工程，所有因为方式见下：\n\n```xml\n<dependencies>\n    ...\n    <dependency>\n        <groupId>com.baomidou</groupId>\n        <artifactId>mybatis-plus-boot-starter</artifactId>\n        <version>3.0.1</version>\n    </dependency>\n    ...\n</dependencies>\n```\n\n第二步：将生成的代码，拷贝到相应的包下\n\n![代码目录结构](https://upload-images.jianshu.io/upload_images/5805596-368a8af54fe639b7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n第三步：在配置文件中进行相应的配置\n\n具体配置可参考官网，这里需要注意这样几个地方：\n\n```yaml\nmybatis-plus:\n  # xml\n  mapper-locations: classpath:mapper/*Mapper.xml\n  # 实体扫描，多个package用逗号或者分号分隔\n  type-aliases-package: com.fengwenyi.mp3demo.model\n  configuration:\n    # 这个配置会将执行的sql打印出来，在开发或测试的时候可以用\n    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\n```\n![日志：分页查询](https://upload-images.jianshu.io/upload_images/5805596-f5b314a660a48844.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n\n第四步：在启动类上添加下面的注解\n\n```java\n@EnableTransactionManagement\n@MapperScan(\"com.fengwenyi.mp3demo.dao\")\n```\n\n## 增删改\n\n**Service**\n\n我们一起去看源码 `com.baomidou.mybatisplus.extension.service.IService<T>`\n\n增加：\n\n```\n    /**\n     * <p>\n     * 插入一条记录（选择字段，策略插入）\n     * </p>\n     *\n     * @param entity 实体对象\n     */\n    boolean save(T entity);\n```\n\n修改：\n\n```java\n    /**\n     * <p>\n     * 根据 ID 选择修改\n     * </p>\n     *\n     * @param entity 实体对象\n     */\n    boolean updateById(T entity);\n\n    /**\n     * <p>\n     * 根据 whereEntity 条件，更新记录\n     * </p>\n     *\n     * @param entity        实体对象\n     * @param updateWrapper 实体对象封装操作类 \n     * {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}\n     */\n    boolean update(T entity, Wrapper<T> updateWrapper);\n```\n\n删除：\n\n```java\n    /**\n     * <p>\n     * 根据 ID 删除\n     * </p>\n     *\n     * @param id 主键ID\n     */\n    boolean removeById(Serializable id);\n\n    /**\n     * <p>\n     * 根据 entity 条件，删除记录\n     * </p>\n     *\n     * @param queryWrapper 实体包装类 \n     * {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}\n     */\n    boolean remove(Wrapper<T> queryWrapper);\n```\n\n**Mapper**\n\n`com.baomidou.mybatisplus.core.mapper.BaseMapper<T>`\n\n增加:\n\n```java\n    /**\n     * <p>\n     * 插入一条记录\n     * </p>\n     *\n     * @param entity 实体对象\n     */\n    int insert(T entity);\n```\n\n修改：\n\n```java\n    /**\n     * <p>\n     * 根据 whereEntity 条件，更新记录\n     * </p>\n     *\n     * @param entity        实体对象 (set 条件值,不能为 null)\n     * @param updateWrapper 实体对象封装操作类（可以为 null,里面的 entity 用于生成 where 语句）\n     */\n    int update(@Param(Constants.ENTITY) T entity, \n               @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);\n\n    /**\n     * <p>\n     * 根据 ID 修改\n     * </p>\n     *\n     * @param entity 实体对象\n     */\n    int updateById(@Param(Constants.ENTITY) T entity);\n```\n\n删除：\n\n```java\n    /**\n     * <p>\n     * 根据 entity 条件，删除记录\n     * </p>\n     *\n     * @param queryWrapper 实体对象封装操作类（可以为 null）\n     */\n    int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);\n\n    /**\n     * <p>\n     * 根据 ID 删除\n     * </p>\n     *\n     * @param id 主键ID\n     */\n    int deleteById(Serializable id);\n```\n\n以上相当于是常用API了，我们去看看，他是怎么实现的。毫无疑问，Mapper是底层，Service调用Mapper去执行sql，完成相关操作，所以，你完全可以直接调用Mapper完成相关操作，就跟使用MyBatis一样。下面我们去看看，他帮我们写的Service是什么样子，这里只看一个修改操作吧。\n\n接口：\n\n```java\n    /**\n     * <p>\n     * 根据 whereEntity 条件，更新记录\n     * </p>\n     *\n     * @param entity        实体对象\n     * @param updateWrapper 实体对象封装操作类\n     * {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}\n     */\n    boolean update(T entity, Wrapper<T> updateWrapper);\n```\n\n实现：\n\n```java\n    @Override\n    public boolean update(T entity, Wrapper<T> updateWrapper) {\n        return ServiceImpl.retBool(baseMapper.update(entity, updateWrapper));\n    }\n\n    /**\n     * <p>\n     * 判断数据库操作是否成功\n     * </p>\n     * <p>\n     * 注意！！ 该方法为 Integer 判断，不可传入 int 基本类型\n     * </p>\n     *\n     * @param result 数据库操作返回影响条数\n     * @return boolean\n     */\n    protected static boolean retBool(Integer result) {\n        return SqlHelper.retBool(result);\n    }\n\n    /**\n     * <p>\n     * 判断数据库操作是否成功\n     * </p>\n     *\n     * @param result 数据库操作返回影响条数\n     * @return boolean\n     */\n    public static boolean retBool(Integer result) {\n        return null != result && result >= 1;\n    }\n```\n\n哈哈，是不是我们自己也会这样写啊！\n\n## 查询\n\n接下来，我们一起讨论下查询吧。\n\nMP 3.x，查询接口发生了很大的变化，反正我是不喜欢的，你就弄一个什么开头啊，到时候，我一点就知道有哪些方法了，他这里有 `list*`, `get*`，反正就是一个字——没必要。\n\n先看下接口说明：\n\n```java\n    /**\n     * <p>\n     * 查询列表\n     * </p>\n     *\n     * @param queryWrapper 实体对象封装操作类 \n     * {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}\n     */\n    List<T> list(Wrapper<T> queryWrapper);\n\n      /**\n     * <p>\n     * 根据 ID 查询\n     * </p>\n     *\n     * @param id 主键ID\n     */\n    T getById(Serializable id);\n\n    /**\n     * <p>\n     * 根据 Wrapper，查询一条记录\n     * </p>\n     *\n     * @param queryWrapper 实体对象封装操作类\n     * {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}\n     */\n    T getOne(Wrapper<T> queryWrapper);\n```\n\n嗯，差不多了吧，这样需要注意这样一个方法：\n\n```java\n    /**\n     * <p>\n     * 从list中取第一条数据返回对应List中泛型的单个结果\n     * </p>\n     *\n     * @param list\n     * @param <E>\n     * @return\n     */\n    public static <E> E getObject(List<E> list) {\n        if (CollectionUtils.isNotEmpty(list)) {\n            int size = list.size();\n            if (size > 1) {\n                SqlHelper.logger.warn(\nString.format(\"Warn: execute Method There are  %s results.\", size));\n            }\n            return list.get(0);\n        }\n        return null;\n    }\n```\n\n**下面说下分页的问题**\n\n根据官网的说法，需要借助插件，这我们是可以理解。\n\n在Spring Boot启动类里面添加：\n\n```java\n    /**\n     * 分页插件\n     */\n    @Bean\n    public PaginationInterceptor paginationInterceptor() {\n        return new PaginationInterceptor();\n    }\n```\n\n这样就可以使用他提供的分页接口了：\n\n```java\n    /**\n     * <p>\n     * 翻页查询\n     * </p>\n     *\n     * @param page         翻页对象\n     * @param queryWrapper 实体对象封装操作类\n     * {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}\n     */\n    IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);\n```\n\n我们去看一下：\n\n```java\n    @Override\n    public IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper) {\n        queryWrapper = (Wrapper<T>) SqlHelper.fillWrapper(page, queryWrapper);\n        return baseMapper.selectPage(page, queryWrapper);\n    }\n\n    /**\n     * <p>\n     * 填充Wrapper\n     * </p>\n     *\n     * @param page    分页对象\n     * @param wrapper SQL包装对象\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static Wrapper<?> fillWrapper(IPage<?> page, Wrapper<?> wrapper) {\n        if (null == page) {\n            return wrapper;\n        }\n        if (ArrayUtils.isEmpty(page.ascs())\n            && ArrayUtils.isEmpty(page.descs())\n            && ObjectUtils.isEmpty(page.condition())) {\n            return wrapper;\n        }\n        QueryWrapper qw;\n        if (null == wrapper) {\n            qw = new QueryWrapper<>();\n        } else {\n            qw = (QueryWrapper) wrapper;\n        }\n        // 排序\n        if (ArrayUtils.isNotEmpty(page.ascs())) {\n            qw.orderByAsc(page.ascs());\n        }\n        if (ArrayUtils.isNotEmpty(page.descs())) {\n            qw.orderByDesc(page.descs());\n        }\n        // MAP 参数查询\n        if (ObjectUtils.isNotEmpty(page.condition())) {\n            qw.allEq(page.condition());\n        }\n        return qw;\n    }\n\n    /**\n     * <p>\n     * 根据 entity 条件，查询全部记录（并翻页）\n     * </p>\n     *\n     * @param page         分页查询条件（可以为 RowBounds.DEFAULT）\n     * @param queryWrapper 实体对象封装操作类（可以为 null）\n     */\n    IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);\n```\n\n分页的代码大抵就是这样，我之前也自己写过，思路还是相当来说比较简单，关键是看你的查询添加如何封装，分页类如何构造。\n\n这里有一点说明：\n\n分页从 **`1`** 开始 !!!\n\n## 枚举类\n\n1、实现 接口\n\n```java\n/**\n * <p>\n * 自定义枚举接口\n * </p>\n *\n * @author hubin\n * @since 2017-10-11\n */\npublic interface IEnum<T extends Serializable> {\n\n    /**\n     * 枚举数据库存储值\n     */\n    T getValue();\n\n}\n```\n\n2、实现注意\n\n```\n    @Override\n    public Integer getValue() {\n        return this.value;\n    }\n\n    @JsonValue\n    public String getDesc() {\n        return desc;\n    }\n```\n\n这是Jackson的写法，我没用FastJson，所以用的伙伴，去官网看一下：[FastJson看官网](http://mp.baomidou.com/#/enum?id=%E4%BA%8C%E3%80%81fastjson)。\n\n3:被忘了在配置文件中添加扫描：\n\n```yaml\nmybatis-plus:\n  # 扫描枚举类 # 支持统配符 * 或者 ; 分割\n  type-enums-package: com.fengwenyi.mp3demo.enums\n```\n\n差不多了吧，好像\n\n## 逻辑删除\n\n1、代码生成器中配置：\n\n```java\nnew StrategyConfig().setLogicDeleteFieldName(\"is_delete\") // 逻辑删除属性名称\n```\n\n或者，你可以手写，参考：\n\n```java\n    @ApiModelProperty(value = \"是否逻辑删除（true：删除；false：正常（默认））\")\n    @TableLogic\n    private Boolean isDelete;\n```\n\n2、自定义数据库的值：\n\n```yaml\nmybatis-plus:\n  global-config:\n    db-config:\n      #逻辑删除配置\n      logic-delete-value: 1\n      logic-not-delete-value: 0\n```\n\n## 逻辑删除\n\n## 乐观锁\n\n## SQL性能分析\n\n## MyBatis-Plus-Example\n\nMyBatis-Plus的代码都会上传到github上\n\nhttps://github.com/fengwenyi/MyBatis-Plus-Example\n\n## 参考资料\n\n*  [MyBatis-Plus](http://mp.baomidou.com/#/)\n\n*  [MyBatis-Plus 使用枚举自动关联注入](https://www.imooc.com/article/details/id/29760)\n\n*  [mybatis-plus插件使用的一些问题](https://www.jianshu.com/p/a5c9bab9584a)\n\n*  [设计模式之Builder模式](https://www.jianshu.com/p/e2a2fe3555b9)\n\n*  [修复Long类型太长，而Java序列化JSON丢失精度问题的方法](https://www.jianshu.com/p/fbcdcfc7cd12)"
  },
  {
    "path": "doc/01-SpringBoot整合MyBatis-Plus.md",
    "content": ""
  },
  {
    "path": "doc/02-mybatis-plus-example.sql",
    "content": "/*\n Navicat Premium Data Transfer\n\n Source Server         : localhost.01-192.168.16.128\n Source Server Type    : MySQL\n Source Server Version : 80021\n Source Host           : 192.168.16.128:3306\n Source Schema         : mybatis-plus-example\n\n Target Server Type    : MySQL\n Target Server Version : 80021\n File Encoding         : 65001\n\n Date: 07/03/2021 18:15:28\n*/\n\nSET NAMES utf8mb4;\nSET FOREIGN_KEY_CHECKS = 0;\n\n-- ----------------------------\n-- Table structure for shop_category\n-- ----------------------------\nDROP TABLE IF EXISTS `shop_category`;\nCREATE TABLE `shop_category`  (\n  `id` bigint(0) NOT NULL COMMENT '主键ID',\n  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '商品类别名称',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;\n\n-- ----------------------------\n-- Table structure for shop_goods\n-- ----------------------------\nDROP TABLE IF EXISTS `shop_goods`;\nCREATE TABLE `shop_goods`  (\n  `id` bigint(0) NOT NULL COMMENT '主键ID',\n  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '商品名称',\n  `category_id` bigint(0) DEFAULT NULL COMMENT '商品类别ID',\n  `stock_num` bigint(0) DEFAULT NULL COMMENT '库存数量',\n  `price` decimal(9, 4) DEFAULT NULL COMMENT '商品单价',\n  `flag` tinyint(1) DEFAULT NULL COMMENT '上下架。0：下架；1：上架。默认0。',\n  `delete_status` tinyint(1) DEFAULT NULL COMMENT '逻辑删除状态。0：正常；1：删除。默认0。',\n  `create_time` datetime(0) DEFAULT NULL COMMENT '创建时间',\n  `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '创建人',\n  `update_time` datetime(0) DEFAULT NULL COMMENT '更新时间',\n  `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '修改人',\n  `version` int(0) DEFAULT NULL COMMENT '版本。默认：0。',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;\n\nSET FOREIGN_KEY_CHECKS = 1;\n"
  },
  {
    "path": "doc/03-MyBatis-Plus代码生成器的使用.md",
    "content": ""
  },
  {
    "path": "doc/04-CRUD.md",
    "content": "# CRUD\n\n- 增加(Create)\n- 检索(Retrieve)\n- 更新(Update)\n- 删除(Delete)\n\n对于MyBatis-Plus而言，增删改查，有三种方式实现。\n\n1、使用MyBatis-Plus提供的服务接口。\n\n2、使用MyBatis的Mapper接口。\n\n3、使用Mybatis-Plus提供的AR特性，Model可进行CRUD操作。\n\nAR即为ActiveRecord,是一种领域模型模式，一个模型类对应一个表。通过实体类对象直接进行表的CRUD操作。\n\n## MyBatis-Plus服务接口\n\n[CategoryRepositoryTests](../src/test/java/com.fengwenyi.mybatisplusexample.CategoryRepositoryTests)\n\n### 数据添加\n\n```\nmpXxxService.save(entity);\n```\n\n### 数据删除\n\n```\nmpXxxService.removeById(entity.getId());\n```\n\n### 数据查询\n\n```\nmpXxxService.list(queryWrapper);\n```\n\n### 数据修改\n\n```\nmpXxxService.updateById(entity);\n```\n\n## MyBatis数据库操作Mapper接口\n\n[CategoryMapperTests](../src/test/java/com.fengwenyi.mybatisplusexample.CategoryMapperTests)\n\n### 数据添加\n\n```\nxxxMapper.insert(entity);\n```\n\n### 数据删除\n\n```\nxxxMapper.deleteById(entity);\n```\n\n### 数据查询\n\n```\nxxxMapper.selectList(queryWrapper);\n```\n\n### 数据修改\n\n```\nxxxMapper.updateById(entity);\n```\n\n## Mybatis-Plus提供的AR\n\n[CategoryModelTests](../src/test/java/com.fengwenyi.mybatisplusexample.CategoryModelTests)\n\n### 数据添加\n\n```\nxxxModel.insert();\n```\n\n### 数据删除\n\n```\nxxxModel.deleteById();\n```\n\n### 数据修改\n\n```\nxxxModel.updateById();\n```"
  },
  {
    "path": "doc/05-mybatis知识点.md",
    "content": ""
  },
  {
    "path": "doc/06-MyBatis-Plus条件查询.md",
    "content": "\n# MyBatis-Plus条件查询\n\n## Wrapper\n\n### QueryWrapper\n\n查询条件构造器\n\n### LambdaQueryWrapper\n\nLambda的使用\n\n### LambdaQueryChainWrapper\n\n链式查询"
  },
  {
    "path": "doc/07-MyBatis-Plus分页查询.md",
    "content": ""
  },
  {
    "path": "doc/08-MyBatis-Plus数据库枚举实现.md",
    "content": "# 枚举\n\n"
  },
  {
    "path": "doc/09-MyBatis-Plus乐观锁.md",
    "content": "# 乐观锁\n\n添加拦截器\n\n```\ninterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 乐观锁插件\n```\n\n乐观锁字段注解：\n\n```\n@Version\n```\n"
  },
  {
    "path": "doc/10-MyBatis-Plus多数据源（动态）.md",
    "content": ""
  },
  {
    "path": "doc/11-MyBatis-Plus连表查询.md",
    "content": ""
  },
  {
    "path": "doc/12-使用p6spy-SQL打印和性能分析.md",
    "content": ""
  },
  {
    "path": "doc/13-MyBatis-Plus逻辑删除.md",
    "content": ""
  },
  {
    "path": "doc/14-MyBatis-Plus数据安全保护.md",
    "content": ""
  },
  {
    "path": "doc/15-MyBatis-Plus字段填充.md",
    "content": "# 自动填充\n\n\n@TableField(.. fill = FieldFill.INSERT)\n\n```\n    /**\n     * 默认不处理\n     */\n    DEFAULT,\n    /**\n     * 插入填充字段\n     */\n    INSERT,\n    /**\n     * 更新填充字段\n     */\n    UPDATE,\n    /**\n     * 插入和更新填充字段\n     */\n    INSERT_UPDATE\n```\n\n实现元对象处理器接口：com.baomidou.mybatisplus.core.handlers.MetaObjectHandler\n\n```\n注意事项：\n\n- 填充原理是直接给entity的属性设置值!!!\n\n- 注解则是指定该属性在对应情况下必有值,如果无值则入库会是null\n\n- MetaObjectHandler提供的默认方法的策略均为:如果属性有值则不覆盖,如果填充值为null则不填充\n\n- 字段必须声明TableField注解,属性fill选择对应策略,该声明告知Mybatis-Plus需要预留注入SQL字段\n\n- 填充处理器MyMetaObjectHandler在 Spring Boot 中需要声明@Component或@Bean注入\n\n- 要想根据注解FieldFill.xxx和字段名以及字段类型来区分必须使用父类的strictInsertFill或者strictUpdateFill方法\n\n- 不需要根据任何来区分可以使用父类的fillStrategy方法\n```\n\n还需要再研究研究"
  },
  {
    "path": "doc/version/3.2.1.md",
    "content": "# 3.2.1\n\n| 项 | 说明 |\n| --- | --- |\n| 构建时间 | 2021.02.02 |\n| 发布时间 | |\n| 版本号 | 3.2.1 |\n\n\n## 新特性\n\n## 优化\n\n## 删除\n\n## 文档\n\n## 开发者"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\n    <modelVersion>4.0.0</modelVersion>\n\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-parent</artifactId>\n        <version>2.4.2</version>\n        <relativePath/> <!-- lookup parent from repository -->\n    </parent>\n\n    <groupId>com.fengwenyi</groupId>\n    <artifactId>MyBatis-Plus-Example</artifactId>\n    <version>3.2.1-BUILD</version>\n    <packaging>jar</packaging>\n    <name>MyBatis-Plus-Example</name>\n    <description>Spring Boot 整合 MyBatis Plus 实例</description>\n\n    <properties>\n        <java.version>1.8</java.version>\n        <swagger2.version>2.9.2</swagger2.version>\n        <JavaLib.version>2.1.1</JavaLib.version>\n        <mybatis-plus.version>3.4.2</mybatis-plus.version>\n        <fastjson.version>1.2.59</fastjson.version>\n        <api-result.version>2.3.1</api-result.version>\n    </properties>\n\n    <dependencies>\n        <!-- Spring Boot : Web -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n\n        <!-- MySQL -->\n        <dependency>\n            <groupId>mysql</groupId>\n            <artifactId>mysql-connector-java</artifactId>\n            <scope>runtime</scope>\n        </dependency>\n\n        <!-- lombok -->\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <optional>true</optional>\n        </dependency>\n\n        <!-- Spring Boot : Test -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n        <!-- mybatis-plus-boot-starter -->\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>mybatis-plus-boot-starter</artifactId>\n            <version>${mybatis-plus.version}</version>\n        </dependency>\n\n        <!-- Swagger -->\n        <dependency>\n            <groupId>io.springfox</groupId>\n            <artifactId>springfox-swagger2</artifactId>\n            <version>${swagger2.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.springfox</groupId>\n            <artifactId>springfox-swagger-ui</artifactId>\n            <version>${swagger2.version}</version>\n        </dependency>\n\n        <!-- JavaLib -->\n        <dependency>\n            <groupId>com.fengwenyi</groupId>\n            <artifactId>JavaLib</artifactId>\n            <version>${JavaLib.version}</version>\n        </dependency>\n\n        <!-- 添加 Api Result 支持 -->\n        <dependency>\n            <groupId>com.fengwenyi</groupId>\n            <artifactId>api-result</artifactId>\n            <version>${api-result.version}</version>\n        </dependency>\n\n        <!-- https://mvnrepository.com/artifact/p6spy/p6spy -->\n        <dependency>\n            <groupId>p6spy</groupId>\n            <artifactId>p6spy</artifactId>\n            <version>3.9.1</version>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "src/main/java/com/fengwenyi/mybatisplusexample/MybatisPlusExampleApplication.java",
    "content": "package com.fengwenyi.mybatisplusexample;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class MybatisPlusExampleApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(MybatisPlusExampleApplication.class, args);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/fengwenyi/mybatisplusexample/config/MyBatisPlusConfiguration.java",
    "content": "package com.fengwenyi.mybatisplusexample.config;\n\nimport com.baomidou.mybatisplus.annotation.DbType;\nimport com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;\nimport com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;\nimport com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;\nimport org.mybatis.spring.annotation.MapperScan;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * @author Erwin Feng\n * @since 2021-03-08\n */\n@Configuration\n@MapperScan(\"com.fengwenyi.mybatisplusexample.mapper\")\npublic class MyBatisPlusConfiguration {\n\n    @Bean\n    public MybatisPlusInterceptor mybatisPlusInterceptor() {\n        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();\n        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 分页插件\n        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 乐观锁插件\n        return interceptor;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/fengwenyi/mybatisplusexample/entity/CategoryEntity.java",
    "content": "package com.fengwenyi.mybatisplusexample.entity;\n\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport java.io.Serializable;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\n/**\n * <p>\n * \n * </p>\n *\n * @author Erwin Feng\n * @since 2021-03-07\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@TableName(\"shop_category\")\npublic class CategoryEntity extends Model<CategoryEntity> {\n\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 主键ID\n     */\n    @TableId(value = \"id\", type = IdType.ASSIGN_ID)\n    private Long id;\n\n    /**\n     * 商品类别名称\n     */\n    @TableField(\"name\")\n    private String name;\n\n\n    @Override\n    protected Serializable pkVal() {\n        return this.id;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/fengwenyi/mybatisplusexample/entity/GoodsEntity.java",
    "content": "package com.fengwenyi.mybatisplusexample.entity;\n\nimport java.math.BigDecimal;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport com.baomidou.mybatisplus.extension.activerecord.Model;\n\nimport java.time.LocalDateTime;\nimport java.io.Serializable;\n\nimport com.fengwenyi.mybatisplusexample.entity.enums.GoodsFlagEnum;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\n/**\n * <p>\n * \n * </p>\n *\n * @author Erwin Feng\n * @since 2021-03-07\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@TableName(\"shop_goods\")\npublic class GoodsEntity extends Model<GoodsEntity> {\n\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 主键ID\n     */\n    @TableId(value = \"id\", type = IdType.ASSIGN_ID)\n    private Long id;\n\n    /**\n     * 商品名称\n     */\n    @TableField(\"name\")\n    private String name;\n\n    /**\n     * 商品类别ID\n     */\n    @TableField(\"category_id\")\n    private Long categoryId;\n\n    /**\n     * 库存数量\n     */\n    @TableField(\"stock_num\")\n    private Long stockNum;\n\n    /**\n     * 商品单价\n     */\n    @TableField(\"price\")\n    private BigDecimal price;\n\n    /**\n     * 上下架。0：下架；1：上架。默认0。\n     */\n    @TableField(\"flag\")\n    private GoodsFlagEnum flag;\n\n    /**\n     * 逻辑删除状态。0：正常；1：删除。默认0。\n     */\n    @TableField(\"delete_status\")\n    @TableLogic\n    private Boolean deleteStatus;\n\n    /**\n     * 创建时间\n     */\n    @TableField(value = \"create_time\", fill = FieldFill.INSERT)\n    private LocalDateTime createTime;\n\n    /**\n     * 创建人\n     */\n    @TableField(value = \"create_by\", fill = FieldFill.INSERT)\n    private String createBy;\n\n    /**\n     * 更新时间\n     */\n    @TableField(value = \"update_time\", fill = FieldFill.UPDATE)\n    private LocalDateTime updateTime;\n\n    /**\n     * 修改人\n     */\n    @TableField(value = \"update_by\", fill = FieldFill.UPDATE)\n    private String updateBy;\n\n    /**\n     * 版本。默认：0。\n     */\n    @TableField(\"version\")\n    @Version\n    private Integer version;\n\n\n    @Override\n    protected Serializable pkVal() {\n        return this.id;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/fengwenyi/mybatisplusexample/entity/enums/GoodsFlagEnum.java",
    "content": "package com.fengwenyi.mybatisplusexample.entity.enums;\n\nimport com.baomidou.mybatisplus.annotation.IEnum;\nimport lombok.Getter;\n\n/**\n * @author Erwin Feng\n * @since 2021-03-08\n */\n@Getter\npublic enum GoodsFlagEnum implements IEnum<Integer> {\n    DOWN(0, \"下架\"),\n    UP(1, \"上架\"),\n    ;\n\n    private final Integer value;\n\n    private final String desc;\n\n    GoodsFlagEnum(Integer value, String desc) {\n        this.value = value;\n        this.desc = desc;\n    }\n\n    @Override\n    public Integer getValue() {\n        return this.value;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/fengwenyi/mybatisplusexample/handler/MyBatisPlusAutoFillMetaObjectHandler.java",
    "content": "package com.fengwenyi.mybatisplusexample.handler;\n\nimport com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.ibatis.reflection.MetaObject;\nimport org.springframework.stereotype.Component;\n\nimport java.time.LocalDateTime;\n\n/**\n * @author Erwin Feng\n * @since 2021-04-01\n */\n@Slf4j\n@Component\npublic class MyBatisPlusAutoFillMetaObjectHandler implements MetaObjectHandler {\n\n    @Override\n    public void insertFill(MetaObject metaObject) {\n        metaObject.setValue(\"createTime\", LocalDateTime.now());\n//        log.info(\"start insert fill ....\");\n//        this.strictInsertFill(metaObject, \"createTime\", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)\n        // 或者\n//        this.strictUpdateFill(metaObject, \"createTime\", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)\n        // 或者\n//        this.fillStrategy(metaObject, \"createTime\", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)\n    }\n\n    @Override\n    public void updateFill(MetaObject metaObject) {\n        metaObject.setValue(\"updateTime\", LocalDateTime.now());\n//        log.info(\"start update fill ....\");\n//        this.strictUpdateFill(metaObject, \"updateTime\", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)\n        // 或者\n//        this.strictUpdateFill(metaObject, \"updateTime\", LocalDateTime::now, LocalDateTime.class); // 起始版本 3.3.3(推荐)\n        // 或者\n//        this.fillStrategy(metaObject, \"updateTime\", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)\n\n\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/fengwenyi/mybatisplusexample/mapper/ICategoryMapper.java",
    "content": "package com.fengwenyi.mybatisplusexample.mapper;\n\nimport com.fengwenyi.mybatisplusexample.entity.CategoryEntity;\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\n\n/**\n * <p>\n *  Mapper 接口\n * </p>\n *\n * @author Erwin Feng\n * @since 2021-03-07\n */\npublic interface ICategoryMapper extends BaseMapper<CategoryEntity> {\n\n}\n"
  },
  {
    "path": "src/main/java/com/fengwenyi/mybatisplusexample/mapper/IGoodsMapper.java",
    "content": "package com.fengwenyi.mybatisplusexample.mapper;\n\nimport com.fengwenyi.mybatisplusexample.entity.GoodsEntity;\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\n\n/**\n * <p>\n *  Mapper 接口\n * </p>\n *\n * @author Erwin Feng\n * @since 2021-03-07\n */\npublic interface IGoodsMapper extends BaseMapper<GoodsEntity> {\n\n}\n"
  },
  {
    "path": "src/main/java/com/fengwenyi/mybatisplusexample/repository/MPCategoryRepository.java",
    "content": "package com.fengwenyi.mybatisplusexample.repository;\n\nimport com.fengwenyi.mybatisplusexample.entity.CategoryEntity;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\n/**\n * <p>\n *  服务类\n * </p>\n *\n * @author Erwin Feng\n * @since 2021-03-07\n */\npublic interface MPCategoryRepository extends IService<CategoryEntity> {\n\n}\n"
  },
  {
    "path": "src/main/java/com/fengwenyi/mybatisplusexample/repository/MPGoodsRepository.java",
    "content": "package com.fengwenyi.mybatisplusexample.repository;\n\nimport com.fengwenyi.mybatisplusexample.entity.GoodsEntity;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\n/**\n * <p>\n *  服务类\n * </p>\n *\n * @author Erwin Feng\n * @since 2021-03-07\n */\npublic interface MPGoodsRepository extends IService<GoodsEntity> {\n\n}\n"
  },
  {
    "path": "src/main/java/com/fengwenyi/mybatisplusexample/repository/impl/CategoryRepositoryImpl.java",
    "content": "package com.fengwenyi.mybatisplusexample.repository.impl;\n\nimport com.fengwenyi.mybatisplusexample.entity.CategoryEntity;\nimport com.fengwenyi.mybatisplusexample.mapper.ICategoryMapper;\nimport com.fengwenyi.mybatisplusexample.repository.MPCategoryRepository;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\n\n/**\n * <p>\n *  服务实现类\n * </p>\n *\n * @author Erwin Feng\n * @since 2021-03-07\n */\n@Service\npublic class CategoryRepositoryImpl extends ServiceImpl<ICategoryMapper, CategoryEntity> implements MPCategoryRepository {\n\n}\n"
  },
  {
    "path": "src/main/java/com/fengwenyi/mybatisplusexample/repository/impl/GoodsRepositoryImpl.java",
    "content": "package com.fengwenyi.mybatisplusexample.repository.impl;\n\nimport com.fengwenyi.mybatisplusexample.entity.GoodsEntity;\nimport com.fengwenyi.mybatisplusexample.mapper.IGoodsMapper;\nimport com.fengwenyi.mybatisplusexample.repository.MPGoodsRepository;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\n\n/**\n * <p>\n *  服务实现类\n * </p>\n *\n * @author Erwin Feng\n * @since 2021-03-07\n */\n@Service\npublic class GoodsRepositoryImpl extends ServiceImpl<IGoodsMapper, GoodsEntity> implements MPGoodsRepository {\n\n}\n"
  },
  {
    "path": "src/main/resources/application-dev.yml",
    "content": "\n#spring\nspring:\n  datasource:\n#    driver-class-name: com.mysql.jdbc.Driver\n#    url: jdbc:mysql://192.168.16.128:3306/mybatis-plus-example\n    username: root\n#    password: kU#m5eHY5iTiQj#q\n    password: 123456\n    driver-class-name: com.p6spy.engine.spy.P6SpyDriver\n    url: jdbc:p6spy:mysql://192.168.16.128:3306/mybatis-plus-example\n#    url: mpw:pBZqJb+r/StFGyRopKJyhZN8dvstxaXxubewECBMWM3e7XK+719AUXlC1y1Cu14i1MwqEdiJbBa+sEwFzyWEgA==\n#    username: mpw:hjR022j+cjKFE+35FLTQgg==\n#    password: mpw:kzLGIBmi4hT+dPmqnXLAIw==\n\nmybatis-plus:\n  configuration:\n    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl\n\nlogback:\n  logDir: log/dev\n\nlogging:\n  level:\n    com.fengwenyi.mybatis_plus_example.service.impl: trace\n    com.fengwenyi.mybatis_plus_example.controller: trace\n"
  },
  {
    "path": "src/main/resources/application-prod.yml",
    "content": ""
  },
  {
    "path": "src/main/resources/application-test.yml",
    "content": ""
  },
  {
    "path": "src/main/resources/application.yml",
    "content": "#app\nserver:\n  port: 8080\n\nspring:\n  application:\n    name: mybatis-plus-example\n  profiles:\n    active: dev\n\n#mybatis\nmybatis-plus:\n  # xml\n  mapper-locations: classpath:mapper/*Mapper.xml\n  # 实体扫描，多个package用逗号或者分号分隔\n  type-aliases-package: com.fengwenyi.mybatisplusexample.entity\n  # 扫描枚举类 # 支持统配符 * 或者 ; 分割\n  type-enums-package: com.fengwenyi.mybatisplusexample.entity.enums\n  global-config:\n    db-config:\n      #数据库大写下划线转换\n      capital-mode: true\n      #逻辑删除配置\n      # logic-delete-field: delete_status # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)\n      logic-delete-value: 1 # 逻辑删除\n      logic-not-delete-value: 0 # 正常\n  configuration:\n    map-underscore-to-camel-case: true\n    cache-enabled: false\n    default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler\n\nlogback:\n  appName: mybatis-plus-example\n  fileType: log\n"
  },
  {
    "path": "src/main/resources/logback-spring.xml",
    "content": "<!-- Logback configuration. See http://logback.qos.ch/manual/index.html -->\n<configuration scan=\"true\" scanPeriod=\"10 seconds\">\n    <!--继承spring boot提供的logback配置-->\n    <!--<include resource=\"org/springframework/boot/logging/logback/base.xml\" />-->\n\n    <!--设置系统日志目录-->\n    <!--<property name=\"APP_DIR\" value=\"spring-boot-log\"/>-->\n\n    <!--application.yml 传递参数，不能使用logback 自带的<property>标签 -->\n    <springProperty scope=\"context\" name=\"logDir\" source=\"logback.logDir\"/>\n    <springProperty scope=\"context\" name=\"appName\" source=\"logback.appName\"/>\n    <springProperty scope=\"context\" name=\"fileType\" source=\"logback.fileType\"/>\n\n    <!-- 彩色日志 -->\n    <!-- 彩色日志依赖的渲染类 -->\n    <conversionRule conversionWord=\"clr\" converterClass=\"org.springframework.boot.logging.logback.ColorConverter\"/>\n    <conversionRule conversionWord=\"wex\" converterClass=\"org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter\"/>\n    <conversionRule conversionWord=\"wEx\" converterClass=\"org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter\"/>\n    <!-- 彩色日志格式 -->\n    <property name=\"CONSOLE_LOG_PATTERN\"\n              value=\"${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}\"/>\n\n    <!-- 控制台输出 -->\n    <appender name=\"CONSOLE\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder>\n            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>\n            <charset>UTF-8</charset> <!-- 此处设置字符集 -->\n        </encoder>\n        <!--此日志appender是为开发使用，只配置最底级别，控制台输出的日志级别是大于或等于此级别的日志信息-->\n        <filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n            <level>debug</level>\n        </filter>\n    </appender>\n\n    <!-- 时间滚动输出 level为 DEBUG 日志 -->\n    <appender name=\"DEBUG_FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <!-- 正在记录的日志文件的路径及文件名 -->\n        <file>${logDir}/${appName}-debug.${fileType}</file>\n        <!--日志文件输出格式-->\n        <encoder>\n            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>\n            <charset>UTF-8</charset> <!-- 此处设置字符集 -->\n        </encoder>\n        <!-- 日志记录器的滚动策略，按日期，按大小记录 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <!--\n                归档的日志文件的路径，例如今天是2017-04-26日志，当前写的日志文件路径为file节点指定，可以将此文件与file指定文件路径设置为不同路径，从而将当前日志文件或归档日志文件置不同的目录。\n                而2017-04-26的日志文件在由fileNamePattern指定。%d{yyyy-MM-dd}指定日期格式，%i指定索引\n            -->\n            <fileNamePattern>${logDir}/${appName}-debug-%d{yyyy-MM-dd}.%i.${fileType}</fileNamePattern>\n            <!--\n                除按日志记录之外，还配置了日志文件不能超过500M，若超过500M，日志文件会以索引0开始，\n                命名日志文件，例如log-error-2017-04-26.0.log\n            -->\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>500MB</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n            <!--日志文件保留天数-->\n            <maxHistory>30</maxHistory>\n        </rollingPolicy>\n        <!-- 此日志文件只记录debug级别的 -->\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>debug</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    </appender>\n\n    <!-- 时间滚动输出 level为 INFO 日志 -->\n    <appender name=\"INFO_FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <!-- 正在记录的日志文件的路径及文件名 -->\n        <file>${logDir}/${appName}-info.${fileType}</file>\n        <!--日志文件输出格式-->\n        <encoder>\n            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>\n            <charset>UTF-8</charset> <!-- 此处设置字符集 -->\n        </encoder>\n        <!-- 日志记录器的滚动策略，按日期，按大小记录 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <!--\n                归档的日志文件的路径，例如今天是2017-04-26日志，当前写的日志文件路径为file节点指定，可以将此文件与file指定文件路径设置为不同路径，从而将当前日志文件或归档日志文件置不同的目录。\n                而2017-04-26的日志文件在由fileNamePattern指定。%d{yyyy-MM-dd}指定日期格式，%i指定索引\n            -->\n            <fileNamePattern>${logDir}/${appName}-info-%d{yyyy-MM-dd}.%i.${fileType}</fileNamePattern>\n            <!--\n                除按日志记录之外，还配置了日志文件不能超过500M，若超过500M，日志文件会以索引0开始，\n                命名日志文件，例如log-error-2017-04-26.0.log\n            -->\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>500MB</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n            <!--日志文件保留天数-->\n            <maxHistory>30</maxHistory>\n        </rollingPolicy>\n        <!-- 此日志文件只记录info级别的 -->\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>info</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    </appender>\n\n    <!-- 时间滚动输出 level为 WARN 日志 -->\n    <appender name=\"WARN_FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <!-- 正在记录的日志文件的路径及文件名 -->\n        <file>${logDir}/${appName}-warn.${fileType}</file>\n        <!--日志文件输出格式-->\n        <encoder>\n            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>\n            <charset>UTF-8</charset> <!-- 此处设置字符集 -->\n        </encoder>\n        <!-- 日志记录器的滚动策略，按日期，按大小记录 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <!--\n                归档的日志文件的路径，例如今天是2017-04-26日志，当前写的日志文件路径为file节点指定，可以将此文件与file指定文件路径设置为不同路径，从而将当前日志文件或归档日志文件置不同的目录。\n                而2017-04-26的日志文件在由fileNamePattern指定。%d{yyyy-MM-dd}指定日期格式，%i指定索引\n            -->\n            <fileNamePattern>${logDir}/${appName}-warn-%d{yyyy-MM-dd}.%i.${fileType}</fileNamePattern>\n            <!--\n                除按日志记录之外，还配置了日志文件不能超过500M，若超过500M，日志文件会以索引0开始，\n                命名日志文件，例如log-error-2017-04-26.0.log\n            -->\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>500MB</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n            <!--日志文件保留天数-->\n            <maxHistory>30</maxHistory>\n        </rollingPolicy>\n        <!-- 此日志文件只记录warn级别的 -->\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>warn</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    </appender>\n\n    <!-- 时间滚动输出 level为 ERROR 日志 -->\n    <appender name=\"ERROR_FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <!-- 正在记录的日志文件的路径及文件名 -->\n        <file>${logDir}/${appName}-error.${fileType}</file>\n        <!--日志文件输出格式-->\n        <encoder>\n            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>\n            <charset>UTF-8</charset> <!-- 此处设置字符集 -->\n        </encoder>\n        <!-- 日志记录器的滚动策略，按日期，按大小记录 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <!--\n                归档的日志文件的路径，例如今天是2017-04-26日志，当前写的日志文件路径为file节点指定，可以将此文件与file指定文件路径设置为不同路径，从而将当前日志文件或归档日志文件置不同的目录。\n                而2017-04-26的日志文件在由fileNamePattern指定。%d{yyyy-MM-dd}指定日期格式，%i指定索引\n            -->\n            <fileNamePattern>${logDir}/${appName}-error-%d{yyyy-MM-dd}.%i.${fileType}</fileNamePattern>\n            <!--\n                除按日志记录之外，还配置了日志文件不能超过500M，若超过500M，日志文件会以索引0开始，\n                命名日志文件，例如log-error-2017-04-26.0.log\n            -->\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>500MB</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n            <!--日志文件保留天数-->\n            <maxHistory>30</maxHistory>\n        </rollingPolicy>\n        <!-- 此日志文件只记录ERROR级别的 -->\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>error</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    </appender>\n\n    <logger name=\"org.springframework.web\" level=\"info\"/>\n    <logger name=\"org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor\" level=\"INFO\"/>\n\n    <!--开发环境:打印控制台-->\n    <springProfile name=\"dev\">\n        <root level=\"info\">\n            <appender-ref ref=\"CONSOLE\"/>\n            <appender-ref ref=\"DEBUG_FILE\"/>\n            <appender-ref ref=\"INFO_FILE\"/>\n            <appender-ref ref=\"WARN_FILE\"/>\n            <appender-ref ref=\"ERROR_FILE\"/>\n        </root>\n    </springProfile>\n\n    <!--测试环境:打印控制台和输出到文件-->\n    <springProfile name=\"test\">\n        <root level=\"info\">\n            <appender-ref ref=\"CONSOLE\"/>\n            <appender-ref ref=\"INFO_FILE\"/>\n            <appender-ref ref=\"WARN_FILE\"/>\n            <appender-ref ref=\"ERROR_FILE\"/>\n        </root>\n    </springProfile>\n\n    <!--生产环境:输出到文件-->\n    <springProfile name=\"prod\">\n        <root level=\"error\">\n            <appender-ref ref=\"CONSOLE\"/>\n            <appender-ref ref=\"DEBUG_FILE\"/>\n            <appender-ref ref=\"INFO_FILE\"/>\n            <appender-ref ref=\"ERROR_FILE\"/>\n        </root>\n    </springProfile>\n\n</configuration>"
  },
  {
    "path": "src/main/resources/mapper/CategoryMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.fengwenyi.mybatisplusexample.mapper.ICategoryMapper\">\n\n</mapper>\n"
  },
  {
    "path": "src/main/resources/mapper/GoodsMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"com.fengwenyi.mybatisplusexample.mapper.IGoodsMapper\">\n\n</mapper>\n"
  },
  {
    "path": "src/main/resources/spy.properties",
    "content": "#3.2.1以上使用\nmodulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory\n#3.2.1以下使用或者不配置\n#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory\n# 自定义日志打印\nlogMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger\n#日志输出到控制台\nappender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger\n# 使用日志系统记录 sql\n#appender=com.p6spy.engine.spy.appender.Slf4JLogger\n# 设置 p6spy driver 代理\nderegisterdrivers=true\n# 取消JDBC URL前缀\nuseprefix=true\n# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.\nexcludecategories=info,debug,result,commit,resultset\n# 日期格式\ndateformat=yyyy-MM-dd HH:mm:ss\n# 实际驱动可多个\n#driverlist=org.h2.Driver\n# 是否开启慢SQL记录\noutagedetection=true\n# 慢SQL记录标准 2 秒\noutagedetectioninterval=2"
  },
  {
    "path": "src/test/java/com/fengwenyi/mybatisplusexample/CategoryMapperTests.java",
    "content": "package com.fengwenyi.mybatisplusexample;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.fengwenyi.mybatisplusexample.entity.CategoryEntity;\nimport com.fengwenyi.mybatisplusexample.mapper.ICategoryMapper;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.Assert;\n\nimport javax.annotation.Resource;\nimport java.util.List;\n\n/**\n *\n * MyBatis Mapper CRUD\n *\n * @author Erwin Feng\n * @since 2021-04-01\n */\n@Component\n@Slf4j\npublic class CategoryMapperTests extends MybatisPlusExampleApplicationTests {\n\n    private CategoryEntity entity;\n\n    @Resource\n    private ICategoryMapper categoryMapper;\n\n    // 创建数据\n    private void create() {\n        entity = new CategoryEntity().setName(\"category test data\");\n        categoryMapper.insert(entity);\n    }\n\n    // 清除数据\n    private void clear() {\n        categoryMapper.deleteById(entity.getId());\n    }\n\n    @Test\n    public void testAdd() {\n        entity = new CategoryEntity().setName(\"category test data\");\n        int resultNum = categoryMapper.insert(entity);\n        Assert.isTrue(resultNum == 1, \"data add test failure\");\n\n\n        clear();\n    }\n\n    @Test\n    public void testDelete() {\n        create();\n\n        int resultNum = categoryMapper.deleteById(entity.getId());\n        Assert.isTrue(resultNum == 1, \"data delete test failure\");\n    }\n\n    @Test\n    public void testUpdate() {\n        create();\n\n        entity.setName(\"category update\");\n        int resultNum = categoryMapper.updateById(entity);\n        Assert.isTrue(resultNum == 1, \"data update test failure\");\n\n        clear();\n    }\n\n    @Test\n    public void testQuery() {\n        create();\n\n        List<CategoryEntity> categoryEntityList = categoryMapper.selectList(new QueryWrapper<>());\n        for (CategoryEntity entity : categoryEntityList) {\n            log.info(entity.toString());\n        }\n\n        clear();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/com/fengwenyi/mybatisplusexample/CategoryModelTests.java",
    "content": "package com.fengwenyi.mybatisplusexample;\n\nimport com.fengwenyi.mybatisplusexample.entity.CategoryEntity;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.Assert;\n\n/**\n *\n * model test\n *\n * <p>\n *     CRUD\n * </p>\n *\n * @author Erwin Feng\n * @since 2021-03-30\n */\n@Component\npublic class CategoryModelTests extends MybatisPlusExampleApplicationTests {\n\n    private CategoryEntity model;\n\n    // 创建数据\n    private void create() {\n        model = new CategoryEntity().setName(\"category test data\");\n        model.insert();\n    }\n\n    // 清除数据\n    private void clear() {\n        model.deleteById();\n    }\n\n    @Test\n    public void testAdd() {\n        model = new CategoryEntity().setName(\"category test data\");\n        boolean testResult = model.insert();\n        Assert.isTrue(testResult, \"data add test failure\");\n\n\n        clear();\n    }\n\n    @Test\n    public void testDelete() {\n        create();\n\n        boolean testResult = model.deleteById();\n        Assert.isTrue(testResult, \"data delete test failure\");\n    }\n\n    @Test\n    public void testUpdate() {\n        create();\n\n        model.setName(\"category update\");\n        boolean testResult = model.updateById();\n        Assert.isTrue(testResult, \"data update test failure\");\n\n        clear();\n    }\n\n\n}\n"
  },
  {
    "path": "src/test/java/com/fengwenyi/mybatisplusexample/CategoryRepositoryTests.java",
    "content": "package com.fengwenyi.mybatisplusexample;\n\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.fengwenyi.mybatisplusexample.entity.CategoryEntity;\nimport com.fengwenyi.mybatisplusexample.repository.MPCategoryRepository;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.Assert;\n\nimport java.util.List;\n\n/**\n *\n * MPCategoryRepository test\n *\n * <p>\n *     CRUD\n * </p>\n *\n * @author Erwin Feng\n * @since 2021-03-08\n */\n@Component\n@Slf4j\npublic class CategoryRepositoryTests extends MybatisPlusExampleApplicationTests {\n\n    @Autowired\n    private MPCategoryRepository mpCategoryRepository;\n\n    private CategoryEntity entity;\n\n    // 创建数据\n    private void create() {\n        entity = new CategoryEntity().setName(\"category test data\");\n        mpCategoryRepository.save(entity);\n    }\n\n    // 清除数据\n    private void clear() {\n        mpCategoryRepository.removeById(entity);\n    }\n\n    @Test\n    public void testAdd() {\n        entity = new CategoryEntity().setName(\"category test data\");\n        boolean testResult = mpCategoryRepository.save(entity);\n        Assert.isTrue(testResult, \"data add test failure\");\n\n\n        clear();\n    }\n\n    @Test\n    public void testDelete() {\n        create();\n\n\n        boolean testResult = mpCategoryRepository.removeById(entity.getId());\n        Assert.isTrue(testResult, \"data delete test failure\");\n    }\n\n    @Test\n    public void testDeleteByWrapper() {\n        create();\n\n\n        LambdaQueryWrapper<CategoryEntity> queryWrapper = new LambdaQueryWrapper<CategoryEntity>()\n                .eq(CategoryEntity::getName, entity.getName())\n                ;\n\n        boolean testResult = mpCategoryRepository.remove(queryWrapper);\n        Assert.isTrue(testResult, \"data delete by wrapper test failure\");\n    }\n\n    @Test\n    public void testUpdate() {\n        create();\n\n        entity.setName(\"category update\");\n        boolean testResult = mpCategoryRepository.updateById(entity);\n        Assert.isTrue(testResult, \"data update test failure\");\n\n        clear();\n    }\n\n    @Test\n    public void testQuery() {\n        create();\n\n        // 根据主键ID查询\n        mpCategoryRepository.getById(entity.getId());\n\n\n        // 查询所有\n        List<CategoryEntity> list = mpCategoryRepository.list();\n\n        // 添加查询\n        LambdaQueryWrapper<CategoryEntity> queryWrapper = new LambdaQueryWrapper<CategoryEntity>()\n                .eq(CategoryEntity::getName, entity.getName())\n                ;\n        mpCategoryRepository.list(queryWrapper);\n\n        // 分页查询\n        int currentPage = 1;\n        int pageSize = 10;\n        Page<CategoryEntity> categoryPage = mpCategoryRepository.page(new Page<>(currentPage, pageSize), queryWrapper);\n\n        long current = categoryPage.getCurrent();\n        long size = categoryPage.getSize();\n        long pages = categoryPage.getPages();\n        List<CategoryEntity> categoryList = categoryPage.getRecords();\n        long total = categoryPage.getTotal();\n\n\n        clear();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/com/fengwenyi/mybatisplusexample/GoodsRepositoryTests.java",
    "content": "package com.fengwenyi.mybatisplusexample;\n\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.fengwenyi.mybatisplusexample.entity.CategoryEntity;\nimport com.fengwenyi.mybatisplusexample.entity.GoodsEntity;\nimport com.fengwenyi.mybatisplusexample.entity.enums.GoodsFlagEnum;\nimport com.fengwenyi.mybatisplusexample.repository.MPCategoryRepository;\nimport com.fengwenyi.mybatisplusexample.repository.MPGoodsRepository;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.Assert;\n\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n/**\n * @author Erwin Feng\n * @since 2021-03-08\n */\n@Component\n@Slf4j\npublic class GoodsRepositoryTests extends MybatisPlusExampleApplicationTests {\n\n    @Autowired\n    private MPCategoryRepository mpCategoryRepository;\n\n    @Autowired\n    private MPGoodsRepository mpGoodsRepository;\n\n    @Test\n    public void testAdd() {\n        LambdaQueryWrapper<CategoryEntity> categoryQueryWrapper = new LambdaQueryWrapper<CategoryEntity>()\n                .eq(CategoryEntity::getName, \"家电\")\n                ;\n        List<CategoryEntity> categoryEntityList = mpCategoryRepository.list(categoryQueryWrapper);\n        CategoryEntity categoryEntity = categoryEntityList.get(0);\n\n        GoodsEntity goodsEntity = new GoodsEntity()\n                .setName(\"平板电脑\")\n                .setPrice(new BigDecimal(\"4699.00\"))\n                .setStockNum(10000L)\n                .setFlag(GoodsFlagEnum.UP)\n                .setCategoryId(categoryEntity.getId())\n                .setCreateBy(\"admin\")\n                ;\n        mpGoodsRepository.save(goodsEntity);\n    }\n\n    @Test\n    public void testQuery() {\n        List<GoodsEntity> goodsEntityList = mpGoodsRepository.list();\n        for (GoodsEntity goodsEntity : goodsEntityList) {\n            log.info(goodsEntity.toString());\n            log.info(goodsEntity.getFlag().getValue() + \"\");\n        }\n    }\n\n    @Test\n    public void testUpdate() {\n        LambdaQueryWrapper<GoodsEntity> goodsQueryWrapper = new LambdaQueryWrapper<GoodsEntity>().eq(GoodsEntity::getName, \"平板电脑\");\n        List<GoodsEntity> goodsEntityList = mpGoodsRepository.list(goodsQueryWrapper);\n        GoodsEntity goodsEntity = goodsEntityList.get(0);\n        goodsEntity.setPrice(new BigDecimal(\"6499.00\"));\n        mpGoodsRepository.updateById(goodsEntity);\n    }\n\n    @Test\n    void testQueryTime() {\n        LambdaQueryWrapper<GoodsEntity> queryWrapper = new LambdaQueryWrapper<GoodsEntity>()\n                .lt(GoodsEntity::getCreateTime, LocalDateTime.parse(\"2021-04-08 10:00:00\")) // where create_time < 2021-04-08 10:00:00\n                .gt(GoodsEntity::getCreateTime, LocalDateTime.parse(\"2021-04-09 10:00:00\")) // where create_time > 2021-04-09 10:00:00\n                ;\n        List<GoodsEntity> list = mpGoodsRepository.list(queryWrapper);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/com/fengwenyi/mybatisplusexample/MPWTests.java",
    "content": "package com.fengwenyi.mybatisplusexample;\n\nimport com.baomidou.mybatisplus.core.toolkit.AES;\nimport com.fengwenyi.javalib.util.PrintUtils;\nimport org.junit.jupiter.api.Test;\n\n/**\n * @author Erwin Feng\n * @since 2021-03-31\n */\npublic class MPWTests {\n\n    @Test\n    public void test() {\n\n        // 生成 16 位随机 AES 密钥\n        String randomKey = AES.generateRandomKey();\n        PrintUtils.info(randomKey);\n\n        String prefix = \"mpw:\";\n\n        // 随机密钥加密\n        String url = \"jdbc:p6spy:mysql://192.168.16.128:3306/mybatis-plus-example\";\n        String result =prefix + AES.encrypt(url, randomKey);\n        PrintUtils.info(result);\n\n        String username = \"root\";\n        result =prefix + AES.encrypt(username, randomKey);\n        PrintUtils.info(result);\n\n        String password = \"123456\";\n        result =prefix + AES.encrypt(password, randomKey);\n        PrintUtils.info(result);\n\n/*\n2021-03-31 00:57:49.494 INFO com.fengwenyi.mybatisplusexample.MPWTests#test : ba8a2cea8df4929f\n2021-03-31 00:57:49.871 INFO com.fengwenyi.mybatisplusexample.MPWTests#test : mpw:pBZqJb+r/StFGyRopKJyhZN8dvstxaXxubewECBMWM3e7XK+719AUXlC1y1Cu14i1MwqEdiJbBa+sEwFzyWEgA==\n2021-03-31 00:57:49.871 INFO com.fengwenyi.mybatisplusexample.MPWTests#test : mpw:hjR022j+cjKFE+35FLTQgg==\n2021-03-31 00:57:49.871 INFO com.fengwenyi.mybatisplusexample.MPWTests#test : mpw:kzLGIBmi4hT+dPmqnXLAIw==\n */\n\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/com/fengwenyi/mybatisplusexample/MybatisPlusExampleApplicationTests.java",
    "content": "package com.fengwenyi.mybatisplusexample;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.context.SpringBootTest;\n\n@SpringBootTest\npublic class MybatisPlusExampleApplicationTests {\n\n    @Test\n    public void contextLoads() {\n    }\n\n\n\n}\n"
  }
]