天天看點

探秘MyBatis-Flex:超越Mybatis-plus的優雅魅力!

作者:蘋果蘋果開花吧

MyBatis-Flex一個優雅的 MyBatis 增強架構。

更輕量

MyBatis-Flex 除了 MyBatis 本身,再無任何第三方依賴,是以會帶來更高的自主性、把控性和穩定性。在任何一個系統中,依賴越多,穩定性越差。

更靈活

MyBatis-Flex 提供了非常靈活的 QueryWrapper,支援關聯查詢、多表查詢、多主鍵、邏輯删除、樂觀鎖更新、資料填充、資料脫敏、等等....

更高的性能

MyBatis-Flex 通過獨特的架構,沒有任何 MyBatis 攔截器、在 SQL 執行的過程中,沒有任何的 SQL Parse,是以會帶來指數級的性能增長。

官網提供的和同類架構的功能對比

功能或特點 MyBatis-Flex MyBatis-Plus Fluent-MyBatis
對 entity 的基本增删改查
分頁查詢
分頁查詢之總量緩存
分頁查詢無 SQL 解析設計(更輕量,及更高性能)
多表查詢: from 多張表
多表查詢: left join、inner join 等等
多表查詢: union,union all
單主鍵配置
多種 id 生成政策
支援多主鍵、複合主鍵
字段的 typeHandler 配置
除了 MyBatis,無其他第三方依賴(更輕量)
QueryWrapper 是否支援在微服務項目下進行 RPC 傳輸 未知
邏輯删除
樂觀鎖
SQL 審計
資料填充 ✔️ (收費)
資料脫敏 ✔️ (收費)
字段權限 ✔️ (收費)
字段加密 ✔️ (收費)
字典回寫 ✔️ (收費)
Db + Row
Entity 監聽
多資料源支援 借助其他架構或收費
多資料源是否支援 Spring 的事務管理,比如 @Transactional 和 TransactionTemplate 等
多資料源是否支援 "非Spring" 項目
多租戶
動态表名
動态 Schema

官網提供的和同類架構的性能對比

  • MyBatis-Flex 的查詢單條資料的速度,大概是 MyBatis-Plus 的 5 ~ 10+ 倍。
  • MyBatis-Flex 的查詢 10 條資料的速度,大概是 MyBatis-Plus 的 5~10 倍左右。
  • Mybatis-Flex 的分頁查詢速度,大概是 Mybatis-Plus 的 5~10 倍左右。
  • Mybatis-Flex 的資料更新速度,大概是 Mybatis-Plus 的 5~10+ 倍。

亮點功能

除了Mybatis-plus帶的那些功能,Mybatis-Flex提供了多主鍵、複合主鍵功能;提供了關聯查詢;特别是關聯查詢在日常業務開發碰到的場景很多。

Mybatis-Flex提供了一對一、一對多、多對一、多對多的場景。

一對一關聯查詢 @RelationOneToOne

假設有一個賬戶,賬戶有身份證,賬戶和身份證的關系是一對一的關系,代碼如下所示:

Account.java :

kotlin複制代碼public class Account implements Serializable {

    @Id(keyType = KeyType.Auto)
    private Long id;

    private String userName;

    @RelationOneToOne(selfField = "id", targetField = "accountId")
    private IDCard idCard;

    //getter setter
}
           

IDCard.java :

kotlin複制代碼@Table(value = "tb_idcard")
public class IDCard implements Serializable {

    private Long accountId;
    private String cardNo;
    private String content;

    //getter setter
}
           

@RelationOneToOne 配置描述:

  • selfField 目前實體類的屬性
  • targetField 目标對象的關系實體類的屬性
PS: 若 selfField 是主鍵,且目前表隻有 1 個主鍵時,可以不填寫。是以,以上的配置可以簡化為 @RelationOneToOne(targetField = "accountId")

假設資料庫 5 條 Account 資料,然後進行查詢:

ini複制代碼List<Account> accounts = accountMapper.selectAllWithRelations();
System.out.println(accounts);
           

其執行的 SQL 如下:

go複制代碼SELECT `id`, `user_name`, `age` FROM `tb_account`

SELECT `account_id`, `card_no`, `content` FROM `tb_idcard`
WHERE account_id IN (1, 2, 3, 4, 5)
           

查詢列印的結果如下:

bash複制代碼[
 Account{id=1, userName='孫悟空', age=18, idCard=IDCard{accountId=1, cardNo='0001', content='内容1'}},
 Account{id=2, userName='豬八戒', age=19, idCard=IDCard{accountId=2, cardNo='0002', content='内容2'}},
 Account{id=3, userName='沙和尚', age=19, idCard=IDCard{accountId=3, cardNo='0003', content='内容3'}},
 Account{id=4, userName='六耳猕猴', age=19, idCard=IDCard{accountId=4, cardNo='0004', content='内容4'}},
 Account{id=5, userName='王麻子叔叔', age=19, idCard=IDCard{accountId=5, cardNo='0005', content='内容5'}}
 ]
           

一對多關聯查詢 @RelationOneToMany​

假設一個賬戶有很多本書籍,一本書隻能歸屬一個賬戶所有;賬戶和書籍的關系是一對多的關系,代碼如下:

Account.java :

kotlin複制代碼public class Account implements Serializable {

    @Id(keyType = KeyType.Auto)
    private Long id;

    private String userName;

    @RelationOneToMany(selfField = "id", targetField = "accountId")
    private List<Book> books;

    //getter setter
}
           

Book.java :

kotlin複制代碼@Table(value = "tb_book")
public class Book implements Serializable {

    @Id(keyType = KeyType.Auto)
    private Long id;
    private Long accountId;
    private String title;

    //getter setter
}
           

@RelationOneToMany 配置描述:

  • selfField 目前實體類的屬性
  • targetField 目标對象的關系實體類的屬性
PS: 若 selfField 是主鍵,且目前表隻有 1 個主鍵時,可以不填寫。是以,以上的配置可以簡化為 @RelationOneToOne(targetField = "accountId")

假設資料庫 5 條 Account 資料,然後進行查詢:

ini複制代碼List<Account> accounts = accountMapper.selectAllWithRelations();
System.out.println(accounts);
           

其執行的 SQL 如下:

go複制代碼SELECT `id`, `user_name`, `age` FROM `tb_account`

SELECT `id`, `account_id`, `title`, `content` FROM `tb_book`
WHERE account_id IN (1, 2, 3, 4, 5)
           

Map 映射

若 Account.books 是一個 Map,而非 List,那麼,我們需要通過配置 mapKeyField 來指定使用用個列來充當 Map 的 Key, 如下代碼所示:

java

kotlin複制代碼public class Account implements Serializable {

    @Id(keyType = KeyType.Auto)
    private Long id;

    private String userName;

    @RelationOneToMany(selfField = "id", targetField = "accountId"
        , mapKeyField = "id") //使用 Book 的 id 來填充這個 map 的 key
    private Map<Long, Book> books;

    //getter setter
}
           
多對多注解 @RelationManyToMany 也是如此。

多對一關聯查詢 @RelationManyToOne​

假設一個賬戶有很多本書籍,一本書隻能歸屬一個賬戶所有;賬戶和書籍的關系是一對多的關系,書籍和賬戶的關系為多對一的關系,代碼如下:

Account.java:

kotlin複制代碼public class Account implements Serializable {

    @Id(keyType = KeyType.Auto)
    private Long id;

    private String userName;

    //getter setter
}
           

Book.java 多對一的配置:

kotlin複制代碼@Table(value = "tb_book")
public class Book implements Serializable {

    @Id(keyType = KeyType.Auto)
    private Long id;
    private Long accountId;
    private String title;

    @RelationManyToOne(selfField = "accountId", targetField = "id")
    private Account account;

    //getter setter
}
           

@RelationManyToOne 配置描述:

  • selfField 目前實體類的屬性
  • targetField 目标對象的關系實體類的屬性
