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

发布于 2024-09-06 02:15:51 字数 3920 浏览 2 评论 0 原文

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

在继续之前

根据 传递持久化 Hibernate参考文档,可以查看

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

即可。现在让我们看看《Java Persistence With Hibernate》一书谈论了什么 saveOrUpdate 方法

Hibernate 查询多个表中的给定 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 (?, ?)

任何解决方法进行翻译以避免在保存之前选择???是的,您可以

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

作为一种避免保存前选择的方法使用这种级联时的默认行为,我通过将 Hibernate 拦截器分配给 Hibernate 会话其事务由 Spring 管理来改进我的代码。

这是我的存储库

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

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

    @Autowired
    private SessionFactory sessionFactory;

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

}

After(使用 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 时不保留我的实体及其关系拦截器以及我应该做什么作为解决方法才能正常工作???

If i have a @OneToMany relationship with @Cascade(CascadeType.SAVE_UPDATE) as follows

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

}

And Many class

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

}

If i have The following scenario

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()));

And i call

sessionFactory.getCurrentSession().save(one);

Before going on

According to Transitive persistence Hibernate reference documentation, you can see

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

ok. Now Let's see what Java Persistence With Hibernate book Talks about saveOrUpdate method

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.

Which can be translated according to

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 (?, ?)

Any workaround To avoid select-before-saving ??? Yes, You can either

  • Add a @Version column (Not applied)
  • Implement isTransient method provided by Hibernate interceptor (The option i have)

So as a way to avoid select-before-saving default behavior when using this kind of cascading, i have improved my code by assigning a Hibernate Interceptor to a Hibernate Session whose Transaction is managed by Spring.

Here goes my repository

Before (Without any Hibernate Interceptor): It works fine!

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

    @Autowired
    private SessionFactory sessionFactory;

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

}

After (With Hibernate Inteceptor): something goes wrong (No SQL query is performed - Neither INSERT Nor 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);
    }

}

My question is: Why Spring does not persist my Entity and its relationships when using Hibernate Interceptor and what should i do as workaround to work fine ???

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

愛放△進行李 2024-09-13 02:15:51

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

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

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

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

Spring maintains an association between the current session and the current transaction (see SessionFactoryUtils.java.) Since there is already a session associated for the current DAO method call, you have to use this Session, or take the plunge of getting involved with the murky details of associating the new session with the previous transaction context. It's probably possible, but with considerable risk, and is definitely not recommended. In hibernate, if you have a session already open, then it should be used.

Having said that, you may be able to get spring to create a new session for you and associate it with the current transaction context. Use SessionFactoryUtils.getNewSession(SessionFactory, Interceptor). If you use this rather than hibernate's sessionFactory, then this should keep the association with the transaction.

Initially, you can code this up directly in the DAO. When it's tried and tested and hopefully found to be working, you can then take steps to move the spring code out of your DAO, such as using AOP to add around advice to the add() methods that create and clean up new session.

Another alternative is to use a global Interceptor. Even though it's global, you can give it locally controllable behaviour. The TransientInterceptor contains a threadLocal<Boolean>. This is the flag for the current thread to indicate if the interceptor should return true for isTransient. You set it to true at the start of the add() method and clear it at the end. E.g.

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

And then in your DAO:

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

You can then setup TransientInterceptor as a global interceptor on the SessionFactory (e.g. LocalSessionFactoryBean.) To make this less invasive, you could create an AOP around advice to apply this behaviour to all your DAO add methods, where appropriate.

陌伤ぢ 2024-09-13 02:15:51

在“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

In the 'after' method you are creating a new session and not flushing it, therefore no update is sent to the database. This has nothing to do with Spring, but is pure Hibernate behavior.

What you probably want is adding an (entity) interceptor to the sessionFactory, probably configured using Spring. You can then just keep your repository's add() method as before.
See http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/orm/hibernate3/LocalSessionFactoryBean.html#setEntityInterceptor%28org.hibernate.Interceptor%29

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文