更便捷的Mybatis增強插件——EasyMybatis


easy-mybatis是一個對Mybatis的增強框架(插件)。在Spring集成Mybatis的基礎上,將項目開發中對數據庫的常用操作統一化。使用本框架可以很便捷的對數據庫進行操作,提高開發效率,減少機械行為。

項目主頁https://mybatis.zuoyu.top

API文檔地址https://mybatis.zuoyu.top/doc/index.html

GitHub地址https://github.com/zuoyuip/easy-mybatis

Gitee地址https://gitee.com/zuoyuip/easy-mybatis

Maven依賴引入


<!-- https://mvnrepository.com/artifact/top.zuoyu.mybatis/easy-mybatis-spring-boot-starter -->
<dependency>
    <groupId>top.zuoyu.mybatis</groupId>
    <artifactId>easy-mybatis-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

Gradle依賴引入


// https://mvnrepository.com/artifact/top.zuoyu.mybatis/easy-mybatis-spring-boot-starter
implementation 'top.zuoyu.mybatis:easy-mybatis-spring-boot-starter:1.0.0'


框架初衷

這個框架的初衷是,減少Java程序員千篇一律的數據庫操作。

對於開發人員來說:

  • 精力應該花費在業務邏輯上,而非機械式的“技術”上。
  • 項目中減少無關痛癢的代碼,從抽象的角度看實現。
  • 各司其職,各勞其力,追求項目角度的服務流水線。

服務分離的時代

如今已很難看到單體架構的項目(感興趣的可以查看我對架構演變的描述《淺談微服務》),目前的項目大都是通過RESTfulMQSocket的方式(協議)進行數據傳輸。

這讓我開始質疑傳統JavaWeb項目中的數據庫操作模式——即Model(DTO)存在的意義。理論上,數據庫設計是不可能完全遵循視圖模型的,這就導致“正確”的做法是在項目中引入VO,由多個DTO來組裝。

那么,為什么不能用靈活的Map來替代呢?

對一個Map的方法進行拓展,增加其對Json的解析能力,那么是不是就可以擺脫POJO的各種麻煩組裝。

思考框架設計

我在思考如何設計這個框架的時候,被需要考慮的方方面面給阻擋住了。

因為一個數據庫框架需要考慮的東西實在太多了,比如:

  1. 事務機制
  2. 類型轉換
  3. 會話管理

···

思來想去,發現自己方向跑偏了,我只是希望統一數據庫操作的接口 + 擺脫Model,沒必要重新平地起牆,完全可以在一個現有的框架基礎上進行封裝。

那么,對這個現有框架的選擇就尤為重要了。

現有框架的選擇

目前Java中主流的數據庫操作框架:

  • Spring JDBC
  • Spring Data JPA
  • Mybatis
  • Hibernate

選擇現有框架有一個原則——“統一數據庫操作的接口 + 擺脫Model”是對該框架的加強,而非變異;不能因為“統一數據庫操作的接口 + 擺脫Model”而無法使用原框架的部分功能。

擺脫Model”這個特點,首先就要排除重度ORM框架,也就是支持JPA操作的數據庫——Spring Data JPAHibernate;原因很簡單,這兩個框架的強大之處恰恰就在它完全面向Model操作。

剩下的就只有兩個框架了,Spring JDBCMybatis。其中,Spring JDBC留給了開發人員大量的可操作空間,更加自由,但恰恰是這種自由使得它更加繁瑣。而Mybatis是一個輕量ORM框架,准確來說Mybatis不能稱為ORM框架,因為它並不是面向Model操作數據庫,僅僅是將數據庫字段與Model字段互相賦值,並沒有做到ORM定義的關系映射。

抉擇

由以上各框架的特點,結合國內Java語言中數據庫操作框架的熱度,毫無疑問的選擇了Mybatis

考慮到SpringBootMybatis優秀的支持級別,我決定基於mybatis-spring-boot-starter開發這款框架,准備來說應該稱其為“插件”。

框架特性

  • 更便捷

摒棄傳統mybatismodelxmldao數據庫操作模式,避繁就簡,快速開發。

  • 更高效

采用預編譯SQL,拒絕運行期間反射生成SQL,性能更高效。

  • 無侵入

只是對Mybatis-Spring的增強插件,對已有工程不做任何修改,仍可使用原生框架的功能,僅僅是簡化了開發階段對數據庫的操作。

  • 統一操作接口

對數據庫的所有操作共用一個接口,降低使用門檻,輕松操作數據庫。

  • 統一操作對象

