【问题标题】:Mapping a @OneToMany relation with a primary key as foreign key将 @OneToMany 关系与主键映射为外键
【发布时间】:2019-09-23 06:07:04
【问题描述】:

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

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

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

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 的自动生成 ID 似乎没有在 ActivityProperty 的第二次插入中使用。

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

【问题讨论】:

    标签: java hibernate jpa


    【解决方案1】:

    据我所知,JPA 不支持 cascade 操作。你对 JPA 的要求太多了。我没有具体的参考资料说明这一点,但您可以使用以下代码尽可能接近。它不会运行,因为hibernate(至少)不知道如何将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,您会看到可怕的事情。

    【讨论】:

    • 好的,非常感谢您的建议。我想我将排除级联并手动保持关系。
    • 非常感谢!我已按照您的建议删除级联插入并保留 @OneToMany 注释仅用于查询目的。
    【解决方案2】:

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

    因此,要么创建一个从 ActivityPropertyActivity 的反向引用并使用它作为映射,否则您必须使用 @JoinColumn 来声明外键。

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

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

    2019 年 5 月 6 日更新 当您启用了级联时,您应该从关系中写入 id,您必须将 activityId 字段设置为只读:

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

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

    【讨论】:

    • 恐怕它与@JoinColumn 注释没有区别,在插入属性时仍然会在 id 上获得 NULL :( ,我们真的需要使它成为双向并保持对完整的 Activity 对象?似乎有点开销。
    • 将 nullable = false 添加到 JoinColumn。我更新了我的答案
    • 当添加nullable = false 现在我得到Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Repeated column in mapping for entity: com.gmv.epssg.oas.model.entity.db.ActivityProperty column: id (should be mapped with insert="false" update="false")
    • 我忘了你必须将activityId设为只读。我将此添加到我的答案中。
    • 很抱歉,但已经尝试将这些属性添加到 activityId 并出现相同的错误:(
    猜你喜欢
    • 2020-09-14
    • 1970-01-01
    • 2013-06-23
    • 2014-05-26
    • 1970-01-01
    • 1970-01-01
    • 2012-11-23
    • 1970-01-01
    • 2017-01-03
    相关资源
    最近更新 更多