PS: 若 targetField 目标對象的是主鍵,且目标對象的表隻有 1 個主鍵時,可以不填寫。是以,以上的配置可以簡化為 @RelationManyToOne(selfField = "accountId")

多對多關聯查詢 @RelationManyToMany​

假設一個賬戶可以有多個角色,一個角色也可以有多個賬戶,他們是多對多的關系,需要通過中間件表 tb_role_mapping 來維護:

tb_role_mapping 的表結構如下:

sql複制代碼CREATE TABLE  `tb_role_mapping`
(
    `account_id`  INTEGER ,
    `role_id`  INTEGER
);
           

Account.java 多對多的配置:

kotlin複制代碼public class Account implements Serializable {

    @Id(keyType = KeyType.Auto)
    private Long id;
    private String userName;

    @RelationManyToMany(
            joinTable = "tb_role_mapping", // 中間表
            selfField = "id", joinSelfColumn = "account_id",
            targetField = "id", joinTargetColumn = "role_id"
    )
    private List<Role> roles;

    //getter setter
}
           

Role.java 多對多的配置:

kotlin複制代碼@Table(value = "tb_role")
public class Role implements Serializable {

    private Long id;
    private String name;

    //getter setter
}
           

@RelationManyToMany 配置描述:

  • selfField 目前實體類的屬性
  • targetField 目标對象的關系實體類的屬性
  • joinTable 中間表
  • joinSelfColumn 目前表和中間表的關系字段
  • joinTargetColumn 目标表和中間表的關系字段

注意:selfField 和 targetField 配置的是類的屬性名,joinSelfColumn 和 joinTargetColumn 配置的是中間表的字段名。

若 selfField 和 targetField 分别是兩張關系表的主鍵,且表隻有 1 個主鍵時,可以不填寫。是以,以上配置可以簡化如下:

kotlin複制代碼public class Account implements Serializable {

    @Id(keyType = KeyType.Auto)
    private Long id;
    private String userName;

    @RelationManyToMany(
            joinTable = "tb_role_mapping", // 中間表
            joinSelfColumn = "account_id",
            joinTargetColumn = "role_id"
    )
    private List<Role> roles;

    //getter setter
}
           

父子關系查詢​

比如在一些系統中,比如菜單會有一些父子關系,例如菜單表如下:

less複制代碼CREATE TABLE `tb_menu`
(
    `id`        INTEGER auto_increment,
    `parent_id`        INTEGER,
    `name`      VARCHAR(100)
);
           

Menu.java 定義如下:

kotlin複制代碼@Table(value = "tb_menu")
public class Menu implements Serializable {

    private Long id;

    private Long parentId;

    private String name;

    @RelationManyToOne(selfField = "parentId", targetField = "id")
    private Menu parent;

    @RelationOneToMany(selfField = "id", targetField = "parentId")
    private List<Menu> children;

    //getter setter
}
           

查詢頂級菜單:

ini複制代碼QueryWrapper qw = QueryWrapper.create();
qw.where(MENU.PARENT_ID.eq(0));

List<Menu> menus = menuMapper.selectListWithRelationsByQuery(qw);
System.out.println(JSON.toJSONString(menus));
           

SQL 執行如下:

go複制代碼SELECT `id`, `parent_id`, `name` FROM `tb_menu` WHERE `parent_id` = 0
SELECT `id`, `parent_id`, `name` FROM `tb_menu` WHERE id = 0
SELECT `id`, `parent_id`, `name` FROM `tb_menu` WHERE parent_id IN (1, 2, 3)
           

JSON 輸出内容如下:

css複制代碼[  {    "children": [      {        "id": 4,        "name": "子菜單",        "parentId": 1      },      {        "id": 5,        "name": "子菜單",        "parentId": 1      }    ],
    "id": 1,
    "name": "頂級菜單1",
    "parentId": 0
  },
  {
    "children": [],
    "id": 2,
    "name": "頂級菜單2",
    "parentId": 0
  },
  {
    "children": [
      {
        "id": 6,
        "name": "子菜單",
        "parentId": 3
      },
      {
        "id": 7,
        "name": "子菜單",
        "parentId": 3
      },
      {
        "id": 8,
        "name": "子菜單",
        "parentId": 3
      }
    ],
    "id": 3,
    "name": "頂級菜單3",
    "parentId": 0
  }
]
           