使用JsonObject為數據對象,提供一系列操作方法,方便從持久化對象組裝為視圖對象。

  • 易上手

整個框架只提供了一個接口、一個注解、兩個對象,僅僅一行配置便可完成對數據庫進行常用操作。

  • ...

安利

  • mybatis-spring-boot環境下,使用該框架(插件),可以減少傳統Mybatis使用中對modelxmldao的機械式開發。

  • 所有的數據庫操作均使用MapperRepository接口,通過注解@Magic("xxx")標記接口的數據表歸屬,即可直接使用。

  • 該框架(插件)不妨礙同時使用傳統Mybatismodelxmldao的數據庫開發方式。

快速上手

安裝

  • 安裝mybatis-spring-boot環境

mybatis-spring-boot的Maven依賴

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>${spring-boot.version}</version>
        <relativePath/>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis-spring-boot.version}</version>
        </dependency>
    </dependencies>

mybatis-spring-boot的Gradle依賴

    plugins {
    id 'org.springframework.boot' version '${springBootVersion}'
    id 'io.spring.dependency-management' version '${springManagementVersion}'
    id 'java'
    }

    dependencies {
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:${mybatisSpringVersion}'
    }
  • 安裝本框架(插件)

Maven依賴引入


<!-- https://mvnrepository.com/artifact/top.zuoyu.mybatis/easy-mybatis-spring-boot-starter -->
<dependency>
    <groupId>top.zuoyu.mybatis</groupId>
    <artifactId>easy-mybatis-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

Gradle依賴引入


// https://mvnrepository.com/artifact/top.zuoyu.mybatis/easy-mybatis-spring-boot-starter
implementation 'top.zuoyu.mybatis:easy-mybatis-spring-boot-starter:1.0.0'

配置

這里以MySQL數據庫為例,Oracle數據庫配置請參考配置說明

  1. 配置spring-boot-jdbc數據庫

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://172.0.0.1:3306/xxxx
    username: xxxx
    password: xxxx

關於springBoot的配置,這里不多贅述,更多移步springBoot官網

  1. 配置easy-mybatis支持的表名(例子)

easy-mybatis:
  table-names: teacher, student

這里的table-names配置,表示需要easy-mybatis框架支持的數據表名,多個表名使用逗號隔開。

即可使用easy-mybatis框架操作teacherstudent兩個數據表,如果需要支持其他數據表,需要在此配置

操作數據庫(例子)


@SpringBootTest
class DemoApplicationTests {

    // 表示該接口用來操作名稱為'teacher'的數據表
    @Magic("teacher")
    private MapperRepository teacherRepository;

    // 表示該接口用來操作名稱為'student'的數據表
    @Magic("student")
    private MapperRepository studentRepository;


    // 查詢teacher表下所有數據
    @Test
    void teacherTest() {
        teachertRepository.selectList().forEach(System.out::println);
    }

    // 查詢student表下符合特定條件的數據
    @Test
    void studentTest() {
        studentRepository.selectListByExample(
          new JsonObject().put("birthday", "2009/12/12 12:12:12")
          ).forEach(System.out::println);
    }

}

使用MapperRepository接口對數據庫進行操作,需要使用@Magic("表名稱")標記該接口的數據表歸屬。

在本例中,@Magic("teacher")表示該MapperRepository"teacher"數據表的操作接口,可以通過teacherRepository調用一系列方法完成對"teacher"數據表的操作。

配置說明

該框架(插件)的全部相關配置


#easy-mybatis的相關配置
easy-mybatis:
  #表示需要載入的數據庫表
  table-names: SYS_USER, SYS_DEPT
  #默認的日期字符串格式
  data-format: yyyy-MM-dd HH:mm:ss
  #當數據庫為Oracle時,數據表對應的主鍵SEQ名稱
  sequences:
    SYS_USER: SEQ_SYS_USER,
    SYS_DEPT: SEQ_SYS_DEPT
  #當數據庫為Oracle時,數據表的日期類型字段格式
  oracle-date-format: yyyy-mm-dd hh24:mi:ss

詳細說明

  1. table-names

此配置表示需要載入的數據表,多個表名由逗號隔開,只有在此處配置了的數據表,才能在項目中使用@Magic("表名稱")標記MapperRepository接口的數據表歸屬。

  1. data-format

此配置表示在JsonObject中的日期存在格式,JsonObject中對日期的存儲類型為String類型,可通過此配置自定義存儲格式,默認格式為yyyy-MM-dd HH:mm:ss

