Spring Data Jpa拋出異常:NonUniqueObjectException的解決方法

1. 起因

  • 接觸Spring data Jpa的時(shí)間不長(zhǎng),在一次SpringBoot項(xiàng)目中添加了一個(gè)Entity,并將其映射到對(duì)應(yīng)的Mysql數(shù)據(jù)庫(kù)表中,id在數(shù)據(jù)庫(kù)表中是自增長(zhǎng)類型,(說(shuō)明:getXXXsetXXX方法使用lombok注解自動(dòng)生成)如下:
import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.Date;

@Getter
@Setter
@Entity
@Table(name = "t_user")
public class User {

    @Id
    private int id;
    private String username;
    private String userAd;

}
  • Dao層接口繼承自JpaRepository類,聲明的空接口(繼承的父類中有常用的方法實(shí)現(xiàn))如下:
import com.maxus.portal.api.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Integer> {

}
  • 然后在程序中生成了一堆User的實(shí)例對(duì)象,然后放入List<User>中,使用saveAll(list);向數(shù)據(jù)庫(kù)中持久化數(shù)據(jù)??刂婆_(tái)報(bào)出以下錯(cuò)誤:
NonUniqueObjectException: A different object with the same identifier value was already associated with the session 

2. 解決辦法

  • User實(shí)體類中的主鍵上(在本例中就是id)添加以下注解:
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    即:
    ......省略

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String username;
    private String userAd;

    ......省略

到這里問(wèn)題就得以順利解決,希望能幫助到遇到相同問(wèn)題的小伙伴們。想了解原因的可以繼續(xù)往下看。


此問(wèn)題產(chǎn)生的原因

  • 我使用的框架是SpringBoot,首先談?wù)撘幌翵pa、Spring Data Jpa、Hibernate三者的關(guān)系:

JPA的是 Java Persistence API 的簡(jiǎn)寫(xiě),是Sun官方提出的一種ORM規(guī)范,注意不是ORM框架——因?yàn)镴PA并未提供ORM實(shí)現(xiàn),它只是制訂了一些規(guī)范,提供了一些編程的API接口,但具體實(shí)現(xiàn)則由服務(wù)廠商來(lái)提供實(shí)現(xiàn)。

Hibernate是JPA規(guī)范的完整實(shí)現(xiàn),并已獲得Sun的兼容認(rèn)證。

Spring Data JPA是Spring官方在JPA規(guī)范的基礎(chǔ)下,只提供了Repository層的實(shí)現(xiàn)。

  • 因此,SpringBoot中的ORM框架也是有Hibernate的。

  • 進(jìn)入正題,這個(gè)異常就是Hibernate拋出的,我們先看下NonUniqueObjectException這個(gè)異常的Hibernate官方表述:

public class NonUniqueObjectException extends HibernateException

This exception is thrown when an operation would break session-scoped identity. This occurs if the user tries to associate two different instances of the same Java class with a particular identifier, in the scope of a single Session.

翻譯過(guò)來(lái)的意思就是(翻譯的比較生硬,望見(jiàn)諒,筆芯):

當(dāng)操作將破壞Session范圍內(nèi)的標(biāo)識(shí)時(shí),將拋出此異常。如果用戶試圖將同一個(gè)Java類的兩個(gè)不同實(shí)例與一個(gè)特定標(biāo)識(shí)符(在一個(gè)Session范圍內(nèi))關(guān)聯(lián),就會(huì)發(fā)生這種情況。

