当我的 Hibernate 事务由 Spring 管理时,如何启用 Hibernate 拦截器?
如果我与 @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 时不保留我的实体及其关系拦截器以及我应该做什么作为解决方法才能正常工作???
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
Spring 维护当前会话和当前事务之间的关联(请参阅 SessionFactoryUtils。 java。)由于已经有一个与当前 DAO 方法调用关联的会话,因此您必须使用此会话,或者冒险参与将新会话与先前事务上下文关联的模糊细节。这可能是可能的,但风险相当大,绝对不推荐。在休眠中,如果您已经打开了一个会话,那么应该使用它。
话虽如此,您也许可以让 spring 为您创建一个新会话并将其与当前事务上下文关联起来。使用 SessionFactoryUtils.getNewSession(SessionFactory, Interceptor)。如果您使用它而不是 hibernate 的 sessionFactory,那么这应该保持与事务的关联。
最初,您可以直接在 DAO 中对其进行编码。当它经过尝试和测试并希望发现可以工作时,您可以采取措施将 spring 代码移出 DAO,例如使用 AOP 将建议添加到创建和清理新会话的 add() 方法。
另一种选择是使用全局拦截器。即使它是全局的,您也可以赋予它本地可控的行为。 TransientInterceptor 包含一个
threadLocal
。这是当前线程的标志,指示拦截器是否应为isTransient
返回 true。您可以在 add() 方法开始时将其设置为 true ,并在最后将其清除。例如,然后在您的 DAO 中:
您可以将 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 forisTransient
. You set it to true at the start of the add() method and clear it at the end. E.g.And then in your DAO:
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.在“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