7

如果我与@Cascade(CascadeType.SAVE_UPDATE) 有@OneToMany 关系,如下所示

public class One {

    private Integer id;

    private List<Many> manyList = new ArrayList<Many>();

    @Id
    @GeneratedValue
    public Integer getId() {
        return this.id;
    }

    @OneToMany
    @JoinColumn(name="ONE_ID", updateable=false, nullable=false)
    @Cascade(CascadeType.SAVE_UPDATE)
    public List<Many> getManyList() {
        return this.manyList;
    }        

}

和许多类

public class Many {

    private Integer id;

    /**
      * required no-arg constructor
      */ 
    public Many() {}

    public Many(Integer uniqueId) {
        this.id = uniqueId
    }

    /**
      * Without @GeneratedValue annotation
      * Hibernate will use assigned Strategy
      */ 
    @Id
    public Integer getId() {
        return this.id;
    }

}

如果我有以下情况

One one = new One();

/**
  * generateUniqueId method will Take care of assigning unique id for each Many instance
  */
one.getManyList().add(new Many(generateUniqueId()));
one.getManyList().add(new Many(generateUniqueId()));
one.getManyList().add(new Many(generateUniqueId()));
one.getManyList().add(new Many(generateUniqueId()));

我打电话给

sessionFactory.getCurrentSession().save(one);

在继续之前

根据Transitive persistence Hibernate参考文档,可以看到

如果将父级传递给 save()、 update() 或 saveOrUpdate(),则所有子级都传递给 saveOrUpdate()

好的。现在让我们看看 Java Persistence With Hibernate 这本书讲了 saveOrUpdate 方法

Hibernate查询给定 id 的 MANY 表如果找到,Hibernate更新该行如果未找到,则需要插入新行并完成。

可以根据哪个翻译

INSERT INTO ONE (ID) VALUES (?)

/**
  * I have four Many instances added To One instance
  * So four select-before-saving
  *
  * I DO NOT NEED select-before-saving 
  * Because i know i have a Fresh Transient instance
  */
SELECT * FROM MANY WHERE MANY.ID = ?
SELECT * FROM MANY WHERE MANY.ID = ?
SELECT * FROM MANY WHERE MANY.ID = ?
SELECT * FROM MANY WHERE MANY.ID = ?

INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?)
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?)
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?)
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?)

任何解决方法以避免保存前选择???是的,您可以

  • 添加 @Version 列(未应用)
  • 实现 Hibernate 拦截器提供的 isTransient 方法(我有这个选项

因此,作为一种在使用这种级联时避免选择前保存默认行为的方法,我通过将 Hibernate Interceptor 分配给其 Transaction 由 Spring 管理的 Hibernate Session 来改进我的代码。

这是我的存储库

之前(没有任何休眠拦截器):它工作正常!

@Repository
public class SomeEntityRepository extends AbstractRepository<SomeEntity, Integer> {

    @Autowired
    private SessionFactory sessionFactory;

    @Override
    public void add(SomeEntity instance) {
        sessionFactory.getCurrentSession().save(instance);
    }

}

之后(使用 Hibernate Inteceptor):出现问题(不执行 SQL 查询 - 既不插入也不选择-前保存)

@Repository
public class SomeEntityRepository extends AbstractRepository<SomeEntity, Integer> {

    @Autowired
    private SessionFactory sessionFactory;

    @Override
    public void add(SomeEntity instance) {
        sessionFactory.openSession(new EmptyInterceptor() {
            /**
              * To avoid select-before-saving
              */
            @Override
            public Boolean isTransient(Object o) {
                return true;
            }
        }).save(instance);
    }

}

我的问题是:为什么 Spring 在使用 Hibernate Interceptor 时不保留我的实体及其关系,我应该怎么做才能正常工作?

4

2 回答 2

3

Spring 维护当前会话和当前事务之间的关联(参见SessionFactoryUtils.java。)由于已经有一个与当前 DAO 方法调用关联的会话,因此您必须使用此 Session,或者冒险卷入其中将新会话与先前事务上下文相关联的详细信息。这可能是可能的,但有相当大的风险,绝对不推荐。在休眠中,如果您已经打开了一个会话,那么应该使用它。

话虽如此,您也许可以让 spring 为您创建一个新会话并将其与当前事务上下文相关联。使用SessionFactoryUtils.getNewSession(SessionFactory, Interceptor). 如果你使用 this 而不是 hibernate 的 sessionFactory,那么这应该保持与事务的关联。

最初,您可以直接在 DAO 中编写代码。当它经过尝试和测试并希望发现它可以工作时,您可以采取措施将 spring 代码移出您的 DAO,例如使用 AOP 将周围的建议添加到创建和清理新会话的 add() 方法中。

另一种选择是使用全局拦截器。即使它是全局的,你也可以给它局部可控的行为。TransientInterceptor 包含一个threadLocal<Boolean>. 这是当前线程的标志,用于指示拦截器是否应为isTransient. 您在 add() 方法开始时将其设置为 true,并在结束时将其清除。例如

   class TransientInterceptor extends EntityInterceptor {
      ThreadLocal<Boolean> transientFlag = new ThreadLocal<Boolean)();
      public boolean isTransient() {
         return transientFlag.get()==Boolean.TRUE;
      }
      static public setTransient(boolean b) {
          transientFlag.set(b);
      }
   }

然后在你的 DAO 中:

@Override
public void add(SomeEntity instance) {
   try {
       TransientInterceptor.set(true);
       sessionFactory.getCurrentSession().save(instance);
   }
   finally {
      TransientInterceptor.set(false);   
   }
}

然后,您可以将 TransientInterceptor 设置为 SessionFactory 上的全局拦截器(例如LocalSessionFactoryBean。)。为了减少侵入性,您可以围绕建议创建一个 AOP,以便在适当的情况下将此行为应用于所有 DAO 添加方法。

于 2010-06-24T20:24:01.563 回答
0

在 'after' 方法中,您正在创建一个新会话而不是刷新它,因此不会向数据库发送更新。这与 Spring 无关,而是纯粹的 Hibernate 行为。

您可能想要的是向 sessionFactory 添加一个(实体)拦截器,可能是使用 Spring 配置的。然后,您可以像以前一样保留存储库的 add() 方法。见http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/orm/hibernate3/LocalSessionFactoryBean.html#setEntityInterceptor%28org.hibernate.Interceptor%29

于 2010-06-21T13:26:59.340 回答