使用带有 Hibernate 的 JPA 注释来描述外键仅在子表中的 @OneToMany 关系

Posted

技术标签:

【中文标题】使用带有 Hibernate 的 JPA 注释来描述外键仅在子表中的 @OneToMany 关系【英文标题】:Using JPA annotations with Hibernate to describe @OneToMany relationship where foreign key is only in child table 【发布时间】:2016-04-25 18:59:27 【问题描述】:

我有一个非常满意的现有数据模型:

public class Garden 
    private String name; // "Oak Grove"
    private List<Plant> plants;


public class Plant 
    private String name; // "Cherry Tomato"

我想在 Hibernate 中使用以下条件进行映射:

    Java 中的Plant 类不维护对其父级Garden 的引用。这使得 Java 层 IMO 中的事情变得更加困难。

    PLANT 表应该有一个GARDEN_ID 列,它是GARDEN(ID) 列的外键。

我在添加@OneToMany 之前的初始设置:

@Entity(name = "GARDEN")
public class Garden 
    @Id
    @Column(name = "ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(name = "NAME", nullable = false)
    private String name; // "Oak Grove"

    // Not yet mapped
    private List<Plant> plants;


@Entity(name = "PLANT")   
public class Plant 
    @Id
    @Column(name = "ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(name = "NAME", nullable = false)
    private String name; // "Cherry Tomato"

如何在List&lt;Plant&gt; plants; 上定义@OneToMany 注释,以便在Plant 中维护外键引用?

如果我只是添加:

@OneToMany(cascade =  CascadeType.ALL )
@JoinColumn(name = "GARDEN_ID")
private List<Plant> plants;

然后用植物拯救花园失败:

Caused by: org.h2.jdbc.JdbcSQLException: NULL not allowed for column "GARDEN_ID"; SQL statement:
insert into PLANT (NAME, ID) values (?, ?) [23502-191]

因此,Hibernate 似乎并没有尝试保留外键。有没有办法在不完全破坏我的对象模型的情况下做到这一点?

编辑:我测试的方式是:

Garden garden = new Garden("Oak Grove");
garden.addPlant(new Plant("Cherry Tomato"));
gardenManager.save(garden);

其中save() 方法看起来非常Hibernate-ey:

public void save(T item) 
    try (Session session = factory.openSession()) 
        Transaction transaction = session.beginTransaction();
        try 
            session.saveOrUpdate(item);
            transaction.commit();
         catch (Exception ex) 
            System.out.println("Error occurred saving item: " + ex.getMessage());
            ex.printStackTrace();
            transaction.rollback();
        
    

【问题讨论】:

您是否尝试过删除 @JoinColumn 并将 mappedBy 添加到 @OneToMany? @OneToMany(cascade = CascadeType.ALL, mappedBy = "garden") @Nicholas 代码已添加。 @RubioRic 我的理解是,当子 (Plant) 类上有相应的 Java 属性 (private Garden garden) 时使用 mappedBy - 这不是我想要的完成。 @CraigOtis 你是对的。我的错。 无法重现(5.0.9.Final)我得到两个插入和更新:休眠:插入成员(id)值(?)休眠:插入课程(id)值(? ) Hibernate:更新课程集 member_id=?哪里 id=?。除了类名之外没有其他区别。 【参考方案1】:

Yogesh Sakurikar 很接近,但双向@JoinColumn 有点偏离。下面你将看到如何双向或单向加入

@Entity(name = "GARDEN")
public class Garden 
    @Id
    @Column(name = "ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(name = "NAME", nullable = false)
    private String name; // "Oak Grove"

    // use this if you don't want a bi-directional relationship
    // @OneToMany
    // @JoinColumn(name = "ID", referencedColumnName="GARDEN_ID")
    // private List<Plant> plants;

    // use this if you want it bi-directional
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "garden")
    private Set<Plant> plants;


@Entity(name = "PLANT")   
public class Plant 
    @Id
    @Column(name = "ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(name = "NAME", nullable = false)
    private String name; // "Cherry Tomato"

    // use this if you don't want a bi-directional relationship
    // @Column(name="GARDEN_ID")
    // private long gardenId;

    // use this if you want a bi-directional relationship
    @ManyToOne
    @JoinColumn(name = "GARDEN_ID", referencedColumnName="ID", nullable = false)
    private Garden garden;


下面的代码假定双向关系。否则你需要知道你的Garden.id,然后才能完全描述任何孩子Plant

Garden garden = new Garden("Oak Grove");
Plant plant = new Plant("Cherry Tomato")
plant.setGarden(garden); //don't forget to set the parent on the child
garden.addPlant(plant);
gardenManager.save(garden);

【讨论】:

问题是我宁愿Plant知道Garden。否则,我需要一些我觉得有点麻烦的“不要忘记”代码。我不认为对象模型层应该做出牺牲来满足数据库 - 相反,它应该围绕数据工作。 那么你需要一个单独的链接表。 将父级设置为子级实际上是“更干净”的方法,因为您不需要知道两者的 ID。您的要求将添加第三个表(您可能应该有第三个实体),这将是 @OneToOnePlant@ManyToOneGadren... 除非你想要相同的 Plant 在多个Gardens,这将使他们成为@ManyToMany,然后肯定需要一个连接表。【参考方案2】:

对于一对多关系,如果植物要持有该关系,则需要使用双向定义它。 这是我认为你应该能够实现它:

@Entity(name = "GARDEN")
public class Garden 
    @Id
    @Column(name = "ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(name = "NAME", nullable = false)
    private String name; // "Oak Grove"

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "garden")
    private List<Plant> plants;


@Entity(name = "PLANT")   
public class Plant 
    @Id
    @Column(name = "ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(name = "NAME", nullable = false)
    private String name; // "Cherry Tomato"

    @ManyToOne
    @JoinColumn(name = "GARDEN_ID", nullable = false)
    Garden garden;

这种双向方法会让实体管理器知道两者之间存在关系,因为一方面是一对多,另一方面是多对一。希望这会有所帮助。

【讨论】:

感谢您的回答,但我正在寻找一种方法来映射当前对象模型,而无需将 Garden 添加到 Plant 不确定这是否会有所帮助,但我也会尝试一下:@OneToMany(cascade=CascadeType.ALL) @JoinTable(name="PLANT", joinColumns=@JoinColumn(name=" GARDEN_ID", referencedColumnName="ID") , inverseJoinColumns=@JoinColumn(name="ID", referencedColumnName="GARDEN_ID")) private Set 植物;

以上是关于使用带有 Hibernate 的 JPA 注释来描述外键仅在子表中的 @OneToMany 关系的主要内容,如果未能解决你的问题,请参考以下文章

要使用的Hibernate或JPA注释

Hibernate @LazyToOne 注释的 JPA 等价物是啥?

Hibernate/JPA - 使用@Future 更新还包含日期字段的实体字段

如何将继承策略与 JPA 注释和 Hibernate 混合使用?

使用 H2、JPA 注释和 Hibernate 问题的一对多关联

有人可以解释 JPA 和 Hibernate 中的 mappedBy 吗?