2010-10-22 130 views
5

我有一個JPA實體是這樣的:如果主鍵由數據庫生成,如何使用em.merge()爲jpa實體插入OR更新?

@Entity 
@Table(name = "category") 
public class Category implements Serializable { 
    private static final long serialVersionUID = 1L; 

    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    @Basic(optional = false) 
    @Column(name = "id") 
    private Integer id; 

    @Basic(optional = false) 
    @Column(name = "name") 
    private String name; 

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "category") 
    private Collection<ItemCategory> itemCategoryCollection; 

    //... 
} 

使用MySQL作爲底層數據庫。 「名稱」被設計爲一個獨特的關鍵。使用Hibernate作爲JPA提供程序。

使用合併方法的問題是,因爲pk是由db生成的,所以如果記錄已經存在(名稱已經存在),那麼Hibernate會嘗試將它插入到數據庫中,我將得到一個唯一的鍵約束違例異常並沒有進行更新。有沒有人有一個很好的做法來處理?謝謝!

P.S:我的解決方法是這樣的:

public void save(Category entity) { 

    Category existingEntity = this.find(entity.getName()); 
    if (existingEntity == null) { 
     em.persist(entity); 
     //code to commit ... 
    } else { 
     entity.setId(existingEntity.getId()); 
     em.merge(entity); 
     //code to commit ... 
    } 
} 

public Category find(String categoryName) { 
    try { 
     return (Category) getEm().createNamedQuery("Category.findByName"). 
       setParameter("name", categoryName).getSingleResult(); 
    } catch (NoResultException e) { 
     return null; 

    } 
} 
+0

http://techblog.bozho.net/?p=266 – Bozho 2010-10-22 21:36:19

回答

9

如何使用em.merge()來插入或更新JPA實體,如果是由數據庫生成主鍵?

無論您是否使用生成的標識符都與IMO無關。這裏的問題是,你想在除了PK和JPA之外的某個唯一密鑰上實現「upsert」,並且JPA並不真正爲其提供支持(merge依賴於數據庫標識)。

所以你有AFAIK 2選項。

首先執行INSERT,並在發生故障時實施一些重試機制,因爲唯一的約束衝突,然後查找並更新現有記錄(使用新的實體管理器)。或者,首先執行一個SELECT,然後根據SELECT的結果插入或更新(這就是你所做的)。這可以工作,但不能100%保證,因爲你可以在兩個併發線程之間存在爭用條件(它們可能找不到給定categoryName的記錄並嘗試並行插入;最慢的線程將失敗)。如果這不太可能,這可能是一個可以接受的解決方案。

更新:有可能是第三個獎勵選項,如果你不介意使用一個MySQL專有的功能,請參閱12.2.5.3. INSERT ... ON DUPLICATE KEY UPDATE Syntax。儘管從未用JPA測試過。

+0

順便說一句,你能解釋什麼是「IMO無關」和「AFAIK 2選項」?謝謝。 – Bobo 2010-10-25 16:05:43

+0

@Bobo嗯,1.請解釋*我*爲什麼使用生成的標識符與問題相關2.您是否看到更多的解決方案(除了使用數據庫專有功能外)。 – 2010-10-25 17:05:20

+1

@Bobo:你的意思是,IMO和AFAIK的含義?如果這是你的意思,在這裏你去:IMO =在我的意見和AFAIK =據我所知。 – 2010-10-25 22:07:12

1

我還沒有見過這個提到過,所以我只想添加一個可能的解決方案,避免進行多個查詢。 Versioning

通常用於檢查optimistic locking方案中正在更新的記錄是否已過時的簡單方法,也可以使用@Version註釋的列來檢查記錄是否持久(存在於db中)。

這一切聽起來很複雜,但實際上並非如此。它歸結爲記錄上的額外列,其值在每次更新時都會更改。我們定義在我們的數據庫中的額外的列的版本是這樣的:

CREATE TABLE example 
(
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, 
    version INT, -- <== It really is that simple! 
    value VARCHAR(255) 
); 

而且標誌着我們的Java類中的相應字段@Version這樣的:

@Entity 
public class Example { 
    @Id 
    @GeneratedValue 
    private Integer id; 

    @Version // <-- that's the trick! 
    private Integer version; 

    @Column(length=255) 
    private String value; 
} 

的@版本註解將使JPA使用此列通過包括其作爲任何更新語句的條件,這樣的樂觀鎖定:

UPDATE example 
SET value = 'Hello, World!' 
WHERE id = 23 
AND version = 2 -- <-- if version has changed, update won't happen 

(JPA做這個汽車(不需要自己寫)

然後它檢查是否更新(如預期的)一個記錄(在這種情況下,對象是陳舊的)。

我們必須確保沒有人可以設置版本字段,否則它會搞亂樂觀鎖定,但如果我們想要,我們可以在version上設置吸氣劑。我們還可以使用版本字段的方法isPersistent,將檢查記錄是否是在DB已經或沒有永遠做一個查詢:

@Entity 
public class Example { 
    // ... 
    /** Indicates whether this entity is present in the database. */ 
    public boolean isPersistent() { 
     return version != null; 
    } 
} 

最後,我們可以在我們的insertOrUpdate方法,使用這種方法:

public insertOrUpdate(Example example) { 
    if (example.isPersistent()) { 
     // record is already present in the db 
     // update it here 
    } 
    else { 
     // record is not present in the db 
     // insert it here 
    } 
} 
+0

忘了提及...你可以在'id'列上做同樣的事情,但*只有*如果它是由DB生成的(它在給定的例子中)。我在一個項目中使用這種技術,其中ID不是由DB生成的,而且非常方便。 – 2015-11-14 22:58:59

相關問題