Mybatis逆向工程和新版本MybatisPlus3.4逆向工程的使用
阅读原文时间:2021年10月02日阅读:11

Mybatis和MybatisPlus3.4的使用

目录

前后端风你开发的项目中,前后端之间是接口进行请求和响应,后端向前端提供请求时就要对外暴露一个URL;URL的设计不能是随意的,需要遵从一定的设计规范----RESTFUL。

RESTful是一种web api的标准,也就是一种url设计风格/规范

  • 每个URL请求路径代表服务器上的唯一资源。

    传统的URL设计:

    http://localhost:8080/goods/delete?goodsId=1 商品1

    http://localhost:8080/goods/delete?goodsId=2 商品2

    RESTful设计:

    http://localhost:8080/goods/delete/1 商品1

    http://localhost:8080/goods/delete/1 商品1

    示例:

        @DeleteMapping("/delete/{id}")
        public ResultVO deleteGoods(@PathVariable("id") Integer goodsId) {
            return new ResultVO(10003,"请求成功",goodsId);
        }
  • 使用不同的请求方式表示不同的操作,保证唯一URL对应唯一资源

    根据id删除一个商品:

     //http://localhost:8080/goods/1    [delete]
        @DeleteMapping("/{id}")
     public ResultVO deleteGoods(@PathVariable("id") Integer goodsId) {
         return new ResultVO(10003,"请求成功",goodsId);
     }

    根据id查询一个商品:

     //http://localhost:8080/goods/1    [get]
        @GetMapping("/{id}")
     public ResultVO getGoods(@PathVariable("id") Integer goodsId) {
         return new ResultVO(10003,"请求成功",goodsId);
     }
  • 接口响应的资源表现形式使用JSON

    • 可以在控制类或者需要的接口方法上添加@ResponseBody注解讲返回的对象格式化为JSON
    • 也可以在控制类上使用@RestController声明控制器
  • 前端(Android\ios\PC)通过无状态的HTTP协议与后端接口交互

mybatis官方提供了一种根据数据库表生成mybatis执行代码的工具,这个工具就是一个逆向工程

逆向工程:针对数据库单表—->生成代码(mapper.xml、mapper.java、pojo。。)

这里我最开始使用的是mybatis逆向工程,可以生成mapper.xml、mapper.java、pojo

后面我想尝试使用MP做逆向工程,所以使用了两种方式,从简洁上来看,还是MP比较方便一点.

2.1 tkMybatis逆向工程

逆向工程并且自动添加swagger注解,注意需要在beans的pom也要添加swagger依赖

2.1.1 导入依赖

<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>2.1.5</version>
</dependency>
  • 修改启动类注解@MapperScan的包,因为这个注解还有个包是org.mybatis.spring.annotation.MapperScan;

2.1.2 添加插件

    <build>
        <plugins>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.5</version>
                <configuration>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                    <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.22</version>
                    </dependency>
                    <!-- https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client --><!-- https://mvnrepository.com/artifact/tk.mybatis/mapper -->
                    <dependency>
                        <groupId>tk.mybatis</groupId>
                        <artifactId>mapper</artifactId>
                        <version>4.1.5</version>
                    </dependency>
                    <dependency>
                        <groupId>com.github.misterchangray.mybatis.generator.plugins</groupId>
                        <artifactId>myBatisGeneratorPlugins</artifactId>
                        <version>1.2</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

同步成功后会出现

2.1.3 添加配置文件

generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <!--导入属性配置-->
    <!--    <properties resource="db.properties"></properties>-->
    <!--指定特定数据库的jdbc驱动jar包的位置-->
    <!--<classPathEntry location="${jdbc.location}"/>-->
    <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>
        <!--结合通用Mapper插件 指定生成 Mapper 的继承模板-->
        <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
            <property name="mappers" value="com.mymall.general.GeneralDao"/>
        </plugin>
        <!--pojo实现序列化接口-->
        <plugin type="org.mybatis.generator.plugins.SerializablePlugin" />
        <!--pojo类中增加toString方法-->
        <plugin type="org.mybatis.generator.plugins.ToStringPlugin" />
        <!--覆盖生成XML文件 每次执行,把以前的mapper.xml覆盖而不是合并-->
