近期遇到一个ORM映射需求,即在“多-多”关系中需要将对应到两端实体的外键作为关系的联合主键,查了N多资料,试了N种方法,终于找到了自认为比较合理的ORM映射方式,在这里贴出来,与大家分享一下。
1、问题描述
这是一个古老的订单问题,涉及到的对象有3个,分别为:订单,订单项,产品。订单与产品间是“多-一”关系,它们间的关系通过订单项来反映:一个订单包含多个订单项(进而包括多种产品),一个订单项对应到一种产品。实际的数据库表结构如下所示(为了简化起见省略了其它的关系字段)。
- 订单表Orders。包括字段:Order ID,Order Amount,Order Date,Required Date,Ship Date,Courier Website,Ship Via,Shipped,PO#,Payment Received。
- 产品表Product。包括字段:Product ID,Product Name,Color,Size,M/F,Price (SRP),Product Class。
- 订单项表Orders Detail。Order ID,Product ID,Unit Price,Quantity。其中Order ID为到订单表(Orders)的外键,Product ID为到产品表(Product)的外键,并且它们放在一起作为订单项表的联合主键。
现在的需求是实现这几个实体及其相互关系的ORM映射,并且不能改变数据库的表结构。
2、解决方案
由于在订单项表中的2个外键需要同时承担联合主键的职责,因此感觉不是很好处理。我采用了下面的解决办法,即:为订单项设立一个联合主键类,同时在主键类中实现外键映射。
下面是订单类Order的代码,其中的映射使用JPA的Annotation来描述的。
@Entity
@Table(name = " Orders " )
public class Order ... {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "[Order ID]")
private Integer orderID;
@Column(name = "[Order Amount]")
private Double orderAmount;
@Column(name = "[Order Date]")
@Temporal(value = TemporalType.DATE)
private Date orderDate;
@Column(name = "[Required Date]")
private Date requiredDate;
@Column(name = "[Ship Date]")
private Date shipDate;
@Column(name = "[Courier Website]")
private String courierWebsite;
@Column(name = "[Ship Via]")
private String shipVia;
@Column(name = "Shipped")
private Boolean Shipped;
@Column(name = "[PO#]")
private String pO;
@Column(name = "[Payment Received]")
private Boolean paymentReceived;
@OneToMany(mappedBy = "pk.order", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<OrderDetail> orderDetails;
public Integer getOrderID() ...{
return orderID;
}
public void setOrderID(Integer orderID) ...{
this.orderID = orderID;
}
public Double getOrderAmount() ...{
return orderAmount;
}
public void setOrderAmount(Double orderAmount) ...{
this.orderAmount = orderAmount;
}
public Date getOrderDate() ...{
return orderDate;
}
public void setOrderDate(Date orderDate) ...{
this.orderDate = orderDate;
}
public Date getRequiredDate() ...{
return requiredDate;
}
public void setRequiredDate(Date requiredDate) ...{
this.requiredDate = requiredDate;
}
public Date getShipDate() ...{
return shipDate;
}
public void setShipDate(Date shipDate) ...{
this.shipDate = shipDate;
}
public String getCourierWebsite() ...{
return courierWebsite;
}
public void setCourierWebsite(String courierWebsite) ...<
@Table(name = " Orders " )
public class Order ... {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "[Order ID]")
private Integer orderID;
@Column(name = "[Order Amount]")
private Double orderAmount;
@Column(name = "[Order Date]")
@Temporal(value = TemporalType.DATE)
private Date orderDate;
@Column(name = "[Required Date]")
private Date requiredDate;
@Column(name = "[Ship Date]")
private Date shipDate;
@Column(name = "[Courier Website]")
private String courierWebsite;
@Column(name = "[Ship Via]")
private String shipVia;
@Column(name = "Shipped")
private Boolean Shipped;
@Column(name = "[PO#]")
private String pO;
@Column(name = "[Payment Received]")
private Boolean paymentReceived;
@OneToMany(mappedBy = "pk.order", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<OrderDetail> orderDetails;
public Integer getOrderID() ...{
return orderID;
}
public void setOrderID(Integer orderID) ...{
this.orderID = orderID;
}
public Double getOrderAmount() ...{
return orderAmount;
}
public void setOrderAmount(Double orderAmount) ...{
this.orderAmount = orderAmount;
}
public Date getOrderDate() ...{
return orderDate;
}
public void setOrderDate(Date orderDate) ...{
this.orderDate = orderDate;
}
public Date getRequiredDate() ...{
return requiredDate;
}
public void setRequiredDate(Date requiredDate) ...{
this.requiredDate = requiredDate;
}
public Date getShipDate() ...{
return shipDate;
}
public void setShipDate(Date shipDate) ...{
this.shipDate = shipDate;
}
public String getCourierWebsite() ...{
return courierWebsite;
}
public void setCourierWebsite(String courierWebsite) ...<