實(shí)體間的多對(duì)多的關(guān)聯(lián)需要一張關(guān)聯(lián)表。如果直接使用 ManyToMany
來映射,JPA 就會(huì)隱式地幫我們自動(dòng)管理關(guān)聯(lián)表,代碼寫出來和其他類型的關(guān)聯(lián)差別不大。例如,某州炒房團(tuán)需要一個(gè)炒房跟蹤系統(tǒng),那么該系統(tǒng)中的炒房客和房子就是多對(duì)多的關(guān)系:
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
(嚴(yán)格點(diǎn)說是賣掉房子的產(chǎn)權(quán)部分),那么系統(tǒng)執(zhí)行的代碼差不多就是 s.getHouses().remove(h)
。看似簡單,然而底層的操作卻性能低下:JPA 會(huì)先從數(shù)據(jù)庫中取出炒房客的所有房產(chǎn)(s.getHouses()
),然后再刪除指定的那套房子;從數(shù)據(jù)庫層面上看,這將先從關(guān)聯(lián)表(speculator_house
)中找到該炒房客的所有房子的外鍵,然后從 house
表載入這些 House
對(duì)象,最后才從 speculator_house
刪除關(guān)聯(lián)。在 ORM 出現(xiàn)前,這種操作只需要改關(guān)聯(lián)表,根本不用關(guān)心其他房子。這種簡單的多對(duì)多映射寫法將關(guān)聯(lián)表隱藏起來,雖然簡化了代碼,卻也可能帶來性能隱患。
很自然地可以想到,如果把關(guān)聯(lián)表也映射成實(shí)體類,就能解決這個(gè)問題。speculator_house
包含兩個(gè)外鍵,可用作聯(lián)合主鍵。如果把它映射為 SpeculatorHouse
類,則該類與 Speculator
和 House
都是多對(duì)一的關(guān)系。關(guān)聯(lián)表實(shí)體類的代碼如下(EmbeddedId
的映射技巧見《JPA 應(yīng)用技巧 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;
// 此處省略若干行
}
Speculator
和 House
也要增加相應(yīng)的關(guān)聯(lián)信息:
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;
// 此處省略若干行
}
這樣既保留了多對(duì)多關(guān)系,又映射了關(guān)聯(lián)表,然后就可以根據(jù)實(shí)際情況選擇隱式或顯示的關(guān)聯(lián)表管理。例如,要得到一個(gè)炒房客的全部房子,就使用隱式管理:s.getHouses()
;而要?jiǎng)h除炒房客和某套房子的關(guān)聯(lián),則用顯示管理:delete from SpeculatorHouse sh where sh.speculator = :s and sh.house = :h
。