【问题标题】:Persistence provider for Java that supports final fields支持 final 字段的 Java 持久性提供程序
【发布时间】:2018-01-19 04:33:21
【问题描述】:

我对 Java 很陌生,但我一直在养成一种习惯,尽可能使用 final 声明不变性,我认为这是一件好事。 (考虑 f#)

我了解到 JPA 不支持 final 字段。休眠,TopLink?我不确定这些,但我现在更喜欢 JPA。

这在理论上是否有可能——比如通过反射——在创建后修改最终字段?我的猜测是......不:)

持久性解决方案当然可以支持带参数的构造函数。至少我认为没有理由使这成为不可能。我猜映射会有点棘手。 这是另一种解决方案。

建议?

编辑:我不熟悉不可变的确切定义,所以我在这篇文章中直观地使用了它。这里声明不变性意味着声明一个字段不能被改变。很抱歉造成误会。

【问题讨论】:

    标签: java persistence


    【解决方案1】:

    对象不可变性(注意不可变对象和声明字段 final 之间的区别 - 只有当所有字段都是 final 时,对象才是不可变的,因此对象的状态在创建后不能改变)是一个非常敏感的话题。我自己也喜欢它们,hibernate 通过@Immutable 支持它们。

    不知道它在 JPA 2 中的状态,但要回答有关最终字段的问题:您可以使用反射更改它们的值 - 但反射在 Java EE 环境中受到严重限制。

    启发主要问题:如果您的 POJO 是不可变的,那么持久解决方案将如何重新创建对象?假设您有两个 final int 字段,以及一个初始化它们的构造函数。持久层不能有关于它们的顺序或名称的任何信息(因为在编译过程中会删除字段和参数名称)。

    Koshuke 发布了一篇关于此的博客(关于 JAXB 支持不可变 bean),但现在找不到。

    【讨论】:

    • Java 8 提供了参数名称,而对于旧版本,您可以在构建期间使用 Paranamer 获取它们。
    【解决方案2】:

    这可能不是您所追求的,但是只有一个 getter 的非最终私有字段很容易支持不变性原则。事实上,编译器会识别这一点并生成与声明为 final 的字段所得到的代码几乎相同的代码。

    这将要求您尽职尽责,不要从包含类中更改您的字段(而 final 会强制执行此操作),但我认为这并不是为您获得的灵活性付出的大代价。

    【讨论】:

    • 我会把这个答案作为正确答案提出来。
    • 虽然很可惜,加上 final 修饰符,很明显你是这样设计的。删除 setter 不是一个好的解决方案,您仍然可以修改状态,并且团队中的某个人最终会这样做,因为它非常诱人,您无法强制执行。框架(至少是持久性框架)应该支持这个恕我直言,这是一个非常有用的工具
    【解决方案3】:

    好问题。实际上,如果字段没有更改,最好将字段声明为 final(然后您会从 JMM 获得初始化保证)。

    JPA 规范对最终字段很明确:

    实体类不能是最终的。 实体类的任何方法或持久实例变量都不能是final的。

    但是,并非所有实现都遵循此规则并处理最终字段:

    • OpenJPA 不支持 final 字段,即它们被视为瞬态。 加载此类实体的最终字段时,将使用以下值初始化 在无参数构造函数中定义。

    • 然而,Hibernate 实现支持 final 字段。施工后的最终领域 在无参数构造函数中初始化的被存储的值替换 在数据库中。

    所以回答你的问题:是的,有支持 final 字段的 JPA 持久性提供程序, 但是我建议不要在实体类中使用最终字段,因为未定义行为 (实际上因提供商而异)。此外,我认为这是一个坏主意 JPA 提供者会在构建后更改 final 字段,因为这样可能会违反可见性保证。

    【讨论】:

      【解决方案4】:

      最终字段在按设计创建后无法修改。不这样做是一个非常糟糕的主意,因为有人可以依赖标准行为。

      一般来说,持久性框架处理“普通”对象的层次结构。您有对象,只能创建和删除,不能修改。这很奇怪,因为当您创建实体时,您会为其创建某种 ID。通过此 ID,您可以将实体与其他人联系起来。当您删除旧实体并创建另一个实体时,您(通常)会获得一个具有另一个 ID 的实体。因此,所有连接(外键)都被破坏了。是目标行为吗?

      【讨论】:

        【解决方案5】:

        这确实是一个奇怪的想法。

        通过创建字段final,您告诉编译器它们将永远在对象创建后不会更改。因此,不持久化它们是一个合理的假设,因为它们永远不会改变。好吧,写这篇文章,我假设你有 java 文化,但你问的问题恰恰相反。

        在 Java 中,持久化的对象“总是”被假定为 POJO(换句话说,Java Beans)。 Java Bean 必须有(被认为是这样的)一个空构造函数,它允许持久性框架等使用其空构造函数来构造它,通过 Class.newInstance()/ 间接调用它。

        有些字段使用了非空构造函数(如 IoC 容器 - Guice、Spring 和 Tapestry IoC),但它超出了 Java Bean 的范围,必须将其视为数据对象。

        【讨论】:

        • 只读数据库怎么样?顺便说一句,我只是在探索可能性。
        • 其他用例:一张表有多个实体。或者通过工厂方法创建这些实体。
        • 对不起。我完全不明白。如果您说的是现有的持久性框架仅支持 JavaBeans,那么您可能是对的。但是,如果你说不可变对象有一些内在的东西使它们不适合持久化,我完全不同意。有人可能会争辩说,Java 语言使处理不可变对象变得乏味,但框架可以轻松缓解这些缺陷。
        • 在您的答案的第一段中,您似乎假设正在从数据库中检索对象,修改然后提交,因此该对象需要按顺序就地修改有用。在许多(可能是大多数)情况下,持久性客户端是一个无状态服务器,其中更新外部化(网页等)并在提交调用(POST,PUT)上实现新对象。在这种情况下,我更喜欢不可变对象,因为它强制引用透明,尤其是在提交之前完成复杂的业务验证时。
        • 甚至在检索、修改和提交实体的封闭流程场景中也是如此。我仍然偏爱不可变对象,因为即使您不能就地更新,您也可以使用不可变对象进行复制时更新,然后提交更新的副本。在这种情况下,我仍然倾向于使用不可变对象,因为您可能仍需要将对象传递给许多验证器,对于这些验证器来说,引用透明是理想的。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-04-03
        • 2013-09-30
        • 2013-06-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多