使用JPA时实现构建器模式的最佳实践和方法

4
我有一个适合建造者模式的类,其中有很多参数,我不想使用大量的构造函数。但我的问题是这个类是一个JPA实体,这对我来说很新颖。由于私有数据成员在构造函数中没有初始化,因此出现了错误,而据我所知,JPA需要一个空的受保护构造函数。请问有谁能帮忙解决吗?如果能提供一个示例就更好了。下面是代码的基本示例,但它非常通用,为了节省时间和空间,我省略了许多访问器和数据成员。
  @Entity//(name= "TABLE_NAME") //name of the entity / table name
public class Bean implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id //primary key
    @GeneratedValue
    Long id;
    private final DateTime date;
    private final String title;
    private final String intro;


    //used by jpa
    protected Bean(){}

    private Bean(Bean Builder beanBuilder){
        this.date = beanBuilder;
        this.title = beanBuilder;

        this.intro = beanBuilder;
    }

    public DateTime getDate() {
        return date;
    }

    public String getTitle() {
        return title;
    }

    public static class BeanBuilder Builder{

        private final DateTime date;
        private final String title; 

        //private optional 

        public BeanBuilder(DateTime date, String title) {
            this.date = date;
            this.title = title;
        }

        public BeanBuilder intro(String intro){
            this.intro = intro;
            return this;
        }

        public BeanBuilder solution(String solution){
            this.intro = solution;
            return this;
        }

        public Bean buildBean(){
            return new Bean(this);
        }

    }

}

你应该阅读一些关于 final 的内容。也许可以参考 https://dev59.com/DWUo5IYBdhLWcg3w7jIq 或 https://en.wikipedia.org/wiki/Final_(Java)。 - user180100
我不希望该对象是可变的... - null
我不会创建一个接受构建器的构造函数。这样,构建器就不需要负责构建对象。该类的目的仅仅是作为一个容器(可以理解为带有硬编码属性的映射表!)。 - Martin Andersson
@MartinAndersson - 看起来我在复制/粘贴代码时失败了。构造函数应该接受一个构建器,然后将属性设置为构建器的值。然后我可以像build.x("valueofx").builder.y(val)这样做,那么所讨论的对象就会有这些值设置,但是在y未设置的情况下,它将为null,这也是可以的。至少这是我理解的方式...可能非常错误 :) - null
@Steve Green 让一个类变成不可变的很简单,只需要不在该对象上提供任何可变行为并保持所有字段私有即可。当然,你可能会意外地从类内部改变一个字段,但从客户端的角度来看,这个类是不可变的。 - plalx
5个回答

2

被标记为final的成员变量必须在构造函数中赋值,并且该值是不可更改的。因此,所有声明的构造函数都必须为所有final字段分配一个值。

这解释了您的编译器错误。

根据JLS

空白的最终实例变量必须在其所在类的每个构造函数结束时明确定义,否则会发生编译时错误(§8.8,§16.9)。


你知道有什么方法可以保持类的不可变性,同时仍然可以将其用于JPA吗?据我所知,JPA需要一个空构造函数。但是,事情肯定不会这么简单...? - null
1
Hibernate 提供了 @Immutable 注解,请参见 http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch12.html - user180100

2

我不确定你的意思是什么,但在使用Hibernate时使用不可变对象并不是一个好主意(这不是说你不能这样做,或者你不应该这样做)。

想一想,因为Hibernate/JPA为对象定义了“states”,它们被认为是可变的;否则,你将拥有一个静态数据库,或者像“插入一次,永远不修改”的数据库。

不可变概念是一个非常知名(现今)的概念,主要借鉴自函数式编程,它并不完全适用于面向对象编程。而且如果你正在使用Hibernate,你不应该有不可变的对象...至少到今天为止。

更新

如果你想要拥有所谓的只读实体,可以使用Hibernate本身的{{link2:@Immutable}}注释。注意实体成员中的集合。


1
@SteveGreen 如果你真的知道你要插入数据库的数据不会改变,那么你可以这样做,并使用名为“Immutable”(或类似的)注释,但它就像拥有一个只读实体。 - x80486
谢谢。我从你们这里得到的印象是,我正在尝试做一个不好的想法 :)我试图通过使用生成器创建一种清晰的方式来生成不同的网页。 例如,一种类型可能有2个图片,另一种类型可能有4个图片。 我希望使用一个tsp根据类型处理它们,但也许还有更好的方法 :) - null
@plalx,我不想在这里开启一个永无止境的线程,但你所说的是完全不同的事情,这是比较VO和任何Hibernate实体时的主要问题。VO是包含属性但没有概念上“身份”的对象(不要混淆此上下文中的“身份”)。 - x80486
我不明白你的评论与我的相关性?重点是,客户端认为对象是不可变的,并且其中一个例子是大多数值对象并没有使用final成员。 - plalx
此外,只追加模型是很常见的,根据定义每个条目都必须是不可变的。 - plalx
显示剩余2条评论

2

我不确定你为什么想这样做。也许更好的方法是将成员变量定义为 @Column(name = "id", nullable = false, updatable = false) 例如


2
JPA 2.1规范中的第2.1节“实体类”指出:

实体类的方法或持久化实例变量不能是final的。

这意味着您无法构建真正不可变的JPA实体。但是,我并不认为这会成为一个大问题。只需不要让实体类公开setter方法即可。

1
实体在 Java 严格不可变性方面应该是可变的。例如,当访问惰性加载的关联时,对象状态会发生变化。
如果您需要以真正的不可变方式(例如用于多线程目的)使用实体数据,则考虑使用 DTO(因为实体也不应同时访问)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接