成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

使用帶有Kotlin的JPA(Hibernate)的優(yōu)秀實(shí)踐

譯文
開發(fā) 前端
本文將和您討論,作為JPA的經(jīng)典實(shí)現(xiàn)—Hibernate,是如何避免各種常見的錯(cuò)誤,以及各種與Kotlin有關(guān)的優(yōu)秀實(shí)踐。

[[413599]]

【51CTO.com快譯】作為適合多平臺(tái)應(yīng)用的靜態(tài)編程語言,Kotlin比Java更簡潔、更有表現(xiàn)力、也更具有代碼的安全性。同時(shí),Kotlin提供了與Java的無縫互操作性。也就是說,Java允許開發(fā)人員將他們的項(xiàng)目遷移到Kotlin處,而無需重寫整個(gè)代碼庫。當(dāng)然,針對(duì)此類遷移,我們可能需要在Kotlin應(yīng)用中使用JPA(Java Persistence API)。雖然許多開發(fā)者普遍認(rèn)為:沒有JPA,就不存在實(shí)體。但是他們?cè)贙otlin中定義JPA時(shí),往往會(huì)遇到各種警告。下面,我們來一起討論:作為JPA的經(jīng)典實(shí)現(xiàn)—Hibernate,是如何避免各種常見的錯(cuò)誤,并充分利用Kotlin的。

JPA實(shí)體的各項(xiàng)規(guī)則

注意,此處的實(shí)體并非常規(guī)的數(shù)據(jù)傳輸對(duì)象(Data Transfer Object,DTO)。為了能夠順暢運(yùn)行,實(shí)體需要得到正確地定義。鏈接中詳細(xì)闡述了針對(duì)JPA的一系列規(guī)范和限制。其中最重要的是如下兩項(xiàng):

  • 實(shí)體類雖然可以擁有其他構(gòu)造函數(shù),但是必須具有一個(gè)無參數(shù)(no-arg)的構(gòu)造函數(shù)。而且這個(gè)無參數(shù)構(gòu)造函數(shù)必須是公共的(public)或受保護(hù)的(protected)。
  • 實(shí)體類的任何方法、或持久性實(shí)例變量,都不能為final類型。

上述規(guī)范足以讓實(shí)體類運(yùn)行起來。不過為了使之更流暢地運(yùn)行,我們需要附加如下兩條規(guī)則:

  • 只有在明確的請(qǐng)求時(shí),所有Lasy關(guān)聯(lián)才能被加載。否則,我們可能會(huì)遇到LazyInitializationException、或各種意外的性能問題。
  • equals()和hashCode()的實(shí)現(xiàn),必須考慮到實(shí)體的可變特性。

無參數(shù)的構(gòu)造函數(shù)

主構(gòu)造函數(shù)(Primary constructors)是Kotlin最受歡迎的特性之一。然而,主構(gòu)造函數(shù)在被加入的同時(shí),替換了原有的默認(rèn)函數(shù)。因此,如果您在Hibernate中使用它,則可能會(huì)碰到諸如:org.hibernate.InstantiationException: No default constructor for entity的異常。

那么為了解決此問題,您可以在所有實(shí)體中,手動(dòng)定義無參數(shù)的構(gòu)造函數(shù)。同時(shí),您最好使用kotlin-jpa編譯器插件,來確保在字節(jié)碼中,為每個(gè)JPA定義相關(guān)的類,如:@Entity、@MappedSuperclass或@Embeddable,生成無參數(shù)的構(gòu)造函數(shù)。

若想啟用該插件,您只需將其添加到kotlin-maven-plugin和compilerPlugins的依賴關(guān)系中即可,請(qǐng)參見如下代碼段:

  1. <plugin> 
  2.    <groupId>org.jetbrains.kotlin</groupId> 
  3.    <artifactId>kotlin-maven-plugin</artifactId> 
  4.    <configuration> 
  5.        <compilerPlugins> 
  6.            ... 
  7.            <plugin>jpa</plugin> 
  8.            ... 
  9.        </compilerPlugins> 
  10.    </configuration> 
  11.    <dependencies> 
  12.        ... 
  13.        <dependency> 
  14.            <groupId>org.jetbrains.kotlin</groupId> 
  15.            <artifactId>kotlin-maven-noarg</artifactId> 
  16.            <version>${kotlin.version}</version> 
  17.        </dependency> 
  18.        ... 
  19.    </dependencies> 
  20. </plugin> 

