我有一个事务可以保存或更新数据库中的一组对象。这是代码:
@Transactional
public void updatePDBEntry(Set<PDBEntry> pdbEntrySet) {
for (PDBEntry pdbEntry : pdbEntrySet) {
PDBEntry existingEntry = findByAccessionCode(pdbEntry.getAccessionCode());
if (existingEntry != null) {
log.debug("Remove previous version of PDBEntry {}", existingEntry);
makeTransient(existingEntry);
}
makePersistent(pdbEntry);
}
}
在genericDAO中:
public void makePersistent(I entity) {
getCurrentSession().saveOrUpdate(entity);
}
public void makeTransient(I entity) {
getCurrentSession().delete(entity);
}
不知怎的,这不起作用,它说因为重复的密钥而无法插入对象,即使我可以在日志中看到它获取makeTransient()。我想这与所有这一切都发生在事务中的事实有关,因此makePersistent()方法可能看不到makeTransient()所做的更改。我可以通过将所有数据从pdbEntry复制到existingEntry然后执行saveOrUpdate(existingEntry)来解决这个问题,但这是一种肮脏的黑客行为。有没有其他方法可以确保makeTransient对makePersistent可见,同时仍然将其全部保留在事务中?
编辑:这是我的PDBEntry域模型:
@Entity
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(callSuper = false, of = { "accessionCode", "date" })
@SuppressWarnings("PMD.UnusedPrivateField")
public class PDBEntry extends DomainObject implements Serializable {
@NaturalId
@NotEmpty
@Length(max = 4)
private String accessionCode;
@NaturalId
@NotNull
@Temporal(TemporalType.DATE)
private Date date;
private String header;
private Boolean isValidDssp;
@Temporal(TemporalType.TIMESTAMP)
private Date lastUpdated = new Date(System.currentTimeMillis());
@OneToOne(mappedBy = "pdbEntry", cascade = CascadeType.ALL)
private ExpMethod expMethod;
@OneToMany(mappedBy = "pdbEntry", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Refinement> refinementSet = new HashSet<Refinement>();
@OneToMany(mappedBy = "pdbEntry", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<HetGroup> hetGroupSet = new HashSet<HetGroup>();
@OneToMany(mappedBy = "pdbEntry", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Chain> chainSet = new HashSet<Chain>();
@OneToMany(mappedBy = "pdbEntry", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Chain> residueSet = new HashSet<Chain>();
public Date getLastUpdated() {
return new Date(lastUpdated.getTime());
}
public void setLastUpdated() throws InvocationTargetException {
throw new InvocationTargetException(new Throwable());
}
public void touch() {
lastUpdated = new Date(System.currentTimeMillis());
}
@Override
public String toString() {
return accessionCode;
}
public PDBEntry(String accessionCode, Date date) throws NullPointerException {
if (accessionCode != null && date != null) {
this.accessionCode = accessionCode;
this.date = date;
} else {
throw new NullPointerException();
}
}
}
@MappedSuperclass
public abstract class DomainObject implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
public Long getId() {
return id;
}
@Override
public abstract boolean equals(Object obj);
@Override
public abstract int hashCode();
@Override
public abstract String toString();
}
编辑:新问题,我创建了一个方法,首先从数据库中删除所有现有对象,如下所示:
@Override
@Transactional
public void updatePDBEntries(Set<PDBEntry> pdbEntrySet) {
findAndRemoveExistingPDBEntries(pdbEntrySet);
savePDBEntries(pdbEntrySet);
}
@Override
@Transactional
public void findAndRemoveExistingPDBEntries(Set<PDBEntry> pdbEntrySet) {
for (PDBEntry pdbEntry : pdbEntrySet) {
PDBEntry existingPDBEntry = findByAccessionCode(pdbEntry.getAccessionCode());
if (existingPDBEntry != null) {
log.info("Delete: {}", pdbEntry);
makeTransient(existingPDBEntry);
}
}
}
@Override
@Transactional
public void savePDBEntries(Set<PDBEntry> pdbEntrySet) {
for (PDBEntry pdbEntry : pdbEntrySet) {
log.info("Save: {}", pdbEntry);
makePersistent(pdbEntry);
}
}
它似乎删除了它遇到的前73个条目,但随后出现错误:
WARN 2010-10-25 14:28:49,406 main JDBCExceptionReporter:100 - SQL Error: 0, SQLState: 23503 ERROR 2010-10-25 14:28:49,406 main JDBCExceptionReporter:101 - Batch entry 0 /* delete nl.ru.cmbi.pdbeter.core.model.domain.PDBEntry */ delete from PDBEntry where id='74' was aborted. Call getNextException to see the cause. WARN 2010-10-25 14:28:49,406 main JDBCExceptionReporter:100 - SQL Error: 0, SQLState: 23503 ERROR 2010-10-25 14:28:49,406 main JDBCExceptionReporter:101 - ERROR: update or delete on table "pdbentry" violates foreign key constraint "fke03a2dc84d44e296" on table "hetgroup" Detail: Key (id)=(74) is still referenced from table "hetgroup". ERROR 2010-10-25 14:28:49,408 main AbstractFlushingEventListener:324 - Could not synchronize database state with session org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
有任何想法可能会出现此错误吗?
编辑:我发现了问题:最后一个错误是由hetgroup模型引起的,如下所示:
@Entity
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(callSuper = false, of = { "pdbEntry", "hetId" })
@SuppressWarnings("PMD.UnusedPrivateField")
// extends DomainObject which contains Id, NaturalId is not enough in this case, since duplicate chains still exist
// in fact this is an error of the PDBFinder and will be fixed in the future
public class HetGroup extends DomainObject implements Serializable {
//@NaturalId
@NotNull
@ManyToOne
private PDBEntry pdbEntry;
//@NaturalId
@NotEmpty
private String hetId;
private Integer nAtom;
@Length(max = 8192)
private String name;
public HetGroup(PDBEntry pdbEntry, String hetId) {
this.pdbEntry = pdbEntry;
pdbEntry.getHetGroupSet().add(this);
this.hetId = hetId;
}
}
特别注意评论的@ NaturalId,我评论了这些因为我的数据中仍然有一些错误导致重复的hetgroups,所以我想我现在只删除它们的UNIQUE约束,但我忘了我我正在使用Lombok为我创建equals和hashcode方法,如下所示:
@EqualsAndHashCode(callSuper = false, of = { "pdbEntry", "hetId" })
这导致了重复HetId的错误。我修复了数据并恢复了@ NaturalId,现在一切正常。
全部谢谢!
答案 0 :(得分:2)
单一交易不应该是问题。
您的建议 - 将数据从pdbEntry复制到existingEntry是一个更好的解决方案。它对数据库密集程度要低得多,而且更容易阅读和理解正在发生的事情。
此外,如果您这样做,则不需要对更改的对象执行saveOrUpdate。 Hibernate实现了所谓的“透明持久性”......这意味着hibernate负责确定需要采用哪些数据操作来使对象与数据库同步。 hibernate中的“更新”操作不适用于已经持久的对象。见here
在这种情况下,代码看起来像这样(注意不需要更新):
public void updatePDBEntry(Set<PDBEntry> pdbEntrySet) {
for (PDBEntry pdbEntry : pdbEntrySet) {
PDBEntry existingEntry = findByAccessionCode(pdbEntry.getAccessionCode());
if (existingEntry != null) {
// copy relevant fields from pdbEntry to existing Entry - preferably with a method on PDBEntry
} else {
makePersistent(pdbEntry); // although better to just call _save_ (not saveOrUpdate) cause you know it's a new object
}
}
}
答案 1 :(得分:2)
不知怎的,这不起作用,它说由于重复的密钥,它无法插入对象,即使我可以在日志中看到它变为makeTransient()。
要了解此处发生的情况,首先需要了解Hibernate不会立即将更改写入数据库,更改会在会话中排队并在flush
时间写入。因此,即使您看到makeTransient()
被调用,这并不意味着在调用方法时实际上已从数据库中删除了相应的记录。当flush
发生时(通过flush()
明确调用commit()
,或者org.hibernate.Transaction.commit()
时执行,将执行SQL删除语句和其他待处理更改HQL查询)。这在文档中有很好的解释:
10.10. Flushing the Session
有时会话会执行 需要同步的SQL语句 JDBC连接的状态与 内存中保存的对象状态。这个 进程,称为 flush ,由 默认为以下几点:
在某些查询执行之前
- 来自
Session.flush()
- 来自
session.save()
SQL语句发布于 以下顺序:
- 所有实体插入的顺序与对应的对象相同 使用
保存Session.delete()
- 所有实体更新
- 所有集合删除
- 所有集合元素删除,更新和插入
- 所有收集插入
- 所有实体删除的顺序与相应的对象相同 已使用
删除 醇>01: @Transactional 02: public void updatePDBEntry(Set<PDBEntry> pdbEntrySet) { 03: for (PDBEntry pdbEntry : pdbEntrySet) { 04: PDBEntry existingEntry = findByAccessionCode(pdbEntry.getAccessionCode()); 05: if (existingEntry != null) { 06: log.debug("Remove previous version of PDBEntry {}", existingEntry); 07: makeTransient(existingEntry); 08: } 09: makePersistent(pdbEntry); 10: } 11: }
一个例外是对象使用 插入本机ID时生成 他们得救了。
...
所以,让我们再看一下你的代码:
session.delete()
session.save()
(在记忆中)flush()
(在记忆中)换句话说,你的问题与交易无关,它只与Hibernate的工作方式和你使用它的方式有关。
是否有其他方法可以确保makeTransient对makePersistent可见,同时仍然将其全部保留在事务中?
好吧,在不改变逻辑的情况下,你可以在delete()
之后明确地帮助Hibernate和{{1}}。
但IMO,整个方法都不是最理想的,你应该更新现有的记录,如果有的话,而不是删除然后插入(除非有充分的理由这样做)。