步入重點(diǎn):

  • 大致的意思就是說(shuō)主鍵不唯一。
  • 但是數(shù)據(jù)庫(kù)中明明設(shè)置的id為自增長(zhǎng),為什么還會(huì)出現(xiàn)主鍵不唯一呢?
  • 熟悉Hibernate的應(yīng)該會(huì)知道它的緩存。 底層使用session.save();保存對(duì)象,這個(gè)時(shí)候Session將這個(gè)對(duì)象放入entityEntries,用來(lái)標(biāo)記對(duì)象已經(jīng)和當(dāng)前的Session建立了關(guān)聯(lián),由于應(yīng)用對(duì)對(duì)象做了保存的操作,Session還要在insertions中登記應(yīng)用的這個(gè)插入行為(行為包括:對(duì)象引用、對(duì)象id、Session、持久化處理類)。即,調(diào)用session.save(user);之后,hibernate并不會(huì)立即提交數(shù)據(jù)庫(kù),而是先將要保存,更新,刪除放進(jìn)了緩存中。等整個(gè)事務(wù)操作完成后,事務(wù)提示,需要將所有緩存flush入數(shù)據(jù)庫(kù),Session啟動(dòng)一個(gè)事務(wù),并按照insert ,update,...,delete的順序提交所有之前登記的操作(注意:所有insert執(zhí)行完畢后才會(huì)執(zhí)行update,這里的特殊處理也可能會(huì)將你的程序搞得一團(tuán)遭,如需要控制操作的順序,需要使用flush)。
  • 每次調(diào)用sessionFactory.getCurrentSession().save(user);的時(shí)候,Hibernate把user實(shí)例對(duì)象保存到了緩存中,因?yàn)樵跀?shù)據(jù)庫(kù)表中設(shè)置的主鍵id是自增長(zhǎng)的,但是在程序中save時(shí),并沒(méi)有給User類的每個(gè)實(shí)例的id賦值。那么在遍歷保存第二個(gè)user的時(shí)候,這兩個(gè)實(shí)例在緩存中就沒(méi)有唯一標(biāo)識(shí),所以Hibernate就會(huì)認(rèn)為具有相同標(biāo)識(shí)符值的另一個(gè)對(duì)象已經(jīng)與Session相關(guān)聯(lián)。也就是上面拋出的那個(gè)異常:
    NonUniqueObjectException: A different object with the same identifier value was already associated with the session
  • 在主鍵id上添加注解@GeneratedValue(strategy = GenerationType.IDENTITY),就是告訴它這個(gè)主鍵會(huì)由數(shù)據(jù)庫(kù)自動(dòng)生成。因此在緩存中會(huì)給每個(gè)實(shí)例添加一個(gè)標(biāo)識(shí),用以區(qū)分所有的實(shí)例,在提交給數(shù)據(jù)庫(kù)后并不會(huì)對(duì)主鍵id產(chǎn)生影響。
1. 關(guān)于@GeneratedValue

為主鍵的值提供生成策略的規(guī)范。@GeneratedValue注解可以被應(yīng)用于一個(gè)實(shí)體或者結(jié)合@Id注解映射的父類的主鍵屬性或者字段,使用@GeneratedValue注解只支持簡(jiǎn)單的主鍵,對(duì)于派生的主鍵則不支持。

  • 這段文字是我翻譯的官方文檔,略顯生硬,旺各位看官見(jiàn)諒,下面貼出來(lái)英文原文:

Provides for the specification of generation strategies for the values of primary keys. The @GeneratedValue annotation may be applied to a primary key property or field of an entity or mapped superclass in conjunction with the @Id annotation. The use of the @GeneratedValue annotation is only required to be supported for simple primary keys. Use of the @GeneratedValue annotation is not supported for derived primary keys.

2. 關(guān)于GenerationType

GenerationType是一個(gè)Enum類型的枚舉類,定義主鍵生成策略的類型。
GenerationType.IDENTITY的意思就是指示持久化提供器必須使用數(shù)據(jù)庫(kù)標(biāo)識(shí)列為實(shí)體分配主鍵。

  • 該枚舉類的源碼如下,屬性上面有相關(guān)注釋。
public enum GenerationType {

    /**
     * Indicates that the persistence provider must assign
     * primary keys for the entity using an underlying
     * database table to ensure uniqueness.
     */
    TABLE,

    /**
     * Indicates that the persistence provider must assign
     * primary keys for the entity using a database sequence.
     */
    SEQUENCE,

    /**
     * Indicates that the persistence provider must assign
     * primary keys for the entity using a database identity column.
     */
    IDENTITY,

    /**
     * Indicates that the persistence provider should pick an
     * appropriate strategy for the particular database. The
     * <code>AUTO</code> generation strategy may expect a database
     * resource to exist, or it may attempt to create one. A vendor
     * may provide documentation on how to create such resources
     * in the event that it does not support schema generation
     * or cannot create the schema resource at runtime.
     */
    AUTO
}

文中描述如有不正確的地方,歡迎指正。
參考鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,279評(píng)論 6 342
  • 本文中我們介紹并比較兩種最流行的開(kāi)源持久框架:iBATIS和Hibernate,我們還會(huì)討論到Java Persi...
    大同若魚(yú)閱讀 4,438評(píng)論 4 27
  • web-inf WEB-INF是Java的WEB應(yīng)用的安全目錄。 所謂安全就是客戶端無(wú)法訪問(wèn),只有服務(wù)端可以訪問(wèn)的...
    尼爾君閱讀 336評(píng)論 0 0
  • 云更容易閱讀 140評(píng)論 0 1
  • 每個(gè)人都因?yàn)椴煌臋C(jī)緣來(lái)到簡(jiǎn)書(shū),每個(gè)人來(lái)簡(jiǎn)書(shū)的目的也不一樣,而但凡在這里堅(jiān)持下去了的,可能都會(huì)在自我梳理的過(guò)程中和...
    厲姣姣閱讀 736評(píng)論 0 3

友情鏈接更多精彩內(nèi)容