JPA陷阱/错误
根據(jù)我在幫助團(tuán)隊(duì)和進(jìn)行培訓(xùn)方面的經(jīng)驗(yàn),這是我遇到的一些陷阱/錯(cuò)誤,這些陷阱/錯(cuò)誤在使用JPA的基于Java的系統(tǒng)中引起了一些問(wèn)題。
- 需要一個(gè)公共的無(wú)參數(shù)構(gòu)造函數(shù)
- 始終使用雙向關(guān)聯(lián)/關(guān)系
- 使用@OneToMany收集可能龐大的集合
需要一個(gè)公共的無(wú)參數(shù)構(gòu)造函數(shù)
是的,JPA @Entity需要零參數(shù)(或默認(rèn)的無(wú)參數(shù))構(gòu)造函數(shù)。 但這可以得到protected 。 您不必將其public 。 這樣就可以實(shí)現(xiàn)更好的面向?qū)ο蟮慕?#xff0c;因?yàn)槟槐貜?qiáng)制使用可公開(kāi)訪(fǎng)問(wèn)的零參數(shù)構(gòu)造函數(shù)。
實(shí)體類(lèi)必須具有no-arg構(gòu)造函數(shù)。 實(shí)體類(lèi)也可以具有其他構(gòu)造函數(shù)。 no-arg構(gòu)造函數(shù)必須是public 或protected 。 [強(qiáng)調(diào)我的]
–摘自Java Persistence API 2.1規(guī)范(Oracle)的2.1節(jié)
如果要建模的實(shí)體在創(chuàng)建時(shí)有一些字段需要初始化,則應(yīng)通過(guò)其構(gòu)造函數(shù)來(lái)完成。
注意:一些JPA提供程序可以通過(guò)在構(gòu)建時(shí)添加一個(gè)無(wú)參數(shù)的構(gòu)造函數(shù)來(lái)克服缺失的無(wú)參數(shù)構(gòu)造函數(shù)。
假設(shè)我們正在建模酒店房間預(yù)訂系統(tǒng)。 在其中,我們可能有諸如房間,預(yù)訂等之類(lèi)的實(shí)體。預(yù)訂實(shí)體可能需要開(kāi)始日期和結(jié)束日期,因?yàn)闆](méi)有停留時(shí)間來(lái)創(chuàng)建一個(gè)預(yù)訂實(shí)體就沒(méi)有多大意義。 在保留的構(gòu)造函數(shù)中將開(kāi)始日期和結(jié)束日期作為參數(shù)包括在內(nèi),將可以提供更好的模型。 保留受保護(hù)的零參數(shù)構(gòu)造函數(shù)會(huì)使JPA滿(mǎn)意。
@Entity public class Reservation { ...public Reservation(RoomType roomType, DateRange startAndEndDates) {if (roomType == null || startAndEndDates == null) {throw new IllegalArgumentException(...);} ...}...protected Reservation() { /* as required by ORM/JPA */ } }注意: Hibernate(JPA提供程序)允許將零參數(shù)構(gòu)造函數(shù)設(shè)為私有。 這使您的JPA代碼不可移植到其他JPA提供程序。
它還有助于在零參數(shù)構(gòu)造函數(shù)中添加注釋,以指示它是出于JPA目的(技術(shù)基礎(chǔ)結(jié)構(gòu))而添加的,并且不是域所必需的(業(yè)務(wù)規(guī)則/邏輯)。
盡管我在JPA 2.1規(guī)范中找不到它,但可嵌入類(lèi)也需要一個(gè)默認(rèn)(無(wú)參數(shù))構(gòu)造函數(shù)。 就像實(shí)體一樣,可以將必需的無(wú)參數(shù)構(gòu)造函數(shù)設(shè)為protected 。
@Embeddable public class DateRange { ...public DateRange(Date start, Date end) {if (start == null || end == null) {throw new IllegalArgumentException(...);}if (start.after(end)) {throw new IllegalArgumentException(...);} ...}...protected DateRange() { /* as required by ORM/JPA */ } }DDD示例項(xiàng)目還通過(guò)使它成為包范圍來(lái)隱藏no-arg構(gòu)造函數(shù)(請(qǐng)參閱Cargo實(shí)體類(lèi),其中no-arg構(gòu)造函數(shù)位于底部附近)。
始終使用雙向關(guān)聯(lián)/關(guān)系
JPA上的教學(xué)材料通常顯示出雙向關(guān)聯(lián)。 但這不是必需的。 例如,假設(shè)我們有一個(gè)包含一個(gè)或多個(gè)項(xiàng)目的訂單實(shí)體。
@Entity public class Order {@Id private Long id;@OneToMany private List<OrderItem> items;... }@Entity public class OrderItem {@Id private Long id;@ManyToOne private Order order;... }很高興知道JPA支持雙向關(guān)聯(lián)。 但是實(shí)際上,這成為維護(hù)的噩夢(mèng)。 如果訂單項(xiàng)不必知道其父訂單對(duì)象,則單向關(guān)聯(lián)就足夠了(如下所示)。 ORM只需要知道如何命名多邊表中的外鍵列。 這是通過(guò)在關(guān)聯(lián)的一側(cè)添加@JoinColumn批注來(lái)提供的。
@Entity public class Order {@Id Long id;@OneToMany@JoinColumn(name="order_id", ...)private List<OrderItem> items;... }@Entity public class OrderItem {@Id private Long id;// @ManyToOne private Order order;... }由于OrderItem不再需要保留對(duì)Order實(shí)體的引用,因此使其變得單向變得更容易。
請(qǐng)注意,有時(shí)可能需要雙向關(guān)聯(lián)。 實(shí)際上,這種情況很少見(jiàn)。
這是另一個(gè)例子。 假設(shè)您有幾個(gè)引用某個(gè)國(guó)家/地區(qū)實(shí)體的實(shí)體(例如,人的出生地,郵政地址等)。 顯然,這些實(shí)體將引用國(guó)家實(shí)體。 但是,國(guó)家是否必須引用所有這些不同的實(shí)體? 很有可能,不是。
@Entity public class Person {@Id Long id;@ManyToOne private Country countryOfBirth;... }@Entity public class PostalAddress {@Id private Long id;@ManyToOne private Country country;... }@Entity public class Country {@Id ...;// @OneToMany private List<Person> persons;// @OneToMany private List<PostalAddress> addresses; }所以,僅僅因?yàn)镴PA支持雙向關(guān)聯(lián), 并不意味著你必須!
使用
假設(shè)您正在建模銀行帳戶(hù)及其交易。 隨著時(shí)間的流逝,一個(gè)帳戶(hù)可以進(jìn)行數(shù)千(甚至數(shù)百萬(wàn))筆交易。
@Entity public class Account {@Id Long id;@OneToMany@JoinColumn(name="account_id", ...)private List<AccountTransaction> transactions;... }@Entity public class AccountTransaction {@Id Long id;... }對(duì)于只有少量交易的帳戶(hù),似乎沒(méi)有任何問(wèn)題。 但是隨著時(shí)間的流逝,當(dāng)一個(gè)帳戶(hù)包含成千上萬(wàn)個(gè)(如果不是上百萬(wàn)個(gè))交易時(shí),您很可能會(huì)遇到內(nèi)存不足的錯(cuò)誤。 那么,有什么更好的映射方法呢?
如果不能確保關(guān)聯(lián)的多面中的最大元素?cái)?shù)都可以全部加載到內(nèi)存中,則最好在@ManyToOne的另一側(cè)使用@ManyToOne 。
@Entity public class Account {@Id Long id;// @OneToMany private List<AccountTransaction> transactions;... }@Entity public class AccountTransaction {@Id Long id;@ManyToOneprivate Account account;...public AccountTransaction(Account account, ...) {...}protected AccountTransaction() { /* as required by ORM/JPA */ } }要檢索一個(gè)帳戶(hù)可能數(shù)千(如果不是幾百萬(wàn))的交易,請(qǐng)使用支持分頁(yè)的存儲(chǔ)庫(kù)。
@Transactional public interface AccountTransactionRepository {Page<AccountTransaction> findByAccount(Long accountId, int offset, int pageSize);... }要支持分頁(yè),請(qǐng)使用Query對(duì)象的setFirstResult(int)和setMaxResults(int)方法。
摘要
我希望這些說(shuō)明可以幫助開(kāi)發(fā)人員避免犯這些錯(cuò)誤。 總結(jié)一下:
- 需要公眾。 JPA要求的無(wú)參數(shù)構(gòu)造函數(shù)可以設(shè)為public或protected 。 如果需要,可以考慮對(duì)其進(jìn)行protected 。
- 一直使用 考慮單向而不是雙向關(guān)聯(lián)/關(guān)系。
- 使用 避免使用@OneToMany收集可能龐大的集合。 考慮@ManyToOne映射關(guān)聯(lián)/關(guān)系的@ManyToOne端,并支持分頁(yè)。
翻譯自: https://www.javacodegeeks.com/2016/02/jpa-pitfalls-mistakes.html
總結(jié)
- 上一篇: JUnit 5 –架构
- 下一篇: 永洪Desktop一个工具就可搞定中国式