【问题标题】:Is there any way to declare final fields for Hibernate-managed objects?有没有办法为 Hibernate 管理的对象声明最终字段?
【发布时间】:2009-05-26 18:41:17
【问题描述】:

我刚刚开始使用 Hibernate,到目前为止我看到的所有示例都非常类似于 Hibernate 文档中的教程:

package org.hibernate.tutorial.domain;
import java.util.Date;

public class Event {

    private Long id;
    private String title;
    private Date date;

    public Event() {}

    /* Accessor methods... */
}

特别是:没有一个字段被声明为final,并且必须有一个无参数的构造函数,这样Hibernate框架才能实例化这个类并设置它的字段。

但事情是这样的 - 我真的不喜欢让我的类以任何方式可变,只要我可以避免它(Java 实践:不可变对象为这样做提供了一个非常有力的论据)。那么有没有办法让 Hibernate 工作,即使我将每个字段都声明为“final”

我知道 Hibernate 使用反射来实例化它的类,因此需要能够调用某种构造函数,而不会冒险选择错误的构造函数或将错误的值传递给它的参数之一,所以它是调用无参数构造函数并一次设置每个字段可能更安全。但是,难道不能向 Hibernate 提供必要的信息,以便它可以安全地实例化不可变对象吗?

public class Event {

    private final Long id;
    private final String title;
    private final Date date;

    public Event(@SetsProperty("id") Long id,
        @SetsProperty("title") String title,
        @SetsProperty("date") Date date) {

        this.id = id;
        this.title = title;
        this.date = new Date(date.getTime());
    }

    /* Accessor methods... */
}

@SetsProperty 注释当然是虚构的,但似乎不应该遥不可及。

【问题讨论】:

    标签: java hibernate final pojo


    【解决方案1】:

    实际上在 JDK 1.5+ 中,hibernate 可以处理(通过反射)更改最终字段。创建一个受保护的默认构造函数(),将字段设置为一些默认值/空等... Hibernate 可以并且将在实例化对象时覆盖这些值。

    这一切都归功于对 Java 1.5 内存模型的更改 - 为启用序列化/反序列化而进行的感兴趣的更改(允许 final 不是那么最终)。

    public class Event {
    
    private final Long id;
    private final String title;
    private final Date date;
    
    // Purely for serialization/deserialization
    protected Event() {
        id = null;
        title = null;
        date = null;
    }
    
    public Event(Long id, String title, Data date) {
        this.id = id;
        this.title = title;
        this.date = date;
    }
    
    /* Accessor methods... */
    

    }

    【讨论】:

      【解决方案2】:

      不可变对象是指没有修改其状态的方法(即其字段)的对象。字段不必是最终的。因此,您可以删除所有修改器并将 Hibernate 配置为使用字段访问而不是访问器,或者您可以将无参数构造函数和修改器标记为已弃用。这有点解决方法,但总比没有好。

      【讨论】:

      • +1:谢谢 - 这对于制作包装类是不可能/不切实际的情况也很有帮助(删除设置器和使用基于字段的访问)。不能说,因为我对幕后发生的那种反射功夫太激动了——私人领域实际上并不是私人的。有点让我觉得我应该为不太熟悉 Hibernate 工作原理的用户(比如我)添加一些 @ReadTheHibernateBookBeforeUsingThisClass 标签。
      【解决方案3】:

      听起来这不是 Hibernate 的用例,因为它执行的许多操作都涉及可变状态:

      • 合并对象
      • 脏状态检查
      • 刷新更改

      话虽如此,如果您担心不可变性,您可以选择为您的对象提供包装器,并使用复制构造函数:

      public class FinalEvent {
          private final Integer id;
      
          public FinalEvent(Event event) {
              id = event.id;
          }
      }
      

      但这确实意味着额外的工作。


      现在我正在考虑它,休眠会话通常是线程绑定的,这至少会使最终字段的一个好处 - 安全发布。

      您还在寻找 final 字段的其他好处吗?

      【讨论】:

      • +1:使用包装类的好主意——当需要最终类/字段时,它看起来是一个很好的解决方案。通过使可变版本仅对数据访问类可见,甚至可以避免在外部使用哪个版本(可变与包装器)上的歧义。至于使用 final,我的主要原因是通常更容易保证(而不是假设)每个字段在构造时只分配一次。我们有很多数据在使用它的 JVM 的整个生命周期中都是不变的,因此明确这一点很有帮助。
      • Final 字段可防止用户意外分配他们不应该拥有的东西并引入微妙的错误。如果您知道某些事情永远不会改变,那么您希望将其定为最终版本。如果您发现它应该改变,您可以随时移除该约束。
      • @corsiKa 访问器定义了该逻辑:如果您不需要更改某些属性,只需不要创建 setter。但是,我同意代码必须尽可能严格,但不能使用 hibernate 的方法。
      【解决方案4】:

      这个问题也困扰了我很长时间。我最近一直在尝试的一个想法是 - 为您的模型类定义只读接口,并让您的 DAO 和任何工厂在对象上返回这些接口。这意味着即使实现是可变的,一旦离开 DAO/工厂对象,就无法再对其进行调整。

      像这样:

      public interface Grape {
        public Color getColor();
      }
      
      public class HibernateGrapeDao {
        public Grape findGrape(int grapeId) {
          HibernateGrape grape = hibernate.find(...
          return grape;
        }
      }
      
      class HibernateGrape implements Grape {
       ....
      }
      

      甚至可能希望将实现类包私有到 dao 包,所以没有人可以直接摆弄它们。多做一点工作,但从长远来看可能有助于保持清洁。而且,显然,要小心整个平等/身份事务。

      【讨论】:

        【解决方案5】:

        您可以通过使用 Builder 模式来实现所需的结果。不久前,我在 Hibernate 论坛上阅读了 posting 讨论这个想法(尽管我自己从未实现过......)

        【讨论】:

          【解决方案6】:

          使用 @Access(AccessType.FIELD) 注释您的类,然后您可以使您的字段成为最终字段。像这样:

          @Access(AccessType.FIELD)
          public final class Event {
          
              private final Long id;
              private final String title;
              private final Date date;
          
              private Event() {
                  id = null;
                  title = null;
                  date = null;
              }
          
              public Event(Long id, String title, Date date) {
                  this.id = id;
                  this.title = title;
                  this.date = date;
              }
          }
          

          【讨论】:

            猜你喜欢
            • 2013-12-22
            • 1970-01-01
            • 2019-06-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多