5. JPA對象繼承關(guān)系

在實(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • title: 【翻譯】SpringData官方文檔第四章date: 2016-11-27tags: 翻譯categ...
    zhanghTK閱讀 1,453評論 0 1
  • 本文參考了Spring Data JPA官方文檔,引用了部分文檔的代碼。 Spring Data JPA是Spri...
    樂百川閱讀 26,178評論 3 19
  • 簡介: 本文由淺入深地講述了使用 Spring Data JPA 需要關(guān)注的各個(gè)方面,為讀者了解和使用該框架提供了...
    AiPuff閱讀 4,572評論 1 28
  • 本章是《 Spring Boot 快速入門 》系列教程的第三章,若要查看本系列的全部章節(jié),請點(diǎn)擊 這里 。 目錄...
    terran4j閱讀 5,964評論 4 10
  • 1.有人說,一首老歌就像一位朋友,若干年后或者剎那之間,那些旋律會(huì)讓你瞬間淚流滿面。 2.既然已無路可退,那就只能...
    Ljx夾心閱讀 383評論 0 0

友情鏈接更多精彩內(nèi)容