5. JPA对象继承关系

在实体建模过程中,有些实体会有多种变形,其中大部分的属性都是共用的,只有一小部分是特有的。这时较优雅的设计是将共用的属性抽象出来形成基类,实现类再去扩展特有属性。领域服务可将通用服务抽象出来形成基类服务,再扩展特有服务。而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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容

  • title: 【翻译】SpringData官方文档第四章date: 2016-11-27tags: 翻译categ...
    zhanghTK阅读 1,305评论 0 1
  • 本文参考了Spring Data JPA官方文档,引用了部分文档的代码。 Spring Data JPA是Spri...
    乐百川阅读 25,863评论 3 19
  • 简介: 本文由浅入深地讲述了使用 Spring Data JPA 需要关注的各个方面,为读者了解和使用该框架提供了...
    AiPuff阅读 4,495评论 1 28
  • 本章是《 Spring Boot 快速入门 》系列教程的第三章,若要查看本系列的全部章节,请点击 这里 。 目录...
    terran4j阅读 5,921评论 4 10
  • 1.有人说,一首老歌就像一位朋友,若干年后或者刹那之间,那些旋律会让你瞬间泪流满面。 2.既然已无路可退,那就只能...
    Ljx夹心阅读 278评论 0 0