<!--                <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin" />-->

        <!-- 自动为entity生成swagger2文档-->
                <plugin type="mybatis.generator.plugins.GeneratorSwagger2Doc">
                    <property name="apiModelAnnotationPackage" value="io.swagger.annotations.ApiModel"/>
                    <property name="apiModelPropertyAnnotationPackage" value="io.swagger.annotations.ApiModelProperty"/>
                </plugin>
        <!--注意, plugin 需要写在commentGenerator上面-->

        <commentGenerator>
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressAllComments" value="false" />
        </commentGenerator>
        <!--jdbc的数据库连接 -->
        <jdbcConnection
                driverClass="com.mysql.cj.jdbc.Driver"
                connectionURL="jdbc:mysql://localhost:3306/mymall?useSSL=false&amp;serverTimezone=GMT%2b8"
                userId="root"
                password="123456">
            <property name="nullCatalogMeansCurrent" value="true"/>
        </jdbcConnection>

        <!-- 生成 JavaBean 对象重写 toString方法 -->
        <!--        <plugin type="org.mybatis.generator.plugins.ToStringPlugin" />-->
        <!-- 生成 JavaBean 对象继承 Serializable 类 -->
        <!--        <plugin type="org.mybatis.generator.plugins.SerializablePlugin" />-->
        <!-- 生成 JavaBean 对象重写 equals 和 hashCode 方法 -->
        <!-- <plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin" /> -->

        <!-- 对应生成的pojo所在包 -->
        <javaModelGenerator targetPackage="com.mymall.entity" targetProject="src/main/java">
            <!-- 是否对model添加 构造函数 -->
            <property name="constructorBased" value="true"/>
        </javaModelGenerator>

        <!-- 对应生成的mapper.xml所在目录 -->
        <sqlMapGenerator targetPackage="/" targetProject="src/main/resources/mappers"/>

        <!-- 配置mapper对应的java映射 -->
        <javaClientGenerator targetPackage="com.mymall.dao" targetProject="src/main/java"
                             type="XMLMAPPER"/>
        <!--    配置需要指定生成的数据库和表,%代表所有表,开发中不要这么做,可能会生成很多无用的表-->
        <!--        <table tableName="%"/>-->
        <table tableName="category" />
        <table tableName="index_img" />
        <table tableName="order_item" />
        <table tableName="orders" />
        <table tableName="product" />
        <table tableName="product_comments" />
        <table tableName="product_img" />
        <table tableName="product_params" />
        <table tableName="product_sku" />
        <table tableName="shopping_cart" />
        <table tableName="user_addr" />
        <table tableName="user_login_history" />
        <table tableName="users" />

        <!--  <table schema="" tableName="oauth_access_token"  enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false">
              <domainObjectRenamingRule searchString="^Tb" replaceString="" />
          </table>
          <table schema="" tableName="oauth_approvals"  enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false">
              <domainObjectRenamingRule searchString="^Tb" replaceString="" />
          </table>-->
        <!--将你要生成的表尽数罗列 -->
    </context>
