【问题标题】:Tomcat, JAX-WS, Hibernate - flaws and solutions?Tomcat、JAX-WS、Hibernate - 缺陷和解决方案?
【发布时间】:2015-02-13 09:49:25
【问题描述】:

我目前正在开发一个客户端 (java/swing) 服务器 (tomcat/hibernate/jax-ws) 应用程序,该应用程序需要许多数据库操作,并且应该能够在服务器端执行长时间运行的后台任务。我选择这种设置主要是为了更好地重用代码。

但是,可能还有许多其他人也面临过并找到解决方案的一些问题:

最大的问题之一是延迟加载与 jax-ws。有一些可行的解决方案,比如覆盖 jax-ws 访问器 (JAX-WS + Hibernate + JAXB: How to avoid LazyInitializationException during marshalling) 解决了这个问题,将休眠的代理替换为 null。

现在我面临这个例子描述的新问题:

实体“客户”位于“国家”内,因此:n:1 关系。

“客户”中的“国家/地区”被标记为延迟加载,以避免不必要的数据库流量。当客户端 UI 想要列出客户(此处不需要国家/地区)时,在 jax-ws 访问器中将 country-proxy 替换为 null ,一切都很好。 但是,在编辑客户时,(我认为)我必须加入该国家/地区,即使不查看/更改它也是如此。否则,它的代理将在通过 jax-ws 发送到客户端时被 null 替换,然后发送回服务器,并提交(带有 null)到数据库中。此后我的客户->国家关联丢失了。

也许有几种解决方案,例如:

  • 将国家标记为“optional=false”,当我忘记预先加入国家然后尝试保存客户时触发异常。使用这种方法,我必须始终加入所有参考文献,即使它们不是编辑过程的一部分。需要“optional=true”的引用会静默传递,编码错误可能会破坏数据库。
  • 不是在 jax-ws 访问器中用 null 替换代理,而是其他一些虚拟类,当从客户端发送回服务器时,它会被原始代理替换。但我不确定这是否可行。
  • 在客户端使用休眠并直接连接到数据库,仅使用 jax-ws 进行非数据库交互
  • 通过发送相应的 jax-ws 请求编写一些代码以允许在客户端内进行延迟加载(必要时)(无法再找到 StackOverflow 链接,因为有人要求这样的东西)。完全感觉像是在重新发明 hibernate...

对于此类应用程序还有其他解决方案、建议、最佳实践、更好的设置吗?

提前谢谢!

【问题讨论】:

    标签: java web-services hibernate tomcat jaxb


    【解决方案1】:

    与传输数据的方式相比,您存储数据的方式显然不同。因此,不使用相同的对象实例进行传输和存储可能是有意义的。

    其中一个解决方案是为此使用不同的类 - DTO 和实体。您存储实体但传输 DTO。这需要额外的努力来实现 DTO 和映射 DTO 实体,但这使您可以清楚地分离层,并且从长远来看可能会更有效(从努力的角度来看)。您可以使用 Dozer 和 likes 在 DTO 和实体之间进行映射。

    另一种方法是不使用不同的类,而是使用不同的对象实例进行传输和存储。这可能类似于@VinZ 的答案。您将源对象中的数据“合并”到目标对象中。前段时间我写了一个JAXB plugin 来生成这样的合并方法,发现该方法在不同的用例中非常有用。
    与 DTO 相比,使用这种方法可以节省大量工作量,但在类级别上没有层分离。

    我个人会使用经过完善和完善的实体结构以及针对传输进行优化的额外 DTO。我还会尝试以某种方式自动生成合并/复制方法,以避免手动编写。也许是一个 JAXB 插件。我喜欢编写 JAXB 插件,所以如果可以使用 JAXB 插件解决问题,我会使用 JAXB 插件来解决。

    希望这会有所帮助。

    【讨论】:

    • 差异仅源于无法在客户端延迟加载引用,因此必须使用解决方法作为代理->null。根据客户实体的每个用例,使用 DTO 可能会产生许多不同的类。此外,我已经为所有实体提供了 equals()、hashCode()、validate() 等方法,以便在 UI 中使用它们。目前感觉直接在客户端中使用 Hibernate 可能是最好的选择?但是,这缺少服务器强制执行的附加安全层...
    • 在使用 DTO 时,如何传输引用(如 Country),然后在服务器端的 Customer 中保持更改?喜欢:使用属性“countryID”作为参考还是完整的 CountryDTO?服务器是否必须先通过此 ID 查询 Country 实体,然后将其分配给 Customer,或者是否可以仅使用 ID 直接设置 Customer 的 Country-reference?
    【解决方案2】:

    您描述的问题不限于您的“JAX-WS 到休眠”场景。同样在其他场景中,您将面临这个“空值”问题。

    一种解决方案是“DIY 合并模式”:

    1. 将实体从客户端发送到服务器。
    2. 在服务器上使用收到的 ID 调用“EntityManager.find”以查找现有实体。
    3. 现在我们自己复制状态。如果 EntityManager.find 返回 null,它的 new -> 只是持久化接收到的对象。

    例子:

    Customer serverCustomer = dao.findById(receivedCustomer.getId());
    if(serverCustomer == null) {
        dao.persist(clientCustomer);
    } else {
        serverCustomer.setName(receivedCustomer.getName());
        serverCustomer.setDate(receivedCustomer.getDate());
        // ... all other fields, except "Country"
        if (receivedCustomer.getCountry() != null) {
           // Country keeps its server state if no new data
           serverCustomer.setDate(receivedCustomer.getCountry());
        }
    }
    

    【讨论】:

    • 这是一个非常具体的解决方案,仅在我根本不需要更改引用的情况下才可行。然而,在某些情况下,我必须能够更改它,并且“null”甚至可能是一个有效的参考(可选参考)。 “null - 由于:编码器忘记加入”和“null - 由于:我们真的想要null”之间必须存在某种明显的区别
    • 如果您只需要使用新数据进行更改的能力,那么您没有问题...请参阅更新的答案。如果您需要删除客户 国家/地区关系的能力,您实际上遇到了问题。您需要更多信息。我建议将您的客户的传输对象定义为“有效负载”以及一些关于它的附加信息。就像一个包含要删除的字段的列表。
    • 这是我第二个要点的方法。区分“null:更新”和“缺失:不更新”。我正在寻找一种解决方案,而不必为每个实体及其用例编写几个自定义合并。无法相信大型系统依赖于数千行手动合并代码。必须有别的东西.. 我想只使用(un)编组代理-> XYZ 和 XYZ-> 代理(而不仅仅是“null”)就足以满足我的用例。但是我不确定hibernate是否支持我自己创建代理...
    猜你喜欢
    • 2017-04-22
    • 1970-01-01
    • 1970-01-01
    • 2023-04-05
    • 2012-04-09
    • 2015-04-22
    • 2010-09-20
    • 2012-11-26
    • 2017-03-16
    相关资源
    最近更新 更多