5. JPA物件繼承關係

JavaBoy_XJ發表於2020-10-10

在實體建模過程中,有些實體會有多種變形,其中大部分的屬性都是共用的,只有一小部分是特有的。這時較優雅的設計是將共用的屬性抽象出來形成基類,實現類再去擴充套件特有屬性。領域服務可將通用服務抽象出來形成基類服務,再擴充套件特有服務。而Repository設計,一般情況也是先抽象基礎,再擴充套件特有方法,呼叫時一般提供泛型支援,根據實現類的型別呼叫具體的Repository。 今天介紹使用@Inheritance註解讓一個Repository支援所有實現類,從而簡化Repository的設計。

一、物件建模

@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 將所有實現類的所有欄位對映到一個表裡。
    InheritanceType.TABLE_PER_CLASS 將每個實現類合併基類的欄位對映到單獨的表裡,每個表相關獨立且沒有關聯。
    InheritanceType.JOINED 將基類和每個實現類分別對映到獨立的表裡,並使用主鍵進行關聯,實現類只包含自己獨有的欄位。
@DiscriminatorColumn(name = "member_type") 用來做實現類區別標識的欄位,如果不指定name,則會自動新建dtype欄位。此欄位系統會自動賦值,不需要人為指定,且不能作為屬性存在。
@DiscriminatorValue("wexin") 實現類區別的標識值,jpa會根據具體標識值將資料持久化到對應的表中,查詢語句也可自動識別型別

二、Repository設計

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);
}
從上面可以看到,所有的實現類都可視為一個整體,可直接使用某個子類的屬性做查詢條件,可以有子類的返回值,也可以設定基類返回值。
如果返回值為基類Member,jpa則返回Hibernate的代理類,需要Hibernate.unproxy(member)才能得到具體的實現類
Member member = memberRepository.findFirstByMemberName("微信會員");
if (Hibernate.unproxy(member) instanceof WeXinMember) {
    WeXinMember weXinMember = (WeXinMember) (Hibernate.unproxy(member));
    System.err.println(weXinMember);
}
如果返回值為實現類則可以直接使用
WeXinMember weXinMember = memberRepository.findFirstByMemberCode("W001");

2. 寫入和刪除

寫入和刪除操作,jpa都視為一個整體,可以直接使用memberRepository預設的方法

WeXinMember weXinMember = new WeXinMember();
weXinMember.setMemberCode("W001");
weXinMember.setMemberName("微信會員");
weXinMember.setMemberType("wexin");
weXinMember.setNickName("twoDog");
weXinMember.setOpenId(UUID.randomUUID().toString());

memberRepository.save(weXinMember);

...
memberRepository.save(weXinMember);
...
memberRepository.delete(weXinMember);

三、Repository查詢語句分析

使用哪種方式構建,主要考慮資料庫的表結構關係

1.InheritanceType.SINGLE_TABLE 構建單表模式

不同的返回值型別,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'

**實現類:**只查詢實現類的欄位

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構建獨立表模式

基類:將所有子表都union進來再查詢,此方法不可取,效能開銷大

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'

**實現類:**只查詢實現類的欄位

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構建關聯表模式

**基類:**將所有子表進行關聯後再查詢,子表多了效能開銷大

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'

**實現類:**關聯主表和當前型別子表查詢

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'

綜上:如果有明確的型別時,查詢方法的返回值應該設定為具體現實類,以便於優化查詢語句
四、適用場景

此功能還是挺新奇的,適用於包含多種變形類操作的場景,此方法比直接使用泛型處理更方便,更容易處理資料,但需要關注資料庫結構與查詢語句的效能影響,建議使用InheritanceType.JOINED模式

五. 原始碼

https://gitee.com/hypier/barry-jpa/tree/master/jpa-section-4

相關文章