@Transactional注释方法有时会抛出NonUniqueObjectException

时间:2013-07-19 18:40:29

标签: java hibernate transactions

我有以下由@Transactional管理的HibernateTransactionManager方法:

@Transactional(rollbackFor = NoActiveTraceException.class)
public void insertTraceEvent(TraceEvent traceEvent) throws NoActiveTraceException {
    Trace trace = traceDao.findActiveForNumber(traceEvent.getSourceParty());
    if (trace == null) {
        throw new NoActiveTraceException(traceEvent.getSourceParty()); // custom
    } else {
        traceEvent.setTrace(trace);
        trace.getTraceEvents().add(traceEvent); // returns set to which I can add
        // there is a Cascade.All on the @OneToMany set

        traceDao.create(traceEvent);
    }
}

// ...in traceDao
@Override
public Long create(TraceEvent traceEvent) {
    getCurrentSession().persist(traceEvent);
    return traceEvent.getTraceEventId();
}

TraceTraceEvent是由Hibernate管理的实体。

当我在开发期间测试此代码时,我从未遇到任何问题。但是在生产中,它会不时地抛出

org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [myobject#2664] 
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:180) ~[hibernate-core-4.1.9.Final.jar:4.1.9.Final]
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:136) ~[hibernate-core-4.1.9.Final.jar:4.1.9.Final]
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:208) ~[hibernate-core-4.1.9.Final.jar:4.1.9.Final]
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:151) ~[hibernate-core-4.1.9.Final.jar:4.1.9.Final]
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:78) ~[hibernate-core-4.1.9.Final.jar:4.1.9.Final]
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:843) ~[hibernate-core-4.1.9.Final.jar:4.1.9.Final]
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:818) ~[hibernate-core-4.1.9.Final.jar:4.1.9.Final]
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:822) ~[hibernate-core-4.1.9.Final.jar:4.1.9.Final]
at com.package.model.dao.impl.TraceDaoHibernateImpl.create(TraceDaoHibernateImpl.java:116) ~[TraceDaoHibernateImpl.class:na]

试图坚持下去。我想不出会导致这种情况的prod和dev之间的任何区别。此过程独立于所有事务(并在事务中运行)。

我理解,一旦我调用.add(traceEvent),它就会弄脏PersistentSet(并且可能persist traceEvent。然后当我调用create(traceEvent)时,它也可以尝试persist,从而在Hibernate Session中添加相同的对象。

为什么会发生异常,为什么会以这样的随机间隔发生?

编辑实体

@Entity
@Table(name = "TRACE_EVENT")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class TraceEvent {
    @Id
    @GenericGenerator(name = "generator", strategy = "increment")
    @GeneratedValue(generator = "generator")
    @Column(name = "trace_event_id")
    private Long traceEventId;

    @Column(nullable = false)
    private Calendar transmissionDate;

    @Column(nullable = false)
    private String sourceParty; 

    @Column
    private String destinationParty;

    @ManyToOne(optional = false)
    @JoinColumn(name = "trace_id")
    private Trace trace;

并且

@Entity
@Table(name = "TRACE")
public class Trace {
    @Id
    @GenericGenerator(name = "generator", strategy = "increment")
    @GeneratedValue(generator = "generator")
    @Column(name = "trace_id")
    private Long traceId;

    @Column(nullable = false)
    private String number;

    @Column(nullable = false)
    private Calendar startDate;

    @Column
    private Calendar endDate;   

    @Column(nullable = false)
    private Boolean isActive = false; // default value of false

    // USED TO BE CASCADE.ALL
    @OneToMany(fetch = FetchType.LAZY, cascade = {CascadeType.REMOVE}, mappedBy = "trace")
    private Set<TraceEvent> traceEvents = new HashSet<>();  

    @ManyToOne
    private Account account;

1 个答案:

答案 0 :(得分:1)

自从我上次使用Hibernate以来已经有一段时间了,这个问题很难解决,所以我只想描述各种选项:

  • 由于您的Trace对象有一个Cascade.All选项,仅仅在集合上添加TraceEvent并调用session.save(trace)是不够的?就像现在一样,我的印象是,当会话关闭时,它会尝试保存Trace对象。或者避免修改集合。只需保存TraceEvent即可。您不会返回Trace以供使用,因此修改它没有意义。下次使用时,它可能会从数据库中加载。以上所有行为可能取决于您的配置,但值得一看。

  • 你使用某种休眠缓存吗?可能是Trace对象可能不是最新版本吗?

  • 如何创建TraceEvent?可能是这个对象是分离的(在先前的请求中加载并现在重新使用?)似乎不太可能,但请检查。

  • 这似乎是一个可以从很多地方调用的函数(即记录用户的动作)。是否有可能其中一个地方正在使用相同的会话并可能修改它?

  • 假设它是从很多地方调用的,您是否可以从日志中将错误与某些特定的业务功能相关联(例如,当用户单击该链接/按钮等时)?

  • 如果所有其他方法都失败了,为什么不在下次更新生产环境时尝试添加EventListenerInterceptor on persist事件(我知道您无法复制此内容)很容易出错)。这将是一个简单的抓住NonUniqueObjectException它可以打印额外的日志信息,然后只是重新抛出