</generatorConfiguration>
  • 调整配置信息

    • 创建GeneralDao接口

      package com.mymall.general;
      
      import tk.mybatis.mapper.common.Mapper;
      import tk.mybatis.mapper.common.MySqlMapper;
      
      /**
       * @author 18230
       * @version 1.0.0
       * @ClassName GereralDao.java
       * @Description
       * @createTime 2021年09月25日 15:50:00
       */
      public interface GeneralDao<T> extends Mapper<T>, MySqlMapper<T> {
      }
      
      
      
      <!--配置GeneralDao-->
      <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
          <property name="mappers" value="com.mymall.general.GneralDao"/>
      </plugin>
    • 配置数据库连接信息

      <!--jdbc的数据库连接,url后面的配置不要修改 -->
      <jdbcConnection
              driverClass="com.mysql.cj.jdbc.Driver"
              connectionURL="jdbc:mysql://localhost:3306/mymall?useSSL=false&amp;serverTimezone=GMT%2b8"
              userId="root"
              password="123456">
      </jdbcConnection>
    • 配置实体类路径

      <!-- 对应生成的pojo所在包 -->
      <javaModelGenerator targetPackage="com.mymall.entity" targetProject="src/main/java">
          <!-- 是否对model添加 构造函数 -->
          <property name="constructorBased" value="true"/>
      </javaModelGenerator>
    • 配置mapper.xml目录

      <!-- 对应生成的mapper.xml所在目录 -->
      <sqlMapGenerator targetPackage="/" targetProject="src/main/resources/mappers"/>
    • 配置dao的目录和生成表的名称

       <!-- 配置mapper对应的java映射 -->
      <javaClientGenerator targetPackage="com.mymall.dao" targetProject="src/main/java"
                           type="XMLMAPPER"/>
      <!--    配置需要指定生成的数据库和表,%代表所有表,开发中不要这么做,可能会生成很多无用的表-->
      <!--        <table tableName="%"/>-->
      <table tableName="category" />
      <table tableName="index_img" />
      <table tableName="order_item" />
      <table tableName="orders" />
      <table tableName="product" />
      <table tableName="product_comments" />
      <table tableName="product_img" />
      <table tableName="product_params" />
      <table tableName="product_sku" />
      <table tableName="shopping_cart" />
      <table tableName="user_addr" />
      <table tableName="user_login_history" />
      <table tableName="users" />
    • 将配置文件generatorConfig.xml配置到逆向工程的maven插件中

      <groupId>org.mybatis.generator</groupId>
      <artifactId>mybatis-generator-maven-plugin</artifactId>
      <version>1.3.5</version>
      <configuration>
          <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
      </configuration>

2.1.4 执行逆向

2.1.5 生成成功

  • 将生成的entity剪切到beans子项目中

    将生成的entity移到到beans目录之后会出现报错,因为使用了tkMapper的注解,所以需要将tkMapper的依赖直接剪切到beans中,mapper项目引用了beans的依赖所以就可以不再pom中声明tkMapper的依赖了.

2.1.6 使用tkMybatis

package com.qc.dao;

import com.qc.TkMapperDemoApplication;
import com.qc.entity.Category;
import org.apache.ibatis.session.RowBounds;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import tk.mybatis.mapper.entity.Example;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @author 18230
 * @version 1.0.0
 * @ClassName CategoryDaoTest.java
 * @Description
 * @createTime 2021年09月05日 20:40:00
 */
@SpringBootTest(classes = TkMapperDemoApplication.class)
@RunWith(SpringRunner.class)
public class CategoryDaoTest {
    @Autowired
    private CategoryDao categoryDao;

    @Test
    public void testInsert() {
        Category category = new Category(0,"test02",1,0,"01.png","hehe","111.jpg","black");
        //将数据插入表中,返回结果,1,成功
        //int i = categoryDao.insert(category);
        //将数据插入后将生成的主键返回给对象保存
        int i = categoryDao.insertUseGeneratedKeys(category);
        System.out.println(category.getCategoryId());
        assertEquals(1,i);
    }

    @Test
    public void testUpdate() {
        Category category = new Category(13,"test03",1,0,"01.png","hehe","111.jpg","black");
        //根据主键修改数据
        int i = categoryDao.updateByPrimaryKey(category);
        assertEquals(1,i);
    }

    @Test
    public void testDelete() {
        //根据主键删除数据
        int i = categoryDao.deleteByPrimaryKey(13);
        assertEquals(1,i);

    }

    @Test
    public void testSelect1() {
        //查询所有
        List<Category> categories = categoryDao.selectAll();
        for (Category category : categories) {
            System.out.println(category);
        }
    }

    @Test
    public void testSelect2() {
    //    根据主键查询
        Category category = categoryDao.selectByPrimaryKey(13);
        System.out.println(category);
    }

    @Test
    public void testSelect3() {
    //    条件查询
    //    1.创建一个example封装,类别category查询条件
        Example example = new Example(Category.class);
        Example.Criteria criteria = example.createCriteria();
        //criteria.andEqualTo("categoryLevel", 1);
        criteria.andLike("categoryName", "%干%");
        List<Category> categories = categoryDao.selectByExample(example);
        for (Category category : categories) {
            System.out.println(category);
        }
    }

