一對一(唯一外鍵關聯)
現在考慮每一個User配給一間Room,形成一對一,user表格透過room_id作為外鍵參考至room:![]() 在表格建立方面,使用 多對一 中的表格建立語句就可以了:
create table room ( id bigint not null auto_increment, address varchar(255), primary key (id) ) create table user ( id bigint not null auto_increment, name varchar(255), room_id bigint unique, primary key (id) ) 物件方面,User的實例會參考至Room實例,而Room實例也參考至User實例:
package onlyfun.caterpillar;
package onlyfun.caterpillar; 使用外鍵來完成一對一,其實就是限制多對一關係中,「多」的一方只能有一個參考至「一」的一方,也就是多對一關係的一個特例,這可以在映射文件中使用 <many-to-one>標籤時,加上"unique"屬性來設定,例如:
<?xml version="1.0" encoding="utf-8"?> 到這邊為止,單向一對一的映射已經完成,如果要再完成雙向一對一的關係,則可以在Room.hbm.xml中使用<one-to-one>標籤來定義:
<?xml version="1.0" encoding="utf-8"?> 在<one-to-one>中,property-ref告訴Hibernate,查詢出user並將其參考至room。 一個儲存的例子如下:
User user1 = new User(); user1.setName("bush"); Room room1 = new Room(); room1.setAddress("NTU-M8-419"); user1.setRoom(room1); User user2 = new User(); user2.setName("caterpillar"); Room room2 = new Room(); room2.setAddress("NTU-M8-418"); user2.setRoom(room2); Session session = HibernateUti.getSessionFactory().openSession(); Transaction tx = session.beginTransaction(); session.save(user1); session.save(user2); tx.commit(); session.close(); 在查詢Room時,User也會一載入,例如:
Session session = HibernateUtil.getSessionFactory().openSession(); Room room = (Room) session.load(Room.class, new Long(23)); System.out.println(room.getUser().getName()); session.close(); 上面的查詢程式,Hibernate將使用以下的SQL:
Hibernate: select room0_.id as id1_1_, room0_.address as address1_1_, user1_.id as id0_0_, user1_.name as name0_0_, user1_.room_id as room3_0_0_ from room room0_ left outer join user user1_ on room0_.id=user1_.room_id where room0_.id=? |
一對一(主鍵關聯)
一對一關聯的另一種方式,是限制兩個實體的主鍵必須一致,如此直接透過兩個表格的主鍵就可確定一對一關聯,而不用額外的外鍵參考。
例如user與room表格,可以如下建立:
id bigint not null,
address varchar(255),
primary key (id)
)
create table user (
id bigint not null auto_increment,
name varchar(255),
primary key (id)
)
User類別與Room類別的設計使用 一對一(唯一外鍵關聯) 中的設計即可,接著在User.hbm.xml方面如下設計:
- User.hbm.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.User" table="user">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="name" column="name"/>
<one-to-one name="room"
class="onlyfun.caterpillar.Room"
cascade="all"/>
</class>
</hibernate-mapping>
在Room.hbm.xml的設計方面如下:
- Room.hbm.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.Room" table="room">
<id name="id" column="id">
<generator class="foreign">
<param name="property">user</param>
</generator>
</id>
<property name="address" column="address" />
<one-to-one name="user"
class="onlyfun.caterpillar.User"
constrained="true"/>
</class>
</hibernate-mapping>
在Room的id主鍵上,使用foreign表示與外鍵共享主鍵,也就是與User實體共享主鍵,而constrained設定為true,表示約束 room的主鍵必須與user中對應資料的主鍵相同。
一個儲存的實例如下:
user1.setName("bush");
Room room1 = new Room();
room1.setAddress("NTU-M8-419");
// 互相設定關聯
user1.setRoom(room1);
room1.setUser(user1);
User user2 = new User();
user2.setName("caterpillar");
Room room2 = new Room();
room2.setAddress("NTU-M8-418");
// 互相設定關聯
user2.setRoom(room2);
room2.setUser(user2);
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
session.save(user1);
session.save(user2);
tx.commit();
session.close();
資料庫中將有以下的儲存結果:
mysql> select * from user;
+----+-------------+
| id | name |
+----+-------------+
| 1 | bush |
| 2 | caterpillar |
+----+-------------+
2 rows in set (0.00 sec)
mysql> select * from room;
+----+------------------+
| id | address |
+----+------------------+
| 1 | NTU-M8-419 |
| 2 | NTU-M8-418 |
+----+------------------+
2 rows in set (0.00 sec)
多對一
一個實體簡單的說就是在資料庫中擁有一個表格,並擁有自已的資料庫識別(Database identity)。
一個簡單的實體與實體間之關係為多對一的關係,例如在學校宿舍中,使用者與房間的關係就是多對一的關係,多個使用者可以居住於一個房間。
如上圖所示的,可以藉由room_id讓使用者與房間產生關聯,您可以如下建立user與room表格:
id bigint not null auto_increment,
address varchar(255),
primary key (id)
)
create table user (
id bigint not null auto_increment,
name varchar(255),
room_id bigint,
primary key (id)
)
用程式來表示的話,首先看看User類別:
- User.java
package onlyfun.caterpillar;
public class User {
private Long id;
private String name;
private Room room;
public User() {}
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 Room getRoom() {
return room;
}
public void setRoom(Room room) {
this.room = room;
}
}
User類別中有一room屬性,將參考至Room實例,多個User實例可共同參考一個Room實例,Room類別設計如下:
- Room.java
package onlyfun.caterpillar;
public class Room {
private Long id;
private String address;
public Room() {}
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;
}
}
在映射文件方面,先來看看Room.hbm.xml:
- Room.hbm.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.Room" table="room">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="address" column="address" />
</class>
</hibernate-mapping>
沒什麼,很簡單的一個映射文件,而在User.hbm.xml中,使用<many-to-one>標籤來映射多對一關係:
- User.hbm.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.User" table="user">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="name" column="name"/>
<many-to-one name="room"
column="room_id"
class="onlyfun.caterpillar.Room"
cascade="all"
outer-join="true"/>
</class>
</hibernate-mapping>
在<many-to-one>的設定中,cascade表示主控方(User)進行save-pdate、delete等相關操作時,被控方(Room)是否也一併進行相關操作,簡單的說,也就是您儲存或更新User實例時,被參考到的Room實例是否一併對資料庫發生儲存或操作,設定為 all,表示主控方任何操作,被控方也進行對應操作。
一個儲存的例子如下:
room1.setAddress("NTU-M8-419");
Room room2 = new Room();
room2.setAddress("NTU-G3-302");
User user1 = new User();
user1.setName("bush");
user1.setRoom(room1);
User user2 = new User();
user2.setName("caterpillar");
user2.setRoom(room1);
User user3 = new User();
user3.setName("momor");
user3.setRoom(room2);
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
session.save(user1); // 主控方操作,被控方也會對應操作
session.save(user2);
session.save(user3);
tx.commit();
session.close();
關於cascade,可以進一步參考 cascade 的意義 。
資料庫中將儲存以下的內容:
mysql> select * from user; +----+-------------+-----------+ | id | name | room_id | +----+-------------+-----------+ | 1 | bush | 1 | | 2 | caterpillar | 1 | | 3 | momor | 2 | +----+-------------+-----------+ 3 rows in set (0.00 sec) mysql> select * from room; +----+-------------------+ | id | address | +----+-------------------+ | 1 | NTU-M8-419 | | 2 | NTU-G3-302 | +----+-------------------+ 2 rows in set (0.00 sec) |
在查詢時的例子如下:
User user = (User) session.load(User.class, new Long(1));
System.out.println(user.getName());
System.out.println(user.getRoom().getAddress());
session.close();
在設定outer-join為true的情況下,Hibernate將使用以下的SQL一次查詢所有的資料:
select
user0_.id as id0_1_,
user0_.name as name0_1_,
user0_.room_id as room3_0_1_,
room1_.id as id1_0_,
room1_.address as address1_0_
from
user user0_
left outer join
room room1_
on user0_.room_id=room1_.id
where
user0_.id=?
在不設定outer-join為true的情況下,Hibernate則使用以下的SQL分別查詢user與room表格:
select
user0_.id as id0_0_,
user0_.name as name0_0_,
user0_.room_id as room3_0_0_
from
user user0_
where
user0_.id=?
Hibernate:
select
room0_.id as id1_0_,
room0_.address as address1_0_
from
room room0_
where
room0_.id=?
一對多
在 多對一 中,User對Room是多對一的關係,User實例維護著對Room實例的參考,如果將這個關係反過來,由Room實例維護對多個User實例的資料,就是一對多的關係。
具體來說,可以設計User類別如下:
- User.java
package onlyfun.caterpillar;
public class User {
private Long id;
private String name;
public User() {}
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;
}
}
而在Room類別中,使用Set來記錄多個User:
- Room.java
package onlyfun.caterpillar;
import java.util.Set;
public class Room {
private Long id;
private String address;
private Set users;
public Room() {}
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;
}
public void addUser(User user) {
users.add(user);
}
public void removeUser(User user) {
users.remove(user);
}
}
這種方式即所謂單向一對多關係,也就是Room實例知道User實例的存在,而User實例則沒有意識到Room實例。
(在 多對一 中,則是單向多對一關係,即User知道Room的存在,但Room不知道User的存在。)
在映射文件上,先來看看User.hbm.xml:
- User.hbm.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.User" table="user">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="name" column="name"/>
</class>
</hibernate-mapping>
在單向關係中,被參考的對象其映射文件就如單一實體一樣的配置,接下來看看Room.hbm.xml,使用<one-to- many>標籤配置一對多:
- Room.hbm.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.Room" table="room">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="address"
column="address"
type="java.lang.String"/>
<set name="users" table="user" cascade="all">
<key column="room_id"/>
<one-to-many class="onlyfun.caterpillar.User"/>
</set>
</class>
</hibernate-mapping>
接著您可以如下儲存物件:
user1.setName("bush");
User user2 = new User();
user2.setName("caterpillar");
User user3 = new User();
user3.setName("momor");
Room room1 = new Room();
room1.setUsers(new HashSet());
room1.setAddress("NTU-M8-419");
room1.addUser(user1);
room1.addUser(user2);
Room room2 = new Room();
room2.setUsers(new HashSet());
room2.setAddress("NTU-G3-302");
room2.addUser(user3);
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
session.save(room1); // cascade 操作
session.save(room2);
tx.commit();
session.close();
資料庫中將儲存以下的表格:
mysql> select * from user; +----+--------------+-----------+ | id | name | room_id | +----+-------------+------------+ | 1 | bush | 1 | | 2 | caterpillar | 1 | | 3 | momor | 2 | +----+-------------+------------+ 3 rows in set (0.01 sec) mysql> select * from room; +----+------------------+ | id | address | +----+------------------+ | 1 | NTU-M8-419 | | 2 | NTU-G3-302 | +----+------------------+ 2 rows in set (0.00 sec) |
關於一對多還有效能方面的一些議題,可以參考 雙向關聯(inverse 的意義)。
多對多

