[標題]:Hibernate一對多(單向)
[時間]:2009-6-12
[摘要]:單向一對多關聯只需要在"一方"進行配置即可,"多方"無需額外配置。
[關鍵字]:外鍵,inverse,Hibernate,Set,一對多,單向,ORM,mapping,關系數據庫,映射
[環境]:MyEclipse7 , JDK6,Hibernate3.2,Tomcat6,MySQL 5.1.34-community
[作者]:Winty ([email protected]) 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還是很有用的,只是用錯了地方。