java - 如何描述一对多关系(JPA/HIbernate)

标签 java hibernate jpa one-to-many many-to-one

我在描述与 JPA 的一对多关系时遇到了一些问题。我面临的问题是我认为我应该能够保存关系的 one 端并让 many 端接收新创建的 id 为 one 一侧。

这是问题域的一般模式:

Item ::= {
   *id,
   name
}

ItemCost ::= {
    *itemId,
    *startDate,
    cost
}
* indicates PK

在上面的模式中,一个项目在给定的时间内会有成本。它在该时间段内只有一次成本,因此这是一对多关系。

这就是我遇到麻烦的地方。以下是如何描述与 POJO 和 JPA 的关系,但我自己陷入了困境。

@Entity
@Table(name = "Item", schema = "dbo")
public class Item implements Serializable {

    private Integer id;
    private String name;
    private Set<ItemCost> costs = new HashSet<>(0);

    public Item() {

    }

    public Item(String name) {
        this.name = name;
    }

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id")
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Column(name = "Name")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "id.itemId")
    public Set<ItemCost> getCosts() {
        return costs;
    }

    public void setCosts(Set<ItemCost> costs) {
        this.costs = costs;
    }

    public void addCost(Date date, int amount) {
        getCosts().add(new ItemCost(date, amount));
    }
}

在 Item 类中,我描述了表示模式的字段以及用于关系的附加字段。由于这种关系是一对多的,所以我添加了该注释并指示它在更改时按我希望的方式级联保存、更新、删除等其子项。此外,我已将 mappedBy 属性添加到注释中,以指示我想通过 ItemCost 中的 id->itemId 字段绑定(bind)关系(我认为这是我的假设错误的地方:我假设这指示 JPA 提供程序,在在这种情况下是 Hibernate,在保存时我希望在传播保存之前将项目 ID 放入此位置的 ItmmCost 中)。

@Entity
@Table(name = "ItemCost", schema = "dbo")
public class ItemCost implements Serializable {

    private Id id;
    private int cost;

    public ItemCost() {
        id = new Id();
    }

    public ItemCost(Date startDate, int cost) {
        id = new Id();
        id.setStartDate(startDate);
        this.cost = cost;
    }

    @Column(name = "cost")
    public int getCost() {
        return cost;
    }

    public void setCost(int cost) {
        this.cost = cost;
    }

    @EmbeddedId
    public Id getId() {
        return id;
    }

    public void setId(Id id) {
        this.id = id;
    }

    @Embeddable
    public static class Id implements Serializable {

        private int itemId;
        private Date startDate;

        public Id() {

        }

        public Id(Date startDate) {
            this.startDate = startDate;
        }

        @Column(name = "itemId")
        public int getItemId() {
            return itemId;
        }

        public void setItemId(int itemId) {
            this.itemId = itemId;
        }

        @Column(name = "startDate")
        public Date getStartDate() {
            return startDate;
        }

        public void setStartDate(Date startDate) {
            this.startDate = startDate;
        }

        @Override
        public int hashCode() {
            int hash = 3;
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            final Id other = (Id) obj;
            if (this.itemId != other.itemId)
                return false;
            if (!Objects.equals(this.startDate, other.startDate))
                return false;
            return true;
        }
    }
}

我还使用字段 cost 以及主键 itemIdstartDate 填写了 Item save。这是一个嵌入式 ID,因为它似乎最能描述具有复合 ID 的场景。此外,我将来可能需要传递此标识符。

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

Item item = new Item("A Jar");
item.addCost(new Date(), 10);

em.persist(item);
em.getTransaction().commit();

em = emf.createEntityManager();
item = em.find(Item.class, item.getId());
Assert.assertNotNull(item);
assertThat(item.getCosts().size(), is(1)); // fails here with a result of 0

测试相当简单,在最后一行失败了。如果我查看数据库,我会在 ItemCost 表中获得一个条目,但它的 itemId 为零(java 中的默认值 int)。

因为级联正在节省我认为我对 mappedBy 的假设是不正确的。是否缺少一些有助于通知 JPA 提供者应将 Item.Id 值应用于映射中定义的子项的元数据?

最佳答案

考虑按以下方式更新您的类(假设使用了 PROPERTY 访问权限):

public class Item implements Serializable {

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "item") //non-owning side
    public Set<ItemCost> getCosts() {
        return costs;
    }
}
public class ItemCost implements Serializable {
    private Item item;

    @ManyToOne //owning side
    @JoinColumn(name = "itemId") //the foreign key (the primary key of "Item")
    @MapsId("itemId")    //is shared with the compound primary key of "ItemCost"
    public Item getItem() {
        return item;
    }

    public void setItem(Item item) {
        this.item = item;
    }
}

此外,您还需要建立双向关系,即

em.getTransaction().begin();
Item item = new Item("A Jar");
// item.addCost(new Date(), 7);
ItemCost itemcost = new ItemCost(new Date(), 7);
itemcost.setItem(item); //owning side
item.getCosts().add(itemcost); //non-owning side
em.persist(item);
em.getTransaction().commit();

以上将产生:

INSERT INTO Item (id, Name) VALUES (1, 'A Jar')
INSERT INTO ItemCost (cost, startDate, itemId) VALUES (7, 2014-04-08 22:50:00, 1)

旁注(关于复合主键类):

  1. 不要在使用 @EmbeddedId@ClassId 注释指定的复合主键类上使用 setter 方法(一旦构造完成,就不应更改),根据 Effective Java,第 15 项:最小化可变性:

    An immutable class is simply a class whose instances cannot be modified. All the information contained in each instance is provided when is created and is fixed for the lifetime of the object. (...) To make class immutable, follow these five rules:

    1. Don't provide any methods that modify the object's state
    2. Ensure that the class can't be extended
    3. Make all fields final
    4. Make all fields private
    5. Ensure exclusive access to any mutable components.

    我相信复合主键类完全符合这条规则。

  2. 根据 JPA 2.0,为 java.util.Datejava.util.Calendar 持久字段指定 @Temporal 注释规范,第 11.1.47 章时间注释:

    The Temporal annotation must be specified for persistent fields or properties of type java.util.Date and java.util.Calendar. It may only be specified for fields or properties of these types.

    使用 @Temporal 注释 java.util.Datejava.util.Calendar 类型 指示在与 JDBC 驱动程序通信时使用哪个相应的 JDBC java.sql 类型(即 TemporalType.DATE 映射到 java.sql.Date等等)。当然,如果您在实体类中使用 java.sql 类型,则不再需要注释(我从您的代码片段中不知道这些类型)。

关于java - 如何描述一对多关系(JPA/HIbernate),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22919153/

相关文章:

java - 无法通过 servlet.xml 设置 session 工厂

java - 在不同线程中调用SQL更新后如何修复 'SQLException SQL0913'

java - 创建@OneToOne时抛出错误无法检索ResultSet

java - 获取按钮android java的xml属性

java - Spring Boot 外部配置和 xml 上下文

Java:连接到 MS-Access 数据库(mdb 或 mde)

mysql - Grails/GORM : How do Domains and Associations map to a MySql Database

java - 如何解决java.lang.NoSuchMethodError : org. hibernate.integrator.internal.IntegratorServiceImpl

java - 来自一对多关系的实体仅在应用程序重启后可见(JPA 和 PostgreSQL)

java - FAIL - 上下文路径/salamander 中的应用程序无法启动