JPA/Hibernate - 不需要的部分回滚和会话处理

Posted

技术标签:

【中文标题】JPA/Hibernate - 不需要的部分回滚和会话处理【英文标题】:JPA/Hibernate - Undesired Partial Rollback and Session Handling 【发布时间】:2015-10-10 01:31:39 【问题描述】:

我正在使用无状态 EJB 类来更新位于数据库中的持久性实体。 EJB 中的方法调用完成工作的实现类。我相信导致问题的原因是一个名为Foo 的实体与一个实体Bar 具有oneToMany 关系。事情已经完成,会话更新为Foo,它“级联”到Bar。当StaleObjectStateException 发生时,事务没有完全回滚,这会导致错误,原因很明显。

EJB

private Session getSession() throws BusinessException 

    if( this.sess == null ) 
            ServiceLocator locator = new ServiceLocator();
            SessionFactory sf = locator.getHibernateSessionFactory();
            this.sess = sf.openSession();
    
    return this.sess;



private ProductionOrderImpl getImpl() throws BusinessException 

    if( this.impl == null ) 
        this.impl = new ProductionOrderImpl( getSession() );
    
    return this.impl;



public void cutoffOrders(  ) throws Exception 

    Transaction tx = null;
    try 
        tx = getSession().beginTransaction();
        getImpl().cutOffFoos(fooTime);
        tx.commit();
     catch (StaleObjectStateException e1)
        if (tx != null) tx.rollback();
        logger.error( "Failed to cutoff order : " + e1 );
        throw new Exception( LocaleMgr.getMessage());
     
      finally 
        // reset implementation object, close session,
        // and reset session object
        impl = null;
        sess.close();
        sess = null;
       

实施

public ProductionOrderImpl(Session sess) 
    this.sess = sess;


public void cutoffFoos(  Timestamp fooTime) throws Exception 
    ... Code that gets fooList ...
    if( fooList != null ) 
        for( Foo foo: fooList ) 
            for( Bar bar : foo.getBarList() ) 
                 ... Code that does things with existing Barlist ...
                 if( ... ) 
                     ... Code that makes new Bar object ...
                     foo.getBarList().add(bar2);
                 
            
            sess.update( foo );
        
    

相关 Foo 代码

@OneToMany(cascade=CascadeType.ALL, mappedBy="foo")
@OrderBy("startTime DESC")
Set<Bar> barList;

所以基本上,当事务尝试回滚时,已更改的 Bar 部分会回滚,但新的 Bar(代码中的 bar2)记录仍然存在..

任何指导将不胜感激。就像我说的,我相信这里的错误与 sess.update(foo); 有关。可能与autocommit 有关,但默认情况下它应该是关闭的。

我相信正在发生的事情是 Session.Update(foo) 反过来创建了两个单独的事务。具体来说,Foo 已更新 (SQL UPDATE),但 Bar 已保存 (SQL INSERT)。由于事务上下文只会真正看到 SQL UPDATE,这就是它的全部反转。将不得不对此进行更多研究..

我已尝试将Session.FlushMode 更改为COMMIT,但似乎仍无法解决问题。但是,它确实部分解决了问题。它会正确回滚条目,但导致 StaleObjectStateException 的特定条目除外。该特定条目实际上已从数据库中删除...

【问题讨论】:

您使用的是什么版本的 EJB?您的持久性提供者是什么?你为什么要 EJB 调用另一个实现类(什么类型?),那个 ,,other'' 类如何访问sess(我假设某种持久性会话)?如果您使用的是 EJB,那么您真的应该使用 Container managed transactions 和 EntityManager 而不是自定义 tx 和会话。关于您的问题,我猜想 EJB 中的 sess 对象与您的 impl 类中的 sess 不同。 sess 通过构造函数传递给 impl。你可能是对的。我已经解决了我的问题,但我会调查你所说的,因为我认为这将是一个更干净的解决方案。 从您的描述来看,这听起来不是这个特殊问题,但请注意,为了使条正确关联到 foo,需要设置条上的 'foo' 成员正确(mappedBy="foo")。 CASCADE 将 session.update() 调用转发到 barList 上的 bar,但关联仍由“mappedBy”管理。 您的 getSession() 方法看起来如何?分别,Hibernate是如何配置的?但是你真的应该尝试使用容器管理的事务并注入 @PersistenceContext 而不是 getSession() 和 getSessin.beginTransaction() & try/catch/finally。 Michal 的问题是我无法更改代码的体系结构,因为它是生产中的代码。从字面上看,它用于生产制成品。资源根本无法改变这些事情。不过谢谢你的建议。 【参考方案1】:

好吧,我设法解决了我的问题。我会等待接受它,以防其他人发布更好的东西,更多的东西……值得赏金。 p>

基本上,通过将FlushMode 更改为手动并在整个过程中手动刷新,我可以更早地捕获StaleObjectException,从而更快地退出代码。我仍然有部分回滚记录的工件。但是,此方法按计划每 2 分钟运行一次,因此在第二次通过时,它修复了所有问题。

我将 EJB 更改为以下内容:

public void cutoffOrders(  ) throws Exception 
  Transaction tx = null;
  try 
      tx = getSession().beginTransaction();
      getSession().setFlushMode(FlushMode.MANUAL);
      getImpl().cutOffFoos(fooTime);
      getSession().flush();
      tx.commit();
   catch (StaleObjectStateException e1)
      if (tx != null) tx.rollback();
      logger.error( "Failed to cutoff order : " + e1 );
      throw new Exception( LocaleMgr.getMessage());
   
    finally 
      // reset implementation object, close session,
      // and reset session object
      impl = null;
      sess.close();
      sess = null;
     

然后实现代码有如下:

public void cutoffFoos(  Timestamp fooTime) throws Exception 
  ... Code that gets fooList ...
  if( fooList != null ) 
      for( Foo foo: fooList ) 
          for( Bar bar : foo.getBarList() ) 
               ... Code that does things with existing Barlist ...
               sess.flush();
               if( ... ) 
                   ... Code that makes new Bar object ...
                   foo.getBarList().add(bar2);
               
          
          sess.flush();
          sess.update( foo );
      
  

【讨论】:

【参考方案2】:

嗯,这是我的两分钱,因为这也与 JPA 有关:

在 Spring Data JPA 中,您可以使用以下内容:

1.@Transactional 在调用仓库之前注解(处理回滚)

2.使用JPA存储库saveAndFlush方法,即:

@Service
public class ProductionOrderServiceImpl extends ProductionOrderService

    @Autowired
    ProductionOrderRepository jpaRepository;

    @Transactional
    public void cutoffOrders( Timestamp fooTime )

    ... Code that gets fooList ...
      if( fooList != null ) 
          for( Foo foo: fooList ) 
              for( Bar bar : foo.getBarList() ) 
                   ... Code that does things with existing Barlist ...
                   Call another similar method with @transactional..//saveAndFlush(BarList);
                   if( ... ) 
                       ... Code that makes new Bar object ...
                       foo.getBarList().add(bar2);
                   
              
              jpaRepository.saveAndFlush(foo);
          
      

    


内部保存和刷新是这样的:

/*
     * (non-Javadoc)
     * @see org.springframework.data.repository.CrudRepository#save(java.lang.Object)
     */
@Transactional
public <S extends T> S save(S entity) 

    if (entityInformation.isNew(entity)) 
        em.persist(entity);
        return entity;
     else 
        return em.merge(entity);
    

然后是 em.flush();

如果在此之后,如果您遇到@Audited 版本控制问题,删除的记录不显示,则设置 org.hibernate.envers.store_data_at_delete=true。

希望为解决方案增加视角。

【讨论】:

我没有使用 Spring,所以并不完全适用.. 只是有点。但是由于您添加了一些见解,并且没有其他人回答..有一些代表。感谢您的努力。

以上是关于JPA/Hibernate - 不需要的部分回滚和会话处理的主要内容,如果未能解决你的问题,请参考以下文章

仅在不处于自动提交模式时回滚和提交

k8s在jenkins构建,如何回滚和升级23

ORACLE 回滚和触发

关于oracle实例恢复的前滚和回滚的理解

Windows使用Jenkins构建前后端分离项目+项目回滚和备份

openge sql 触发器的回滚和提交效果