注意
data-format的格式修改,請務必將oracle-date-format的格式與之匹配。

  1. sequences

此配置表示當數據庫為Oracle時,每個數據表的主鍵生成器名稱,以鍵值對的格式進行參數配置。若此處沒有對數據表的主鍵生成器進行配置,那么在做該數據表的數據插入行為時需自定義填充主鍵數值。

  1. oracle-date-format

此配置表示當數據庫為Oracle時,傳入日期格式字段時的參數格式,即TO_DATE(field, '$dateFormat')

注意
oracle-date-format的格式修改,請務必將data-format的格式與之匹配。

操作接口約定

本框架(插件)的數據庫操作唯一接口——top.zuoyu.mybatis.service.MapperRepository

  1. ByExample為結尾的方法

此類是根據JsonObject類型的參數example中鍵值對,對符合要求的數據進行操作,例如:


    // 構建example,這里指字段"birthday"為"2009/12/12 12:12:12"
    JsonObject example = new JsonObject().put("birthday", "2009/12/12 12:12:12");
    // 查詢所有字段"birthday"為"2009/12/12 12:12:12"的學生數據
    List<JsonObject> studentList = studentRepository.selectListByExample(example);

這里的selectListByExample方法就是根據參數example中鍵值對查找符合要求的數據。
同理, countByExample方法就是獲取符合參數example中鍵值對的數據總量...

  1. By為結尾的方法

此類是根據String類型的參數suffixSql對方法追加參數中SQL語句的內容,例如:


    // 查找"is_delete"字段為0的數據,並以年齡進行排序
    List<JsonObject> teachers = teacherRepository.selectListBy("WHERE is_delete = 0 ORDER BY age");

    // 將"birthday"字段大於"1990/1/1 00:00:00"的數據,"salary"字段改為20000。 (也就是出生日期在1990年之前的老師,工資改為2W)
    JsonObject example = new JsonObject().put("salary", 20000);
    int count = teacherRepository.updateBy(example, "WHERE birthday < '1990/1/1 00:00:00'");

這里的selectListBy方法就是查找出符合參數中SQL語句要求的內容,updateBy方法就是將符合參數中SQL語句要求的數據修改為example定義的數值。

同理,countBy方法就是獲取符合參數中SQL語句要求的數據總量...

  1. insertinsertBatch方法

當數據庫為MySQL時,如果數據表主鍵自增,則無需定義主鍵數值;若主鍵為非自增,則需定義主鍵數值。

當數據庫為Oracle時,如果在配置中定義了數據表的主鍵生成器,則無需定義主鍵數值;反之,則需定義主鍵數值。

  • MapperRepository提供了豐富的方法,以方便對數據庫的CURD操作,更多關於MapperRepository的使用說明請移步MapperRepository說明MapperRepository的API文檔

操作對象約定

數據庫接口的操作對象主要為JsonObject類型和JsonArray類型。

  1. 其中top.zuoyu.mybatis.json.JsonObject為鍵值對容器,以鍵值的形式裝載,數據結構為:

{
    'field1' : 'value1',
    'field2' : 'value2',
    'field3' : 'value3',
    ...
}

鍵的類型為java.lang.String類型,值的類型可以為任何類型。

JsonObject提供了豐富的方法,以方便操作鍵值對和類型轉換,更多關於JsonObject的使用說明請移步JsonObject說明JsonObject的API文檔

  1. 其中top.zuoyu.mybatis.json.JsonArray為數組容器,以多個單位的形式裝載,數據結構為:

[
    object1,
    object2,
    object3,
    ...
]

每個單位可以為任何類型,且各單位類型允許與其他單位類型不同。

JsonArray提供了豐富的方法,以方便操作鍵值對和類型轉換,更多關於JsonArray的使用說明請移步JsonArray說明JsonArray的API文檔


具體方法可參考API文檔

MapperRepository說明

查詢所有selectList

    /**
     * 查詢所有
     *
     * @return 所有數據
     */
    List<JsonObject> selectList();

查詢符合樣例的數據selectListByExample

    /**
     * 根據已有鍵值查詢
     *
     * @param example - 已有鍵值
     * @return 符合要求的數據集合
     */
    List<JsonObject> selectListByExample(JsonObject example);

根據主鍵查詢唯一對象selectByPrimaryKey

    /**
     * 根據主鍵查詢唯一對象
     *
     * @param primaryKey - 主鍵
     * @return 唯一對象
     */
    JsonObject selectByPrimaryKey(Serializable primaryKey);

