一對多數據關聯
一.單向一對多數據關聯
一個用戶有多個地址,在用戶類TUser中包含地址類TAddress集合。
1.數據模型

2.表定義sql
use sample;
DROP TABLE T_Address;
DROP TABLE T_User;
CREATE TABLE T_User (
id INT NOT NULL AUTO_INCREMENT
, name VARCHAR(50)
, age INT
, PRIMARY KEY (id)
);
CREATE TABLE T_Address (
id INT NOT NULL AUTO_INCREMENT
, address VARCHAR(200)
, zipcode VARCHAR(10)
, tel VARCHAR(20)
, type VARCHAR(20)
, user_id INT NOT NULL
, idx INT
, PRIMARY KEY (id)
, INDEX (user_id)
, CONSTRAINT FK_T_Address_1 FOREIGN KEY (user_id)
REFERENCES T_User (id)
);
3.POJO類
TUser.java
package cn.blogjava.start;
import java.util.Set;
public class TUser implements java.io.Serializable {
// Fields
private Integer id;
private Integer age;
private String name;
private Set address;
// Constructors
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Set getAddress() {
return address;
}
public void setAddress(Set address) {
this.address = address;
}
/** default constructor */
public TUser() {
}
/** constructor with id */
public TUser(Integer id) {
this.id = id;
}
// Property accessors
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
TAddress.java
package cn.blogjava.start;
import java.io.Serializable;
public class TAddress implements Serializable {
private Integer id;
private String address;
private String zipcode;
private String tel;
private String type;
private Integer userId;
private Integer idx;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Integer getIdx() {
return idx;
}
public void setIdx(Integer idx) {
this.idx = idx;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
}
3.配置文件
TUser.hbm.xml
<?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="cn.blogjava.start.TUser" table="T_User" catalog="sample"
dynamic-update="true" dynamic-insert="true"
>
<id name="id" type="integer">
<column name="id" />
<generator class="native" />
</id>
<property name="name" type="string" column="name" />
<property name="age" type="java.lang.Integer" column="age" />
<set name="address" table="t_address" cascade="all" order-by="zipcode asc">
<key column="user_id">
</key>
<one-to-many class="cn.blogjava.start.TAddress" />
</set>
</class>
</hibernate-mapping>
TAddress.hbm.xml
注意:沒有配置user_id字段。
<?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="cn.blogjava.start.TAddress" table="T_Address" catalog="sample">
<id name="id" type="integer">
<column name="id" />
<generator class="native" />
</id>
<property name="address" type="string" column="address" />
<property name="zipcode" type="string" column="zipcode" />
<property name="tel" type="string" column="tel" />
<property name="type" type="string" column="type" />
<property name="idx" type="java.lang.Integer" column="idx" />
</class>
</hibernate-mapping>
4.測試代碼
package cn.blogjava.start;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import junit.framework.Assert;
import junit.framework.TestCase;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
public class HibernateTest extends TestCase {
Session session = null ;
protected void setUp() {
try {
Configuration config = new Configuration().configure();
SessionFactory sessionFactory = config.buildSessionFactory();
session = sessionFactory.openSession();
} catch (HibernateException e) {
e.printStackTrace();
}
}
protected void tearDown() {
try {
session.close();
} catch (HibernateException e) {
e.printStackTrace();
}
}
/**
* 對象持久化測試(Insert方法)
*/
public void testInsert() {
Transaction tran = null ;
try {
TUser user = new TUser();
user.setName( " byf " );
user.setAge( new Integer( 26 ));
TAddress addr = new TAddress();
addr.setTel( " 1123 " );
addr.setZipcode( " 233123 " );
addr.setAddress( " HongKong " );
TAddress addr2 = new TAddress();
addr2.setTel( " 139 " );
addr2.setZipcode( " 116001 " );
addr2.setAddress( " dalian " );
TAddress addr3 = new TAddress();
addr3.setTel( " 136 " );
addr3.setZipcode( " 100080 " );
addr3.setAddress( " beijing " );
// 設置關聯
HashSet set = new HashSet();
set.add(addr);
set.add(addr2);
set.add(addr3);
user.setAddress(set);
tran = session.beginTransaction();
// 插入user信息
session.save(user);
session.flush();
tran.commit();
Assert.assertEquals(user.getId().intValue() > 0 , true );
} catch (HibernateException e) {
e.printStackTrace();
Assert.fail(e.getMessage());
if (tran != null ) {
try {
tran.rollback();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
/**
* 對象讀取測試(Select方法)
*/
public void testSelect(){
String hql = " from TUser where name='byf' " ;
try {
List userList = session.createQuery(hql).list();
TUser user = (TUser)userList.get( 0 );
System.out.println( " user name is " + user.getName());
for (Iterator iter = user.getAddress().iterator(); iter.hasNext();) {
TAddress addr = (TAddress) iter.next();
System.out.println( " user address is " + addr.getAddress());
}
Assert.assertEquals(user.getName(), " byf " );
} catch (Exception e) {
e.printStackTrace();
Assert.fail(e.getMessage());
}
}
}
說明:
一個問題,由于是單向關聯,為了保持關聯關系,我們只能通過主控方對被動方進行級聯更新。如果被關聯方的字段為NOT NULL屬性,當Hibernate創建或者更新關聯關系時,可能出現約束違例。
例子中T_Address表中的user_id 為NOT NULL,如果在TAddress.hbm.xml映射了全部字段時。創建一個用戶并賦予她地址信息,對于T_Address表而言,hibernate會執行兩條sql語句來保存地址信息。
要執行兩條SQL語句,是因為關聯是單向的,就是說對于TAddress對象而言,并不知道自己應該與那一個TUser對象關聯,只能先將user_id設為一個空值。
之后,根據配置文件
<set name="address" table="t_address" cascade="all" order-by="zipcode asc">
<key column="user_id">
</key>
<one-to-many class="cn.blogjava.start.TAddress" />
</set>
由TUser對象將自身的id賦給addr.user_id,這樣導致addr屬性值變動,在事物提交的時候,會進行update。
1)當save該用戶的時候,
insert into t_address (user_id, address, zipcode, tel) value (null, "HongKong", "233123", "1123")
2)當tx.commit()時:
update t_address user_id="1", address="HongKong", zipcode="233123",tel="1123" where id=2;
這樣,在save user時,就會出現約束違例。
調整方法:
可以在定義數據表字段時候,不加NOT NULL約束。或者在開始為user_id隨意賦一個非空值(因為還要update,不正確也沒關系),或者將user_id字段從TAddress.hbm.xml中刪除(本例就是這樣實現)。
但是這些都是權宜之計,用兩條SQL語句完成一次數據庫操作,性能低下。
而雙向一對多解決了這個問題。
下面來實現雙向關聯:
修改配置文件
TUser.hbm.xml
<?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="cn.blogjava.start.TUser" table="T_User" catalog="sample"
dynamic-update="true" dynamic-insert="true"
>
<id name="id" type="integer">
<column name="id" />
<generator class="native" />
</id>
<property name="name" type="string" column="name" />
<property name="age" type="java.lang.Integer" column="age" />
<set
name="address"
table="t_address"
inverse="true"
cascade="all"
order-by="zipcode asc"
>
<key column="user_id">
</key>
<one-to-many class="cn.blogjava.start.TAddress" />
</set>
</class>
</hibernate-mapping>
設定inverse="true",表明將TUser類作為被動類,將數據關聯的維護工作交給關聯對象TAddress來管理。
在one-to-many模型中,將many一方設為主控方有助于性能的改善。(讓總理記住每個人困難,但是每個人記住總理方便)
TAddress.hbm.xml
<?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="cn.blogjava.start.TAddress" table="T_Address" catalog="sample">
<id name="id" type="integer">
<column name="id" />
<generator class="native" />
</id>
<property name="address" type="string" column="address" />
<property name="zipcode" type="string" column="zipcode" />
<property name="tel" type="string" column="tel" />
<property name="type" type="string" column="type" />
<property name="idx" type="java.lang.Integer" column="idx" />
<many-to-one
name="user"
class="cn.blogjava.start.TUser"
cascade="none"
outer-join="auto"
update="true"
insert="true"
access="property"
column="user_id"
not-null="true"
/>
</class>
</hibernate-mapping>
2.對TAddress.java做如下改造:
去掉user_id字段,增加user字段,和getter,setter方法。
package cn.blogjava.start;
import java.io.Serializable;
public class TAddress implements Serializable {
private Integer id;
private String address;
private String zipcode;
private String tel;
private String type;
private Integer idx;
private TUser user;
public TUser getUser() {
return user;
}
public void setUser(TUser user) {
this.user = user;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Integer getIdx() {
return idx;
}
public void setIdx(Integer idx) {
this.idx = idx;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
}
4.測試代碼
既然TUser不維護關聯關系,需要TAddress需要自己來維護TUser,所以需要addr.setUser(user);
package cn.blogjava.start;

import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import junit.framework.Assert;
import junit.framework.TestCase;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;



public class HibernateTest extends TestCase
{
Session session = null;


protected void setUp()
{

try
{
Configuration config = new Configuration().configure();
SessionFactory sessionFactory = config.buildSessionFactory();
session = sessionFactory.openSession();

} catch (HibernateException e)
{
e.printStackTrace();
}
}


protected void tearDown()
{

try
{
session.close();

} catch (HibernateException e)
{
e.printStackTrace();
}
}

/** *//**
* 對象持久化測試(Insert方法)
*/

public void testInsert()
{
Transaction tran = null;

try
{
TUser user = new TUser();
user.setName("byf");
user.setAge(new Integer(26));
TAddress addr = new TAddress();
addr.setTel("1123");
addr.setZipcode("233123");
addr.setAddress("HongKong");
addr.setUser(user);
TAddress addr2 = new TAddress();
addr2.setTel("139");
addr2.setZipcode("116001");
addr2.setAddress("dalian");
addr2.setUser(user);

TAddress addr3 = new TAddress();
addr3.setTel("136");
addr3.setZipcode("100080");
addr3.setAddress("beijing");
addr3.setUser(user);
//設置關聯
HashSet set = new HashSet();
set.add(addr);
set.add(addr2);
set.add(addr3);
user.setAddress(set);
tran = session.beginTransaction();
//插入user信息
session.save(user);
session.flush();
tran.commit();
Assert.assertEquals(user.getId().intValue()>0 ,true);

} catch (HibernateException e)
{
e.printStackTrace();
Assert.fail(e.getMessage());

if(tran != null)
{

try
{
tran.rollback();

} catch (Exception e1)
{
e1.printStackTrace();
}
}
}
}

/** *//**
* 對象讀取測試(Select方法)
*/

public void testSelect()
{
String hql = " from TUser where name='byf'";

try
{
List userList = session.createQuery(hql).list();
TUser user = (TUser)userList.get(0);
System.out.println("user name is " + user.getName());

for (Iterator iter = user.getAddress().iterator(); iter.hasNext();)
{
TAddress addr = (TAddress) iter.next();
System.out.println("user address is " + addr.getAddress());
}
Assert.assertEquals(user.getName(), "byf");

} catch (Exception e)
{
e.printStackTrace();
Assert.fail(e.getMessage());
}
}
}