多對多由於使用了中介表格,在查詢效率不彰,且在程式的物件模式上,多對多會使得物件與物件之間彼此依賴,並不是一個很好的設計方式,在設計上應避免使用多對多關係。
如果一定要使用多對多關係的話,在表格上先如下建立:
id integer not null auto_increment,
address varchar(255),
primary key (id)
)
create table user (
id integer not null auto_increment,
name varchar(255),
primary key (id)
)
create table user_server (
user_id integer not null,
server_id integer not null,
primary key (user_id, server_id)
)
先設計User類別如下:
- User.java
package onlyfun.caterpillar;
import java.util.Set;
public class User {
private Integer id;
private String name;
private Set servers;
public User() {}
public Integer getId() {
return id;
}
public void setId(Integer 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類別如下:
- Server.java
package onlyfun.caterpillar;
import java.util.Set;
public class Server {
private Integer id;
private String address;
private Set users;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Set getUsers() {
return users;
}
public void setUsers(Set users) {
this.users = users;
}
}
在映射文件上,使用<many-to-many>標籤來完成映射關係:
- User.hbm.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.User" table="user">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="name" column="name"/>
<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>
注意到cascade是設定為save-update,因為在多對多的關係中,很少因為刪除其中之一,而所關聯的實體都要一併刪除的,所以設定save- update,表示在save或update時,一併對關聯的物件進行對應的save或update。
Server.hbm.xml的定義如下:
- Server.hbm.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.Server" table="server">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="address"/>
<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>
一個儲存時的例子如下:
server1.setAddress("PC-219");
server1.setUsers(new HashSet());
Server server2 = new Server();
server2.setAddress("PC-220");
server2.setUsers(new HashSet());
Server server3 = new Server();
server3.setAddress("PC-221");
server3.setUsers(new HashSet());
User user1 = new User();
user1.setName("caterpillar");
user1.setServers(new HashSet());
User user2 = new User();
user2.setName("momor");
user2.setServers(new HashSet());
// 多對多,互相參考
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 = HibernateUtil.getSessionFactory().openSession();
Transaction tx= session.beginTransaction();
session.save(user1);
session.save(user2);
tx.commit();
session.close();
執行後資料庫的內容如下:
mysql> select * from user; +----+--------------+ | id | name | +----+--------------+ | 1 | caterpillar | | 2 | momor | +----+--------------+ 2 rows in set (0.00 sec) mysql> select * from user_serv +----------+-------------+ | user_id | server_id | +----------+-------------+ | 1 | 1 | | 1 | 2 | | 1 | 3 | | 2 | 1 | | 2 | 2 | +----------+-------------+ 5 rows in set (0.00 sec) mysql> select * from server; +----+-----------+ | id | address | +----+-----------+ | 1 | PC-219 | | 2 | PC-221 | | 3 | PC-220 | +----+-----------+ 3 rows in set (0.00 sec) |