我有一个像这样的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;
}
}
答案 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
}
}