    @Test
    public void testSelect4() {
    //    分页查询
        int pageNum = 2;
        int pageSize= 5;
        int start =(pageNum-1)*pageSize;
        RowBounds rowBounds = new RowBounds(start,pageSize);
        List<Category> categories = categoryDao.selectByRowBounds(new Category(), rowBounds);
        for (Category category : categories) {
            System.out.println(category);
        }
    //    查询总记录数
        int i = categoryDao.selectCount(new Category());
        System.out.println(i);
    }

    @Test
    public void testSelect5() {
        //准备条件
        Example example = new Example(Category.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("categoryLevel", 1);
        //准备分页条件
        int pageNum = 1;
        int pageSize= 3;
        int start =(pageNum-1)*pageSize;
        RowBounds rowBounds = new RowBounds(start,pageSize);
        //条件分页显示
        List<Category> categories = categoryDao.selectByExampleAndRowBounds(example, rowBounds);
        for (Category category : categories) {
            System.out.println(category);
        }
    //    查询满足条件的记录数
        int i = categoryDao.selectCountByExample(example);
        System.out.println(i);
    }
}

2.2 MybatisPlus使用教程

2.2.1 在mapper子项目中添加依赖

在创建mapper子项目的时候已经添加了mybatis-plus-boot-starter,所以这里不需要额外添加依赖

2.2.2 配置application.yml

当时用mysql8时,需要mysql的驱动同:com.mysql.cj.jdbc.Driver,同时需要增加时区设置:serverTimezone=GMT%2b8

datasource:
    druid:
      #数据库连接驱动
      driver-class-name: com.mysql.cj.jdbc.Driver
      #数据库连接地址
      url: jdbc:mysql://localhost:3306/mymall?serverTimezone=GMT%2b8&useSSL=false&allowPublicKeyRetrieval=true
      #数据库连接用户名和密码
      username: root
      password: 123456

2.2.3 配置@MapperScan

package com.mymall;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.oas.annotations.EnableOpenApi;

@SpringBootApplication
@MapperScan("com.mymall.dao")
@EnableOpenApi
public class ApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiApplication.class, args);
    }
}

2.2.4 编写实体类Users

package com.mymall.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Users {
    Integer userId;
    String username;
    String password;
}

2.2.5 编写Mapper.java继承模版BaseMapper

package com.mymall.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mymall.entity.Users;
import org.springframework.stereotype.Repository;

@Repository
public interface UsersMapper extends BaseMapper<Users> {
}

2.2.6 测试是否能够正常调用MP

package com.mymall;

import com.mymall.entity.Users;
import com.mymall.dao.UsersMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
class ApiApplicationTests {
    @Autowired
    private UsersMapper usersMapper;

    @Test
    void contextLoads() {
        List<Users> users = usersMapper.selectList(null);
        for (Users user : users) {
            System.out.println(user);
        }
        System.out.println(users);
    }

}

测试成功!

2.2.7 配置日志

mybatis-plus:
  configuration:
#    配置日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

显示效果

2.2.8 CRUD扩展

2.2.8.1 数据库插入的id的默认值:全局的唯一id
  • 生成策略:分布式系统唯一ID生成方案汇总 - nick hao - 博客园 (cnblogs.com)

  • 雪花算法:snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake。雪花算法支持的TPS可以达到419万左右(2^22*1000)。

  • 注意:使用雪花或者uuid的时候在数据中的字段不能是自增,不然不会生效.

  • 设置要求:

    • 主键需要设置成自增

    • 实体类字段上添加MP注解@TableId(type = IdType.ASSIGN_ID)

    • IdType类型源码

      package com.baomidou.mybatisplus.annotation;
      
      import lombok.Getter;
      
      /**
       * 生成ID类型枚举类
       *
       * @author hubin
       * @since 2015-11-10
       */
      @Getter
      public enum IdType {
          /**
           * 数据库ID自增
           * <p>该类型请确保数据库设置了 ID自增 否则无效</p>
           */
          AUTO(0),
          /**
           * 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
           */
          NONE(1),
          /**
           * 用户输入ID
           * <p>该类型可以通过自己注册自动填充插件进行填充</p>
           */
          INPUT(2),
      /* 以下2种类型、只有当插入对象ID 为空,才自动填充。 */
      /**
       * 分配ID (主键类型为number或string),
       * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
       *
       * @since 3.3.0
       */
      ASSIGN_ID(3),
      /**
       * 分配UUID (主键类型为 string)
       * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
       */
      ASSIGN_UUID(4),
      
      private final int key;
      
      IdType(int key) {
          this.key = key;
      }
      }
  • Insert自动生成id

    @Test//测试插入
    public void insertTest(){
        User user = new User();
        user.setName("wsk");
        user.setEmail("2803708553@qq.com");
        Integer result = userMapper.insert(user); //会帮我们自动生成id
        System.out.println(result); //受影响的行数
        System.out.println(user); //通过日志发现id会自动回填
    }

