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

时间:2010-10-22 14:56:56

标签: mysql jpa auto-increment

我有一个像这样的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提供程序。

使用merge方法的问题是因为pk是由db生成的,所以如果记录已经存在(名称已经存在),那么Hibernate会尝试将其插入到db中,我将获得一个唯一的键约束违例异常和没有做更新。有没有人有良好的做法来处理?谢谢!

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;

    }
}

2 个答案:

答案 0 :(得分:9)

  

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

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

所以你有AFAIK 2选项。

首先执行INSERT并在失败的情况下实施一些重试机制,因为存在唯一的约束违规,然后查找并更新现有记录(使用新的实体管理器)。

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

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

答案 1 :(得分: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)
);

使用@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。我们还可以在方法isPersistent中使用版本字段,该方法将在不进行查询的情况下检查记录是否已存在于数据库中:

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