在以上的父子關系查詢中,預設的遞歸查詢深度為 3 個層級,若需要查詢指定遞歸深度,需要添加如下配置:

ini複制代碼QueryWrapper qw = QueryWrapper.create();
qw.where(MENU.PARENT_ID.eq(0));

//設定遞歸查詢深度為 10 層
RelationManager.setMaxDepth(10);
List<Menu> menus = menuMapper.selectListWithRelationsByQuery(qw);
           
RelationManager.setMaxDepth(10) 的配置,隻在目前第一次查詢有效,查詢後會清除設定。

MyBatis-Flex 邏輯删除

假設在 tb_account 表中,存在一個為 is_deleted 的字段,用來辨別該資料的邏輯删除,那麼 tb_account 表 對應的 "Account.java" 實體類應該配置如下:

kotlin複制代碼@Table("tb_account")
public class Account {

    @Column(isLogicDelete = true)
    private Boolean isDelete;
    
    //Getter Setter...
}
           

此時,當我們執行如下的删除代碼是:

ini複制代碼accountMapper.deleteById(1);
           

MyBatis 執行的 SQL 如下:

go複制代碼UPDATE `tb_account` SET `is_delete` = 1 
WHERE `id` = ? AND `is_delete` = 0
           

可以看出,當執行 deleteById 時,MyBatis 隻是進行了 update 操作,而非 delete 操作。

注意事項

當 "tb_account" 的資料被删除時( is_delete = 1 時),我們通過 MyBatis-Flex 的 selectOneById 去查找資料時,會查詢不到資料。 原因是 selectOneById 會自動添加上 is_delete = 0 條件,執行的 sql 如下:

sql複制代碼SELECT * FROM tb_account where id = ? and is_delete = 0
           

不僅僅是 selectOneById 方法會添加 is_delete = 0 條件,BaseMapper 的以下方法也都會添加該條件:

  • selectOneBy**
  • selectListBy**
  • selectCountBy**
  • paginate

同時,比如 Left Join 或者子查詢等,若 子表也設定了邏輯删除字段, 那麼子表也會添加相應的邏輯删除條件,例如:

scss複制代碼QueryWrapper query1 = QueryWrapper.create()
    .select()
    .from(ACCOUNT)
    .leftJoin(ARTICLE).as("a").on(ACCOUNT.ID.eq(ARTICLE.ACCOUNT_ID))
    .where(ACCOUNT.AGE.ge(10));
           

其執行的 SQL 如下:

go複制代碼SELECT * FROM `tb_account` 
    LEFT JOIN `tb_article` AS `a` ON `tb_account`.`id` = `a`.`account_id` 
WHERE `tb_account`.`age` >= 10 
  AND `tb_account`.`is_delete` = 0 AND `a`.`is_delete` = 0
           

自動添加上 tb_account.is_delete = 0 AND a.is_delete = 0 條件。

示例 2:

csharp複制代碼QueryWrapper query2 = QueryWrapper.create()
    .select()
    .from(ACCOUNT)
    .leftJoin(
            //子查詢
            select().from(ARTICLE).where(ARTICLE.ID.ge(100))
    ).as("a").on(
            ACCOUNT.ID.eq(raw("a.id"))
    )
    .where(ACCOUNT.AGE.ge(10));
           

其執行的 SQL 如下:

go複制代碼SELECT * FROM `tb_account` 
    LEFT JOIN (
        SELECT * FROM `tb_article` WHERE `id` >= 100 AND `is_delete` = 0
        ) AS `a` 
    ON `tb_account`.`id` = a.id 
WHERE `tb_account`.`age` >= 10 AND `tb_account`.`is_delete` = 0
           

資料脫敏

資料脫敏是什麼

