[標題]:Hibernate一對多(單向)
[時間]:2009-6-12
[摘要]:單向一對多關聯只需要在"一方"進行配置即可,"多方"無需額外配置。
[關鍵字]:外鍵,inverse,Hibernate,Set,一對多,單向,ORM,mapping,關系數據庫,映射
[環境]:MyEclipse7 , JDK6,Hibernate3.2,Tomcat6,MySQL 5.1.34-community
[作者]:Winty (wintys@gmail.com) http://www.blogjava.net/wintys
[正文]:
Hibernate一對多關聯,例如一個用戶有多張銀行卡(只考慮用戶到銀行卡的單向一對多關聯)。由於是學習Hibernate原理,並沒有使用工具自動生成代碼等。
單向一對多關聯只需要在"一方"進行配置即可,"多方"無需額外配置。
a.Java程序中所要做的一對多:
public class User{
...
private Set<Card> cards;
...
}
public class Card{
...
}

b.Hibernate中所要做的一對多:
User.hbm.xml: ... <set name="cards" inverse="false" cascade="all"> <key column="userId" /> <one-to-many class="wintys.hibernate.onetomany.Card" /> </set> ...
c.數據庫中的一對多:
而對應的數據庫中,只要相應在Card對應的物理表中添加外鍵userId(不為設為NOT NULL)即可。

