java - 当我的 Hibernate 事务由 Spring 管理时如何启用 Hibernate 拦截器?

标签 java performance hibernate spring 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;
    }        

}

还有很多类

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引用文档,可以看

If a parent is passed to save(), update() or saveOrUpdate(), all children are passed to saveOrUpdate()

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

Hibernate queries the MANY table for the given id, and if it is found, Hibernate updates the row. If it is not found, insertion of a new row is required and done.

可以翻译成

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 拦截器分配给一个 Hibernate session 来改进我的代码,其事务是由 Spring 管理

这是我的仓库

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

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

}

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

最佳答案

Spring 维护当前 session 和当前事务之间的关联(请参阅 SessionFactoryUtils.java 。)由于已经存在与当前 DAO 方法调用相关联的 session ,因此您必须使用此 session ,或者冒险参与其中以及将新 session 与先前的事务上下文相关联的模糊细节。这可能是可能的,但有相当大的风险,绝对不推荐。在 hibernate 状态下,如果您已经打开了一个 session ,那么应该使用它。

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

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

另一种选择是使用全局拦截器。即使它是全局的,您也可以赋予它本地可控的行为。 TransientInterceptor 包含一个 threadLocal<Boolean> .这是当前线程的标志,用于指示拦截器是否应为 isTransient 返回 true .您在 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 添加方法。

关于java - 当我的 Hibernate 事务由 Spring 管理时如何启用 Hibernate 拦截器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3055761/

相关文章:

java - 如何在 Spring Boot 的 native 查询中使用 all(array[])?

java - 如何找出导致 poi 损坏 xlsx/xlsm 文件的原因

java - Hibernate 按列映射集合

java - JPA - 如何在不构造父对象的情况下构造 ManyToOne 关系的子对象

java - 如何从 XML 节点获取文本而不修剪两个 unicode 字符之间的空格

java - 一次性将多个 Java Web 应用程序部署到 Glassfish

performance - Python列表追加和扩展 - 速度慢

c++ - C 与 C++ 在内存分配方面的性能

c# - JIT 编译器是否优化(内联)不必要的变量声明?

hibernate - 仅当子方法中发生异常时,才仅对事务进行回滚