随着《網絡安全法》的頒布施行,對個人隐私資料的保護已經上升到法律層面。 資料脫敏是指對某些敏感資訊通過脫敏規則進行資料的變形, 實作敏感隐私資料的可靠保護。在涉及客戶安全資料或者一些商業性敏感資料的情況下,在不違反系統規則條件下,對真實資料進行改造并提供使用, 如身份證号、手機号、卡号、客戶号等個人資訊都需要進行資料脫敏。

@ColumnMask

MyBatis-Flex 提供了 @ColumnMask() 注解,以及内置的 9 種脫敏規則,幫助開發者友善的進行資料脫敏。例如:

java

kotlin複制代碼@Table("tb_account")
public class Account {

    @Id(keyType = KeyType.Auto)
    private Long id;

    @ColumnMask(Masks.CHINESE_NAME)
    private String userName;
}
           

以上的示例中,使用了 CHINESE_NAME 的脫敏規則,其主要用于處理 "中文名字" 的場景。當我們查詢到 userName 為 張三豐 的時候,其内容自動被處理成 張**。

除此之外,MyBatis-Flex 還提供了如下的 8 中脫敏規則(共9種),友善開發者直接使用:

  • 手機号脫敏
  • 固定電話脫敏
  • 身份證号脫敏
  • 車牌号脫敏
  • 位址脫敏
  • 郵件脫敏
  • 密碼脫敏
  • 銀行卡号脫敏

自定義脫敏規則

當 Mybaits-Flex 内置的 9 中脫敏規則無法滿足要求時,我們還可以自定義脫敏規則,其步驟如下:

1、通過 MaskManager 注冊新的脫敏規則:

kotlin複制代碼MaskManager.registerMaskProcesser("自定義規則名稱"
        , data -> {
            return data;
        })
           

2、使用自定義的脫敏規則

kotlin複制代碼@Table("tb_account")
public class Account {

    @Id(keyType = KeyType.Auto)
    private Long id;

    @ColumnMask("自定義規則名稱")
    private String userName;
}
           

取消脫敏處理

在某些場景下,程式希望查詢得到的資料是原始資料,而非脫敏資料。比如要去查詢使用者的手機号,然後給使用者發送短信。又或者說,我們進入編輯頁面編輯使用者資料, 如果編輯頁面展示的是脫敏資料,然後再次點選儲存,那麼資料庫的真實資料也會被脫敏覆寫。

是以,MaskManager 提供了 execWithoutMask、skipMask、restoreMask 三個方法來處理這種場景:

推薦使用execWithoutMask方法,該方法使用了模版方法設計模式,保障跳過脫敏處理并執行相關邏輯後自動恢複脫敏處理。

execWithoutMask方法實作如下:

scss複制代碼public static <T> T execWithoutMask(Supplier<T> supplier) {
    try {
        skipMask();
        return supplier.get();
    } finally {
        restoreMask();
    }
}
           

使用方法:

ini複制代碼AccountMapper mapper = ...;
List<Account> accounts = MaskManager.execWithoutMask(mapper::selectAll);
System.out.println(accounts);
           

skipMask和restoreMask方法需配套使用,推薦使用try{...}finally{...}模式,如下例所示。 使用這兩個方法可以自主要制跳過脫敏處理和恢複脫敏處理的時機。 當跳過脫敏處理和恢複脫敏處理無法放在同一個方法中時,可以使用這兩個方法。 此時需要仔細處理代碼分支及異常,以防止跳過脫敏處理後未恢複脫敏處理,導緻安全隐患。

scss複制代碼try {
    MaskManager.skipMask();
    
    //此處查詢到的資料不會進行脫敏處理
    accountMapper.selectListByQuery(...);
} finally {
    MaskManager.restoreMask();
}
           

SQL 審計

SQL 審計是一項非常重要的工作,是企業資料安全體系的重要組成部分,通過 SQL 審計功能為資料庫請求進行全程記錄,為事後追溯溯源提供了一手的資訊,同時可以通過可以對惡意通路及時警告管理者,為防護政策優化提供資料支撐。

同時、提供 SQL 通路日志長期存儲,滿足等保合規要求。

開啟審計功能^1.0.5​

