如何控制 Wicket 表单中的 JPA 持久性?

发布于 2024-10-23 22:54:28 字数 1035 浏览 3 评论 0原文

我正在使用 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 技术交流群。

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

发布评论

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

评论(2

嗼ふ静 2024-10-30 22:54:28

编辑

显而易见的解决方案是在加载实体(即行)以进行表单绑定时锁定该实体。这将确保拥有锁的请求干净地读取/绑定/写入,而不会在后台发生并发写入。这并不理想,因此您需要权衡潜在的性能问题(并发写入级别)。

除此之外,假设您对属性子组上的“最后写入获胜”感到满意,那么 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.

九厘米的零° 2024-10-30 22:54:28

经过一些实验我得出了答案。感谢@artbristol,他为我指明了正确的方向。

  1. 我在我的架构中设定了一条规则:只能调用 DAO 保存方法来保存分离的实体。如果实体已附加,则 DAO 会抛出 IllegalStateException。这有助于追踪修改事务外部实体的任何代码。
  2. 接下来,我修改了 LoadableDetachableModel 以具有两个变体。用于只读数据视图的经典变体从 JPA 返回实体,这将支持延迟加载。第二种变体用于表单绑定,使用 Dozer 创建本地副本。
  3. 我已将基本 DAO 扩展为具有两个保存变体。一种使用 merge 保存整个对象,另一种使用 Apache Beanutils 复制属性列表。

这至少可以避免重复的代码。缺点是需要配置 Dozer,这样它就不会通过遵循延迟加载的引用来拉入整个数据库,并且有更多的代码通过名称引用属性,从而放弃了类型安全。

After some experiments I've come up with an answer. Thanks to @artbristol, who pointed me in the right direction.

  1. I have set a rule in my architecture: DAO save methods must only be called to save detached entities. If the entity is attached, the DAO throws an IllegalStateException. This helped track down any code that was modifying entities outside a transaction.
  2. Next, I modified my 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.
  3. I have extended my base DAO to have two save variants. One saves the entire object using 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.

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