2.2.8.2 自动填充

创建时间、更改时间! 这些操作一般都是自动化完成,我们不希望手动更新

阿里巴巴开发手册︰几乎所有的表都要配置 gmt_create、gmt_modified !而且需要自动化

  • 方式一:数据库级别(工作中不允许修改数据库级别)

    1、在表中增加字段:create_time,update_time

    -- auto-generated definition
    create table mp_user
    (
        user_id     bigint auto_increment comment '用户id'  primary key,
        username    varchar(32)                        not null comment '用户名',
        password    varchar(32)                        not null comment '密码',
        create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
        update_time datetime default CURRENT_TIMESTAMP not null comment '更新时间'
    );

    2、更新实体类中的成员变量

    package com.mymall.entity;
    
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableId;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.util.Date;
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class MpUser {
        @TableId(type = IdType.ASSIGN_ID)
        Long userId;
        String username;
        String password;
        Date createTime;
        Date updateTime;
    }

    3、再次运行之前插入或更新一条数据的代码显示成功自动插入时间

  • 方式二:代码级别

    1、删除数据库的默认值,更新操作!

    2、实体类字段属性上需要增加注解

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class MpUser {
        @TableId(type = IdType.ASSIGN_ID)
        Long userId;
        String username;
        String password;
        //插入时注解
        @TableField(fill = FieldFill.INSERT)
        Date createTime;
        //更新时注解
        @TableField(fill = FieldFill.UPDATE)
        Date updateTime;
    }

    注解详情

    package com.baomidou.mybatisplus.annotation;
    /**
     * 字段填充策略枚举类
     * <p>
     * 判断注入的 insert 和 update 的 sql 脚本是否在对应情况下忽略掉字段的 if 标签生成
     * <if test="...">......</if>
     * 判断优先级比 {@link FieldStrategy} 高
     * </p>
     * @author hubin
     * @since 2017-06-27
     */
    public enum FieldFill {
        /**
         * 默认不处理
         */
        DEFAULT,
        /**
         * 插入时填充字段
         */
        INSERT,
        /**
         * 更新时填充字段
         */
        UPDATE,
        /**
         * 插入和更新时填充字段
         */
        INSERT_UPDATE
    }

    3、编写处理器来处理这个注解即可!

    @Slf4j
    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
        // 或者
        this.strictInsertFill(metaObject, "createTime", () -&gt; LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
        // 或者
        this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
    }
    
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)
        // 或者
        this.strictUpdateFill(metaObject, "updateTime", () -&gt; LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
        // 或者
        this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
    }
    }

    注意事项:

    • 填充原理是直接给entity的属性设置值!!!
    • 注解则是指定该属性在对应情况下必有值,如果无值则入库会是null
    • MetaObjectHandler提供的默认方法的策略均为:如果属性有值则不覆盖,如果填充值为null则不填充
    • 字段必须声明TableField注解,属性fill选择对应策略,该声明告知Mybatis-Plus需要预留注入SQL字段
    • 填充处理器MyMetaObjectHandler在 Spring Boot 中需要声明@Component@Bean注入
    • 要想根据注解FieldFill.xxx字段名以及字段类型来区分必须使用父类的strictInsertFill或者strictUpdateFill方法
    • 不需要根据任何来区分可以使用父类的fillStrategy方法

    4.测试运行插入或更新语句

2.2.9 乐观锁

在面试过程中经常被问到乐观锁/悲观锁,这个其实很简单

乐观锁:顾名思义十分乐观,他总是认为不会出现问题,无论干什么都不上锁!如果出现了问题,再次更新值测试

悲观锁:顾名思义十分悲观,他总是认为出现问题,无论干什么都会上锁!再去操作!

当要更新一条记录的时候,希望这条记录没有被别人更新

