Hibernate中的一对多关联技术
【创建数据库脚本】
DROP TABLE IF EXISTS subitem;
DROP TABLE IF EXISTS item;
CREATE TABLE item(
iid INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(50)
);
CREATE TABLE subitem(
sid INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(50),
iid INT,
CONSTRAINT fk_iid FOREIGN KEY(iid) REFERENCES item(iid) ON DELETE CASCADE
);
现在必须清楚以下关系:
- 增加子表数据的时候要选择好与父表的关系。
- 父表数据增加时要考虑子表数据的级联保存问题。
基于*.hbm.xml文件配置
【创建Item类】
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
@SuppressWarnings("serial")
public class Item implements Serializable {
private int iid;
private String title;
private Set<Subitem> subitems = new HashSet<Subitem>();
//方法略
}
【创建Subitem类】
import java.io.Serializable;
@SuppressWarnings("serial")
public class Subitem implements Serializable {
private int sid;
private String title;
private Item item;
//方法略
}
整个一对多关系主要是依靠Set集合。类定义完成后,关键是对*.hbm.xml文件的修改(一对多关系由一方控制多方)。
【创建Item.hbm.xml文件】
<hibernate-mapping>
<class name="com.gub.vo.Item" table="item" schema="company">
<id name="iid" column="iid"/>
<property name="title" column="title"/>
<!--inverse:控制反转-->
<set name="subitems" cascade="all" fetch="select" >
<!--关联的数据列-->
<key>
<column name="iid" />
</key>
<!--一对多所包含的数据类型-->
<one-to-many class="com.gub.vo.Subitem" />
</set>
</class>
</hibernate-mapping>
【创建Subitem.hbm.xml文件】
<hibernate-mapping>
<class name="com.gub.vo.Subitem" table="subitem" schema="company">
<id name="sid" column="sid"/>
<property name="title" column="title"/>
<!--多对一关系-->
<many-to-one name="item" class="com.gub.vo.Item" fetch="select">
<column name="iid" />
</many-to-one>
</class>
</hibernate-mapping>
【实现Item单表数据的增加】
public static void main(String[] args) {
Item item = new Item();
item.setIid(1);
item.setTitle("类别1");
HibernateSessionFactory.getSession().save(item);
HibernateSessionFactory.getSession().beginTransaction().commit();
HibernateSessionFactory.closeSession();
}
【实现Item与Subitem数据的增加】
public static void main(String[] args) {
Item item = new Item();
item.setIid(1);
item.setTitle("图书");
for(int x=0;x<3;x++){
Subitem subitem = new Subitem();
subitem.setSid(x+1);
subitem.setTitle("文学类-"+x);
subitem.setItem(item);
item.getSubitems().add(subitem);
HibernateSessionFactory.getSession().evict(item);
}
HibernateSessionFactory.getSession().save(item);
HibernateSessionFactory.getSession().beginTransaction().commit();
HibernateSessionFactory.closeSession();
}
观察控制台输出可以发现,Hibernate在进行一对多多表增加时有如下流程:
- 查询要增加的子表数据是否存在,若数据不存在可以进行数据增加操作。
- 增加父表数据,但此时并没有取得关联外键字段的内容。
- 增加子表数据,但是由于没有取得关联外键字段的内容,所以将子表的与之关联的字段设为空值。
- 更新子表,同步父表与子表的外键关联字段
之所以会造成这种情况,是因为父表没有把自己的控制权转交给子表(先增加主表,在增加子表,最后进行更新)。此时可以将父表的控制转交给子表(使用控制反转)。
【修改Item.hbm.xml文件】
<hibernate-mapping>
<class name="com.gub.vo.Item" table="item" schema="company">
<id name="iid" column="iid"/>
<property name="title" column="title"/>
<!--inverse:控制反转-->
<set name="subitems" cascade="all" inverse="true" >
<!--关联的数据列-->
<key>
<column name="iid" />
</key>
<!--一对多所包含的数据类型-->
<one-to-many class="com.gub.vo.Subitem" />
</set>
</class>
</hibernate-mapping>
再次执行增加操作,观察控制台输出:
在进行子表数据增加的时候,利用控制反转相当于告诉了Hibernate,此时的父表中的数据不是有自己进行维护,而是由子表帮助进行维护,这样子表就可以直接处理父表的外键字段内容,就只会有增加数据的执行语句了。
【观察更新操作】
public static void main(String[] args) {
Item item = new Item();
item.setIid(1);
item.setTitle("HAHA");
Subitem subitem = new Subitem();
subitem.setItem(item);
//subitem.setSid(1);
subitem.setTitle("*******");
item.getSubitems().add(subitem);
HibernateSessionFactory.getSession().update(item);
HibernateSessionFactory.getSession().beginTransaction().commit();
HibernateSessionFactory.closeSession();
}
如果我们为子表的sid字段设置内容,则直接对subitem表进行修改操作。
如果我们不是指sid字段的内容,则会在subitem表中新增一条数据。
【根据ID查询】
public static void main(String[] args) {
Item item = (Item)HibernateSessionFactory.getSession().get(Item.class,2);
System.out.println("标题:"+item.getTitle());
HibernateSessionFactory.closeSession();
}
由于此时没有涉及到Subitem的任何内容,只查询item,可以直接查出。
public static void main(String[] args) {
Item item = (Item)HibernateSessionFactory.getSession().get(Item.class,2);
System.out.println("标题:"+item.getTitle());
System.out.println("栏目信息:"+item.getSubitems());
HibernateSessionFactory.closeSession();
}
此时发现出现了两条查询语句。
默认情况下,多方的数据不会自己进行加载,只有在调用与多方有关的数据时才会进行数据加载 。
public static void main(String[] args) {
Item item = (Item)HibernateSessionFactory.getSession().get(Item.class,2);
System.out.println("标题:"+item.getTitle());
HibernateSessionFactory.closeSession();
System.out.println("栏目信息:"+item.getSubitems());
}
若在多方加载之前关闭session连接,发现此时出现了异常信息,延时加载出现了问题(Hibernate的所有操作都要有session支持)。
修改Item.hbm.xml文件,将lazy设置为true,再次执行,f发现数据读取成功。
<hibernate-mapping>
<class name="com.gub.vo.Item" table="item" schema="company">
<id name="iid" column="iid"/>
<property name="title" column="title"/>
<!--inverse:控制反转-->
<set name="subitems" cascade="all" inverse="true" fetch="select" lazy="true" >
<!--关联的数据列-->
<key>
<column name="iid" />
</key>
<!--一对多所包含的数据类型-->
<one-to-many class="com.gub.vo.Subitem" />
</set>
</class>
</hibernate-mapping>
基于Annotation的配置
在Item类的getSubitems()方法上加上如下注解:
//配置了一对多的关系,设置了级联操作及延时加载
@OneToMany(cascade = CascadeType.ALL,mappedBy = "item",fetch = FetchType.LAZY)
public Set<Subitem> getSubitems() {
return subitems;
}
在Subitem类的getItem()方法上加上如下注解:
//配置多对一关系,同时设置关联的数据列
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="iid")
public Item getItem() {
return item;
}
此时配置完成,运行上面的所有测试程序,发现于hbm.xml文件配置结果一致。