Java 如何确保事务中发生的数据库更改也被该事务中的所有人看到
我有一个在数据库中保存或更新一组对象的事务。代码如下:Java 如何确保事务中发生的数据库更改也被该事务中的所有人看到,java,hibernate,transactions,Java,Hibernate,Transactions,我有一个在数据库中保存或更新一组对象的事务。代码如下: @Transactional public void updatePDBEntry(Set<PDBEntry> pdbEntrySet) { for (PDBEntry pdbEntry : pdbEntrySet) { PDBEntry existingEntry = findByAccessionCode(pdbEntry.getAccessionCode()); if (existi
@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);
}
}
不知怎的,这不起作用,它说它无法插入对象,因为有一个重复的键,尽管我可以在日志中看到它进入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();
}
特别要注意注释的@naturaid,我对这些进行了注释,因为我的数据中仍然存在一些错误,导致了重复的组,所以我认为我现在应该删除对它们的唯一约束,但我忘记了我正在使用Lombok为我创建equals和hashcode方法,从这行代码中可以看出:
@EqualsAndHashCode(callSuper = false, of = { "pdbEntry", "hetId" })
这导致了重复HetId的错误。我修复了数据并恢复了@naturaid's,现在一切正常
谢谢大家 单笔交易不应该是问题所在 您的建议-将数据从pdbEntry复制到existingEntry是一个更好的解决方案。对于一个人来说,它的数据库密集度要低得多,并且更容易阅读和理解正在发生的事情 另外,如果您是这样做的,那么就不需要对更改的对象执行saveOrUpdate。Hibernate实现了所谓的“透明持久性”。。。这意味着hibernate负责确定需要使用哪些数据操作来将对象与数据库同步。hibernate中的“更新”操作不适用于已持久化的对象。看 在这种情况下,代码如下所示(注意:不需要更新):
public void updatepddbentry(Set pdbEntrySet){
用于(PDBEntry PDBEntry:pdbEntrySet){
PDBEntry existingEntry=findByAccessionCode(PDBEntry.getAccessionCode());
if(existingEntry!=null){
//将相关字段从pdbEntry复制到现有条目-最好使用pdbEntry上的方法
}否则{
makePersistent(pdbEntry);//尽管最好只调用_save(而不是saveOrUpdate),因为您知道它是一个新对象
}
}
}
单笔交易不应该是问题所在
您的建议-将数据从pdbEntry复制到existingEntry是一个更好的解决方案。对于一个人来说,它的数据库密集度要低得多,并且更容易阅读和理解正在发生的事情
另外,如果您是这样做的,那么就不需要对更改的对象执行saveOrUpdate。Hibernate实现了所谓的“透明持久性”。。。这意味着hibernate负责确定需要使用哪些数据操作来将对象与数据库同步。hibernate中的“更新”操作不适用于已持久化的对象。看
在这种情况下,代码如下所示(注意:不需要更新):
public void updatepddbentry(Set pdbEntrySet){
用于(PDBEntry PDBEntry:pdbEntrySet){
PDBEntry existingEntry=findByAccessionCode(PDBEntry.getAccessionCode());
if(existingEntry!=null){
//将相关字段从pdbEntry复制到现有条目-最好使用pdbEntry上的方法
}否则{
makePersistent(pdbEntry);//尽管最好只调用_save(而不是saveOrUpdate),因为您知道它是一个新对象
}
}
}
不知怎的,这不起作用,它说它无法插入对象,因为有一个重复的键,尽管我可以在日志中看到它进入makeTransient()
要了解这里发生了什么,首先需要了解Hibernate不会立即将更改写入数据库,更改将在会话中排队,并在flush
时间写入。因此,即使您看到调用了makeTransient()
,这并不意味着在调用该方法时,相应的记录实际上已从数据库中删除。SQL delete语句和其他挂起的更改将在flush
发生时执行(在commit()
时间显式调用flush()
,或在执行HQL查询时显式调用)。这在文档中有很好的解释:
有时会话将执行
同步所需的SQL语句
JDBC连接的状态与
保存在内存中的对象的状态。这
该过程称为flush,由
默认设置为以下几点:
- 在执行某些查询之前
- 从
org.hibernate.Transaction.commit()
- 从
Session.flush()
session.save()保存。
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: }
01:@Transactional
02:
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
@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;
}
}
@EqualsAndHashCode(callSuper = false, of = { "pdbEntry", "hetId" })
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
}
}
}
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: }