乐观锁实现方式:

  • 取出记录时,获取当前version

  • 更新时,带上这个version

  • 执行更新时,set version = newVersion where version = oldVersion

  • 如果version不对,就更新失败

    乐观锁:先查询,获得版本号
    -- A
    update user set name = "wsk",version = version+1
    where id = 1 and version = 1
    -- B  (B线程抢先完成,此时version=2,会导致A线程修改失败!)
    update user set name = "wsk",version = version+1
    where id = 1 and version = 1
  1. 更新数据库,添加version字段

    -- auto-generated definition
    create table mp_user
    (
        user_id     bigint auto_increment comment '用户id' primary key,
        username    varchar(32)                        not null comment '用户名',
        password    varchar(32)                        not null comment '密码',
        version     int                                null comment '乐观锁',
        create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
        update_time datetime default CURRENT_TIMESTAMP not null comment '更新时间'
    );
  2. 实体类添加加对应的字段和注解

     @Version
    Integer version;
  3. 注册组件

    //扫描mapper文件夹
    @MapperScan("com.mymall.dao")//交给mybatis做的,可以让这个配置类做扫描
    @EnableTransactionManagement//自动管理事务
    @Configuration//配置类
    public class MyBatisPlusConfig {
        //注册乐观锁插件
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor() {
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
            return interceptor;
        }
    }
  4. 测试

    • 测试乐观锁成功

      @Test
       void testOptimisticLocker1() {
              //1、查询用户信息
              MpUser mpUser = mpUserMapper.selectById(1L);
              //2、修改用户信息
              mpUser.setUsername("qq");
              //3、执行更新操作
              int update = mpUserMapper.updateById(mpUser);
              System.out.println(mpUser);
          }
    • 模拟多线程测试乐观锁插入数据失败

       @Test
      void testOptimisticLocker2() {
          //线程1
          MpUser mpUser = mpUserMapper.selectById(1L);
          mpUser.setUsername("aa");
          mpUser.setPassword("666666");
          //模拟另外一个线程执行了插队操作
          MpUser mpUser1 = mpUserMapper.selectById(1L);
          mpUser1.setUsername("bb");
          mpUser1.setPassword("12344444");
          mpUserMapper.updateById(mpUser1);
          //自旋锁来多次尝试提交!
          mpUserMapper.updateById(mpUser);//如果没有乐观锁就会覆盖插队线程的值
      }

2.2.10 查询

  • 通过id查询单个用户

    @Test
    public void testSelectById(){
        User user = userMapper.selectById(1L);
        System.out.println(user);
    }
  • 通过id查询多个用户

    @Test
    public void testSelectBatchIds(){
        List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L));
        users.forEach(System.out::println);
    }
  • 条件查询 通过map封装

    @Test
    public void testMap(){
        HashMap<String, Object> map = new HashMap<>();
        //自定义要查询的
        map.put("name","www");
        map.put("age",18);
        List<User> users = userMapper.selectByMap(map);
        users.forEach(System.out::println);
    }
  • 分页查询

    分页在网站的使用十分之多!

    1、原始的limit分页

    2、PageHelper第三方插件

    3、MybatisPlus其实也内置了分页插件!

    使用步骤:

2.2.11 删除

  • 基本删除操作

    @Test
    public void testDeleteById(){
        userMapper.deleteById(1);
    }
    @Test
    public void testDeleteBatchIds(){
      userMapper.deleteBatchIds(Arrays.asList(1,2));
    }
    @Test
    public void testD(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("age","18");
        map.put("name","lol");
        userMapper.deleteByMap(map);
    }
  • 逻辑删除

    物理删除:从数据库中直接删除

    逻辑删除:在数据库中没有被删除,而是通过一个变量来使他失效! deleted=0 ==> deleted=1

    管理员可以查看被删除的记录!防止数据的丢失,类似于回收站!

    实现步骤:

查看数据库,发现记录还在,deleted变为1

再次测试查询被删除的用户,发现查询为空

2.2.12 性能分析插件(新版本已经移除)

我们在平时的开发中,会遇到一些慢Sql。测试、druid···

MybatisPlus也提供了性能分析插件,如果超过这个时间就停止运行!

2.2.13 条件构造器wrapper

警告:

