可以拥有不可变的JPA实体吗?

Bil*_*ory 16 hibernate jpa immutability

在我们的hibernate项目中,实体使用java bean模式进行编码.在我们的代码中有很多地方有人忘记了设置mutator而我们因NOT NULL字段而得到异常.

是否有人使用构建器构建其实体或使其不可变?

我正在尝试找到一种不符合java bean模式风格的有效模式.

谢谢

Ara*_*ram 14

如果你使你的bean不可变,那么你必须使用字段级访问,这有自己的一组问题,这里详细讨论.我们采取的方法是让Builder/Factory为我们执行/验证必要性等规则.

  • 这就是我们所做的(这可能不是全局适用于所有应用程序).假设我们有一个人类.然后我们将拥有PersonFactoryBuilder,它将创建一个Person对象,但会将其强制转换为ReadablePerson.ReadablePerson是一个只暴露"get"方法的接口,从而使其不可变.在从DB获取时,我们有PersonRepository(如DAO),它从DB中获取人,并根据intent(更新或显示)来转换/返回ReadablePerson或Person(mutable).这个设计灵感来自于joda-time库.http://joda-time.sourceforge.net (3认同)

Vad*_*huk 10

在我们的项目中,我们使用vanilla builders方法(@see Effective Java).请考虑以下示例:

@Entity
public class Person {
   public static class Builder {
        private String firstName;
        private String lastName;
        private PhoneNumber phone;

        public Builder() {}

        public Builder withFullName(String fullName) {
            Preconditions.notNull(fullName); 
            String[] split = fullName.split(" ");
            if (split == null || split.length != 2) {
                throw new IllegalArgumentException("Full name should contain First name and Last name. Full name: " + fullName);
            }  
            this.firstName = split[0];
            this.lastName = split[1];
            return this;
        }

        public Builder withPhone(String phone) {
            // valueOf does validation
            this.phone = PhoneNumber.valueOf(phone);
            return this;
        }

        public Person build() {
            return new Person(this);
        }
   }

   //@Columns
   private long id;//@Id
   private String firstName;
   private String lastName;
   private String phoneNumber;

   // hibernate requires default constructor
   private Person() {} 

   private Person(Builder builder) {
       this.firstName = Preconditions.notNull(builder.firstName);
       this.lastName = Preconditions.notNull(builder.lastName);
       this.phoneNumber = builder.phone != null ? builder.phone : null;
   }

   //Getters
   @Nonnull
   public String getFirstName() { return firstName;}
   @Nonnull
   public String getLastName() { return lastName;}
   @Nullable
   public String getPhoneName() { return phone;}
   public long getId() { return id;}
}
Run Code Online (Sandbox Code Playgroud)

如果您希望有时改变实体,我会建议引入new Builder(Person person)哪些将复制所有数据,因此您可以使用构建器对其进行变更.当然它会产生新的一个实体,所以旧的实体仍然是只读的.

用法(带变异)非常简单:

Person.Builder personBuilder = new Person.Builder();
Person person = personBuilder.withFullName("Vadim Kirilchuk").withPhone("12345678").build();

Person modified = new Person.Builder(person).withPhone("987654321").build();
Run Code Online (Sandbox Code Playgroud)

另外值得注意的是,在这个例子中,Person不是100%不可变的(并且不能是)类:首先因为id将由jpa设置,也可以在运行时获取惰性关联,最后因为你可以' t有字段final(由于所需的默认构造函数):(后一点也是多线程环境的一个问题,即有可能在#build()之后传递给另一个线程的实体可能导致所有类型的错误,因为另一个线程不保证看到完全构造的对象.

JPA 2.1规范"2.1实体类"一节说:

实体类的方法或持久性实例变量可能不是最终的.

另一种类似的方法:http://vlkan.com/blog/post/2015/03/21/immutable-persistence/

在我的情况下,我只是添加id到构建器,而不是在草稿上构建服务..

  • 取决于 JPA 提供商,hibernate 确实可以与 private 一起使用。https://coderanch.com/t/639487/certification/Private-Constructor-error (2认同)