一对多是数据库第三设计范式的主要描述点,而在实际的工作之中,一对多的关系使用的一定是最多的,像“类型-子类型”就属于一对多的关系。
范例:数据库的创建脚本
-- 删除数据表
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完成开发。