查詢符合條件的數據selectListBy

    /**
     * 查詢符合條件的數據
     *
     * @param suffixSql - 條件語句(例如:where field = xxx)
     * @return 數據數量
     */
    List<JsonObject> selectListBy(String suffixSql);

查詢特定的字段或結果selectFields

    /**
     * 查詢特定的字段或結果
     * (例如:"fieldA, fieldB, fieldC")
     * (例如:"COUNT(field)")
     *
     * @param fields - 特定的字段或結果
     * @return 特定的字段或結果
     */
    JsonArray selectFields(String fields);

根據樣例查詢特定的字段或結果selectFieldsByExample

    /**
     * 根據已有鍵值查詢特定的字段或結果
     * (例如:"fieldA, fieldB, fieldC")
     * (例如:"COUNT(field)")
     *
     * @param fields  - 特定的字段或結果
     * @param example - 已有鍵值
     * @return 特定的字段或結果
     */
    JsonArray selectFieldsByExample(String fields, JsonObject example);

根據主鍵查詢特定的字段或結果selectFieldsByPrimaryKey

    /**
     * 根據主鍵查詢特定的字段或結果
     *
     * @param fields     - 特定的字段或結果(例如:"fieldA, fieldB, fieldC")
     * @param primaryKey - 主鍵
     * @return 特定的字段或結果
     */
    JsonArray selectFieldsByPrimaryKey(String fields, Serializable primaryKey);

根據主鍵集合查詢特定的字段或結果selectFieldsByPrimaryKeys

    /**
     * 根據主鍵集合查詢特定的字段或結果
     *
     * @param fields      - 特定的字段或結果(例如:"fieldA, fieldB, fieldC")
     * @param primaryKeys - 主鍵集合
     * @return 特定的字段或結果
     */
    JsonArray selectFieldsByPrimaryKeys(String fields, Serializable[] primaryKeys);

查詢符合條件的數據selectFieldsBy

    /**
     * 查詢符合條件的數據
     *
     * @param fields    - 特定的字段或結果(例如:"fieldA, fieldB, fieldC")
     * @param suffixSql - 條件語句(例如:where field = xxx)
     * @return 特定的字段或結果
     */
    JsonArray selectFieldsBy(String fields, String suffixSql);

查詢符合條件的數據數量countBy

    /**
     * 查詢符合條件的數據數量
     *
     * @param suffixSql - 條件語句(例如:where field = xxx)
     * @return 數據數量
     */
    long countBy(String suffixSql);

查詢是否存在符合樣例的數據數量countByExample

    /**
     * 根據已有鍵值查詢是否存在符合條件的數據數量
     *
     * @param example - 已有鍵值
     * @return 數據數量
     */
    long countByExample(JsonObject example);

是否存在符合條件的數據existsBy

    /**
     * 是否存在符合條件的數據
     *
     * @param suffixSql - 條件語句(例如:where field = xxx)
     * @return 是否存在
     */
    boolean existsBy(String suffixSql);

是否存在符合樣例的數據existsByExample

    /**
     * 根據已有鍵值查詢是否存在符合條件的數據
     *
     * @param example - 已有鍵值
     * @return 是否存在
     */
    boolean existsByExample(JsonObject example);

新增對象insert

    /**
     * 新增對象
     *
     * @param jsonObject - 對象鍵值
     * @return 變動數據的數量
     */
    int insert(JsonObject jsonObject);

批量新增對象insertBatch

    /**
     * 批量新增對象
     *
     * @param jsonObjects - 對象鍵值集合
     * @return 變動數據的數量
     */
    int insertBatch(List<JsonObject> jsonObjects);

根據主鍵修改對象屬性updateByPrimaryKey

    /**
     * 根據主鍵修改對象屬性
     *
     * @param jsonObject - 包含主鍵對象鍵值
     * @return 變動數據的數量
     */
    int updateByPrimaryKey(JsonObject jsonObject);

修改特定條件的對象屬性updateBy

    /**
     * 修改特定條件的對象屬性
     *
     * @param jsonObject - 要修改的鍵值
     * @param suffixSql  - 條件語句(例如:where field = xxx)
     * @return 變動數據的數量
     */
    int updateBy(JsonObject jsonObject, String suffixSql);

批量根據主鍵修改對象屬性updateByPrimaryKeyBatch

    /**
     * 批量根據主鍵修改對象屬性
     *
     * @param jsonObjects - 對象鍵值集合
     * @return 變動數據的數量
     */
    int updateByPrimaryKeyBatch(List<JsonObject> jsonObjects);

