使用主键作为外键映射@OneToMany关系

时间:2019-05-05 10:33:37

标签: java hibernate jpa

我正在尝试映射JPA下图中描述的一对多表关系:

Activity Diagram JPA

可以看出,"activity_property"表使用复合主键,其中(id,name)和列"id"是表中列"id"的外键"activity"

我当前的实体是通过这种方式映射的(为了清楚起见,一些辅助方法已被省略):

Activity.java

@Entity
@Getter
@Setter
public class Activity {
   @Id
   @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "act_id_generator")
   @SequenceGenerator(name = "act_id_generator", sequenceName = "activity_id_seq", allocationSize = 1, initialValue = 1)
   private Integer id;

   private String name;

   @OneToMany(mappedBy = "id.activityId", cascade = CascadeType.ALL, orphanRemoval = true)
   private List<ActivityProperty> properties;
}

ActivityProperty.java

@Entity
@Getter
@Setter
@Table(name = "activity_property")
public class ActivityProperty {
    @EmbeddedId
    private ActivityPropertyId id;

    private String value;

    @Enumerated(EnumType.STRING)
    private PropertyType type;
}

ActivityPropertyId.java

@Embeddable
@Getter
@Setter
@ToString
public class ActivityPropertyId implements Serializable {

    @Column(name = "id")
    private Integer activityId;

    private String name;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ActivityPropertyId that = (ActivityPropertyId) o;
        return activityId.equals(that.activityId) &&
                name.equals(that.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(activityId, name);
    }
}

当我尝试通过这种方式坚持一项活动时:

Activity activity = createActivity("Activity_1");
activity.addProperty(ActivityProperty.from("Prop1", "value1", PropertyType.PARAMETER));
activityDAO.persist(activity);

我可以在Hibernate中看到以下痕迹:

Hibernate: call next value for activity_id_seq
Hibernate: insert into activity (name, id) values (?, ?)
Hibernate: insert into activity_property (type, value, id, name) values (?, ?, ?, ?)
05-05-2019 12:26:29.623 [main] WARN  o.h.e.jdbc.spi.SqlExceptionHelper.logExceptions - SQL Error: 23502, SQLState: 23502
05-05-2019 12:26:29.624 [main] ERROR o.h.e.jdbc.spi.SqlExceptionHelper.logExceptions - NULL not allowed for column "ID"; SQL statement:
insert into activity_property (type, value, id, name) values (?, ?, ?, ?) [23502-199]

Activity的第二次插入中似乎没有使用ActivityProperty的自动生成的ID。

我不知道如何正确映射这种关系。我的JPA注释中是否缺少任何内容?

2 个答案:

答案 0 :(得分:1)

mappedBy属性用于指示此关系不用于持久化,而mappedBy中的属性用于此目的。

因此,请创建从ActivityPropertyActivity的反向引用,并使用由其映射的反向引用,或者您必须使用@JoinColumn声明外键。

那看起来像。属性name指向目标表中外键的名称。

@JoinColumn(name = "id", nullable = false)
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<ActivityProperty> properties;

更新05/06/2019 启用级联后,您应该从关系中写入ID,并且必须将activityId字段设置为只读:

@Column(name = "id", insertable = false, updateable = false)
private Integer activityId;

在此处了解有关关系映射的更多信息:https://thoughts-on-java.org/ultimate-guide-association-mappings-jpa-hibernate/

答案 1 :(得分:1)

据我所知,JPA不支持通过cascade操作。您向JPA提出的要求过多。我没有特定的参考说明,但是使用下面的代码,您可以尽可能地接近。它不会运行,因为(至少)休眠状态不知道如何自动将activity.id映射到ActivityProperty类的PK。

@Entity
@Getter
@Setter
@Table(name = "activity_property")
@IdClass(ActivityPropertyId.class)
public class ActivityProperty {
    @ManyToOne
    @Id
    @JoinColumn(name="id", referencedColumnName="id")
    private Activity activity;

    @Id
    private String name;
    private String value;

}

@Getter
@Setter
@ToString
public class ActivityPropertyId implements Serializable {
    public ActivityPropertyId() {}

    @Column(name = "id")
    private Integer activity;

    private String name;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ActivityPropertyId that = (ActivityPropertyId) o;
        return activity.equals(that.activity) &&
                name.equals(that.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(activity, name);
    }
}

这将产生异常

  
    

原因:java.lang.IllegalArgumentException:无法将java.lang.Integer字段model.ActivityPropertyId.activity设置为model.Activity

  

我还没有找到解决这个问题的好方法。恕我直言cascade是傻瓜的天堂,您应该自己保存ActivityProperty实例,并使用双向映射仅用于查询。这确实是它打算使用的方式。如果您通过级联在OneToMany列表上执行一些简单的保存操作,并查看生成的SQL,您会发现可怕的事情。