Mybatis逆向工程和新版本MybatisPlus3.4逆向工程的使用

QCTech發表於2021-10-01

Mybatis和MybatisPlus3.4的使用

1 RESTFUL

前後端風你開發的專案中,前後端之間是介面進行請求和響應,後端向前端提供請求時就要對外暴露一個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對應唯一資源

    • @GetMapping 查詢
    • @PutMapping 修改
    • @PostMapping 新增
    • @DeleteMapping 刪除

    根據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協議與後端介面互動

2 逆向工程

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;
image-20210925153510152

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>

同步成功後會出現

image-20210925154311915

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介面

      image-20210925155522704
      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 執行逆向

image-20210925161111865

2.1.5 生成成功

image-20210925163516160
  • 將生成的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

顯示效果

image-20211001090948807

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會自動回填
    }
    
image-20211001103514119
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、再次執行之前插入或更新一條資料的程式碼顯示成功自動插入時間

    image-20211001112149597

  • 方式二:程式碼級別

    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", () -> 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", () -> 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其實也內建了分頁外掛!

    使用步驟:

    1. 配置攔截器元件

      package com.mymall.config;
      
      import com.baomidou.mybatisplus.annotation.DbType;
      import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
      import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
      import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
      import org.mybatis.spring.annotation.MapperScan;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.transaction.annotation.EnableTransactionManagement;
      @Configuration
      @MapperScan("com.mymall.dao")
      @EnableTransactionManagement
      public class MybatisPlusConfig {
          @Bean
          public MybatisPlusInterceptor mybatisPlusInterceptor() {
              MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
              //新增樂觀鎖
              interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
              //新的分頁外掛,一緩和二緩遵循mybatis的規則,
              //需要設定 MybatisConfiguration#useDeprecatedExecutor = false
              // 避免快取出現問題(該屬性會在舊外掛移除後一同移除)
         //也可以不傳DbType.MYSQL,為空時會通過URL判斷資料庫型別     
              interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
              return interceptor;
          }
          @Bean
          public ConfigurationCustomizer configurationCustomizer() {
              return configuration -> configuration.setUseDeprecatedExecutor(false);
          }
      }
      
      
    2. 測試分頁查詢

      @Test
      public void testPage(){
          //引數一current:當前頁   引數二size:頁面大小
          //使用了分頁外掛之後,所有的分頁操作都變得簡單了
          Page<User> page = new Page<>(2,5);
      	//第二個引數是高階查詢,暫設為null  
          userMapper.selectPage(page,null);
          page.getRecords().forEach(System.out::println);
          System.out.println("總頁數==>"+page.getTotal());
      }
      

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

    管理員可以檢視被刪除的記錄!防止資料的丟失,類似於回收站!

    實現步驟:

    1. 資料庫中新增deleted欄位

       deleted     int      default 0                 null comment '邏輯刪除:0顯示1刪除',
      
    2. 新增配置application.yml

      mybatis-plus:
        global-config:
          db-config:
            logic-delete-field: deleted  # 全域性邏輯刪除的實體欄位名(since 3.3.0,配置後可以忽略不配置步驟2)
            logic-delete-value: 1 # 邏輯已刪除值(預設為 1)
            logic-not-delete-value: 0 # 邏輯未刪除值(預設為 0)
      
    3. 在實體類上新增deleted欄位並加上@TableLogic註解(上面配置了application.yml後可以不新增註解,但是deleted欄位一定要新增)

      @TableLogic
      private Integer deleted;
      
    4. 測試刪除一條資料後查詢是否資料還存在,測試結果,只是deleted設定為了1

      @Test
      void testLogicDelete() {
          //刪除id為2的資料
        int i = mpUserMapper.deleteById(2);
        MpUser mpUser = mpUserMapper.selectById(2);
        System.out.println(mpUser);
      }
      

在這裡插入圖片描述

檢視資料庫,發現記錄還在,deleted變為1

在這裡插入圖片描述
再次測試查詢被刪除的使用者,發現查詢為空
在這裡插入圖片描述

2.2.12 效能分析外掛(新版本已經移除)

我們在平時的開發中,會遇到一些慢Sql。測試、druid···

MybatisPlus也提供了效能分析外掛,如果超過這個時間就停止執行!

2.2.13 條件構造器wrapper

警告:

不支援以及不贊成在 RPC 呼叫中把 Wrapper 進行傳輸

  1. wrapper 很重
  2. 傳輸 wrapper 可以類比為你的 controller 用 map 接收值(開發一時爽,維護火葬場)
  3. 正確的 RPC 呼叫姿勢是寫一個 DTO 進行傳輸,被呼叫方再根據 DTO 執行相應的操作
  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();
        }
    }
    
    

    生成成功

image-20211001170639766image-20211001170639766image-20211001170639766image-20211001170639766image-20211001170639766image-20211001170639766

3 常見問題解決辦法

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表生成了其他表

image-20210906124200886

解決辦法:

  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>
    

相關文章