與之對(duì)應(yīng)的在Gradle(譯者注:一個(gè)基于Apache Ant和Apache Maven概念的項(xiàng)目自動(dòng)化構(gòu)建開源工具)中的代碼段為:

  1. buildscript { 
  2.     dependencies { 
  3.         classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version" 
  4.     } 
  5. apply plugin: "kotlin-jpa" 

打開各種類和屬性

根據(jù)JPA規(guī)范,所有與JPA相關(guān)的類和屬性都必須是open的。不過,某些JPA提供程序可能不會(huì)強(qiáng)制執(zhí)行該規(guī)范。例如,Hibernate在遇到最終實(shí)體類時(shí),是不會(huì)拋出異常的。然而,由于final類無法被子類化(subclassed),因此Hibernate的代理機(jī)制會(huì)就此關(guān)閉。而沒有了代理,又何談lazy加載呢?而且,由于程序急需獲取所有的ToOne關(guān)聯(lián),因此它很可能會(huì)導(dǎo)致嚴(yán)重的性能問題。

不過,對(duì)于使用靜態(tài)編織(static weaving)的EclipseLink而言,情況則不同,畢竟它的lazy加載機(jī)制并不會(huì)用到子類化。

如下代碼段所示,與Java不同的是,在Kotlin中,所有的類、屬性、以及方法,默認(rèn)都是final類型的。您必須將它們明確地標(biāo)記為open:

  1. @Table(name = "project"
  2. @Entity 
  3. open class Project { 
  4.     @Id 
  5.     @GeneratedValue(strategy = GenerationType.IDENTITY) 
  6.     @Column(name = "id", nullable = false
  7.     open var id: Long? = null 
  8.     @Column(name = "name", nullable = false
  9.     open var name: String? = null 
  10.     ... 

或者如下面的代碼段所示,您最好使用全開放(all-open)式的編譯器插件(https://kotlinlang.org/docs/all-open-plugin.html),來默認(rèn)開啟所有與JPA相關(guān)的類和屬性。通過正確的配置,它能夠適用于所有被注釋為@Entity、 @MappedSuperclass、以及@Embeddable的類:

  1. <plugin> 
  2.    <groupId>org.jetbrains.kotlin</groupId> 
  3.    <artifactId>kotlin-maven-plugin</artifactId> 
  4.    <configuration> 
  5.        <compilerPlugins> 
  6.            ... 
  7.            <plugin>all-open</plugin> 
  8.        </compilerPlugins> 
  9.        <pluginOptions> 
  10.            <option>all-open:annotation=javax.persistence.Entity</option
  11.            <option>all-open:annotation=javax.persistence.MappedSuperclass</option
  12.            <option>all-open:annotation=javax.persistence.Embeddable</option
  13.        </pluginOptions> 
  14.    </configuration> 
  15.    <dependencies> 
  16.        <dependency> 
  17.            <groupId>org.jetbrains.kotlin</groupId> 
  18.            <artifactId>kotlin-maven-allopen</artifactId> 
  19.            <version>${kotlin.version}</version> 
  20.        </dependency> 
  21.    </dependencies> 
  22. </plugin> 

與之對(duì)應(yīng)的在Gradle中的代碼段為:

  1. buildscript { 
  2.     dependencies { 
  3.         classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version" 
  4.     } 
  5. apply plugin: "kotlin-allopen" 
  6. allOpen { 
  7.     annotations("javax.persistence.Entity""javax.persistence.MappedSuperclass""javax.persistence.Embedabble"

針對(duì)JPA實(shí)體使用各種數(shù)據(jù)類

數(shù)據(jù)類(Data classes)是專為DTO設(shè)計(jì)的一項(xiàng)超棒的Kotlin功能。它被默認(rèn)設(shè)計(jì)、并配備了各種非常實(shí)用的針對(duì)equals()、hashCode()、以及toString()的實(shí)現(xiàn)。不過,此類實(shí)現(xiàn)并不太適合JPA實(shí)體。其原因在于,雖然數(shù)據(jù)類被設(shè)計(jì)為final類,但是它不能夠像Kotlin那樣被標(biāo)記為open。因此,為了適用于實(shí)體,而將它們標(biāo)記為open的唯一方法便是,啟用全開放式的編譯器插件。

如下代碼段所示,我們將使用以下實(shí)體,來進(jìn)一步檢查數(shù)據(jù)類。它帶有一個(gè)已生成的id、一個(gè)name屬性、以及兩個(gè)lazy的OneToMany關(guān)聯(lián):

  1. @Table(name = "client"
  2. @Entity 
  3. data class Client( 
  4.    @Id 
  5.    @GeneratedValue(strategy = GenerationType.IDENTITY) 
  6.    @Column(name = "id", nullable = false
  7.    var id: Long? = null
  8.    @Column(name = "name", nullable = false
  9.    var name: String? = null
  10.    @OneToMany(mappedBy = "client", orphanRemoval = true
  11.    var projects: MutableSet<Project> = mutableSetOf(), 
  12.    @JoinColumn(name = "client_id"
  13.    @OneToMany 
  14.    var contacts: MutableSet<Contact> = mutableSetOf(), 

意外獲取LAZY關(guān)聯(lián)

默認(rèn)情況下,所有ToMany關(guān)聯(lián)都是lazy的,其原因在于:非必要地加載它們,往往很容易會(huì)影響到程序性能。例如,equals()、hashCode()、以及toString()在實(shí)現(xiàn)的過程中,通常會(huì)用到包括lazy在內(nèi)的所有屬性。因此,調(diào)用它們會(huì)導(dǎo)致向數(shù)據(jù)庫產(chǎn)生不需要的請(qǐng)求、以及出現(xiàn)LazyInitializationException。而且,數(shù)據(jù)類的默認(rèn)行為是:在其方法中,使用來自主構(gòu)造函數(shù)的所有字段。

在此,我們可以使用IDE來生成toString(),以通過簡單的覆蓋方式,排除所有的LAZY字段。如下代碼段所示,由于JPA Buddy有著自己的toString()產(chǎn)生機(jī)制,因此它完全不會(huì)提供LAZY字段。

  1. @Override 
  2. override fun toString(): String { 
  3.    return this::class.simpleName + "(id = $id , name = $name )" 

當(dāng)然,僅從equals()和hashCode()中排除LAZY字段是遠(yuǎn)遠(yuǎn)不夠的,畢竟它們可能仍然包含著可變的屬性。

Equals()和HashCode()的問題

由于JPA實(shí)體在本質(zhì)上是可變的,因此為其實(shí)現(xiàn)equals()和hashCode(),并不像常規(guī)的DTO那么簡單。某些實(shí)體的id甚至都是由數(shù)據(jù)庫所生成的,因此id會(huì)在實(shí)體首次被持久化后發(fā)生變化。這就意味著我們將沒有可依賴的字段,去計(jì)算hashCode。

下面,讓我們對(duì)Client實(shí)體進(jìn)行一個(gè)簡單的測試。

  1. val awesomeClient = Client(name = "Awesome client"
  2. val hashSet = hashSetOf(awesomeClient) 
  3. clientRepository.save(awesomeClient) 
  4. assertTrue(awesomeClient in hashSet) 

如上面的代碼段所說,即便該實(shí)體被添加到前面幾行的集合中,它的最后一行斷言也會(huì)出現(xiàn)錯(cuò)誤。畢竟,id在被首次生成時(shí),hashCode就會(huì)發(fā)生改變。這就導(dǎo)致了HashSet在不同的存儲(chǔ)桶中是無法查找到該實(shí)體的。可見,如果id是在實(shí)體對(duì)象的創(chuàng)建期間被設(shè)置的(例如,是由應(yīng)用程序設(shè)置的UUID),那么就不會(huì)出現(xiàn)問題;而如果是由數(shù)據(jù)庫生成的id(其實(shí)更為常見),就會(huì)出現(xiàn)上述問題。

對(duì)此,我們可以在使用實(shí)體的數(shù)據(jù)類時(shí),持續(xù)性地覆蓋equals()和hashCode()。如果您想詳細(xì)地了解具體使用方法,請(qǐng)參見--https://vladmihalcea.com/the-best-way-to-implement-equals-hashcode-and-tostring-with-jpa-and-hibernate/。其中,對(duì)于Client實(shí)體而言,其對(duì)應(yīng)的代碼段為:

  1. override fun equals(other: Any?): Boolean { 
  2.    if (this === other) return true 
  3.    if (other == null ||Hibernate.getClass(this) !=Hibernate.getClass(other)) return false 
  4.    other as Client 
  5.    return id != null && id == other.id 
  6. override fun hashCode(): Int = 1756406093 

使用由應(yīng)用程序設(shè)置的ID

其實(shí),數(shù)據(jù)類的各種方法主要是由主構(gòu)造函數(shù)中那些指定的字段所生成的。如果只包含了eager immutable字段,那么數(shù)據(jù)類就不會(huì)存在上述問題。如下代碼段展示了由應(yīng)用程序設(shè)置的不可變id的字段:

  1. @Table(name = "contact"
  2. @Entity 
  3. data class Contact( 
  4.    @Id 
  5.    @Column(name = "id", nullable = false
  6.    val id: UUID, 
  7. ) { 
  8.    @Column(name = "email", nullable = false
  9.    val email: String? = null 
  10.    // other properties omitted 

如果您更喜歡使用由數(shù)據(jù)庫來生成id的話,則可以參照如下代碼段,以實(shí)現(xiàn)在構(gòu)造函數(shù)中使用不可變的自然id:

  1. @Table(name = "contact"
  2. @Entity 
  3. data class Contact( 
  4.    @NaturalId 
  5.    @Column(name = "email", nullable = false, updatable = false
  6.    val email: String 
  7. ) { 
  8.    @Id 
  9.    @GeneratedValue(strategy = GenerationType.IDENTITY) 
  10.    @Column(name = "id", nullable = false
  11.    var id: Long? = null 
  12.    
  13.    // other properties omitted 

雖然您可以放心地使用上述方法,但是它幾乎違背了使用數(shù)據(jù)類的初衷。畢竟,該用法不但讓分解(decomposition)變得無效,而且讓toString()對(duì)于實(shí)體而言,還不如直接使用普通的舊類。

空指針安全性(null-safety)

Kotlin相對(duì)于Java的一項(xiàng)優(yōu)勢(shì)便是,內(nèi)置有空指針安全性的功能。我們可以通過非空約束(non-null constraints)在數(shù)據(jù)庫端確保空指針的安全性。其最簡單的實(shí)現(xiàn)方法為,在主構(gòu)造函數(shù)中使用非空類型,來定義各種非空的屬性(請(qǐng)參考如下代碼段):

  1. @Table(name"contact"
  2. @Entity 
  3. class Contact( 
  4.    @NaturalId 
  5.    @Column(name = "email", nullable = false, updatable = false
  6.    val email: String, 
  7.    @Column(name = "name", nullable = false
  8.    var name: String 
  9.    @ManyToOne(fetch = FetchType.LAZY, optional = false
  10.    @JoinColumn(name = "client_id", nullable = false
  11.    var client: Client 
  12. ) { 
  13.    // id and other properties omitted 

當(dāng)然,如果您需要從構(gòu)造函數(shù)中(例如:在數(shù)據(jù)類中)排除它們,則可以提供默認(rèn)值,或?qū)ateinit的修飾符添加到其屬性之中(請(qǐng)參考如下代碼段):

  1. @Entity 
  2. data class Contact( 
  3.    @NaturalId 
  4.    @Column(name = "email", nullable = false, updatable = false
  5.    val email: String, 
  6. ) { 
  7.    @Column(name = "name", nullable = false
  8.    var name: String = "" 
  9.    @ManyToOne(fetch = FetchType.LAZY, optional = false
  10.    @JoinColumn(name = "client_id", nullable = false
  11.    lateinit var client: Client 
  12.    // id and other properties omitted 

據(jù)此,如果該屬性在數(shù)據(jù)庫中被確認(rèn)為非空,那么我們便可以省略在Kotlin代碼中,對(duì)于所有空值的檢查。

小結(jié)

讓我們通過如下列表,一起來總結(jié)一下如何在Kotlin中定義JPA實(shí)體:

  • 請(qǐng)將所有與JPA相關(guān)的類、及其屬性標(biāo)記為open,以避免出現(xiàn)顯著的性能問題。
  • 為ManytoOne和OnetoOne關(guān)聯(lián)開啟lazy加載,或者將全開放式的編譯器插件,應(yīng)用到所有被注釋為@Entity、@MappedSuperclass、以及@Embeddable的類。
  • 為了避免出現(xiàn)InstantiationException,請(qǐng)?jiān)谒信cJPA相關(guān)的類中,定義無參數(shù)的構(gòu)造函數(shù),或使用kotlin-jpa編譯器插件。
  • 通過啟用全開放式的插件,在已編譯的字節(jié)碼中創(chuàng)建數(shù)據(jù)類,并使之具有open屬性。
  • 覆蓋equals()、hashCode()、以及toString()。
  • 讓JPA Buddy生成諸如:equals()、hashCode()、以及toString()等有效的實(shí)體。

此外,如果您想深入研究此類實(shí)踐,請(qǐng)通過鏈接,參考我們?cè)贕itHub存儲(chǔ)庫中為您準(zhǔn)備的帶有測試的示例。

原文標(biāo)題:Best Practices of Using JPA(Hibernate) With Kotlin,作者:Andrey Oganesyan

【51CTO譯稿,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文譯者和出處為51CTO.com】

責(zé)任編輯:華軒 來源: 51CTO
相關(guān)推薦

2022-08-19 09:01:59

ReactTS類型

2009-06-19 18:36:15

JPAHibernate

2023-10-07 16:20:34

JavaAPI

2009-06-02 11:25:22

HibernateJPA映射

2022-04-28 08:05:05

數(shù)據(jù)庫數(shù)據(jù)庫交互

2022-03-05 23:08:14

開源軟件開發(fā)技術(shù)

2024-06-12 13:57:00

2019-09-17 09:44:45

DockerHTMLPython

2021-04-15 08:08:48

微前端Web開發(fā)

2019-11-27 10:55:36

云遷移云計(jì)算云平臺(tái)

2022-06-27 10:05:09

物聯(lián)網(wǎng)安全UPS網(wǎng)絡(luò)安全

2020-03-09 14:10:48

代碼開發(fā)工具

2021-08-17 15:00:10

BEC攻擊網(wǎng)絡(luò)攻擊郵件安全

2021-07-06 14:17:16

MLOps機(jī)器學(xué)習(xí)AI

2022-12-21 08:20:01

2023-08-10 17:40:33

人工智能

2022-04-07 09:30:00

自動(dòng)化LinodeKubernetes

2021-10-06 23:31:45

HibernateJPASpring Data

2020-11-25 10:26:24

云計(jì)算云安全數(shù)據(jù)

2021-01-20 10:53:41

云計(jì)算云存儲(chǔ)云遷移
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 一区二区三区高清在线观看 | 国产精品揄拍一区二区久久国内亚洲精 | 自拍中文字幕 | 日韩视频在线一区 | 999国产视频| 免费欧美视频 | 成人精品久久久 | 久久久国产一区二区三区 | 婷婷福利| 日操操夜操操 | 伊人影院在线观看 | 色妹子综合网 | 欧美一区免费 | 欧美一区精品 | 国产乱码精品一区二区三区中文 | 国产成人在线一区二区 | 国产在线观看 | av一区二区三区四区 | 亚洲成av片人久久久 | 网站一区二区三区 | 日韩av在线一区二区 | 国产精品色哟哟网站 | 成人免费大片黄在线播放 | 国产精品激情 | 一本一道久久a久久精品综合蜜臀 | 999视频| 四虎影音 | www国产成人免费观看视频,深夜成人网 | av资源中文在线 | 亚洲性爰 | 国产成人高清成人av片在线看 | 一区二区免费在线 | 国产一级片在线观看视频 | 国产精品三级久久久久久电影 | 蜜桃视频一区二区三区 | 羞羞视频在线观免费观看 | 国产中的精品av涩差av | 日本成人片在线观看 | 国产欧美精品一区二区三区 | 国产精品久久久久久亚洲调教 | 色婷婷av久久久久久久 |