不支持以及不赞成在 RPC 调用中把 Wrapper 进行传输

  1. 常用api请到网页中查看条件构造器 | MyBatis-Plus (baomidou.com)

  2. 使用示例

    • 查询不为空且比某值大的数据

          @Test
        void testWrapper() {
          //参数是一个wrapper ,条件构造器,和刚才的map对比学习!
          //查询username不为空,password不为空,user_id大于18的用户
          //注意这里的column参数一定是字段名,不是实体类中的成员变量
          QueryWrapper<MpUser> wrapper = new QueryWrapper<>();
          wrapper
                  .isNotNull("username")
                  .isNotNull("password")
                  .ge("user_id",2);
          List<MpUser> userList = mpUserMapper.selectList(wrapper);
          userList.forEach(System.out::println);
      }
    • 查询某字段值为确定值的数据,返回结果有一个和多个的可能

       @Test
      public void testWrapper2() {
          //查询name=wsk的用户
          QueryWrapper<MpUser> wrapper = new QueryWrapper<>();
          wrapper.eq("username","MPInsertTest1");
          //查询一个数据selectOne,若查询出多个会报错
          //Expected one result (or null) to be returned by selectOne(), but found: *
          //若出现多个结果使用list或map
          MpUser mpUser = mpUserMapper.selectOne(wrapper);//查询一个数据,若出现多个结果使用list或map
          System.out.println(mpUser);
          List<MpUser> mpUsers = mpUserMapper.selectList(wrapper);
      }
    • 查询字段值范围在某个区间的数据

      @Test
      public void testWrapper3() {
          //查询age在10-20之间的用户的用户数量
          QueryWrapper<User> wrapper = new QueryWrapper<>();
          wrapper.between("age", 10, 20);//区间
          Integer count = userMapper.selectCount(wrapper);//输出查询的数量selectCount
          System.out.println(count);
      }
    • 模糊查询,notLike,likeRight,likeLeft

      @Test
      public void testWrapper4() {
          //模糊查询
          QueryWrapper<User> wrapper = new QueryWrapper<>();
          wrapper
              .notLike("name","s")
              .likeRight("email","t");//qq%  左和右?
          List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
          maps.forEach(System.out::println);
      }
    • 子查询

      @Test
      public void testWrapper5() {
          // SELECT id,name,age,email,version,deleted,create_time,update_time
          //FROM user
          //WHERE deleted=0 AND id IN
          //(select id from user where id<5)
          QueryWrapper<User> wrapper = new QueryWrapper<>();
          //id 在子查询中查出来
          wrapper.inSql("id","select id from user where id<5");
          List<Object> objects = userMapper.selectObjs(wrapper);
          objects.forEach(System.out::println);
      }
    • 通过id进行排序

      @Test
      public void testWrapper6() {
          QueryWrapper<User> wrapper = new QueryWrapper<>();
          //通过id进行降序排序
          wrapper.orderByDesc("id");
          List<User> userList = userMapper.selectList(wrapper);
          userList.forEach(System.out::println);
      }

