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

时间:2010-12-30 05:10:52

标签: hibernate jpa immutability

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

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

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

由于

4 个答案:

答案 0 :(得分:14)

如果你使你的bean不可变,那么你必须使用字段级别的访问权限,这会带来一系列问题,如详细讨论here。我们采取的方法是让Builder / Factory为我们执行/验证必要性等规则。

答案 1 :(得分: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;}
}

如果你想有时改变实体,我会建议你引入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();

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

JPA 2.1规范,部分" 2.1实体类",说:

  

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

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

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

答案 2 :(得分:0)

@Immutable注释可以在Entity上使用。 JPA将忽略对实体所做的所有更新。

https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/annotations/Immutable.html

答案 3 :(得分:0)

我认为,逻辑(域)层的责任是防止此类事情在一开始就发生。

我认为您应该具有特定于域的对象,并应用所有业务规则限制并对其进行检查。然后,当您决定保留它们时,首先将它们映射(转换)为实体,然后将它们传递给持久层。

与API级别验证类似,JPA抛出的bean验证异常是防止进一步处理的安全网,在这种情况下,这种处理会导致数据损坏。