2010-10-22 23 views
5

Tôi có một thực thể JPA như thế này:cách sử dụng em.merge() để chèn HOẶC cập nhật cho các thực thể jpa nếu khóa chính được tạo bởi cơ sở dữ liệu?

@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; 

    //... 
} 

Sử dụng Mysql như cơ sở dữ liệu cơ bản. "name" được thiết kế như một khóa duy nhất. Sử dụng Hibernate như nhà cung cấp JPA.

Vấn đề với việc sử dụng phương pháp hợp nhất là vì pk được tạo bởi db, vì vậy nếu bản ghi đã tồn tại (tên đã có) thì Hibernate sẽ cố gắng chèn nó vào db và tôi sẽ nhận được một ngoại lệ và không thực hiện cập nhật. Có ai có thực hành tốt để xử lý điều đó không? Cảm ơn bạn!

Tái bút: Cách giải quyết của tôi là như thế này:

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

Trả lời

9

Làm thế nào để sử dụng em.merge() để chèn hoặc cập nhật cho các tổ chức JPA nếu khóa chính được tạo ra bởi cơ sở dữ liệu?

Cho dù bạn đang sử dụng số nhận dạng được tạo hay không là IMO không liên quan. Vấn đề ở đây là bạn muốn triển khai "upsert" trên một số khóa duy nhất khác với PK và JPA không thực sự cung cấp hỗ trợ cho điều đó (merge dựa vào nhận dạng cơ sở dữ liệu).

Vì vậy, bạn có tùy chọn AFAIK 2.

Thực hiện INSERT trước và triển khai một số cơ chế thử lại trong trường hợp thất bại do vi phạm ràng buộc duy nhất và sau đó tìm và cập nhật bản ghi hiện có (sử dụng trình quản lý thực thể mới).

Hoặc, thực hiện lệnh SELECT trước và sau đó chèn hoặc cập nhật tùy thuộc vào kết quả của lệnh SELECT (đây là những gì bạn đã làm). Điều này hoạt động nhưng không được đảm bảo 100% vì bạn có thể có điều kiện chủng tộc giữa hai luồng đồng thời (chúng có thể không tìm thấy bản ghi cho categoryName đã cho và cố gắng chèn song song; luồng chậm nhất sẽ thất bại). Nếu điều này là không chắc, nó có thể là một giải pháp có thể chấp nhận được.

Cập nhật: Có thể có tùy chọn thưởng thứ ba nếu bạn không ngại sử dụng tính năng độc quyền của MySQL, xem 12.2.5.3. INSERT ... ON DUPLICATE KEY UPDATE Syntax. Không bao giờ được thử nghiệm với JPA mặc dù.

+0

BTW, bạn vui lòng giải thích tùy chọn "IMO không có liên quan" và "AFAIK 2" là gì? Cảm ơn. – Bobo

+0

@Bobo Vâng, 1. hãy giải thích * tôi * tại sao việc sử dụng số nhận dạng được tạo có liên quan đến câu hỏi 2. bạn có thấy nhiều giải pháp hơn (ngoài việc sử dụng các tính năng sở hữu cơ sở dữ liệu) hay không. –

+1

@Bobo: Ý của bạn là ý nghĩa của IMO và AFAIK? Nếu đây là những gì bạn có nghĩa là, ở đây bạn đi: IMO = Trong ý kiến ​​của tôi và AFAIK = Theo như tôi biết. –

1

Tôi chưa từng thấy điều này được đề cập trước đây nên tôi chỉ muốn thêm một giải pháp có thể tránh được việc thực hiện nhiều truy vấn. Versioning.

Thường được sử dụng như một cách đơn giản để kiểm tra xem bản ghi đang được cập nhật đã biến mất trong optimistic locking của kịch bản, cột được chú thích với @Version cũng có thể được sử dụng để kiểm tra xem bản ghi có liên tục (có trong db) hay không.

Tất cả điều này nghe có vẻ phức tạp, nhưng thực sự thì không. Những gì nó sôi xuống là một cột phụ trên bản ghi có giá trị thay đổi trên mỗi lần cập nhật.Chúng tôi xác định một phiên bản cột bổ sung trong cơ sở dữ liệu của chúng tôi như thế này:

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

Và đánh dấu lĩnh vực tương ứng trong lớp Java của chúng tôi với @Version như thế này:

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

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

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

Chú thích @version sẽ làm cho JPA sử dụng cột này với khóa lạc quan bằng cách bao gồm nó như là một điều kiện trong bất kỳ báo cáo cập nhật, như thế này:

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

(JPA thực hiện điều này tự động matically, không cần phải viết nó cho mình)

Sau đó, nó kiểm tra xem một bản ghi đã được cập nhật (như mong đợi) hay không (trong trường hợp đó đối tượng đã cũ).

Chúng ta phải đảm bảo rằng không ai có thể thiết lập trường phiên bản hoặc nó sẽ làm hỏng khóa lạc quan, nhưng chúng tôi có thể làm cho một getter trên version nếu chúng ta muốn. Chúng tôi cũng có thể sử dụng trường phiên bản trong một phương pháp isPersistent rằng sẽ kiểm tra xem các kỷ lục là trong DB đã hay không mà không bao giờ thực hiện một truy vấn:

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

Cuối cùng, chúng ta có thể sử dụng phương pháp này trong phương pháp insertOrUpdate của chúng tôi:

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

Quên đề cập đến ... Bạn có thể làm tương tự trên cột 'id', nhưng * chỉ * nếu nó được tạo bởi DB (mà nó nằm trong ví dụ đã cho). Tôi đang sử dụng tecnique này trong một dự án trong đó ID không được tạo ra bởi DB và nó có nhiều tiện dụng. –

Các vấn đề liên quan