10 19Hibernate之数据关联(一对多)

一对多是数据库第三设计范式的主要描述点,而在实际的工作之中,一对多的关系使用的一定是最多的,像“类型-子类型”就属于一对多的关系。
范例:数据库的创建脚本

-- 删除数据表
DROP TABLE IF EXISTS subitem;
DROP TABLE IF EXISTS item;
-- 创建数据表
CREATE TABLE item(
	iid INT AUTO_INCREMENT,
	ititle VARCHAR(32),
	CONSTRAINT pk_iid PRIMARY KEY(iid)
);

CREATE TABLE subitem (
	sid INT AUTO_INCREMENT,
	stitle VARCHAR(50),
	iid INT,
	CONSTRAINT pk_sid PRIMARY KEY(sid),
	CONSTRAINT fk_subitem_item FOREIGN KEY(iid) REFERENCES item(iid) ON DElETE CASCADE
);

现在必须梳理关系:
(1)增加子表数据的时候要选择好与父表的关系;
(2)父表数据增加时要考虑子表数据的级联没保存问题(购物车应用)。

1 基于*.hbm.xml文件配置

在MyEclipse之中默认生成的就是一对多关系,但是有一个前提:必须要有外键名字。一对多映射的时候不需要考虑多个操作同时选中,完全可以一个一个生成。
范例:修改Item.java类

package org.lks.pojo;

import java.util.HashSet;
import java.util.Set;

@SuppressWarnings("serial")
public class Item implements java.io.Serializable {
	private Integer iid;
	private String ititle;
	private Set<Subitem> subitems = new HashSet<Subitem>(0);
}

范例:观察Subitem.java类

package org.lks.pojo;

@SuppressWarnings("serial")
public class Subitem implements java.io.Serializable {
	private Integer sid;
	private Item item;
	private String stitle;
}

整个一对多的关系里面就是依靠了Set集合。而现在所有的关键部分就都落在*.hbm.xml文件上。

范例:观察Item.hbm.xml文件

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="org.lks.pojo.Item" table="item" catalog="hedb">
        <id name="iid" type="java.lang.Integer">
            <column name="iid" />
            <generator class="native"></generator>
        </id>
        <property name="ititle" type="java.lang.String">
            <column name="ititle" length="32" />
        </property>
        <!-- MyEclipse默认支持的就是一对多关系 -->
        <set name="subitems" cascade="all">
            <key> <!-- 关联的数据列 -->
                <column name="iid" />
            </key>
            <!-- 一对多所包含的数据类型 -->
            <one-to-many class="org.lks.pojo.Subitem" />
        </set>
    </class>
</hibernate-mapping>

范例:观察Subitem.hbm.xml文件

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="org.lks.pojo.Subitem" table="subitem" catalog="hedb">
        <id name="sid" type="java.lang.Integer">
            <column name="sid" />
            <generator class="native" />
        </id>
        <!-- 多对一关系,name表示属性名称,而class描述的是属性类型 -->
        <many-to-one name="item" class="org.lks.pojo.Item" fetch="select">
            <column name="iid" />
        </many-to-one>
        <property name="stitle" type="java.lang.String">
            <column name="stitle" length="50" />
        </property>
    </class>
</hibernate-mapping>

下面按照开发的正常思路编写,一定是先有“一”方,再有“多”方。
范例:实现item数据的增加

package org.lks.test;

import org.lks.dbc.HibernateSessionFactory;
import org.lks.pojo.Item;

public class TestItemInsertA {

	public static void main(String[] args) {
		Item item = new Item();
		item.setItitle("lks");
		System.out.println(HibernateSessionFactory.getSession().save(item));
		HibernateSessionFactory.getSession().beginTransaction().commit();
		HibernateSessionFactory.closeSession();
		System.exit(0);
	}
}

Hibernate: insert into hedb.item (ititle) values (?)
1

现在由于只有item一个POJO类出现,所以只会执行一条语句。
范例:实现item与subitem的添加

package org.lks.test;

import org.lks.dbc.HibernateSessionFactory;
import org.lks.pojo.Item;
import org.lks.pojo.Subitem;

public class TestItemInsertB {

