gpt4 book ai didi

java - 如何确保事务中发生的数据库更改也能被该事务中的所有内容看到

转载 作者:行者123 更新时间:2023-12-01 19:21:30 26 4
gpt4 key购买 nike

我有一个事务可以保存或更新数据库中的一组对象。这是代码:

@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() 。我想这与所有这些都发生在事务内这一事实有关,因此 makeTransient() 所做的更改可能不会被 makePersistent() 方法看到。我可以通过将所有数据从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: 23503ERROR 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: 23503ERROR 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 sessionorg.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update

Any ideas how this error might arise?

EDIT: I found the problem: the last error was caused by the hetgroup model, which is as follows:

@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,现在一切正常。

谢谢大家!

最佳答案

Somehow this doesn't work, it says it can't insert the object because of a duplicate key, even though I can see in the logs that it gets to makeTransient().

要了解这里发生的情况,您首先需要了解 Hibernate 不会立即将更改写入数据库,更改会在 session 中排队并写入 flush时间。所以即使你看到makeTransient()被调用,这并不意味着调用该方法时相应的记录已从数据库中实际删除。 SQL 删除语句和其他挂起的更改将在 flush 时执行。将会发生(通过在 flush() 时间显式调用 commit() ,或执行 HQL 查询时)。文档对此进行了很好的解释:

10.10. Flushing the Session

Sometimes the Session will execute the SQL statements needed to synchronize the JDBC connection's state with the state of objects held in memory. This process, called flush, occurs by default at the following points:

  • before some query executions
  • from org.hibernate.Transaction.commit()
  • from Session.flush()

The SQL statements are issued in the following order:

  1. all entity insertions in the same order the corresponding objects were saved using session.save()
  2. all entity updates
  3. all collection deletions
  4. all collection element deletions, updates and insertions
  5. all collection insertions
  6. all entity deletions in the same order the corresponding objects were deleted using Session.delete()

An exception is that objects using native ID generation are inserted when they are saved.

...

那么,让我们再看看您的代码:

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: }
  • 在 04 处,您执行查询(这将刷新待处理的更改(如果有))
  • 在 07,您执行 session.delete() (内存中)
  • 在 09 点,您执行 session.save() (内存中)
  • 回到04,更改被刷新并且Hibernate按照上面指定的顺序发出SQL语句
    • 由于记录尚未删除,因此插入因关键违规而失败

换句话说,您的问题与事务无关,它只是与 Hibernate 的工作方式以及您使用它的方式有关。

Is there another way to make sure the makeTransient is visible to makePersistent, while still keeping it all within a transaction?

好吧,在不改变逻辑的情况下,你可以帮助 Hibernate 和 flush()明确在 delete() 之后.

但是在我看来,整个方法并不是最理想的,您应该更新现有记录(如果有),而不是删除然后插入(除非有充分的理由这样做)。

引用文献

关于java - 如何确保事务中发生的数据库更改也能被该事务中的所有内容看到,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4013007/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com