我在描述与JPA的一对多关系时遇到了一些麻烦。我面临的问题是,我认为我应该能够保存关系的one
方并让many
方接收one
方的新创建ID。
以下是问题域的一般架构:
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-&gt; itemId字段绑定关系(我认为这是我的假设是错误的:我假设这指示了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
以及主键itemId
和startDate
。这是一个嵌入式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
测试相当严格,最后一行失败了。如果我查看db,我会在ItemCost表中获得一个条目,但它的itemId为零(java中默认的int值)。
因为,级联节省了我认为我对mappedBy
的假设是inccorect。是否有一些缺少的元数据有助于告知JPA提供者它应该将Item.Id
值应用于映射中定义的子项?
答案 0 :(得分:1)
考虑以下列方式更新您的类(假设使用了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)
附注(关于复合主键类):
根据Effective Java,不要在使用@EmbeddedId
或@ClassId
注释指定的复合主键类上使用setter方法(一旦构造它就不应该更改) ,第15项:尽量减少可变性:
不可变类只是一个无法修改其实例的类。 每个实例中包含的所有信息都是在何时提供的 创建并在对象的生命周期内固定。 (...)制作 class immutable,遵循以下五条规则:
- 不提供任何修改对象状态的方法
- 确保不能扩展课程
- 将所有字段设为最终
- 将所有字段设为私有
- 确保对任何可变组件的独占访问。
醇>
我认为复合主键类非常适合此规则。
根据JPA 2.0规范第11.1.47章时间注释,为@Temporal
和java.util.Date
个持久字段指定java.util.Calendar
注释:
必须为持久字段指定
Temporal
注释 java.util.Date和java.util.Calendar类型的属性。它可能只是 为这些类型的字段或属性指定。
使用java.util.Date
为java.util.Calendar
和@Temporal
类型添加注释
指示与JDBC驱动程序通信时要使用的相应JDBC java.sql
类型(即TemporalType.DATE
映射到java.sql.Date
等)。当然,如果您在实体类中使用java.sql
类型,则不再需要注释(我不知道您的代码片段中的这些类型)。