【问题标题】:How to control JPA persistence in Wicket forms?如何控制 Wicket 表单中的 JPA 持久性?
【发布时间】:2011-03-18 09:41:32
【问题描述】:

我正在使用 JPA 2.0(Hibernate 实现)、Spring 和 Wicket 构建应用程序。一切正常,但我担心我的表单行为是基于副作用。

作为第一步,我使用的是OpenEntityManagerInViewFilter。我的域对象由LoadableDetachableModel 获取,它在其load 方法中执行entityManager.find()。在我的表单中,我在这个模型周围包裹了一个CompoundPropertyModel 来绑定数据字段。

我关心的是表单提交操作。目前,我的表单提交将form.getModelObject() 的结果传递到使用@Transactional 注释的服务方法中。因为模型内部的实体仍然附加到实体管理器,@Transactional 注释足以提交更改。

这很好,直到我有多个表单对同一个实体进行操作,每个表单都会更改字段的子集。是的,它们可以同时访问。我已经想到了几个选项,但我想知道我错过的任何想法以及有关管理它以实现长期可维护性的建议:

  • 将我的实体分割成对应于编辑表单的子组件,并创建一个主实体将它们链接在一起形成@OneToOne 关系。 导致表格设计难看,并且以后很难更改表格。
  • 立即分离由LoadableDetachableModel 加载的实体,并手动合并服务层中的正确字段。 很难管理延迟加载,可能需要为每个表单提供专门的模型版本,以确保加载正确的子实体。
  • 在为表单创建模型时将实体克隆到本地副本,然后在服务层中手动合并正确的字段。 需要实现很多复制构造函数/克隆方法。
  • 使用 Hibernate 的 dynamicUpdate 选项仅更新实体的更改字段。 在整个应用程序中导致非标准 JPA 行为。在受影响的代码中不可见,并导致与 Hibernate 实现密切相关。

【问题讨论】:

  • 欢迎来到我的噩梦 ;),我们遇到了同样的问题。如果您使用带有就地编辑的列表/表格并将新条目添加到列表中,您也会遇到此问题。由于 JPA merge() 返回一个新副本并且您可能会保留 UI 层中的陈旧对象。我认为唯一干净的方法是使用 DTO(或您从不附加到 EM 的 JPA 实体)。许多丑陋的代码来来回复制更改。我在这里期待答案。当我在这里或在检票口 ML 上问时,我从来没有得到过一次 ..
  • 如果您使用克隆选项,您可以考虑使用像 Dozer 这样的 bean 映射器来节省您编写代码的时间。但是不要它如何与延迟加载的 bean 属性交互。
  • 我现在正在尝试 Dozer 方法 - 我的 @ElementCollection 列表字段似乎有问题,可能是由于缺少公共设置器。
  • 同一页面中的多个表单,还是多个窗口?我没有发现问题...“同时”是什么意思?
  • @tetsuo - 我的意思是不同会话中的多个用户。举个简单的例子,A​​lice 打开联系地址表单,而 Bob 打开针对同一客户的定价表单。如果 Alice 先点击“保存”,那么当 Bob 点击“保存”时,她的变化会发生什么?实际情况稍微复杂一些,因为有些表单是具有就地编辑功能的表格。

标签: java wicket jpa-2.0


【解决方案1】:

编辑

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

除此之外,假设您对属性子组的“最后写入获胜”感到满意,那么 Hibernate 的“动态更新”似乎是最明智的解决方案,除非您考虑很快切换 ORM。我觉得奇怪的是,JPA 似乎没有提供任何允许您只更新脏字段的东西,并且发现它很可能在将来会这样做。

附加(我的原始答案)

与此正交的是如何确保在您的模型加载实体以进行表单绑定时打开事务。令人担忧的是,实体属性会在该点和事务之外更新,这会使 JPA 实体处于不确定状态。

正如 Adrian 在他的评论中所说,显而易见的答案是使用传统的每请求事务过滤器。这保证了请求中的所有操作都发生在单个事务中。但是,它肯定会在每个请求上使用数据库连接。

有一个更优雅的解决方案,代码为here。该技术是延迟实例化实体管理器并仅在需要时开始事务(即当第一个 EntityModel.getObject() 调用发生时)。如果在请求周期结束时有一个事务打开,则它被提交。这样做的好处是永远不会浪费任何数据库连接。

给出的实现使用 wicket RequestCycle 对象(请注意,这在 v1.5 及更高版本中略有不同),但整个实现实际上是相当通用的,因此您可以(例如)通过 servlet 过滤器将它与 wicket 一起使用.

【讨论】:

  • 现在这是一个我以前从未见过的有趣答案。我会试一试,看看它对我有用。
  • 我现在详细查看了您的链接,并实现了我自己的变体,它使用 Spring 的 PlatformTransactionManager 来控制事务。效果很好,我的代码也清晰多了。
  • @Adrian - 你能分享一下代码吗?我很想看看它如何与@Transactional 注释交互。
  • @DuncanMcGregor - 我已将代码分离到一个库中,我会尽快发布。 @Transactional 注释不能很好地工作,但这并没有给我带来太大的问题。几乎我的所有代码都是通过 Wicket 请求周期或通过 CXF 访问的,其中每一个都允许我轻松拦截请求结束并提交事务。我的服务层总是通过JpaHandle 类提取实体管理器,并且在计时器线程上运行的少数服务手动管理它们的事务。
【解决方案2】:

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

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

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

【讨论】:

  • 我仍然很难理解为什么这些都是必要的。在您给出的示例中:“Alice 打开联系地址表单,而 Bob 打开针对同一客户的定价表单。如果 Alice 先点击‘保存’,当 Bob 点击‘保存’时,她的变化会发生什么?”。答案是,只要 Bob 提交的表单没有绑定到任何这些字段,Alice 的更改什么都不会发生。当 Bob 提交时,表单模型会加载实体(现在有 Alice 的更改)。然后使用 Bob 表单字段中的数据更新实体。
  • 其中一个关键驱动因素是表单在我的页面内的代码运行之前更新字段,因此在服务层有机会begin 事务之前。我可能会跳过所有这些,因为有明确的证据表明 Wicket 在调用begin 之前修改实体属性是安全且无竞争的,或者用新的BeginTransactionInViewFilter 替换OpenEntityManagerInViewFilter 没有性能问题。否则(根据en.wikibooks.org/wiki/Java_Persistence/Transactions)我依赖于未定义的行为。
  • 我看到您的担忧现在可能是竞争条件。
猜你喜欢
  • 2011-04-21
  • 2021-02-17
  • 1970-01-01
  • 1970-01-01
  • 2014-06-25
  • 2013-02-15
  • 1970-01-01
  • 2014-03-28
  • 1970-01-01
相关资源
最近更新 更多