当我拥有Spring管理的Hibernate事务时,如何启用Hibernate Interceptor?

Art*_*ald 6 java performance spring hibernate interceptor

如果我与@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;
    }        

}
Run Code Online (Sandbox Code Playgroud)

很多课

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;
    }

}
Run Code Online (Sandbox Code Playgroud)

如果我有以下场景

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()));
Run Code Online (Sandbox Code Playgroud)

我打电话给

sessionFactory.getCurrentSession().save(one);
Run Code Online (Sandbox Code Playgroud)

继续之前

根据Transitive持久性 Hibernate参考文档,您可以看到

如果父节点传递给save(),update()或saveOrUpdate(),则所有子节点都传递给saveOrUpdate()

好.现在让我们看看Java Persistence With Hibernate一书中有关saveOrUpdate方法的内容

Hibernate 在MANY表中查询给定的id,如果找到,则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 (?, ?)
Run Code Online (Sandbox Code Playgroud)

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

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

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

这是我的存储库

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

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

    @Autowired
    private SessionFactory sessionFactory;

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

}
Run Code Online (Sandbox Code Playgroud)

之后(使用Hibernate Inteceptor):出现问题(没有执行SQL查询 - 无论是INSERT还是SELECT-BEFORE-SAVING)

@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);
    }

}
Run Code Online (Sandbox Code Playgroud)

我的问题是:为什么Spring在使用Hibernate拦截器时不会持久保存我的实体及其关系,我该怎么办才能解决这个问题呢?

mdm*_*dma 3

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

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

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

另一种选择是使用全局拦截器。即使它是全局的,您也可以赋予它本地可控的行为。TransientInterceptor 包含一个threadLocal<Boolean>. 这是当前线程的标志,指示拦截器是否应返回 true 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);
      }
   }
Run Code Online (Sandbox Code Playgroud)

然后在你的 DAO 中:

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

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