使用JPA映射多态关系的正确方法是什么?

时间:2015-09-03 10:51:54

标签: java hibernate jpa relationship

由于标题可能有点模糊,这里是对我的问题的解释(这不容易归纳为简洁的标题)。

我的问题很简单(我想要达到的目的对我来说似乎非常直观),尽管解决它可能不是。我有两个代表绑定概念的抽象类。作为一个例子,我选择Project和Task。给定项目可以有许多任务。这些类是抽象的,因为有不同类型的项目和不同类型的任务,但项目和任务类型是相同的。例如,有开发项目和开发任务(可能看起来有点奇怪,但这是一个简化的例子)。当然还有其他类型的项目和任务,但让我们坚持这些。

我想要实现的是用JPA映射我在两个抽象类中的关系,并在具体类中从中受益。使事情变得复杂的是我还必须映射继承:带有鉴别器的单个表,它也是类ID的一部分。我将泛型类型添加到抽象类中,以确保我可以强制具体类引用正确的关联类(与DeveloperTask绑定的DevelopmentProject)。

问题是,无论我尝试什么,我总会得到某种错误。我在这里寻求的是那些已经取得这样成绩的人。我必须告诉你,我正在使用遗留数据库(我很清楚当前的设计存在很多问题,但遗憾的是我无能为力)。

为了帮助您,我加入了我制作的示例代码,这样您就会看到我在上面的段落中的含义。

@Entity
@Table(name = "PROJECT")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.INTEGER)
public abstract class Project<T extends Task> {
    /* Fields */
    private ProjectId id;

    private List<T> tasks;

    /* Constructors */
    protected Project() {}

    public Project(ProjectId id) {
        this.id = id;
    }

    /* Getters */
    @EmbeddedId
    public ProjectId getId() {
        return id;
    }

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "project", targetEntity = Task.class)
    public List<T> getTasks() {
        return tasks;
    }

    /* Setters */
    public void setTasks(List<T> tasks) {
        this.tasks = tasks;
    }
}


@Embeddable
public class ProjectId {
    /* Fields */
    private Integer type;
    private Integer number;

    /* Constructors */
    @SuppressWarnings("unused")
    private ProjectId() {} /* Empty constructor for hibernate */

    public ProjectId(Integer type, Integer number) {
        this.type = type;
        this.number = number;
    }

    /* Getters */
    @Column(name = "TYPE", nullable = false)
    private Integer getType() { /* Only for Hibernate */
        return type;
    }

    @Column(name = "NUMBER", nullable = false)
    public Integer getNumber() {
        return number;
    }

    /* Setters */
    @SuppressWarnings("unused")
    private void setType(Integer type) { /* Only for Hibernate */
        this.type = type;
    }

    public void setNumber(Integer number) {
        this.number = number;
    }
}


@Entity
@Table(name = "TASK")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.INTEGER)
public abstract class Task<P extends Project> {
    /* Fields */
    private TaskId id;

    private P project;

    /* Constructors */
    protected Task() {}

    public Task(TaskId id) {
        this.id = id;
    }

    /* Getters */
    @EmbeddedId
    public TaskId getId() {
        return id;
    }

    @ManyToOne(fetch = FetchType.EAGER, optional = false, targetEntity = Project.class)
    @JoinColumns({
        @JoinColumn(name = "TYPE", referencedColumnName = "TYPE", insertable = false, updatable = false),
        @JoinColumn(name = "PROJECT_NUMBER", referencedColumnName = "NUMBER", insertable = false, updatable = false)
    })
    public P getProject() {
        return project;
    }

    /* Setters */
    public void setId(TaskId id) {
        this.id = id;
    }

    public void setProject(P project) {
        this.project = project;
    }
}


@Embeddable
public class TaskId {
    /* Fields */
    private Integer type;
    private Integer projectNumber;
    private Integer number;

    /* Constructors */
    @SuppressWarnings("unused")
    private TaskId() {} /* Empty constructor for Hibernate */

    public TaskId(Integer type, Integer projectNumber, Integer number) {
        this.type = type;
        this.projectNumber = projectNumber;
        this.number = number;
    }

    /* Getters */
    @Column(name = "TYPE", nullable = false)
    private Integer getType() { /* Only for Hibernate */
        return type;
    }

    @Column(name = "PROJECT_NUMBER", nullable = false)
    public Integer getProjectNumber() {
        return projectNumber;
    }

    @Column(name = "NUMBER", nullable = false)
    public Integer getNumber() {
        return number;
    }

    /* Setters */
    @SuppressWarnings("unused")
    private void setType(Integer type) { /* Only for Hibernate */
        this.type = type;
    }

    public void setProjectNumber(Integer projectNumber) {
        this.projectNumber = projectNumber;
    }

    public void setNumber(Integer number) {
        this.number = number;
    }
}


@Entity
@DiscriminatorValue(value = "1")
public class DevelopmentProject extends Project<DevelopmentTask> {
    /* Constructors */
    @SuppressWarnings("unused")
    private DevelopmentProject() { /* Empty constructor for Hibernate */
        super();
    }

    public DevelopmentProject(DevelopmentProjectId id) {
        super(id);
    }
}


public class DevelopmentProjectId extends ProjectId {
    /* Constructors */
    public DevelopmentProjectId(Integer number) {
        super(1, number); /* Development type is forced */
    }
}


@Entity
@DiscriminatorValue(value = "1")
public class DevelopmentTask extends Task<DevelopmentProject> {
    /* Constructors */
    @SuppressWarnings("unused")
    private DevelopmentTask() { /* Empty constructor for Hibernate */
        super();
    }

    public DevelopmentTask(DevelopmentTaskId id) {
        super(id);
    }
}


public class DevelopmentTaskId extends TaskId {
    /* Constructors */
    public DevelopmentTaskId(Integer number) {
        super(1, number); /* Development type is forced */
    }
}

编辑: 我稍微纠正了我的例子(将一个projectNumber添加到Task,没有它,这种关系似乎毫无意义,甚至是错误的)。我还在我的关系中添加了targetEntity(我忘记了,你会在下面看到原因)。

现在我将尝试向你解释我的路径。

  • 首先,我在我的超类上使用了MappedSuperclass注释,而在我的关系中没有使用targetEntity。因为泛型而导致我出错:Hibernate不知道要映射什么并建议我删除泛型,要么使用targetEntity,要么使用@Type(但最后一个是Hibernate特定的我不想使用它) 。所以我用Project.class和Task.class设置了targetEntity(在超类级别,没有别的东西可以做了)。
  • 然后,由于MappedSuperclass注释,我遇到了另一个问题。 Hibernate告诉我有一个未知实体项目(或任务)的引用[我正在调整我在我的真实项目中遇到的错误但是我很确定一旦我设法设置那些错误我会有同样的错误两张桌子并测试它们]。我搜索了那个错误,最后发现MappedSuperclass是问题,我可以在抽象类上使用Entity。我也是。
  • 之后,这就是我得到的最后一个错误:在DevelopmentProject中为列TYPE重复列映射。但是,如果我尝试在任何地方设置insertable = false和updatable = false,则定义此列(id,relationship)......这不会改变任何内容。我担心我的先例问题与How to map to attributes from embedded class to a single database column with JPA?相同。但在那种情况下,有一个简单而不太难看的解决方法。在这种情况下,我看不到它。

我真的很想知道是否有人尝试过这样的事情并取得成功,即使解决方案不同,因为它尽可能方便和优雅。

0 个答案:

没有答案