2.2.14 代码生成器,MP逆向工程

  • 添加依赖

    MyBatis-Plus 从 3.0.3 之后移除了代码生成器与模板引擎的默认依赖,需要手动添加生成器和模版引擎依赖

    这里尝试使用3.5.1版本(以下教程仅适用 3.5.1 以上版本,对历史版本的不兼容)

    // 注意!!当前包未传递依赖 mp 包,需要自己引入
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
        <version>Latest Version</version>
    </dependency>

    添加 模板引擎 依赖,MyBatis-Plus 支持 Velocity(默认)、Freemarker、Beetl,用户可以选择自己熟悉的模板引擎,如果都不满足您的要求,可以采用自定义模板引擎。这里按照官方文档选择了freemarker

    Velocity(默认):
    <dependency>
        <groupId>org.apache.velocity</groupId>
        <artifactId>velocity-engine-core</artifactId>
        <version>latest-velocity-version</version>
    </dependency>
    
    Freemarker:
    <!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
    <dependency>
        <groupId>org.freemarker</groupId>
        <artifactId>freemarker</artifactId>
        <version>2.3.31</version>
    </dependency>
    
    Beetl:
    <dependency>
        <groupId>com.ibeetl</groupId>
        <artifactId>beetl</artifactId>
        <version>latest-beetl-version</version>
    </dependency>
  • mpper子项目中创建代码生成器配置类

    package com.mymall;
    
    import com.baomidou.mybatisplus.generator.FastAutoGenerator;
    import com.baomidou.mybatisplus.generator.config.OutputFile;
    import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
    
    import java.util.Collections;
    
    /**
     * @author 18230
     * @version 1.0.0
     * @ClassName CodeGenerator.java
     * @Description
     * @createTime 2021年09月30日 22:37:00
     */
    public class CodeGenerator {
        public static void main(String[] args) {
            /*
             *  url    jdbc路径  jdbc:mysql://127.0.0.1:3306/mybatis-plus
             *  username    数据库账号   root
             *  password    数据库密码   123456
             */
            FastAutoGenerator.create("jdbc:mysql://localhost:3306/mymall?serverTimezone=GMT%2b8&useSSL=false&allowPublicKeyRetrieval=true",
                    "root", "123456")
                    .globalConfig(builder -> {
                        builder.author("QC") // 设置作者
                                .enableSwagger() // 开启 swagger 模式
                                .fileOverride() // 覆盖已生成文件
                                //禁止打开目录
                                .disableOpenDir()
                                // 指定输出目录,这里使用绝对路径
                                .outputDir("F:\\JavaProjcet\\MyMallPractice2\\mapper\\src\\main\\java");
                        //如果使用/src/main/java会在根目录下创建文件,也就是没成功
                        //源码:this.outputDir = System.getProperty("os.name").toLowerCase().contains("windows") ? "D://" : "/tmp";
                        //System.getProperty("os.name")获取操作系统名称,如果名称包含windows就"D://"否则就"/tmp"
                        //判断是windows系统,否则就是linux系统,所以还是只能使用绝对地址
                        //.outputDir("classpath:/src/main/java");
                    })
                    .packageConfig(builder -> {
                        builder.parent("com.mymall") // 设置父包名
                                //.moduleName("mapper") // 设置父包模块名,也就是在父包名中添加一个包
                                //但是这里设置相对路径成功了一次,但后面还是生成了根目录,所以还是使用绝对路径
                                //.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "/src/main/resources/mapper")); // 设置mapperXml生成路径
                                .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "F:\\JavaProjcet\\MyMallPractice2\\mapper\\src\\main\\resources\\mapper")); // 设置mapperXml生成路径
                    })
                    .strategyConfig(builder -> {
                        builder.addInclude("users") // 设置需要生成的表名
                                .addInclude("category")
                                .addInclude("index_img")
                                .addInclude("mp_user")
                                .addInclude("product")
                                .addInclude("product_sku")
                                .addInclude("product_img")
                                .addInclude("orders")
                                .addInclude("product_comments")
                                .addInclude("product_params")
                                //.setEntityLombokModel(true)    //开启lombook支持,新版本已经没有了
                                .addTablePrefix("mp_"); // 设置过滤表前缀,也就是将前缀去掉,只保留后面对的内容作为实体名
                    })
                    .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                    .execute();
        }
    }

    生成成功

3.1 数据库url中的SSL问题

解决办法:将gerneratorConfig.xml中的url改成useSSL=false

connectionURL="jdbc:mysql://localhost:3306/mymall?useSSL=false&amp;serverTimezone=GMT%2b8"&amp;useUnicode=true&amp;characterEncoding=utf-8

3.2 插件没找到

解决:调整mybatis-generator-maven-plugin版本

        <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.5</version>

3.3 将数据库中的其他表都生成了

解决:在gerneratorConfig.xml只添加需要生成的表名

<table tableName="category" />

3.4 users表生成了其他表

解决办法:

  1. 修改我的表名称

  2. 想办法不让MySql保留表影响到我的逆向工程

    要保证只生成自己指定的 database 中的 User 表,首先要在 中的 connectionURL 中指定数据库的实例名,然后在 中添加相关配置信息,即 ,即可保证只生成自己需要的 User 类。

    配置如下:

    <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                            connectionURL="jdbc:mysql://localhost:3306/xxx"
                            userId="xxx"
                            password="xxx">
                <property name="nullCatalogMeansCurrent" value="true"/>
            </jdbcConnection>