根據主鍵刪除對象deleteByPrimaryKey

    /**
     * 根據主鍵刪除對象
     *
     * @param primaryKey - 主鍵
     * @return 變動數據的數量
     */
    int deleteByPrimaryKey(Serializable primaryKey);

刪除符合條件的數據deleteBy

    /**
     * 刪除符合條件的數據
     *
     * @param suffixSql - 條件語句(例如:where field = xxx)
     * @return 變動數據的數量
     */
    int deleteBy(String suffixSql);

批量根據主鍵刪除對象deleteByPrimaryKeys

    /**
     * 批量根據主鍵刪除對象
     *
     * @param primaryKeys - 主鍵組
     * @return 變動數據的數量
     */
    int deleteByPrimaryKeys(Serializable[] primaryKeys);

根據樣例刪除對象deleteByExample

    /**
     * 根據已有鍵值刪除對象
     *
     * @param example - 已有鍵值
     * @return 變動數據的數量
     */
    int deleteByExample(JsonObject example);

JsonObject說明

top.zuoyu.mybatis.json.JsonObject為鍵值對容器,以鍵值的形式裝載,數據結構為:

{
    'field1' : 'value1',
    'field2' : 'value2',
    'field3' : 'value3',
    ...
}

鍵的類型為java.lang.String類型,值的類型可以為任何類型。

類型轉換

內部默認已包含常規的類型轉換,默認支持以下類型:

  • java.math.BigInteger
  • java.math.BigDecimal
  • java.lang.Boolean
  • java.lang.Byte
  • java.lang.Character
  • java.util.Date
  • java.lang.Double
  • java.lang.Float
  • java.lang.Integer
  • java.lang.Long
  • java.lang.Short
  • java.lang.String

如果默認的轉換器無法滿足需求,或者需要轉換為其他類型,可通過自定義轉換器ConvertClass<T>進行實現,接口方法:

public interface ConvertClass<T> {

    /**
     * 將給定的 value 對象轉換為 T 類型
     *
     * @param value - 給定對象
     * @return 目標轉換類型的對象
     */
    T convert(Object value);

    /**
     * 將給定的 value 對象轉換為 T 類型, 否則返回  defaultValue
     *
     * @param value        - 給定對象
     * @param defaultValue - 默認值
     * @return 目標轉換類型的對象
     */
    T convert(Object value, T defaultValue);
}

示例

    @Test
    void convertTest() {
        // 使用JSON字符串初始化對象
        JsonObject student = new JsonObject(studentJSON);

        // 獲取學生的性別,如果無法轉換則返回"未知"
        String sex = student.getValue("sex", new SexConvert());

        // 獲取學生的性別,如果無法轉換則返回"未錄入"
        String sexOrDefault = student.getValue("sex", new SexConvert(), "未錄入");
    }

    static class SexConvert implements ConvertClass<String> {

        @Override
        public String convert(Object value) {
            if (value instanceof Integer) {
                if ((Integer) value == 1) {
                    return "男";
                } else if ((Integer)value == 0) {
                    return "女";
                }
            }
            return "未知";
        }

        @Override
        public String convert(Object value, String defaultValue) {
            if (value instanceof Integer) {
                if ((Integer) value == 1) {
                    return "男";
                } else if ((Integer)value == 0) {
                    return "女";
                }
            }
            return defaultValue;
        }
    }

本示例也可使用StringConvert轉換器,StringConvertConvertClass<T>的子接口。

也可以通過toClass方法,將JsonObject對象轉換為指定類型的實體類。

子接口:

  • BigDecimalConvert
  • BigIntegerConvert
  • BooleanConvert
  • ByteConvert
  • CharacterConvert
  • DateConvert
  • DoubleConvert
  • FloatConvert
  • IntegerConvert
  • LongConvert
  • ShortConvert
  • StringConvert

更多方法請移步JsonObject的API文檔

JsonArray說明

top.zuoyu.mybatis.json.JsonArray為數組容器,以多個單位的形式裝載,數據結構為:


[
    object1,
    object2,
    object3,
    ...
]

每個單位可以為任何類型,且各單位類型允許與其他單位類型不同。

類型轉換

具體使用方法與JsonObject在類型轉換操作一致,參考JsonObject的類型轉換操作


更多方法請移步JsonArray的API文檔


開源地址

項目主頁https://mybatis.zuoyu.top

API文檔地址https://mybatis.zuoyu.top/doc/index.html

GitHub地址https://github.com/zuoyuip/easy-mybatis

Gitee地址https://gitee.com/zuoyuip/easy-mybatis


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM