Category(商品类别)和Item(商品)是多对多的关系,在关系数据模型中,无法直接表达CATEGORIES表和ITEMS表之间的多对多,需要创建一个连接表CATEGORY_ITEM,它同时参照CATEGORIES表和ITEMS表。
1.映射单向多对多关联关系
仅建立Category类到Item类的单向多对多关联,在Category类中定义items属性,而在Item类中不定义categories属性。
数据库表:
create table CATEGORIES(
ID bigint not null,
NAME varchar(15),
primary key (ID)
);
create table ITEMS(
ID bigint not null,
NAME varchar(15),
BASE_PRICE double precision,
primary key(ID)
);
create table CATEGORY_ITEM(
CATEGORY_ID bigint not null,
ITEM_ID bigint not null,
primary key(CATEGORY_ID,ITEM_ID),
foreign key (CATEGORY_ID) references CATEGORIES(ID),
foreign key (ITEM_ID) references ITEMS(ID)
);
Category.java:
public class Category implements Serializable {
private Long id;
private String name;
private Set items=new HashSet();
constructor()...; getter()...; setter()...;
}
Category.hbm.xml:
<hibernate-mapping >
<class name="mypack.Category" table="CATEGORIES" >
<id name="id" type="long" column="ID">
<generator class="increment"/>
</id>
<property name="name" column="NAME" type="string" />
<set name="items" table="CATEGORY_ITEM"
lazy="true"
cascade="save-update">
<key column="CATEGORY_ID" />
<many-to-many class="mypack.Item" column="ITEM_ID" />
</set>
</class>
</hibernate-mapping>
<set>元素的<key>子元素指定 CATEGORY_ITEM表中参照 CATEGORIES表的外键为 CATEGORY_ID, <many-to-many>子元素的class属性指定items集合中存放的是Item对象,column属性指定 CATEGORY_ITEM表中参照ITEMS表的外键是 ITEM_ID。
也可以使用<idbag>、<list>和<map>元素来映射Category类的items集合。
Item.java:
public class Item implements Serializable {
private Long id;
private String name;
private double basePrice;
constructor()...; getter()...; setter()...;
}
Item.hbm.xml:
<hibernate-mapping >
<class name="mypack.Item" table="ITEMS" >
<id name="id" type="long" column="ID">
<generator class="increment"/>
</id>
<property name="name" column="NAME" type="string" />
<property name="basePrice" column="BASE_PRICE" type="double" />
</class>
</hibernate-mapping>
2.映射双向多对多关联关系
建立Category类到Item类的双向多对多关联,在Category类中定义items属性,而在Item类中也要定义categories属性。
在双向多对多关联中,必须把其中一端的inverse属性设为true。inverse属性为false的一端可以使用<set>、<idbag>、<list>和<map>元素,而在inverse属性为true的一端只能使用<set>和<bag>元素。
<idbag>和<bag>元素的相同点在于都允许在集合中存放重复元素,并且都不支持按索引位置排序,区别在于<idbag>元素要求在连接表中必须定义代理主键,而<bag>元素没有此要求。
2.1.关联两端均使用<set>元素
数据库表:
create table CATEGORIES(
ID bigint not null,
NAME varchar(15),
primary key (ID)
);
create table ITEMS(
ID bigint not null,
NAME varchar(15),
BASE_PRICE double precision,
primary key(ID)
);
create table CATEGORY_ITEM(
CATEGORY_ID bigint not null,
ITEM_ID bigint not null,
primary key(CATEGORY_ID,ITEM_ID),
foreign key (CATEGORY_ID) references CATEGORIES(ID),
foreign key (ITEM_ID) references ITEMS(ID)
);
Category.java:
public class Category implements Serializable {
private Long id;
private String name;
private Set items=new HashSet();
constructor()...; getter()...; setter()...;
}
Category.hbm.xml:
<hibernate-mapping >
<class name="mypack.Category" table="CATEGORIES" >
<id name="id" type="long" column="ID">
<generator class="increment"/>
</id>
<property name="name" column="NAME" type="string" />
<set name="items" table="CATEGORY_ITEM"
lazy="true"
cascade="save-update">
<key column="CATEGORY_ID" />
<many-to-many class="mypack.Item" column="ITEM_ID" />
</set>
</class>
</hibernate-mapping>
Item.java:
public class Item implements Serializable {
private Long id;
private String name;
private double basePrice;
private Set categories=new HashSet();
constructor()...; getter()...; setter()...;
}
Item.hbm.xml:
<hibernate-mapping >
<class name="mypack.Item" table="ITEMS" >
<id name="id" type="long" column="ID">
<generator class="increment"/>
</id>
<property name="name" column="NAME" type="string" />
<property name="basePrice" column="BASE_PRICE" type="double" />
<set name="categories" table="CATEGORY_ITEM"
lazy="true"
inverse="true"
cascade="save-update">
<key column="ITEM_ID" />
<many-to-many class="mypack.Category" column="CATEGORY_ID" />
</set>
</class>
</hibernate-mapping>
2.2.在inverse端使用<bag>元素
假如在Category类中已经定义了List类型的items集合,并在Item类中定义了List类型的categories集合,只有在non-inverse的一端可以使用<list>元素,那么在inverse的一端使用什么元素,显然不能使用<set>,因为<set>不能映射<list>类型的集合,为此,Hibernate提供了<bag>元素。
数据库表:
create table CATEGORIES(
ID bigint not null,
NAME varchar(15),
primary key (ID)
);
create table ITEMS(
ID bigint not null,
NAME varchar(15),
BASE_PRICE double precision,
primary key(ID)
);
create table CATEGORY_ITEM(
POSITION bigint not null,
CATEGORY_ID bigint not null,
ITEM_ID bigint not null,
primary key(POSITION,CATEGORY_ID),
foreign key (CATEGORY_ID) references CATEGORIES(ID),
foreign key (ITEM_ID) references ITEMS(ID)
);
Category.java:
public class Category implements Serializable {
private Long id;
private String name;
private List items=new ArrayList();
constructor()...; getter()...; setter()...;
}
Category.hbm.xml:
<hibernate-mapping >
<class name="mypack.Category" table="CATEGORIES" >
<id name="id" type="long" column="ID">
<generator class="increment"/>
</id>
<property name="name" column="NAME" type="string" />
<list name="items" table="CATEGORY_ITEM"
lazy="true"
cascade="save-update">
<key column="CATEGORY_ID" />
<index column="POSITION" />
<many-to-many class="mypack.Item" column="ITEM_ID" />
</list>
</class>
</hibernate-mapping>
Item.java:
public class Item implements Serializable {
private Long id;
private String name;
private double basePrice;
private List categories=new ArrayList();
constructor()...; getter()...; setter()...;
}
Item.hbm.xml:
<hibernate-mapping >
<class name="mypack.Item" table="ITEMS" >
<id name="id" type="long" column="ID">
<generator class="increment"/>
</id>
<property name="name" column="NAME" type="string" />
<property name="basePrice" column="BASE_PRICE" type="double" />
<bag name="categories" table="CATEGORY_ITEM"
lazy="true"
inverse="true"
cascade="save-update">
<key column="ITEM_ID" />
<many-to-many class="mypack.Category" column="CATEGORY_ID" />
</bag>
</class>
</hibernate-mapping>
2.3.使用组件类集合
Item和Order之间也是多对多的关联关系,可以通过专门的组件类LineItem来描述Order与Item的关联关系。
LineItem类作为组件类没有OID,LINEITEMS表为连接表。
数据库表:
create table ORDERS(
ID bigint not null,
ORDER_NUMBER varchar(15),
primary key (ID)
);
create table ITEMS(
ID bigint not null,
NAME varchar(15),
BASE_PRICE double precision,
primary key(ID)
);
create table LINEITEMS(
ORDER_ID bigint not null,
ITEM_ID bigint not null,
BASE_PRICE double precision not null,
QUANTITY int not null,
primary key(ORDER_ID,ITEM_ID,BASE_PRICE,QUANTITY),
oreign key (ORDER_ID) references ORDERS(ID),
foreign key (ITEM_ID) references ITEMS(ID)
);
Order.java:
public class Order implements Serializable {
private Long id;
private String orderNumber;
private double price;
private Set lineItems=new HashSet();
constructor()...; getter()...; setter()...;
}
Order.hbm.xml:
<hibernate-mapping >
<class name="mypack.Order" table="ORDERS" >
<id name="id" type="long" column="ID">
<generator class="increment"/>
</id>
<property name="orderNumber" column="ORDER_NUMBER" type="string" />
<property name="price" formula=
"(select sum(line.BASE_PRICE*line.QUANTITY) from LINEITEMS line where line.ORDER_ID=ID)" />
<set name="lineItems" lazy="true" table="LINEITEMS" >
<key column="ORDER_ID" />
<composite-element class="mypack.LineItem" >
<parent name="order" />
<many-to-one name="item" class="mypack.Item" column="ITEM_ID" not-null="true"/>
<property name="quantity" column="QUANTITY" type="int" not-null="true" />
<property name="basePrice" column="BASE_PRICE" type="double" not-null="true" />
</composite-element>
</set>
</class>
</hibernate-mapping>
Order类的price属性被映射为了派生属性,因此在ORDERS表中不必定义PRICE字段。
Item.java:
public class Item implements Serializable {
private Long id;
private String name;
private double basePrice;
private Set lineItems=new HashSet();
constructor()...; getter()...; setter()...;
}
Item.hbm.xml:
<hibernate-mapping >
<class name="mypack.Item" table="ITEMS" >
<id name="id" type="long" column="ID">
<generator class="increment"/>
</id>
<property name="name" column="NAME" type="string" />
<property name="basePrice" column="BASE_PRICE" type="double" />
<set name="lineItems" lazy="true" inverse="true" table="LINEITEMS" >
<key column="ITEM_ID" />
<composite-element class="mypack.LineItem" >
<parent name="item" />
<many-to-one name="order" class="mypack.Order" column="ORDER_ID" not-null="true"/>
<property name="quantity" column="QUANTITY" type="int" not-null="true" />
<property name="basePrice" column="BASE_PRICE" type="double" not-null="true" />
</composite-element>
</set>
</class>
</hibernate-mapping>
LineItem.java:
public class LineItem implements Serializable {
private Item item;
private Order order;
private double basePrice;
private int quantity;
constructor()...; getter()...; setter()...;
}
2.4.把多对多关联分解为两个一对多关联
对于Order与Item的多对多关联,也可以把它分解为两个一对多关联。LineItem为独立的实体类,有单独的OID。Order类与LineItem类,以及Item类与LineItem类都是一对多的双向关联关系。
LINEITEMS表作为连接表,LINEITEMS表有单独的ID代理主键。
数据库表:
create table ORDERS(
ID bigint not null,
ORDER_NUMBER varchar(15),
primary key (ID)
);
create table ITEMS(
ID bigint not null,
NAME varchar(15),
BASE_PRICE double precision,
primary key(ID)
);
create table LINEITEMS(
ID bigint not null,
ORDER_ID bigint not null,
ITEM_ID bigint not null,
BASE_PRICE double precision,
QUANTITY int,
primary key(ID),
foreign key (ORDER_ID) references ORDERS(ID),
foreign key (ITEM_ID) references ITEMS(ID)
);
Order.java:
public class Order implements Serializable {
private Long id;
private String orderNumber;
private double price;
private Set lineItems=new HashSet();
constructor()...; getter()...; setter()...;
public void setLineItems(Set lineItems) {
this.lineItems = lineItems;
calculatePrice();
}
private void calculatePrice(){
double totalPrice=0;
if(lineItems==null) return;
Iterator it=lineItems.iterator();
while(it.hasNext()){
LineItem line=(LineItem)it.next();
totalPrice=totalPrice+line.getUnitPrice();
}
setPrice(totalPrice);
}
}
在setLineItems()方法中自动为price属性赋值,因此无需在Order.hbm.xml中映射price属性,也无须在ORDERS表中定义PRICE字段。setLineItems()方法调用calculatePrice()方法计算price属性值。
Order.hbm.xml:
<hibernate-mapping >
<class name="mypack.Order" table="ORDERS" >
<id name="id" type="long" column="ID">
<generator class="increment"/>
</id>
<property name="orderNumber" column="ORDER_NUMBER" type="string" />
<set name="lineItems" lazy="true" inverse="true" cascade="save-update">
<key column="ORDER_ID" />
<one-to-many class="mypack.LineItem" />
</set>
</class>
</hibernate-mapping>
Item.java:
public class Item implements Serializable {
private Long id;
private String name;
private double basePrice;
private Set lineItems=new HashSet();
constructor()...; getter()...; setter()...;
}
Item.hbm.xml:
<hibernate-mapping >
<class name="mypack.Item" table="ITEMS" >
<id name="id" type="long" column="ID">
<generator class="increment"/>
</id>
<property name="name" column="NAME" type="string" />
<property name="basePrice" column="BASE_PRICE" type="double" />
<set name="lineItems" lazy="true" inverse="true" cascade="save-update">
<key column="ITEM_ID" />
<one-to-many class="mypack.LineItem" />
</set>
</class>
</hibernate-mapping>
LineItem.java:
public class LineItem implements Serializable {
private Long id;
private Item item;
private Order order;
private double basePrice;
private int quantity;
constructor()...; getter()...; setter()...;
}
LineItem.hbm.xml:
<hibernate-mapping >
<class name="mypack.LineItem" table="LINEITEMS" >
<id name="id" type="long" column="ID">
<generator class="increment"/>
</id>
<property name="quantity" column="QUANTITY" type="int" />
<property name="basePrice" column="BASE_PRICE" type="double" />
<many-to-one name="order" column="ORDER_ID" class="mypack.Order" not-null="true" />
<many-to-one name="item" column="ITEM_ID" class="mypack.Item" not-null="true" />
</class>
</hibernate-mapping>
所有的多对多关联都可以分解为两个一对多关联,按照这种方式映射多对多关联,会使域模型和关系数据模型具有更好的可扩展性。