Mybaits-Flex 的 SQL 審計功能,預設是關閉的,若開啟審計功能,許添加如下配置。

arduino複制代碼AuditManager.setAuditEnable(true)
           

預設情況下,Mybaits-Flex 的審計消息(日志)隻會輸出到控制台,如下所示:

sql複制代碼>>>>>>Sql Audit: {platform='mybatis-flex', module='null', url='null', user='null', userIp='null', hostIp='192.168.3.24', query='SELECT * FROM `tb_account` WHERE `id` = ?', queryParams=[1], queryTime=1679991024523, elapsedTime=1}
>>>>>>Sql Audit: {platform='mybatis-flex', module='null', url='null', user='null', userIp='null', hostIp='192.168.3.24', query='SELECT * FROM `tb_account` WHERE `id` = ?', queryParams=[1], queryTime=1679991024854, elapsedTime=3}
>>>>>>Sql Audit: {platform='mybatis-flex', module='null', url='null', user='null', userIp='null', hostIp='192.168.3.24', query='SELECT * FROM `tb_account` WHERE `id` = ?', queryParams=[1], queryTime=1679991025100, elapsedTime=2}
           

Mybaits-Flex 消息包含了如下内容:

  • platform:平台,或者是運作的應用
  • module:應用子產品
  • url:執行這個 SQL 涉及的 URL 位址
  • user:執行這個 SQL 涉及的 平台使用者
  • userIp:執行這個 SQL 的平台使用者 IP 位址
  • hostIp:執行這個 SQL 的伺服器 IP 位址
  • query:SQL 内容
  • queryParams:SQL 參數
  • queryTime:SQL 執行的時間點(目前時間)
  • elapsedTime:SQL 執行的消耗時間(毫秒)
  • metas:其他擴充元資訊

事務管理

MyBatis-Flex 提供了一個名為 Db.tx() 的方法^1.0.6,用于進行事務管理,若使用 Spring 架構的場景下,也可使用 @Transactional 注解進行事務管理。

Db.tx() 方法定義如下:

scss複制代碼boolean tx(Supplier<Boolean> supplier);
boolean tx(Supplier<Boolean> supplier, Propagation propagation);

<T> T txWithResult(Supplier<T> supplier);
<T> T txWithResult(Supplier<T> supplier, Propagation propagation);
           

方法:

  • tx:傳回結果為 Boolean,傳回 null 或者 false 或者 抛出異常,事務復原
  • txWithResult:傳回結果由 Supplier 參數決定,隻有抛出異常時,事務復原

參數:

  • supplier:要執行的内容(代碼)
  • propagation:事務傳播屬性

事務傳播屬性 propagation 是一個枚舉類,其枚舉内容如下:

scss複制代碼//若存在目前事務,則加入目前事務,若不存在目前事務,則建立新的事務
REQUIRED(0),

//若存在目前事務,則加入目前事務,若不存在目前事務,則已非事務的方式運作
SUPPORTS(1),

//若存在目前事務,則加入目前事務,若不存在目前事務,則抛出異常
MANDATORY(2),

//始終以新事務的方式運作,若存在目前事務,則暫停(挂起)目前事務。
REQUIRES_NEW(3),

//以非事務的方式運作,若存在目前事務,則暫停(挂起)目前事務。
NOT_SUPPORTED(4),

//以非事務的方式運作,若存在目前事務,則抛出異常。
NEVER(5),

//暫時不支援
NESTED(6),
           

Db.tx() 代碼示例:

kotlin複制代碼Db.tx(() -> {

    //進行事務操作

    return true;
});
           

若 tx() 方法抛出異常,或者傳回 false,或者傳回 null,則復原事務。隻有正常傳回 true 的時候,進行事務送出。

嵌套事務

示例代碼:

kotlin複制代碼Db.tx(() -> {

    //進行事務操作

    boolean success = Db.tx(() -> {
        //另一個事務的操作
        return true;
    });


    return true;
});
           

支援無限極嵌套,預設情況下,嵌套事務直接的關系是:REQUIRED(若存在目前事務,則加入目前事務,若不存在目前事務,則建立新的事務)。

