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ō)明:getXXX和setXXX方法使用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
}
文中描述如有不正確的地方,歡迎指正。
參考鏈接