在實(shí)體建模過程中,有些實(shí)體會(huì)有多種變形,其中大部分的屬性都是共用的,只有一小部分是特有的。這時(shí)較優(yōu)雅的設(shè)計(jì)是將共用的屬性抽象出來形成基類,實(shí)現(xiàn)類再去擴(kuò)展特有屬性。領(lǐng)域服務(wù)可將通用服務(wù)抽象出來形成基類服務(wù),再擴(kuò)展特有服務(wù)。而Repository設(shè)計(jì),一般情況也是先抽象基礎(chǔ),再擴(kuò)展特有方法,調(diào)用時(shí)一般提供泛型支持,根據(jù)實(shí)現(xiàn)類的類型調(diào)用具體的Repository。
今天介紹使用@Inheritance注解讓一個(gè)Repository支持所有實(shí)現(xiàn)類,從而簡化Repository的設(shè)計(jì)。
一、對象建模
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "member_type")
public class Member {
@Id
private String memberCode;
private String memberName;
}
@DiscriminatorValue("store")
public class StoreMember extends Member {
private String memberCard;
private Integer memberLevel;
}
@DiscriminatorValue("wexin")
public class WeXinMember extends Member{
private String openId;
private String nickName;
}
-
@Inheritance用來配置父類-
InheritanceType.SINGLE_TABLE將所有實(shí)現(xiàn)類的所有字段映射到一個(gè)表里。 -
InheritanceType.TABLE_PER_CLASS將每個(gè)實(shí)現(xiàn)類合并基類的字段映射到單獨(dú)的表里,每個(gè)表相關(guān)獨(dú)立且沒有關(guān)聯(lián)。 -
InheritanceType.JOINED將基類和每個(gè)實(shí)現(xiàn)類分別映射到獨(dú)立的表里,并使用主鍵進(jìn)行關(guān)聯(lián),實(shí)現(xiàn)類只包含自己獨(dú)有的字段。
-
-
@DiscriminatorColumn(name = "member_type")用來做實(shí)現(xiàn)類區(qū)別標(biāo)識的字段,如果不指定name,則會(huì)自動(dòng)新建dtype字段。此字段系統(tǒng)會(huì)自動(dòng)賦值,不需要人為指定,且不能作為屬性存在。 -
@DiscriminatorValue("wexin")實(shí)現(xiàn)類區(qū)別的標(biāo)識值,jpa會(huì)根據(jù)具體標(biāo)識值將數(shù)據(jù)持久化到對應(yīng)的表中,查詢語句也可自動(dòng)識別類型
二、Repository設(shè)計(jì)
1. 查詢
@Repository
public interface MemberRepository extends JpaRepository<Member, String> {
WeXinMember findFirstByNickName(String openId);
WeXinMember findFirstByMemberCode(String memberCode);
StoreMember findTop1ByMemberCode(String memberCode);
Member findFirstByMemberName(String memberName);
}
- 從上面可以看到,所有的實(shí)現(xiàn)類都可視為一個(gè)整體,可直接使用某個(gè)子類的屬性做查詢條件,可以有子類的返回值,也可以設(shè)置基類返回值。
- 如果返回值為基類Member,jpa則返回Hibernate的代理類,需要
Hibernate.unproxy(member)才能得到具體的實(shí)現(xiàn)類
Member member = memberRepository.findFirstByMemberName("微信會(huì)員");
if (Hibernate.unproxy(member) instanceof WeXinMember) {
WeXinMember weXinMember = (WeXinMember) (Hibernate.unproxy(member));
System.err.println(weXinMember);
}
- 如果返回值為實(shí)現(xiàn)類則可以直接使用
WeXinMember weXinMember = memberRepository.findFirstByMemberCode("W001");
2. 寫入和刪除
寫入和刪除操作,jpa都視為一個(gè)整體,可以直接使用memberRepository默認(rèn)的方法
WeXinMember weXinMember = new WeXinMember();
weXinMember.setMemberCode("W001");
weXinMember.setMemberName("微信會(huì)員");
weXinMember.setMemberType("wexin");
weXinMember.setNickName("twoDog");
weXinMember.setOpenId(UUID.randomUUID().toString());
memberRepository.save(weXinMember);
...
memberRepository.save(weXinMember);
...
memberRepository.delete(weXinMember);
三、Repository查詢語句分析
使用哪種方式構(gòu)建,主要考慮數(shù)據(jù)庫的表結(jié)構(gòu)關(guān)系
1. InheritanceType.SINGLE_TABLE 構(gòu)建單表模式
不同的返回值類型,SQL語句有差別
基類:將所有字段都查詢出來,有不必要的性能開銷
Member findFirstByMemberCode(String memberCode);
SELECT
member0_.member_code AS member_c2_0_0_,
member0_.member_name AS member_n3_0_0_,
member0_.member_card AS member_c4_0_0_,
member0_.member_level AS member_l5_0_0_,
member0_.nick_name AS nick_nam6_0_0_,
member0_.open_id AS open_id7_0_0_,
member0_.member_type AS member_t1_0_0_
FROM
member member0_
WHERE
member0_.member_code = 'W001'
實(shí)現(xiàn)類:只查詢實(shí)現(xiàn)類的字段
WeXinMember findFirstByMemberCode(String memberCode);
SELECT
wexinmembe0_.member_code AS member_c2_0_,
wexinmembe0_.member_name AS member_n3_0_,
wexinmembe0_.nick_name AS nick_nam6_0_,
wexinmembe0_.open_id AS open_id7_0_
FROM
member wexinmembe0_
WHERE
wexinmembe0_.member_type = 'wexin'
AND wexinmembe0_.member_code = 'W001'
2. InheritanceType.TABLE_PER_CLASS構(gòu)建獨(dú)立表模式
基類:將所有子表都union進(jìn)來再查詢,此方法不可取,性能開銷大
Member findFirstByMemberCode(String memberCode);
SELECT
member0_.member_code AS member_c1_0_0_,
member0_.member_name AS member_n2_0_0_,
member0_.member_card AS member_c1_1_0_,
member0_.member_level AS member_l2_1_0_,
member0_.nick_name AS nick_nam1_2_0_,
member0_.open_id AS open_id2_2_0_,
member0_.clazz_ AS clazz_0_
FROM
(SELECT
member_code,
member_name,
NULL AS member_card,
NULL AS member_level,
NULL AS nick_name,
NULL AS open_id,
0 AS clazz_
FROM
member UNION ALL SELECT
member_code,
member_name,
member_card,
member_level,
NULL AS nick_name,
NULL AS open_id,
1 AS clazz_
FROM
store_member UNION ALL SELECT
member_code,
member_name,
NULL AS member_card,
NULL AS member_level,
nick_name,
open_id,
2 AS clazz_
FROM
we_xin_member) member0_
WHERE
member0_.member_code = 'W001'
實(shí)現(xiàn)類:只查詢實(shí)現(xiàn)類的字段
WeXinMember findFirstByMemberCode(String memberCode);
SELECT
wexinmembe0_.member_code AS member_c1_0_,
wexinmembe0_.member_name AS member_n2_0_,
wexinmembe0_.nick_name AS nick_nam1_2_,
wexinmembe0_.open_id AS open_id2_2_
FROM
we_xin_member wexinmembe0_
WHERE
wexinmembe0_.member_code = 'W001'
3. InheritanceType.JOINED構(gòu)建關(guān)聯(lián)表模式
基類:將所有子表進(jìn)行關(guān)聯(lián)后再查詢,子表多了性能開銷大
Member findFirstByMemberCode(String memberCode);
SELECT
member0_.member_code AS member_c2_0_0_,
member0_.member_name AS member_n3_0_0_,
member0_1_.member_card AS member_c1_1_0_,
member0_1_.member_level AS member_l2_1_0_,
member0_2_.nick_name AS nick_nam1_2_0_,
member0_2_.open_id AS open_id2_2_0_,
member0_.member_type AS member_t1_0_0_
FROM
member member0_
LEFT OUTER JOIN
store_member member0_1_ ON member0_.member_code = member0_1_.member_code
LEFT OUTER JOIN
we_xin_member member0_2_ ON member0_.member_code = member0_2_.member_code
WHERE
member0_.member_code = 'W001'
實(shí)現(xiàn)類:關(guān)聯(lián)主表和當(dāng)前類型子表查詢
WeXinMember findFirstByMemberCode(String memberCode);
SELECT
wexinmembe0_.member_code AS member_c2_0_,
wexinmembe0_1_.member_name AS member_n3_0_,
wexinmembe0_.nick_name AS nick_nam1_2_,
wexinmembe0_.open_id AS open_id2_2_
FROM
we_xin_member wexinmembe0_
INNER JOIN
member wexinmembe0_1_ ON wexinmembe0_.member_code = wexinmembe0_1_.member_code
WHERE
wexinmembe0_.member_code = 'W001'
綜上:如果有明確的類型時(shí),查詢方法的返回值應(yīng)該設(shè)置為具體現(xiàn)實(shí)類,以便于優(yōu)化查詢語句
四、適用場景
此功能還是挺新奇的,適用于包含多種變形類操作的場景,此方法比直接使用泛型處理更方便,更容易處理數(shù)據(jù),但需要關(guān)注數(shù)據(jù)庫結(jié)構(gòu)與查詢語句的性能影響,建議使用InheritanceType.JOINED模式
五. 源代碼
https://gitee.com/hypier/barry-jpa/tree/master/jpa-section-4