在关系型数据库中,如果表与表之间存在多对多的关系,是不符合关系型数据库设计范式的。一般需要把一个多对多关系映射为一张关联双方的表,这张表叫做连接表。
不管是单向映射还是双向映射,多对多关系都是基于连接表实现的。
1、下面通过用户(User)和角色(Role)来讲解多对多的关联映射。一个用户(User)有多个角色(Role),一个角色对应多个用户,因此用户和角色之间构成多对多的关联关系。
实体类User和Role的数据库表设计如下:
CREATE TABLE user (
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50)
);
CREATE TABLE role (
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50)
);
连接表user_role可以有两中建表方式:
第一种:
CREATE TABLE user_role (
user_id INT(11) NOT NULL,
role_id INT(11) NOT NULL,
foreign key(user_id) references user (id),
foreign key(role_id) references role (id)
);
第二种:
CREATE TABLE user_role (
user_id INT(11) NOT NULL,
role_id INT(11) NOT NULL,
foreign key(user_id) references user (id),
foreign key(role_id) references role (id),
primary key(user_id, role_id)
);
注意,创建连接表user_role时,不能另外创建一个主键字段。要么像第一种方式其主键由数据库自动生成,要么像第二种方式将两个外键结合声明成一个复合主键。
1. 双向的多对多关联映射(User<--->Role)
实体类User:
public class User {
private int id;
private String name;
private Set roles = new HashSet();
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Set getRoles() {
return roles;
}
public void setRoles(Set roles) {
this.roles = roles;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
实体类Role:
public class Role {
private int id;
private String name;
private Set users = new HashSet();
public Set getUsers() {
return users;
}
public void setUsers(Set users) {
this.users = users;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
实体类User的映射文件:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="User" table="user">
<id name="id">
<generator class="native"></generator>
</id>
<property name="name"></property>
<set name="roles" table=“user_role” cascade=“save-update” inverse = “false”>
<key column="user_id"></key>
<many-to-many class="Role" column="role_id"></many-to-many>
</set>
</class>
</hibernate-mapping>
下面介绍下<set>元素中各属性和子元素的含义。
name:说明在User类中,存放Role对象的集合属性是roles。
table:说明在这个多对多关联中,存储关联关系的连接表是user_role。
inverse:说明关联关系的主控方,值为true或false,默认的inverse=“false”(当inverse省略时的值)。值为false的一端为主控方,来维护关联双方数据库更新的同步。这个映射文件中inverse的值为false,表明User为主导方,当User对象的数据库表发生更新时,关联的Role对象的数据库表也随之更新。
cascade:说明级联关系的模式,即操作一个对象时,是否同样操作所关联的对象。默认的cascade=“none”,cascade的取值可以为none、save-update、delete、all等。all:在所有的操作的情况下均进行级联;none:在所有操作的情况下均不进行级联操作;save-update:在执行更新操作时级联;delete:在执行删除操作时级联。inverse和cascade配合起来使用。
key:说明连接表user_role中使用外键user_id来关联表user。
many-to-many:说明这个多对多关系中,另一个多方实体类是Role,并且使用连接表的role_id字段来关联表role。
实体类Role的映射文件:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="Role" table="role">
<id name="id">
<generator class="native"></generator>
</id>
<property name="name"></property>
<set name="users" table="user_role">
<key column="role_id"></key>
<many-to-many class="User" column="user_id"></many-to-many>
</set>
</class>
</hibernate-mapping>
2.单向的多对多关联映射(User--->Role)
对于单向的多对多关联映射,只需把双向关联的一端去掉就得到单向的多对多关联映射。对于从User到Role的单向关联关系,将Role一端的关联关系去掉即可,因此实体类User和它的映射文件不变。
实体类Role:
public class Role {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
实体类Role的映射文件:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="Role" table="role">
<id name="id">
<generator class="native"></generator>
</id>
<property name="name"></property>
</class>
</hibernate-mapping>
3. 双向多对多关联应该注意的事项
(1)在双方的映射文件中应该都设置cascade属性为:cascade=“save-update”,这样如果在存储时没有把关联的另一方存入,Hibernate会进行级联存储,避免发生存储临时对象的错误。
(2)在双方的映射文件中,应该设置一方的inverse属性为inverse=“true”,另一方为inverse=“false”,这样数据库只根据一方的状态存入关联关系,避免重复存取关联表。
(3)在程序中设置好双方对象的关联关系,这样才能保证无论主控方是哪一方,它们的关联关系都能存入数据库。