入门 23 - 多对多实体映像
假设现在有User与Server两个类别,一个User可以被授权使用多台Server,而在Server上也记录授权使用它的使用者,就User与Server两者而言即使多对多的关系。
在程序设计时,基本上是不建议直接在User与Server之间建立多对多关系,这会使得User与Server相互依赖,通常会透过一个中介类别来维护两者之间的多对多关系,避免两者的相互依赖。
如果一定要直接建立User与Server之间的多对多关系,Hibernate也是支持的,基本上只要您了解之前介绍的几个实体映像,建立多对多关联在配置上并不困难。
先看一下我们设计的User与Server类别:
User.java
package onlyfun.caterpillar;
import java.util.*;
public class User {
private long id;
private String name;
private Set servers = new HashSet();
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set getServers() {
return servers;
}
public void setServers(Set servers) {
this.servers = servers;
}
}
Server.java
package onlyfun.caterpillar;
import java.util.*;
public class Server {
private long id;
private String address;
private Set users = new HashSet();
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Set getUsers() {
return users;
}
public void setUsers(Set users) {
this.users = users;
}
}
这边各使用HashSet来保存彼此的关系,在多对多关系映射上,我们可以建立单向或双向关系,这边直接介绍双向关系映像,并藉由设定inverse= "true",将关系的维护交由其中一方来维护,这么作的结果,在原始码的撰写上,也比较符合Java的对象关系维护,也就是双方都要设置至对方的参考。
首先来看看User.hbm.xml:
User.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.User" table="USER">
<id name="id" column="USER_ID" unsaved-value="0">
<generator class="increment"/>
</id>
<property name="name">
<column name="NAME" length="16" not-null="true"/>
</property>
<set name="servers"
table="USER_SERVER"
cascade="save-update">
<key column="USER_ID"/>
<many-to-many class="onlyfun.caterpillar.Server"
column="SERVER_ID"/>
</set>
</class>
</hibernate-mapping>
在数据库中,数据表之间的多对多关系是透过一个中介的数据表来完成,例如在这个例子中,USER数据表与USER_SERVER数据表是一对多,而 USER_SERVER对SERVER是多对一,从而完成USER至SERVER的多对多关系,在USER_SERVER数据表中,将会有USER_ID 与SERVER_ID共同作为主键,USER_ID作为一个至USER的外键参考,而SERVER_ID作为一个至SERVER的外键参考。
来看看Server.hbm.xml映射文件:
Server.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.Server" table="SERVER">
<id name="id" column="SERVER_ID" unsaved-value="0">
<generator class="increment"/>
</id>
<property name="address" type="string"/>
<set name="users"
table="USER_SERVER"
inverse="true"
cascade="save-update">
<key column="SERVER_ID"/>
<many-to-many class="onlyfun.caterpillar.User"
column="USER_ID"/>
</set>
</class>
</hibernate-mapping>
设置上与User.hbm.xml是类似的,只是增加了inverse="true",表示将关系的维护交由另一端,注意我们在User与 Server的cascade都是设置为save-update,在多对多的关系中,all、delete等cascade是没有意义的,因为多对多中, 并不能因为父对象被删除,而造成被包括的子对象被删除,因为可能还有其它的父对象参考至这个子对象。
我们使用下面这个程序来测试:
HibernateTest.java
import onlyfun.caterpillar.*;
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.*;
public class HibernateTest {
public static void main(String[] args) throws HibernateException {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Server server1 = new Server();
server1.setAddress("PC-219");
Server server2 = new Server();
server2.setAddress("PC-220");
Server server3 = new Server();
server3.setAddress("PC-221");
User user1 = new User();
user1.setName("caterpillar");
User user2 = new User();
user2.setName("momor");
user1.getServers().add(server1);
user1.getServers().add(server2);
user1.getServers().add(server3);
server1.getUsers().add(user1);
server2.getUsers().add(user1);
server3.getUsers().add(user1);
user2.getServers().add(server1);
user2.getServers().add(server3);
server1.getUsers().add(user2);
server3.getUsers().add(user2);
Session session = sessionFactory.openSession();
Transaction tx= session.beginTransaction();
session.save(user1);
session.save(user2);
tx.commit();
session.close();
sessionFactory.close();
}
}
注意由于设定了inverse="true",所以必须分别设定User与Server之间的相互参考,来看看实际上数据库中是如何储存的:
mysql> select * FROM USER;
+---------+-------------+
| USER_ID | NAME |
+---------+-------------+
| 1 | caterpillar |
| 2 | momor |
+---------+-------------+
2 rows in set (0.00 sec)
mysql> select * FROM USER_SERVER;
+-----------+---------+
| SERVER_ID | USER_ID |
+-----------+---------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 1 | 2 |
| 2 | 2 |
+-----------+---------+
5 rows in set (0.00 sec)
mysql> select * FROM SERVER;
+-----------+---------+
| SERVER_ID | address |
+-----------+---------+
| 1 | PC-219 |
| 2 | PC-221 |
| 3 | PC-220 |
+-----------+---------+
3 rows in set (0.00 sec)
有关于多对多更多的例子与说明,您可以参考Hibernate in Action的6.3。