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
}