如果主键是由数据库生成的,如何使用 em.merge() 插入或更新 jpa 实体?

发布于 2024-09-28 18:40:54 字数 1376 浏览 1 评论 0原文

我有一个像这样的 JPA 实体:

@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 作为底层数据库。 “name”被设计为唯一的键。使用 Hibernate 作为 JPA 提供程序。

使用合并方法的问题是,因为 pk 是由 db 生成的,所以如果记录已经存在(名称已经存在),那么 Hibernate 将尝试将其插入到 db 中,我将得到一个唯一的键约束冲突异常,并且不执行更新 。有没有人有一个好的做法来处理这个问题?谢谢你!

PS:我的解决方法是这样的:

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;

    }
}

I have an JPA entity like this:

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

    //...
}

Use Mysql as the underlying database. "name" is designed as a unique key. Use Hibernate as JPA provider.

The problem with using merge method is that because pk is generated by db, so if the record already exist (the name is already there) then Hibernate will trying inserting it to db and I will get an unique key constrain violation exception and not doing the update . Does any one have a good practice to handle that? Thank you!

P.S: my workaround is like this:

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;

    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

注定孤独终老 2024-10-05 18:40:54

如果主键是由数据库生成的,如何使用 em.merge() 插入或更新 jpa 实体?

无论您是否使用生成的标识符,IMO 都无关紧要。这里的问题是,您想要在 PK 之外的某个唯一键上实现“upsert”,而 JPA 并没有真正提供对此的支持(merge 依赖于数据库身份)。

所以你有 AFAIK 2 个选择。

首先执行 INSERT 并实现一些重试机制,以防由于违反唯一约束而失败,然后查找并更新现有记录(使用新的实体管理器)。

或者,首先执行 SELECT,然后根据 SELECT 的结果插入或更新(这就是您所做的)。这可以工作,但不能 100% 保证,因为两个并发线程之间可能存在竞争条件(它们可能找不到给定类别名称的记录并尝试并行插入;最慢的线程将失败)。如果这不太可能,这可能是一个可接受的解决方案。

更新:如果您不介意使用 MySQL 专有功能,可能还有第三个奖励选项,请参阅 12.2.5.3。 INSERT ... ON DUPLICATE KEY UPDATE 语法。但从未使用 JPA 进行过测试。

How to use em.merge() to insert OR update for jpa entities if primary key is generated by database?

Whether you're using generated identifiers or not is IMO irrelevant. The problem here is that you want to implement an "upsert" on some unique key other than the PK and JPA doesn't really provide support for that (merge relies on database identity).

So you have AFAIK 2 options.

Either perform an INSERT first and implement some retry mechanism in case of failure because of a unique constraint violation and then find and update the existing record (using a new entity manager).

Or, perform a SELECT first and then insert or update depending on the outcome of the SELECT (this is what you did). This works but is not 100% guaranteed as you can have a race condition between two concurrent threads (they might not find a record for a given categoryName and try to insert in parallel; the slowest thread will fail). If this is unlikely, it might be an acceptable solution.

Update: There might be a 3rd bonus option if you don't mind using a MySQL proprietary feature, see 12.2.5.3. INSERT ... ON DUPLICATE KEY UPDATE Syntax. Never tested with JPA though.

污味仙女 2024-10-05 18:40:54

我之前没有看到过这一点,所以我只是想添加一个可能的解决方案来避免进行多个查询。 版本控制

通常用作在乐观锁定场景中检查正在更新的记录是否已过时的简单方法,还可以使用 @Version 注释的列来检查记录是否是持久的(存在于数据库中)。

这一切听起来可能很复杂,但事实并非如此。它归结为记录上的一个额外列,其值在每次更新时都会发生变化。我们在数据库中定义一个额外的列版本,如下所示:

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

并用 @Version 标记 Java 类中的相应字段,如下所示:

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

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

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

@Version 注释将使 JPA 通过包含该列来使用乐观锁定的列作为任何更新语句中的条件,如下所示:

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

(JPA 自动执行此操作,无需您自己编写)

然后,它检查一条记录是否已更新(按预期)(在这种情况下)该对象已过时)。

我们必须确保没有人可以设置版本字段,否则会弄乱乐观锁定,但如果需要,我们可以在 version 上创建一个 getter。我们还可以在方法 isPercient 中使用版本字段,该方法将检查记录是否已经在数据库中,而无需进行查询:

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

I haven't seen this mentioned before so I just would like to add a possible solution that avoids making multiple queries. Versioning.

Normally used as a simple way to check whether a record being updated has gone stale in optimistic locking scenario's, columns annotated with @Version can also be used to check whether a record is persistent (present in the db) or not.

This all may sound complicated, but it really isn't. What it boils down to is an extra column on the record whose value changes on every update. We define an extra column version in our database like this:

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

And mark the corresponding field in our Java class with @Version like this:

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

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

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

The @Version annotation will make JPA use this column with optimistic locking by including it as a condition in any update statements, like this:

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

(JPA does this automatically, no need to write it yourself)

Then afterwards it checks whether one record was updated (as expected) or not (in which case the object was stale).

We must make sure nobody can set the version field or it would mess up optimistic locking, but we can make a getter on version if we want. We can also use the version field in a method isPersistent that will check whether the record is in the DB already or not without ever making a query:

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

Finally, we can use this method in our insertOrUpdate method:

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
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文