@Transactional

MyBatis-Flex 已支援 Spring 架構的 @Transactional,在使用 SpringBoot 的情況下,可以直接使用 @Transactional 進行事務管理。 同理,使用 Spring 的 TransactionTemplate 進行事務管理也是沒問題的。

注意:若項目未使用 SpringBoot,隻用到了 Spring,需要參考 MyBatis-Flex 的 FlexTransactionAutoConfiguration 進行事務配置,才能正常使用 @Transactional 注解。

特征

  • 1、支援嵌套事務
  • 2、支援多資料源
注意:在多資料源的情況下,所有資料源的資料庫請求(Connection)會執行相同的 commit 或者 rollback,但并非原子操作。例如:
csharp複制代碼@Transactional
public void doSomething(){

    try{
        DataSourceKey.use("ds1");
        Db.updateBySql("update ....");
    }finally{
        DataSourceKey.clear()
    }

    try{
        DataSourceKey.use("ds2");
        Db.updateBySql("update ...");
    }finally{
        DataSourceKey.clear()
    }

    //抛出異常
    int x = 1/0;
}
           

在以上的例子中,兩次 Db.update(...) 雖然是兩個不同的資料源,但它們都在同一個事務 @Transactional 裡,是以,當抛出異常的時候, 它們都會進行復原(rollback)。

以上提到的 并非原子操作,指的是:

假設在復原的時候,恰好其中一個資料庫出現了異常(比如 網絡問題,資料庫崩潰),此時,可能隻有一個資料庫的資料正常復原(rollback)。 但無論如何,MyBatis-Flex 都會保證在同一個 @Transactional 中的多個資料源,保持相同的 commit 或者 rollback 行為。

字段權限

字段權限,指的是在一張表中設計了許多字段,但是不同的使用者(或者角色)查詢,傳回的字段結果是不一緻的。 比如:tb_account 表中,有 user_name 和 password 字段,但是 password 字段隻允許使用者本人查詢, 或者超級管理者查詢,這種場景下,我們會用到 字段權限 的功能。

在 @Table() 注解中,有一個配置名為 onSet,用于設定這張表的 設定 監聽,這裡的 設定 監聽指的是: 當我們使用 sql 、調用某個方法去查詢資料,得到的資料内容映射到 entity 實體,mybatis 通過 setter 方法去設定 entity 的值時的監聽。

以下是示例:

step 1: 為實體類編寫一個 set 監聽器(SetListener)

typescript複制代碼public class AccountOnSetListener implements SetListener {
    @Override
    public Object onSet(Object entity, String property, Object value) {
        if (property.equals("password")){

            //去查詢目前使用者的權限
            boolean hasPasswordPermission = getPermission();
            
            //若沒有權限,則把資料庫查詢到的 password 内容修改為 null
            if (!hasPasswordPermission){
                value = null;
            }
        }
        return value;
    }
}
           

step 2: 為實體類配置 onSet 監聽

kotlin複制代碼@Table(value = "tb_account", onSet = AccountOnSetListener.class)
public class Account {

    @Id(keyType = KeyType.Auto)
    private Long id;

    private String userName;
    
    private String password;
    
    //getter setter
}
           

字段加密

字段加密,指的是資料庫在存入了明文内容,但是當我們進行查詢時,傳回的内容為加密内容,而非明文内容。

以下是 MyBatis-Flex 字段加密示例:

step 1: 為實體類編寫一個 set 監聽器(SetListener)

typescript複制代碼public class AccountOnSetListener implements SetListener {
    @Override
    public Object onSet(Object entity, String property, Object value) {
        
        if (value != null){
            //對字段内容進行加密
            value = encrypt(value);
        }
        
        return value;
    }
}
           

step 2: 為實體類配置 onSet 監聽

kotlin複制代碼@Table(value = "tb_account", onSet = AccountOnSetListener.class)
public class Account {

    @Id(keyType = KeyType.Auto)
    private Long id;

    private String userName;
    
    private String password;
    
    //getter setter
}           

繼續閱讀