神奇好望角 The Magical Cape of Good Hope

庸人不必自扰,智者何需千虑?
posts - 26, comments - 50, trackbacks - 0, articles - 11
  BlogJava :: 首页 ::  :: 联系 :: 聚合  :: 管理

JPA 应用技巧 3:映射多对多的关联表

Posted on 2011-09-27 11:04 蜀山兆孨龘 阅读(3241) 评论(2)  编辑  收藏 所属分类: Java EE

实体间的多对多的关联需要一张关联表。如果直接使用 ManyToMany 来映射,JPA 就会隐式地帮我们自动管理关联表,代码写出来和其他类型的关联差别不大。例如,某州炒房团需要一个炒房跟踪系统,那么该系统中的炒房客和房子就是多对多的关系:

        public class Speculator implements Serializable {
            @Id
            private Integer id;
            @ManyToMany
            @JoinTable(joinColumns = @JoinColumn(name = "speculator_id"),
                    inverseJoinColumns = @JoinColumn(name = "house_id"))
            private List<House> houses;
            // 此处省略若干行
        }

        public class House implements Serializable {
            @Id
            private Integer id;
            @ManyToMany(mappedBy = "houses")
            private List<Speculator> speculators;
            // 此处省略若干行
        }
    

如果炒房客 s 要卖掉房子 h(严格点说是卖掉房子的产权部分),那么系统执行的代码差不多就是 s.getHouses().remove(h)。看似简单,然而底层的操作却性能低下:JPA 会先从数据库中取出炒房客的所有房产(s.getHouses()),然后再删除指定的那套房子;从数据库层面上看,这将先从关联表(speculator_house)中找到该炒房客的所有房子的外键,然后从 house 表载入这些 House 对象,最后才从 speculator_house 删除关联。在 ORM 出现前,这种操作只需要改关联表,根本不用关心其他房子。这种简单的多对多映射写法将关联表隐藏起来,虽然简化了代码,却也可能带来性能隐患。

很自然地可以想到,如果把关联表也映射成实体类,就能解决这个问题。speculator_house 包含两个外键,可用作联合主键。如果把它映射为 SpeculatorHouse 类,则该类与 SpeculatorHouse 都是多对一的关系。关联表实体类的代码如下(EmbeddedId 的映射技巧见《JPA 应用技巧 2:主键外键合体映射》):

        @Embeddable
        public class SpeculatorHouseId implements Serializable {
            private Integer speculatorId;
            private Integer houseId;
            // 此处省略若干行
        }

        @Entity
        @Table(name = "speculator_house")
        public class SpeculatorHouse implements Serializable {
            @EmbeddedId
            private SpeculatorHouseId id;
            @MapsId("speculatorId")
            @ManyToOne
            private Speculator speculator;
            @MapsId("houseId")
            @ManyToOne
            private House house;
            // 此处省略若干行
        }
    

SpeculatorHouse 也要增加相应的关联信息:

        public class Speculator implements Serializable {
            @Id
            private Integer id;
            @ManyToMany
            @JoinTable(joinColumns = @JoinColumn(name = "speculator_id"),
                    inverseJoinColumns = @JoinColumn(name = "house_id"))
            private List<House> houses;
            @OneToMany(mappedBy = "speculator")
            private List<SpeculatorHouse> speculatorHouses;
            // 此处省略若干行
        }

        public class House implements Serializable {
            @Id
            private Integer id;
            @ManyToMany(mappedBy = "houses")
            private List<Speculator> speculators;
            @OneToMany(mappedBy = "house")
            private List<SpeculatorHouse> speculatorHouses;
            // 此处省略若干行
        }
    

这样既保留了多对多关系,又映射了关联表,然后就可以根据实际情况选择隐式或显示的关联表管理。例如,要得到一个炒房客的全部房子,就使用隐式管理:s.getHouses();而要删除炒房客和某套房子的关联,则用显示管理:delete from SpeculatorHouse sh where sh.speculator = :s and sh.house = :h


评论

# re: JPA 应用技巧 3:映射多对多的关联表[未登录]  回复  更多评论   

2012-06-14 16:05 by DD
但是,我按照你这样配起来,怎么删speculator?。。我现在无法删除speculator。

# re: JPA 应用技巧 3:映射多对多的关联表  回复  更多评论   

2012-10-11 15:06 by merge
对于中间表操作描述详细,收藏了!

只有注册用户登录后才能发表评论。


网站导航: