是否可以拥有不可变的 JPA 实体?

发布于 2024-10-09 12:59:13 字数 180 浏览 0 评论 0 原文

在我们的 Hibernate 项目中,实体是使用 Java bean 模式进行编码的。我们的代码中有很多地方有人忘记设置赋值器,并且由于 NOT NULL 约束而导致异常。

是否有人使用构建器来构建他们的实体或使它们不可变?

我正在尝试找到一种不属于 Java bean 模式风格的有效模式。

In our Hibernate project, the entities are coded using the Java beans pattern. There's quite a few spots in our code where someone has forgotten to set a mutator and we get an exception due to a NOT NULL constraint.

Is anyone using a builder to construct their entities or making them immutable?

I'm trying to find an effective pattern that is not in the style of the Java beans pattern.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

吝吻 2024-10-16 12:59:13

如果你让你的bean不可变,那么你必须使用字段级访问,这会带来一系列问题,正如彻底讨论的此处。我们采取的方法是让构建器/工厂为我们执行/验证需求等规则。

If you make your beans immutable then you have to use field level access and this comes with its own set of problems as discussed thoroughly here. The approach we took is to have a Builder/Factory enforcing/validating the requiredness etc rules for us.

智商已欠费 2024-10-16 12:59:13

在我们的项目中,我们确实使用普通构建器方法(@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) 它将把所有数据复制回来,这样您就可以使用 builder 来改变它。当然,它会产生新的实体,因此旧的实体仍然是只读的。

用法(带有突变)非常简单:

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 设置,也可以在运行时获取惰性关联,最后因为您不能拥有最终字段(由于需要默认构造函数):( 后一点也是多线程环境的一个问题,即实体可能传递给#build() 之后的另一个线程可能会导致各种错误,因为另一个线程不能保证看到完全构造的对象

JPA 2.1 规范的“2.1 实体类”部分说:

实体类不能有任何方法或持久化实例变量
最终。

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

在我的例子中,我只是将 id 添加到构建器,而不是在草稿之上构建服务。

In our project we do use vanilla builders approach (@see Effective Java). Consider the following example:

@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;}
}

In case you want to sometimes mutate the entity I would recomend to introduce new Builder(Person person) which will copy all the data back, so you can mutate it with builder. Of course it will produce new one entity, so the old one remains read only.

The usage (with mutation) is as simple as:

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

Person modified = new Person.Builder(person).withPhone("987654321").build();

Also it is important to note that in this example Person is not 100% immutable (and can't be) class: first of all because id will be set by jpa, also lazy associations may be fetched at runtime and lastly because you can't have fields final(due to required default constructor) :( The latter point is also a concern for multithreaded environments, i.e. it is possible that entity passed to another thread just after #build() may lead to all kind of errors as abother thread is not guaranteed to see fully constructed object.

The JPA 2.1 specification, section "2.1 The Entity Class", says:

No methods or persistent instance variables of the entity class may be
final.

One more similar approach: http://vlkan.com/blog/post/2015/03/21/immutable-persistence/

In my case I would just add id to the builder instead building service on top of drafts..

无人问我粥可暖 2024-10-16 12:59:13

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

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

@Immutable annotation can be used on Entity. JPA Will ignore all updates made to entity.

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文