	public static void main(String[] args) {
		Item item = new Item();
		item.setItitle("hhy2");
		for(int i = 0; i < 10; i++){
			Subitem subitem = new Subitem();
			subitem.setStitle("fool-" + i);
			subitem.setItem(item);
			item.getSubitems().add(subitem);
		}
		System.out.println(HibernateSessionFactory.getSession().save(item));
		HibernateSessionFactory.getSession().beginTransaction().commit();
		HibernateSessionFactory.closeSession();
		System.exit(0);
	}
}

Hibernate: insert into hedb.item (ititle) values (?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
3
Hibernate: update hedb.subitem set iid=? where sid=?
Hibernate: update hedb.subitem set iid=? where sid=?
Hibernate: update hedb.subitem set iid=? where sid=?
Hibernate: update hedb.subitem set iid=? where sid=?
Hibernate: update hedb.subitem set iid=? where sid=?
Hibernate: update hedb.subitem set iid=? where sid=?
Hibernate: update hedb.subitem set iid=? where sid=?
Hibernate: update hedb.subitem set iid=? where sid=?
Hibernate: update hedb.subitem set iid=? where sid=?
Hibernate: update hedb.subitem set iid=? where sid=?

也就是说现在发现对于iid的操作控制,它的流程:
(1)先进行数据的增加操作;
|————第一步:先增加了item数据,但是此时并没有取得增长后的iid;
|————第二步:再增加subitem数据,可是此时没有取得增长后的iid,所以subitem中的iid是null;
(2)随后为了同步subitem中的iid,那么进行了更新subitem指定数据的操作。
之所以现在会造成这样一种情况,是因为item中把自己的控制没有转交给subitem,相当于item只是自己管自己了,但是没有考虑到subitem,所以造成的结果就是先自己增加了item,而后再自己增加了subitem,都完成增加之后,再执行了更新操作。

那么此时可以将item操作控制转交给subitem,那么就可以使用控制反转的概念。
范例:修改Item.hbm.xml文件

<set name="subitems"  cascade="all" inverse="true">
    <key> <!-- 关联的数据列 -->
        <column name="iid" />
    </key>
    <!-- 一对多所包含的数据类型 -->
    <one-to-many class="org.lks.pojo.Subitem" />
</set>
Hibernate: insert into hedb.item (ititle) values (?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
4

也就是说在进行item数据增加的时候,利用控制反转,相当于告诉了Hibernate,此时的item中的数据不是由自己进行维护,而是由subitem帮助进行维护,这样subitem就可以直接处理item中的iid内容,那么就只有增加数据的语句了。

可是这个时候有人提出来了,能不能先有subitem再有item呢?
范例:反着操作

package org.lks.test;

import org.lks.dbc.HibernateSessionFactory;
import org.lks.pojo.Item;
import org.lks.pojo.Subitem;

public class TestSubitemInsert {

	public static void main(String[] args) {
		Item item = new Item();
		item.setItitle("lks2");
		Subitem subitem = new Subitem();
		subitem.setStitle("fool");
		subitem.setItem(item);
		item.getSubitems().add(subitem);
		System.out.println(HibernateSessionFactory.getSession().save(subitem));
		HibernateSessionFactory.getSession().beginTransaction().commit();
		HibernateSessionFactory.closeSession();
		System.exit(0);
	}
}

Hibernate: insert into hedb.item (ititle) values (?)
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
23

在进行子表数据增加的时候只会考虑子表的数据,但是必须要提供有对应的父表数据。

对于数据的修改操作观察子表对应的操作(不会去考虑持久态的情况)。
范例:观察更新操作——update()方法操作

package org.lks.test;

import org.lks.dbc.HibernateSessionFactory;
import org.lks.pojo.Item;
import org.lks.pojo.Subitem;

public class TestSubitemInsert {

	public static void main(String[] args) {
		Item item = new Item();
		item.setIid(3);
		item.setItitle("hhy");
		Subitem subitem = new Subitem();
		subitem.setStitle("big fool");
		subitem.setItem(item);
		item.getSubitems().add(subitem);
		//用update测试是为了观察出数据的级联操作,而实际中只会使用Query更新
		HibernateSessionFactory.getSession().update(item);
		HibernateSessionFactory.getSession().beginTransaction().commit();
		HibernateSessionFactory.closeSession();
		System.exit(0);
	}
}
Hibernate: insert into hedb.subitem (iid, stitle) values (?, ?)
Hibernate: update hedb.item set ititle=? where iid=?

现在的操作之中,发现更新数据的时候(此为不标准做法),那么它的做法是增加了一个新的子类型,而后又更新了类型数据。但是以上的代码里面在保存subitem的时候并没有保存sid的数据。
范例:保存sid数据观察

package org.lks.test;

import org.lks.dbc.HibernateSessionFactory;
import org.lks.pojo.Item;
import org.lks.pojo.Subitem;

public class TestSubitemInsert {

	public static void main(String[] args) {
		Item item = new Item();
		item.setIid(3);
		item.setItitle("hhy");
		Subitem subitem = new Subitem();
		subitem.setSid(24);
		subitem.setStitle("super big fool");
		subitem.setItem(item);
		item.getSubitems().add(subitem);
		HibernateSessionFactory.getSession().update(item);
		HibernateSessionFactory.getSession().beginTransaction().commit();
		HibernateSessionFactory.closeSession();
		System.exit(0);
	}
}

Hibernate: update hedb.item set ititle=? where iid=?
Hibernate: update hedb.subitem set iid=?, stitle=? where sid=?

此时由于sid的数据存在,所以不会在subitem中出现增加操作了。

但是所有的操作都不如查询问题明显。
范例:根据id查询

package org.lks.test;

import org.lks.dbc.HibernateSessionFactory;
import org.lks.pojo.Item;

public class TestItemGet {

	public static void main(String[] args) {
		Item item = (Item) HibernateSessionFactory.getSession().get(Item.class, 3);
		System.out.println(item.getItitle());
		HibernateSessionFactory.closeSession();
		System.exit(0);
	}
}

Hibernate: 
select item0_.iid as iid1_0_0_, item0_.ititle as ititle2_0_0_ 
from hedb.item item0_ 
where item0_.iid=?
hhy

此时由于item没有涉及到subitem的任何操作,那么发现只发出了一条查询指令。

范例:继续数据查询

package org.lks.test;

import org.lks.dbc.HibernateSessionFactory;
import org.lks.pojo.Item;

public class TestItemGet {

	public static void main(String[] args) {
		Item item = (Item) HibernateSessionFactory.getSession().get(Item.class, 3);
		System.out.println(item.getItitle());
		System.out.println(item.getSubitems());
		HibernateSessionFactory.closeSession();
		System.exit(0);
	}
}

Hibernate: 因为现在要调用多方
    select-->HibernateSessionFactory.getSession().get(Item.class, 3)
        item0_.iid as iid1_0_0_,
        item0_.ititle as ititle2_0_0_ 
    from
        hedb.item item0_ 
    where
        item0_.iid=?
hhy
Hibernate: 根据item的信息查询所有的subitem信息
    select-->item.getSubitems()
        subitems0_.iid as iid2_1_0_,
        subitems0_.sid as sid1_1_0_,
        subitems0_.sid as sid1_1_1_,
        subitems0_.iid as iid2_1_1_,
        subitems0_.stitle as stitle3_1_1_ 
    from
        hedb.subitem subitems0_ 
    where
        subitems0_.iid=?
[org.lks.pojo.Subitem@1440c311, org.lks.pojo.Subitem@6079cf5, org.lks.pojo.Subitem@30cecdca, org.lks.pojo.Subitem@5226e402, org.lks.pojo.Subitem@1ddd3478, org.lks.pojo.Subitem@783ec989, org.lks.pojo.Subitem@1e6308a9, org.lks.pojo.Subitem@189b5fb1, org.lks.pojo.Subitem@21fff664, org.lks.pojo.Subitem@6edc4161, org.lks.pojo.Subitem@5486887b]

默认情况下,多方的数据不会自动进行加载,只有在调用与多方有关的数据时才会进行数据加载。

范例:观察程序问题

package org.lks.test;

import org.lks.dbc.HibernateSessionFactory;
import org.lks.pojo.Item;

public class TestItemGet {

	public static void main(String[] args) {
		Item item = (Item) HibernateSessionFactory.getSession().get(Item.class, 3);
		System.out.println(item.getItitle());
		HibernateSessionFactory.closeSession();
		System.out.println(item.getSubitems());
		System.exit(0);
	}
}

Hibernate: 
    select
        item0_.iid as iid1_0_0_,
        item0_.ititle as ititle2_0_0_ 
    from
        hedb.item item0_ 
    where
        item0_.iid=?
hhy
Exception in thread "main" org.hibernate.LazyInitializationException: 
failed to lazily initialize a collection of role: 
org.lks.pojo.Item.subitems, could not initialize proxy - no Session

此时出现的异常信息:延迟加载出现了问题,因为在调用多方数据前已经关闭了当前的Session,所以没有Session供我们使用,那么自然就无法进行数据的读取了。

那么现在如果真的有这样“非正确”的要求,可以将延迟加载关闭。
范例:修改Item.hbm.xml文件

<set name="subitems"  cascade="all" inverse="true" lazy="false">

在所有的正规开发之中只要是延迟加载都设置为true(lazy="true")。那么关闭延迟加载后的执行:

Hibernate: 
    select
        item0_.iid as iid1_0_0_,
        item0_.ititle as ititle2_0_0_ 
    from
        hedb.item item0_ 
    where
        item0_.iid=?
Hibernate: 
    select
        subitems0_.iid as iid2_1_0_,
        subitems0_.sid as sid1_1_0_,
        subitems0_.sid as sid1_1_1_,
        subitems0_.iid as iid2_1_1_,
        subitems0_.stitle as stitle3_1_1_ 
    from
        hedb.subitem subitems0_ 
    where
        subitems0_.iid=?
hhy
[org.lks.pojo.Subitem@1440c311, org.lks.pojo.Subitem@30cecdca, org.lks.pojo.Subitem@5226e402, org.lks.pojo.Subitem@1ddd3478, org.lks.pojo.Subitem@783ec989, org.lks.pojo.Subitem@1e6308a9, org.lks.pojo.Subitem@189b5fb1, org.lks.pojo.Subitem@6edc4161, org.lks.pojo.Subitem@5486887b, org.lks.pojo.Subitem@f973499, org.lks.pojo.Subitem@4d33940d]

在“一”方数据查询的时候,所有对应的“多”方数据都会查询出来。所以延迟加载一旦取消了,那么一定会有“1+N”次查询情况。

2 基于Annotation的配置

还是很幸运的是在MyEclipse里面,Annotation的配置还是很容易的,也不用你去编写多少的代码,都可以自动生成。
范例:观察Item.java类的定义

package org.lks.pojo;

import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@SuppressWarnings("serial")
@Entity
@Table(name = "item", catalog = "hedb")
public class Item implements java.io.Serializable {
	private Integer iid;
	private String ititle;
	private Set<Subitem> subitems = new HashSet<Subitem>(0);

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name = "iid", unique = true, nullable = false)

	public Integer getIid() {
		return this.iid;
	}

	public void setIid(Integer iid) {
		this.iid = iid;
	}

	@Column(name = "ititle", length = 32)

	public String getItitle() {
		return this.ititle;
	}

	public void setItitle(String ititle) {
		this.ititle = ititle;
	}
	
	//配置了一对多的关联关系,设置了级联以及延迟加载
	@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "item")
	public Set<Subitem> getSubitems() {
		return this.subitems;
	}

	public void setSubitems(Set<Subitem> subitems) {
		this.subitems = subitems;
	}

}

范例:观察Subitem.java配置

package org.lks.pojo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@SuppressWarnings("serial")
@Entity
@Table(name = "subitem", catalog = "hedb")
public class Subitem implements java.io.Serializable {
	private Integer sid;
	private Item item;
	private String stitle;

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name = "sid", unique = true, nullable = false)

	public Integer getSid() {
		return this.sid;
	}

	public void setSid(Integer sid) {
		this.sid = sid;
	}
	
	//配置多对一的关系,同时设置关联的数据列
	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "iid")

	public Item getItem() {
		return this.item;
	}

	public void setItem(Item item) {
		this.item = item;
	}

	@Column(name = "stitle", length = 50)

	public String getStitle() {
		return this.stitle;
	}

	public void setStitle(String stitle) {
		this.stitle = stitle;
	}

}

Annotation的配置要完胜*.hbm.xml文件的配置,所以在新的项目开发之中都会使用Annotation完成开发。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值