在Hibernate中使用联合主键作为外键

论坛 期权论坛 编程之家     
选择匿名的用户   2021-5-31 21:38   53   0

近期遇到一个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) {
this.courierWebsite = courierWebsite;
}


public String getShipVia() {
return shipVia;
}


public void setShipVia(String shipVia) {
this.shipVia = shipVia;
}


public Boolean getShipped() {
return Shipped;
}


public void setShipped(Boolean shipped) {
Shipped
= shipped;
}


public String getPO() {
return pO;
}


public void setPO(String po) {
pO
= po;
}


public Boolean getPaymentReceived() {
return paymentReceived;
}


public void setPaymentReceived(Boolean paymentReceived) {
this.paymentReceived = paymentReceived;
}


public Set<OrderDetail> getOrderDetails() {
Session session
= HibernateUtil.currentSession();
session.beginTransaction();
session.lock(
this, LockMode.NONE);
Set
<OrderDetail> orderDetails = this.orderDetails;
return orderDetails;
}


public void setOrderDetails(Set<OrderDetail> orderDetails) {
this.orderDetails = orderDetails;
}


@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result
= prime * result + ((orderID == null) ? 0 : orderID.hashCode());
return result;
}


@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Order other = (Order) obj;
if (orderID == null) {
if (other.orderID != null)
return false;
}
else if (!orderID.equals(other.orderID))
return false;
return true;
}


}

下面是产品类Product的代码。

@Entity
@Table(name
= " Product " )
public class Product {

@Id
@Column(name
= "[Product ID]")
private Integer productID;

@Column(name
= "[Product Name]")
private String productName;

@Column(name
= "Color")
private String color;

@Column(name
= "Size")
private String size;

@Column(name
= "[M/F]")
private String m_F;

@Column(name
= "[Price (SRP)]")
private double price_SRP;

@Column(name
= "[Product Class]")
private String productClass;

public Integer getProductID() {
return productID;
}


public void setProductID(Integer productID) {
this.productID = productID;
}


public String getProductName() {
return productName;
}


public void setProductName(String productName) {
this.productName = productName;
}


public String getColor() {
return color;
}


public void setColor(String color) {
this.color = color;
}


public String getSize() {
return size;
}


public void setSize(String size) {
this.size = size;
}


public String getM_F() {
return m_F;
}


public void setM_F(String m_f) {
m_F
= m_f;
}


public double getPrice_SRP() {
return price_SRP;
}


public void setPrice_SRP(double price_SRP) {
this.price_SRP = price_SRP;
}


public String getProductClass() {
return productClass;
}


public void setProductClass(String productClass) {
this.productClass = productClass;
}


@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result
= prime * result
+ ((productID == null) ? 0 : productID.hashCode());
return result;
}


@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Product other = (Product) obj;
if (productID == null) {
if (other.productID != null)
return false;
}
else if (!productID.equals(other.productID))
return false;
return true;
}


}

注意到产品类中并没有订单项对象,从实际的逻辑上来考虑产品并不知道订单项的存在,因此这样设计成单向的关系是合理的。

下面是订单项主键类OrderDetailPK的源代码。

@Embeddable
public class OrderDetailPK implements Serializable {

@ManyToOne(cascade
= CascadeType.ALL,fetch=FetchType.LAZY)
@JoinColumn(name
= "[Order ID]")
Order order;

@OneToOne(fetch
=
FetchType.LAZY)
@JoinColumn(name
= "[Product ID]"
)
private
Product product;

public OrderDetailPK() {

}


public OrderDetailPK(Order order, Product product) {
this.order = order;
this.product = product;
}


public Order getOrder() {
return order;
}


public void setOrder(Order order) {
this.order = order;
}


public Product getProduct() {
return product;
}


public void setProduct(Product product) {
this.product = product;
}


@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result
= prime * result + ((order == null) ? 0 : order.hashCode());
result
= prime * result + ((product == null) ? 0 : product.hashCode());
return result;
}


@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final OrderDetailPK other = (OrderDetailPK) obj;
if (order == null) {
if (other.order != null)
return false;
}
else if (!order.equals(other.order))
return false;
if (product == null) {
if (other.product != null)
return false;
}
else if (!product.equals(other.product))
return false;
return true;
}


}
private

作为联合主键类,该类必须实现equals方法和hashcode方法。注意到该类的关键部分是:

@ManyToOne(cascade = CascadeType.ALL,fetch=FetchType.LAZY)
@JoinColumn(name
= "[Order ID]"
)
private Order order;

@OneToOne(fetch
=
FetchType.LAZY)
@JoinColumn(name
= "[Product ID]"
)
private
Product product;

在该类中定义了2个属性,分别用来表示到Order和Product的映射。这种主键类定义方式是符合逻辑的:订单项由产品和订单共同唯一确定,且从订单项可以导航到相应的订单和产品信息。

以上面的主键类作为主键,订单项类OrderDetail的代码如下所示。

@Entity
@Table(name
= " [Orders Detail] " )
public class OrderDetail {

@Column(name
= "[Unit Price]")
private Double unitPrice;

@Column(name
= "Quantity")
private Integer quantity;

@EmbeddedId
private OrderDetailPK pk=new OrderDetailPK();

public Double getUnitPrice() {
return unitPrice;
}


public void setUnitPrice(Double unitPrice) {
this.unitPrice = unitPrice;
}


public Integer getQuantity() {
return quantity;
}


public void setQuantity(Integer quantity) {
this.quantity = quantity;
}


@Transient
public Order getOrder() {
Session session
= HibernateUtil.currentSession();
session.beginTransaction();
session.lock(
this, LockMode.NONE);
Order order
=this.pk.getOrder();
return order;
}


public void setOrder(Order order) {
this.pk.setOrder(order);
}


@Transient
public Product getProduct() {
Session session
= HibernateUtil.currentSession();
session.beginTransaction();
session.lock(
this, LockMode.NONE);
Product product
=this.pk.getProduct();
return product;
}


public void setProduct(Product product) {
this.pk.setProduct(product);
}

}

在该类中包含了刚才定义的订单项主键类,同时暴露出了瞬态属性order和product(通过get方法和set方法),这两个属性从主键类中获取相应的实体对象,代码是:

@Transient
public Order getOrder() {
Session session
= HibernateUtil.currentSession();
session.beginTransaction();
session.lock(
this, LockMode.NONE);
Order order
=this.pk.getOrder();
return order;
}


public void setOrder(Order order) {
this.pk.setOrder(order);
}


@Transient
public Product getProduct() {
Session session
= HibernateUtil.currentSession();
session.beginTransaction();
session.lock(
this, LockMode.NONE);
Product product
=this.pk.getProduct();
return product;
}


public void setProduct(Product product) {
this.pk.setProduct(product);
}

3、总结

在数据库中如果存在多-多关系,该关系需要单独映射成实体,并且需要在该关系中使用关系的两端外键作为关系的联合主键,则可以采用本文所述的这种映射方式:为联合外键构建相应的主键类,并在主键类中定义外键映射,最后把主键类作为嵌入式主键嵌入到实体中。

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:3875789
帖子:775174
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP