简体   繁体   English

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

[英]How to describe one to many relationship (JPA/HIbernate)

I am having some trouble describing a one-to-many relationship with JPA. 我在描述与JPA的一对多关系时遇到了一些麻烦。 The problem that I am facing is that I think I should be able to save the one side of the relationship and have the many side receive the newly create id for the one side. 我现在面临的问题是,我想我应该能够保存one关系的一侧,并有many方面得到了新创建的ID one侧面。

Here is a general schema of the problem domain: 这是问题域的一般架构:

Item ::= {
   *id,
   name
}

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

In the above schema an item will have a cost for some given amount of time. 在上述架构中,某项商品在一定时间内会产生费用。 It will only have one cost for that time period thus this is a one-to-many relationship. 在该时间段内只有一个成本,因此这是一对多的关系。

This is where I get into trouble. 这就是我遇到麻烦的地方。 Here is how go about describing the relationship with POJOs and JPA and am getting myself stuck. 这是描述与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));
    }
}

In the Item class I have described the fields that represent the schema plus an additional field for the relationship. 在Item类中,我描述了表示模式的字段以及关系的其他字段。 Since the relationship is one-to-many I have added that annotation and instructed it to cascade all as I want it save, update, delete etc its children when it is changed. 由于关系是一对多的,因此我添加了该批注,并指示它级联所有我希望它在更改时保存,更新,删除等子项。 Additionally, I have added the mappedBy attribute to the annotation to indicate that I want to relationship bound by the id->itemId fields in the ItemCost (I think this is where my assumption is wrong: I am assuming this instructs the JPA provider, in this case Hibernate, that upon save I want the item id to be placed into the ItmmCost at this location before propagating the save). 另外,我在注释中添加了appedBy属性,以表明我想通过ItemCost中的id-> itemId字段进行绑定(我认为这是我的假设错误的地方:我假设这会指示JPA提供程序,这种情况下是休眠的,在保存时我希望将项目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;
        }
    }
}

I have additionally filled out the Item save with its field cost and additionally with its primary key of itemId and startDate . 我还用字段cost ,主键itemIdstartDate填充了项目保存。 This is an embedded id as that seems to best describe the scenario with a compound id. 这是一个嵌入式ID,因为它似乎最好地描述了带有复合ID的方案。 Additionally, I may need to pass around this identifier in the future. 另外,将来我可能需要传递该标识符。

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

The test is rather strait forward and fails on the very last line. 该测试相当困难,并且在最后一行失败。 If I look at the db I am getting an entry in the ItemCost table but it has an itemId of zero (the default value of int in java). 如果查看数据库,我将在ItemCost表中获得一个条目,但它的itemId为零(java中int的默认值)。

Since, the cascade is saving I figure that my assumption about the mappedBy is inccorect. 因为,级联正在保存,所以我认为我对mappedBy假设是不正确的。 Is there some piece of meta data that is missing that will help inform the JPA provider that it should apply the Item.Id value to the children defined in the mapping? 是否缺少一些元数据,这些元数据将有助于通知JPA提供程序应将Item.Id值应用于映射中定义的子级?

Consider updating your classes in the following way (the assumption is that PROPERTY access is used): 考虑以以下方式更新您的类(假设使用了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;
    }
}

Additionally you would need to set up bidirectional relationship, ie 另外,您将需要建立双向关系,即

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();

The above will produce: 以上将产生:

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

Side notes (regarding your compound primary key class): 旁注(关于复合主键类):

  1. Don't use setter methods on compound primary key class specified with @EmbeddedId or @ClassId annotation (once it has been constructed it should not be changed), according to Effective Java, Item 15: Minimize mutability: 根据有效Java,条款15:最小化可变性,请勿在通过@EmbeddedId@ClassId批注指定的复合主键类上使用setter方法(一旦构造,就不应对其进行更改)。

    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. 确保独家访问任何可变组件。

    I believe a compound primary key class fits perfectly into this rule. 我相信复合主键类完全适合此规则。

  2. Specify @Temporal annotation for java.util.Date and java.util.Calendar persistent fields, according to JPA 2.0 Specification, chapter 11.1.47 Temporal Annotation: 根据JPA 2.0规范的第11.1.47章“时间注释”,为java.util.Datejava.util.Calendar持久字段指定@Temporal注释:

    The Temporal annotation must be specified for persistent fields or properties of type java.util.Date and java.util.Calendar. 必须为java.util.Date和java.util.Calendar类型的持久字段或属性指定Temporal注释。 It may only be specified for fields or properties of these types. 只能为这些类型的字段或属性指定。

    Annotating java.util.Date and java.util.Calendar types with @Temporal indicates which of the corresponding JDBC java.sql types to use when communication with JDBC driver (ie TemporalType.DATE maps to java.sql.Date and so on). @Temporal注释java.util.Datejava.util.Calendar类型表示与JDBC驱动程序通信时要使用的对应JDBC java.sql类型(即TemporalType.DATE映射到java.sql.Date等)。 Of course, if you use java.sql types in your entity classes then the annotation is no longer required (I am not aware of these types from your code snippets). 当然,如果您在实体类中使用java.sql类型,则不再需要注释(我从您的代码片段中不知道这些类型)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM