如何控制 Wicket 表单中的 JPA 持久性?
我正在使用 JPA 2.0(Hibernate 实现)、Spring 和 Wicket 构建一个应用程序。一切正常,但我担心我的表单行为是基于副作用。
第一步,我使用 OpenEntityManagerInViewFilter
。我的域对象由 LoadableDetachableModel
获取,该模型在其 load
方法中执行 entityManager.find()
。在我的表单中,我在该模型周围封装了一个CompoundPropertyModel
来绑定数据字段。
我关心的是表单提交操作。目前,我的表单提交将 form.getModelObject()
的结果传递到用 @Transactional
注释的服务方法中。由于模型内的实体仍然附加到实体管理器,因此 @Transactional
注释足以提交更改。
这很好,直到我有多个表单对同一实体进行操作,每个表单都会更改字段的子集。是的,它们可以同时访问。我想到了几个选项,但我想知道我错过的任何想法以及管理此问题以实现长期可维护性的建议:
- 将我的实体分段为与编辑表单相对应的子组件,并创建一个主组件将它们链接在一起形成
@OneToOne
关系的实体。 导致表格设计丑陋,并且很难在以后更改表单。 - 立即分离由
LoadableDetachableModel
加载的实体,并在服务层中手动合并正确的字段。 难以管理延迟加载,可能需要为每个表单提供专门的模型版本,以确保加载正确的子实体。 - 在为表单创建模型时将实体克隆到本地副本,然后手动合并服务层中的正确字段。 需要实现大量复制构造函数/克隆方法。
- 使用 Hibernate 的
dynamicUpdate
选项仅更新实体的更改字段。 导致整个应用程序出现非标准 JPA 行为。在受影响的代码中不可见,并且与 Hibernate 实现紧密相关。
I'm building an application using JPA 2.0 (Hibernate implementation), Spring, and Wicket. Everything works, but I'm concerned that my form behaviour is based around side effects.
As a first step, I'm using the OpenEntityManagerInViewFilter
. My domain objects are fetched by a LoadableDetachableModel
which performs entityManager.find()
in its load
method. In my forms, I wrap a CompoundPropertyModel
around this model to bind the data fields.
My concern is the form submit actions. Currently my form submits pass the result of form.getModelObject()
into a service method annotated with @Transactional
. Because the entity inside the model is still attached to the entity manager, the @Transactional
annotation is sufficient to commit the changes.
This is fine, until I have multiple forms that operate on the same entity, each of which changes a subset of the fields. And yes, they may be accessed simultaneously. I've thought of a few options, but I'd like to know any ideas I've missed and recommendations on managing this for long-term maintainability:
- Fragment my entity into sub-components corresponding to the edit forms, and create a master entity linking these together into a
@OneToOne
relationship. Causes an ugly table design, and makes it hard to change forms later. - Detach the entity immediately it's loaded by the
LoadableDetachableModel
, and manually merge the correct fields in the service layer. Hard to manage lazy loading, may need specialised versions of the model for each form to ensure correct sub-entities are loaded. - Clone the entity into a local copy when creating the model for the form, then manually merge the correct fields in the service layer. Requires implementation of a lot of copy constructors / clone methods.
- Use Hibernate's
dynamicUpdate
option to only update changed fields of the entity. Causes non-standard JPA behaviour throughout the application. Not visible in the affected code, and causes a strong tie to Hibernate implementation.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
编辑
显而易见的解决方案是在加载实体(即行)以进行表单绑定时锁定该实体。这将确保拥有锁的请求干净地读取/绑定/写入,而不会在后台发生并发写入。这并不理想,因此您需要权衡潜在的性能问题(并发写入级别)。
除此之外,假设您对属性子组上的“最后写入获胜”感到满意,那么 Hibernate 的“dynamicUpdate”似乎是最明智的解决方案,除非您考虑很快切换 ORM。我觉得很奇怪,JPA 似乎没有提供任何允许您只更新脏字段的东西,并且发现将来很可能会这样做。
其他(我原来的答案)
与此正交的是如何确保当您的模型加载用于表单绑定的实体时打开事务。令人担忧的是,实体属性会在此时更新,并且在事务之外,这会使 JPA 实体处于不确定状态。
正如阿德里安在评论中所说,显而易见的答案是使用传统的每个请求交易过滤器。这保证了请求中的所有操作都发生在单个事务中。但是,它肯定会对每个请求使用数据库连接。
有一个更优雅的解决方案,带有代码 这里。该技术是延迟实例化实体管理器,并仅在需要时(即,当第一次 EntityModel.getObject() 调用发生时)开始事务。如果在请求周期结束时有一个事务打开,则该事务将被提交。这样做的好处是永远不会浪费任何数据库连接。
给出的实现使用 wicket RequestCycle 对象(请注意,这在 v1.5 及以后版本中略有不同),但整个实现实际上相当通用,因此您可以通过 servlet Filter 在 wicket 之外使用它(例如)。
EDIT
The obvious solution is to lock the entity (i.e. row) when you load it for form binding. This would ensure that the lock-owning request reads/binds/writes cleanly, with no concurrent writes taking place in the background. It's not ideal, so you'd need to weigh up the potential performance issues (level of concurrent writes).
Beyond that, assuming you're happy with "last write wins" on your property sub-groups, then Hibernate's 'dynamicUpdate' would seem like the most sensible solution, unless your thinking of switching ORMs anytime soon. I find it strange that JPA seemingly doesn't offer anything that allows you to only update the dirty fields, and find it likely that it will in the future.
Additional (my original answer)
Orthogonal to this is how to ensure you have a transaction open when when your Model loads an entity for form binding. The concern being that the entities properties are updated at that point and outside of transaction this leaves a JPA entity in an uncertain state.
The obvious answer, as Adrian says in his comment, is to use a traditional transaction-per-request filter. This guarantees that all operations within the request occur in single transaction. It will, however, definitely use a DB connection on every request.
There's a more elegant solution, with code, here. The technique is to lazily instantiate the entitymanager and begin the transaction only when required (i.e. when the first EntityModel.getObject() call happens). If there is a transaction open at the end of the request cycle, it is committed. The benefit of this is that there are never any wasted DB connections.
The implementation given uses the wicket RequestCycle object (note this is slightly different in v1.5 onwards), but the whole implementation is in fact fairly general, so and you could use it (for example) outwith wicket via a servlet Filter.
经过一些实验我得出了答案。感谢@artbristol,他为我指明了正确的方向。
IllegalStateException
。这有助于追踪修改事务外部实体的任何代码。LoadableDetachableModel
以具有两个变体。用于只读数据视图的经典变体从 JPA 返回实体,这将支持延迟加载。第二种变体用于表单绑定,使用 Dozer 创建本地副本。merge
保存整个对象,另一种使用 Apache Beanutils 复制属性列表。这至少可以避免重复的代码。缺点是需要配置 Dozer,这样它就不会通过遵循延迟加载的引用来拉入整个数据库,并且有更多的代码通过名称引用属性,从而放弃了类型安全。
After some experiments I've come up with an answer. Thanks to @artbristol, who pointed me in the right direction.
IllegalStateException
. This helped track down any code that was modifying entities outside a transaction.LoadableDetachableModel
to have two variants. The classic variant, for use in read-only data views, returns the entity from JPA, which will support lazy loading. The second variant, for use in form binding, uses Dozer to create a local copy.merge
, and the other uses Apache Beanutils to copy a list of properties.This at least avoids repetitive code. The downsides are the requirement to configure Dozer so that it doesn't pull in the entire database by following lazy loaded references, and having yet more code that refers to properties by name, throwing away type safety.