Spring?Data?JPA?注解Entity關(guān)聯(lián)關(guān)系使用詳解
首先,實(shí)體與實(shí)體之間的關(guān)聯(lián)關(guān)系一共分為四種,分別為OneToOne、OneToMany、ManyToOne和ManyToMany;而實(shí)體之間的關(guān)聯(lián)關(guān)系又分為雙向和單向。實(shí)體之間的關(guān)聯(lián)關(guān)系是在JPA使用中最容易發(fā)生問(wèn)題的地方。
1、OneToOne關(guān)聯(lián)關(guān)系
@OneToOne一般表示對(duì)象之間一對(duì)一的關(guān)聯(lián)關(guān)系,它可以放在field上面,也可以放在get/set方法上面。其中JPA協(xié)議有規(guī)定,如果配置雙向關(guān)聯(lián),維護(hù)關(guān)聯(lián)關(guān)系的是擁有外鍵的一方,而另一方必須配置mappedBy;如果是單項(xiàng)關(guān)聯(lián),直接配置在擁有外鍵的一方即可。
舉例說(shuō)明:
user表是用戶的主信息,user_info是用戶的拓展信息,兩者之間是一對(duì)一的關(guān)系。user_info表里面有一個(gè)user_id作為關(guān)聯(lián)關(guān)系的外鍵,如果是單項(xiàng)關(guān)聯(lián),我們的寫法如下:
@Data @Entity @NoArgsConstructor @AllArgsConstructor @Builder public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String name; private String email; private String sex; private String address; }
我們只需要在擁有外鍵的一方配置@OneToOne注解就可以了
@Entity @Data @Builder @AllArgsConstructor @NoArgsConstructor @ToString(exclude = "user") public class UserInfo { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private Integer ages; private String telephone; @OneToOne private User user; }
這就是單向關(guān)聯(lián)關(guān)系,那么如何設(shè)置雙向關(guān)聯(lián)關(guān)系呢? 我們保持UserInfo不變,在User實(shí)體對(duì)象里面添加一段代碼即可
@OneToOne(mappedBy = "user") private UserInfo userInfo; @Data @Entity @NoArgsConstructor @AllArgsConstructor @Builder public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Integer id; private String name; private String email; private String sex; private String address; @OneToOne(mappedBy = "user") private UserInfo userInfo; }
1.1 解讀OneToOne源碼
public @interface OneToOne { Class targetEntity() default void.class; CascadeType[] cascade() default {}; FetchType fetch() default EAGER; boolean optional() default true; String mappedBy() default ""; boolean orphanRemoval() default false; }
targetEntity:作為關(guān)聯(lián)目標(biāo)的實(shí)體類。
cascade:級(jí)聯(lián)操作策略,就是我們常說(shuō)的級(jí)聯(lián)操作。
fetch:數(shù)據(jù)獲取方式EAGER(立即加載)/LAZY(延遲加載) optional:表示關(guān)聯(lián)的實(shí)體是否能夠存在null值 mappedBy:關(guān)聯(lián)關(guān)系被誰(shuí)維護(hù)的一方對(duì)象里面的屬性名字,雙向關(guān)聯(lián)的時(shí)候必填。
1.2 mappedBy 注意事項(xiàng)
- 只有關(guān)聯(lián)關(guān)系的維護(hù)方才能操作兩個(gè)實(shí)體之間外鍵的關(guān)系。被維護(hù)方即使設(shè)置維護(hù)方屬性進(jìn)行存儲(chǔ)也不會(huì)更新外鍵關(guān)聯(lián)
- mappedBy不能與@JoinColumn或者@JoinTable同時(shí)使用,因?yàn)闆](méi)有任何意義,關(guān)聯(lián)關(guān)系不在這里面維護(hù)。
- mappedBy的值是指另一方的實(shí)體里面屬性的字段,而不是數(shù)據(jù)庫(kù)字段,也不是實(shí)體的對(duì)象的名字。也就是維護(hù)關(guān)聯(lián)關(guān)系的一方屬性字段名稱,或者加了@JoinColumn 或 @JoinTable注解的屬性字段名稱。如上面的User例子user里面的mappedBy的值,就是userinfo里面的user字段的名字。
1.3 CascadeType 用法
在CascadeType的用法中,CascadeType的枚舉值只有5個(gè),分別如下:
- CascadeType.PERSIST 級(jí)聯(lián)新建
- CascadeType.REMOVE 級(jí)聯(lián)刪除
- CascadeType.PEFRESH 級(jí)聯(lián)刷新
- CascadeType.MERGE 級(jí)聯(lián)更新
- CascadeType.ALL 四項(xiàng)全選
測(cè)試級(jí)聯(lián)新建和級(jí)聯(lián)刪除:
第一步: 在@OneToOne上面添加 cascade = {CascadeType.PERSIST,CascadeType.REMOVE},代碼如下所示:
@Entity @Data @Builder @AllArgsConstructor @NoArgsConstructor @ToString(exclude = "user") public class UserInfo { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private Integer ages; private String telephone; @OneToOne(cascade = {CascadeType.PERSIST,CascadeType.REMOVE}) private User user; }
新增測(cè)試方法:
@Test public void tesyPersistAndRemove(){ User user = User.builder() .name("jackxx") .email("123456@126.com") .build(); UserInfo userInfo = UserInfo.builder() .ages(12) .user(user) .telephone("12345678") .build(); // 新建UserInfo,級(jí)聯(lián)新建User userInfoRepo.save(userInfo); // 刪除UserInfo,級(jí)聯(lián)刪除User userInfoRepo.delete(userInfo); }
執(zhí)行SQL如下所示:
從上面運(yùn)行結(jié)果中可以看到,執(zhí)行insert的時(shí)候,會(huì)先插入user表,再插入user_info表。 執(zhí)行delete的時(shí)候,先刪除user_info表中數(shù)據(jù),再刪除user表中的數(shù)據(jù)。
上面只是講述級(jí)聯(lián)刪除的場(chǎng)景,下面我們?cè)僬f(shuō)一下關(guān)聯(lián)關(guān)系的刪除場(chǎng)景該怎么做?
1.4 orphanRemoval屬性用法
orphanRemoval表示當(dāng)關(guān)聯(lián)關(guān)系被刪除的時(shí)候,是否應(yīng)用級(jí)聯(lián)刪除。
首先我們,沿用上面的例子,當(dāng)我們刪除userinfo的時(shí)候,把user置空
userInfo.setUser(null); userInfoRepo.delete(userInfo);
再看運(yùn)行結(jié)果
Hibernate: delete from user_info where id=?
我們只刪除了UserInfo的數(shù)據(jù),沒(méi)有刪除user的數(shù)據(jù),說(shuō)明沒(méi)有進(jìn)行級(jí)聯(lián)刪除,我們將orphanRemoval屬性設(shè)置為true
@OneToOne(cascade = {CascadeType.PERSIST},orphanRemoval = true) private User user;
測(cè)試代碼:
@Test public void testRemove(){ User user = User.builder() .name("jackxx") .email("123456@126.com") .build(); UserInfo userInfo = UserInfo.builder() .ages(12) .user(user) .telephone("12345678") .build(); // 新建UserInfo,級(jí)聯(lián)新建User userInfoRepo.save(userInfo); userInfo.setUser(null); // 刪除UserInfo,級(jí)聯(lián)刪除User userInfoRepo.delete(userInfo); }
執(zhí)行結(jié)果如下所示:
在執(zhí)行結(jié)果中多了一條update語(yǔ)句,是因?yàn)槿サ袅薈ascadeType.REMOVE,這個(gè)時(shí)候不會(huì)進(jìn)行級(jí)聯(lián)刪除了。當(dāng)我們把user對(duì)象更新為null的時(shí)候,就會(huì)執(zhí)行一個(gè)update語(yǔ)句把關(guān)聯(lián)關(guān)系去掉。
1.5 orphanRemoval 和 CascadeType.REMOVE的區(qū)別
- CascadeType.REMOVE 級(jí)聯(lián)刪除,先刪除user表的數(shù)據(jù),再刪除user_info表的數(shù)據(jù)。 (因?yàn)榇嬖谕怄I關(guān)聯(lián),無(wú)法先刪除user_info表的數(shù)據(jù))
- orphanRemoval = true 先將user_info表中的數(shù)據(jù)外鍵user_id 更新為 null,然后刪除user_info表的數(shù)據(jù),再刪除user表的數(shù)據(jù)。
2、@JoinColumns & @JoinColumn
這兩個(gè)注解是集合關(guān)系,他們可以同時(shí)使用,@JoinColumn表示單字段,@JoinColumns表示多個(gè)@JoinColumn
@JoinColumn源碼
public @interface JoinColumn { String name() default ""; String referencedColumnName() default ""; boolean unique() default false; boolean nullable() default true; boolean insertable() default true; boolean updatable() default true; String columnDefinition() default ""; String table() default ""; ForeignKey foreignKey() default @ForeignKey(PROVIDER_DEFAULT); }
- name :代表外鍵的字段名。
- referencedColumnName :關(guān)聯(lián)表對(duì)應(yīng)的字段,如果不注明,默認(rèn)就是關(guān)聯(lián)表的主鍵
- unique:外鍵字段是否唯一
- nullable:外鍵字段是否允許為空
- insertable:是否跟隨一起新增
- updateable:是否跟隨一起更新
- columnDefinition:為列生成DDL時(shí)使用的SQL片段
- foreignKey:外鍵策略
// 外鍵策略 public enum ConstraintMode { // 創(chuàng)建外鍵約束 CONSTRAINT, // 不創(chuàng)建外鍵約束 NO_CONSTRAINT, // 采用默認(rèn)行為 PROVIDER_DEFAULT }
foreignKey的用法:
@OneToOne(cascade = {CascadeType.PERSIST},orphanRemoval = true) @JoinColumn(foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT), name = "user_id") private User user;
JoinColumns的用法:
@OneToOne(cascade = {CascadeType.PERSIST},orphanRemoval = true) @JoinColumns({ @JoinColumn(name = "user_id",referencedColumnName = "ID"), @JoinColumn(name = "user_ZIP",referencedColumnName = "ZIP") }) private User user;
3、@ManyToOne & @OneToMany
@ManyToOne代表多對(duì)一的關(guān)聯(lián)關(guān)系,而@OneToMany代表一對(duì)多,一般兩個(gè)成對(duì)使用表示雙向關(guān)聯(lián)關(guān)系。在JPA協(xié)議中也是明確規(guī)定:維護(hù)關(guān)聯(lián)關(guān)系的是擁有外鍵的一方,而另一方必須配置mappedBy
public @interface OneToMany { Class targetEntity() default void.class; CascadeType[] cascade() default {}; FetchType fetch() default LAZY; String mappedBy() default ""; boolean orphanRemoval() default false; } public @interface ManyToOne { Class targetEntity() default void.class; CascadeType[] cascade() default {}; FetchType fetch() default EAGER; boolean optional() default true; }
使用這兩個(gè)字段,需要注意以下幾點(diǎn):
- @ManyToOne 一定是維護(hù)外鍵關(guān)系的一方,所以沒(méi)有mappedBy字段;
- @ManyToOne 刪除的時(shí)候一定不能把One的一方刪除了,所以也沒(méi)有orphanRemoval選項(xiàng);
- @ManyToOne 的Lazy效果和 @OneToOne 的一樣,所以和上面的用法基本一致;
- @OneToMany 的Lazy是有效果的;
3.1 Lazy機(jī)制
舉例說(shuō)明 : 假設(shè)User有多個(gè)地址Address
@Data @Entity @NoArgsConstructor @AllArgsConstructor @Builder public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Integer id; private String name; private String email; private String sex; @OneToMany(mappedBy = "user",fetch = FetchType.LAZY) private List<UserAddress> address; }
@OneToMany 雙向關(guān)聯(lián)并且采用LAZY的機(jī)制
@Entity @Data @Builder @AllArgsConstructor @NoArgsConstructor public class UserAddress { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String address; @ManyToOne(cascade = CascadeType.ALL) private User user; }
測(cè)試代碼 :
@Test @Transactional public void testUserAddress(){ User user = User.builder() .name("jackxx") .email("123456@126.com") .build(); UserAddress userAddress = UserAddress.builder() .address("shanghai1") .user(user) .build(); UserAddress userAddress1 = UserAddress.builder() .address("shanghai2") .user(user) .build(); addressRepo.saveAll(Lists.newArrayList(userAddress,userAddress1)); User u = userRepo.findById(1).get(); System.out.println(u.getName()); System.out.println(u.getAddress()); }
運(yùn)行結(jié)果如下所示:
可以看到當(dāng)我們想要輸出Address信息的時(shí)候,才會(huì)加載Addres的信息
4、ManyToMany
@ManyToMany代表多對(duì)多的關(guān)聯(lián)關(guān)系、這種關(guān)聯(lián)關(guān)系任何一方都可以維護(hù)關(guān)聯(lián)關(guān)系。
我們假設(shè)user表和room表是多對(duì)多的關(guān)系,如下所示:
@Data @Entity @NoArgsConstructor @AllArgsConstructor @Builder public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Integer id; private String name; @ManyToMany(mappedBy = "users") private List<Room> rooms; }
@Entity @Data @Builder @AllArgsConstructor @NoArgsConstructor @ToString public class Room { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String title; @ManyToMany private List<User> users; }
這種方法實(shí)不可取,當(dāng)用到@ManyToMany的時(shí)候一定是三張表,不要想著建兩張表,兩張表肯定是違背表的原則
改進(jìn)方法:創(chuàng)建中間表 修改Romm里面的內(nèi)容
@Entity @Data @Builder @AllArgsConstructor @NoArgsConstructor @ToString public class Room { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String title; @ManyToMany @JoinTable(name = "user_room", joinColumns = @JoinColumn(name = "room_id"), inverseJoinColumns = @JoinColumn(name = "user_id")) private List<User> users; }
可以看到我們通過(guò)@JoinTable注解創(chuàng)建一張中間表,并且添加了兩個(gè)設(shè)定的外鍵,我們來(lái)看看@JoinTable的源碼:
public @interface JoinTable { String name() default ""; String catalog() default ""; String schema() default ""; JoinColumn[] joinColumns() default {}; JoinColumn[] inverseJoinColumns() default {}; ForeignKey foreignKey() default @ForeignKey(PROVIDER_DEFAULT); ForeignKey inverseForeignKey() default @ForeignKey(PROVIDER_DEFAULT); UniqueConstraint[] uniqueConstraints() default {}; Index[] indexes() default {}; }
- name:中間表名稱
- joinColumns:維護(hù)關(guān)聯(lián)關(guān)系一方的外鍵字段的名字
- inverseJoinColumns:另一方表的外鍵字段的名字
在現(xiàn)實(shí)開(kāi)發(fā)中,@ManyToMany注解用的比較少,一般都會(huì)使用成對(duì)的@ManyToOne 和 @OneToMany代替,因?yàn)槲覀兊闹虚g表可能還有一些約定的公共字段,如ID,update_time,create_time等其他字段
4.1 利用@ManyToOne 和 @OneToMany表達(dá)多對(duì)多的關(guān)聯(lián)關(guān)系
在上面的Demo中,我們稍作修改,新建一張user_room 中間表來(lái)存儲(chǔ)雙方的關(guān)聯(lián)關(guān)系和額外字段
如下所示: user_room中間表
@Entity @Data @Builder @AllArgsConstructor @NoArgsConstructor public class user_room { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Date createTime; private Date updateTime; @ManyToOne private User user; @ManyToOne private Room room; }
user表
@Data @Entity @NoArgsConstructor @AllArgsConstructor @Builder public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Integer id; @OneToMany(mappedBy = "user") private List<user_room> userRoomList; }
room表
@Entity @Data @Builder @AllArgsConstructor @NoArgsConstructor @ToString public class Room { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @OneToMany(mappedBy = "room") private List<user_room> roomList; }
以上就是Spring Data JPA 注解Entity關(guān)聯(lián)關(guān)系使用詳解的詳細(xì)內(nèi)容,更多關(guān)于Spring Data JPA Entity的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot使用mybatis-plus分頁(yè)查詢無(wú)效的問(wèn)題解決
MyBatis-Plus提供了很多便捷的功能,包括分頁(yè)查詢,本文主要介紹了SpringBoot使用mybatis-plus分頁(yè)查詢無(wú)效的問(wèn)題解決,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12Springmvc nginx實(shí)現(xiàn)動(dòng)靜分離過(guò)程詳解
這篇文章主要介紹了Springmvc nginx實(shí)現(xiàn)動(dòng)靜分離過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09Java定時(shí)調(diào)用.ktr文件的示例代碼(解決方案)
這篇文章主要介紹了Java定時(shí)調(diào)用.ktr文件的示例代碼,本文給大家分享遇到問(wèn)題及解決方法,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04Mybatis Plus框架項(xiàng)目落地實(shí)踐分析總結(jié)
這篇文章主要為大家介紹了Mybatis Plus框架項(xiàng)目落地實(shí)踐分析總結(jié),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03java如何連接數(shù)據(jù)庫(kù)executeUpdate()和executeQuery()
這篇文章主要介紹了java如何連接數(shù)據(jù)庫(kù)executeUpdate()和executeQuery(),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03JavaWeb請(qǐng)求轉(zhuǎn)發(fā)和請(qǐng)求包含實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了JavaWeb請(qǐng)求轉(zhuǎn)發(fā)和請(qǐng)求包含實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02關(guān)于bootstrap.yml和bootstrap.properties的優(yōu)先級(jí)問(wèn)題
這篇文章主要介紹了關(guān)于bootstrap.yml和bootstrap.properties的優(yōu)先級(jí)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03淺談Java 類中各成分加載順序和內(nèi)存中的存放位置
下面小編就為大家?guī)?lái)一篇淺談Java 類中各成分加載順序和內(nèi)存中的存放位置。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02