詳細的MyEclipse WebProject如下:
1、實體類:
用戶類User.java:
package wintys.hibernate.onetomany;
import java.util.Set;
public class User {
private String id;
private String name;
private Set<Card> cards;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setCards(Set<Card> cards) {
this.cards = cards;
}
public Set<Card> getCards() {
return cards;
}
}
銀行卡類Card.java:
package wintys.hibernate.onetomany;
public class Card {
private String id;
private float balance;
public Card(){
}
public Card(float balance){
this.balance = balance;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public float getBalance() {
return balance;
}
public void setBalance(float balance) {
this.balance = balance;
}
}
2、數據庫表:
數據庫是MySQL 5.1.34-community。
用戶表:
CREATE TABLE myuser( id VARCHAR(50) NOT NULL, name VARCHAR(100), PRIMARY KEY(id) );
銀行卡表:
CREATE TABLE mycard( id VARCHAR(50) NOT NULL, balance FLOAT(7,2), userId VARCHAR(50), PRIMARY KEY(id) );
3、映射文件:
用戶類映射文件/src/wintys/hibernate/onetomany/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"> <!-- Mapping file autogenerated by MyEclipse Persistence Tools --> <hibernate-mapping> <class name="wintys.hibernate.onetomany.User" table="myuser" catalog="db"> <id name="id" type="string"> <column name="id" not-null="true"/> <generator class="uuid.hex" /> </id> <property name="name" type="java.lang.String" column="name" /> <set name="cards" inverse="false" cascade="all"> <key column="userId" /> <one-to-many class="wintys.hibernate.onetomany.Card" /> </set> </class> </hibernate-mapping>
銀行卡映射文件/src/wintys/hibernate/onetomany/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"> <!-- Mapping file autogenerated by MyEclipse Persistence Tools --> <hibernate-mapping> <class name="wintys.hibernate.onetomany.Card" table="mycard" catalog="db"> <id name="id" type="string"> <column name="id" not-null="true"/> <generator class="uuid.hex" /> </id> <property name="balance" /> </class> </hibernate-mapping>
Hibernate配置文件:/src/hibernate.cfg.xml:
<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <!-- Generated by MyEclipse Hibernate Tools. --> <hibernate-configuration> <session-factory> <property name="connection.username">root</property> <property name="connection.url"> jdbc:mysql://localhost:3306/db?useUnicode=true&characterEncoding=utf-8 </property> <property name="dialect"> org.hibernate.dialect.MySQLDialect </property> <property name="myeclipse.connection.profile">MySQLDriver</property> <property name="connection.password">root</property> <property name="connection.driver_class"> com.mysql.jdbc.Driver </property> <property name="show_sql">true</property> <mapping resource="wintys/hibernate/onetomany/User.hbm.xml" /> <mapping resource="wintys/hibernate/onetomany/Card.hbm.xml" /> </session-factory> </hibernate-configuration>
4、使用測試:
/src/wintys/hibernate/onetomany/HibernateDAO.java:
package wintys.hibernate.onetomany;
import java.util.List;
public interface HibernateDAO {
public void insert();
public List<User> selectAll();
}
/src/wintys/hibernate/onetomany/HibernateDAOBean.java:
package wintys.hibernate.onetomany;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
public class HibernateDAOBean implements HibernateDAO {
public void insert() throws HibernateException {
Transaction tc = null;
try{
Set<Card> cards = new HashSet<Card>();
Card c1,c2,c3;
c1 = new Card(7641.96f);
c2 = new Card(654.8f);
c3 = new Card(3650f);
cards.add(c1);
cards.add(c2);
cards.add(c3);
User user = new User();
user.setName("Tom");
user.setCards(cards);
Session session = HibernateUtil.getSession();
tc = session.beginTransaction();
/*
配置文件中的cascade="true"時,所以無需手動保存c1,c2,c3
session.save(c1);
session.save(c2);
session.save(c3);
*/
session.save(user);
tc.commit();
}catch(HibernateException e){
try{
if(tc != null)
tc.rollback();
}catch(Exception ex){
System.err.println(ex.getMessage());
}
System.err.println(e.getMessage());
}finally{
HibernateUtil.closeSession();
}
}
@SuppressWarnings("unchecked")
public List<User> selectAll() throws HibernateException {
List<User> users = null;
Transaction tc = null;
try{
Session session = HibernateUtil.getSession();
tc = session.beginTransaction();
Query query = session.createQuery("from User");
users = query.list();
tc.commit();
}catch(HibernateException e){
try{
if(tc != null){
tc.rollback();
users = null;
}
}catch(Exception ex){
System.err.println(ex.getMessage());
}
System.err.println(e.getMessage());
}finally{
//HibernateUtil.closeSession();
}
return users;
}
}
/src/wintys/hibernate/onetomany/HibernateUtil.java:
package wintys.hibernate.onetomany;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
/**
* Hibernate Session管理
* @author Winty
*/
public class HibernateUtil {
private static SessionFactory factory = null;
private static ThreadLocal<Session> threadLocal;
static {
try{
factory = new Configuration()
.configure()
.buildSessionFactory();
}catch(HibernateException e){
System.err.println(e.getMessage());
}
threadLocal = new ThreadLocal<Session>();
}
private HibernateUtil(){
}
public static Session getSession()throws HibernateException{
Session session = threadLocal.get();
if(session == null){
session = factory.openSession();
threadLocal.set(session);
}
return session;
}
public static void closeSession()throws HibernateException{
Session session = threadLocal.get();
if(session != null){
session.close();
}
threadLocal.set(null);
}
}
/index.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%>
<%@ page import="wintys.hibernate.onetomany.*"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP 'index.jsp' starting page</title>
</head>
<body>
<%
List<User> users = null;
HibernateDAO dao = new HibernateDAOBean();
dao.insert();
users = dao.selectAll();
Iterator<User> it = users.iterator();
while(it.hasNext()){
User user = it.next();
String id = user.getId();
String name = user.getName();
out.println("id:" + id + "<br />");
out.println("name:" + name + "<br />");
out.println("cards:<br />");
Set<Card> cards = user.getCards();
Iterator<Card> itc = cards.iterator();
while(itc.hasNext()){
Card card = itc.next();
String cardId = card.getId();
float balance = card.getBalance();
out.println(" cardId:" + cardId + "<br />");
out.println(" balance:" + balance + "<br />");
}
out.println("<hr/>");
}
%>
</body>
</html>
5、運行結果:
控制台顯示:
...... Hibernate: insert into db.myuser (name, id) values (?, ?) Hibernate: insert into db.mycard (balance, id) values (?, ?) Hibernate: insert into db.mycard (balance, id) values (?, ?) Hibernate: insert into db.mycard (balance, id) values (?, ?) Hibernate: update db.mycard set userId=? where id=? Hibernate: update db.mycard set userId=? where id=? Hibernate: update db.mycard set userId=? where id=? ......
index.jsp頁面顯示:
id:402881e421d4d0be0121d4d20e140005 name:Tom cards: cardId:402881e421d4d0be0121d4d20e230008 balance:654.8 cardId:402881e421d4d0be0121d4d20e230006 balance:7641.96 cardId:402881e421d4d0be0121d4d20e230007 balance:3650.0

6、注意的問題:
a、錯誤提示:Field 'userId' doesn't have a default value。
一開始把"userId"設成NOT NULL,但是Hibernate先執行的是:
"insert into db.mycard (balance, id) values (?, ?)"
然後才執行"update db.mycard set userId=? where id=?",
而userId在insert時是沒有寫入值的,所以就會報錯。把userId的NOT NULL去掉即可。
b、User.hbm.xml中要設置cascade="all",或其它有效值,不然,在保存User對象時,相關的Card對象不會被保存。
c、User.hbm.xml中set標簽的inverse屬性不能設置為"true",inverse的默認值是"false",所以不加inverse也可以。看書上說:在一對多的關聯關系實現中,最好設置inverse="true",將有助於性能的改善。所以一開始就用了inverse="true",User和Card對象都分別正確寫入數據庫了,但是就是userId字段沒有被自動寫入。
myuser表:
+--------------------------------------------+------+
| id | name |
+--------------------------------------------+------+
| 402881e421d4d0be0121d4d20e140005 | Tom |
+--------------------------------------------+------+
mycard表:
+--------------------------------------------+---------+---------
| id | balance | userId
+--------------------------------------------+---------+---------
| 402881e421d4d0be0121d4d20e230006 | 7641.96 | NULL
| 402881e421d4d0be0121d4d20e230007 | 3650.00 | NULL
|
| 402881e421d4d0be0121d4d20e230008 | 654.80 | NULL
+--------------------------------------------+---------+---------
搞了半天,原來在本例應該把inverse設為false。inverse還是很有用的,只是用錯了地方。