首页 > WEB开发 > 后台开发 > Hibernate关联关系映射及CRUD
2014
10-18

Hibernate关联关系映射及CRUD

二、关联关系的CRUD

单个对象的CRUD操作见《Hibernate入门》一文。当一个对象和另外一个对象有关联关系时,对一个对象作CRUD操作,另外一个对象是否也跟着进行CRUD操作?这是本节要说的内容,本节的操作都是以一对多双向关联Person、Group为例(这两个类见1.1.3)。

1、save

测试保存“多”的一方:首先什么设置也不做,看看默认情况下,保存Person对象时,是否也保存了Group对象:

@Test
public void testSavePerson(){
	//创建Person对象
	Person p = new Person();
	p.setName("xiaoming");
	//创建Group对象
	Group g = new Group();
	g.setName("y11");
	//建立关联
	p.setGroup(g);

	session.beginTransaction();
	//① session.save(g);
	session.save(p);// ②
	session.getTransaction().commit();
}

当进行上面的保存操作时,编译器报错,说明默认情况下保存操作并不会级联进行。要想在保存Person对象的同时保存关联的Group对象,有两种解决方法:

1)先保存Group对象,再保存Person对象,即去掉代码中①处的注释

2)修改Person对象中@ManyToOne注解:@ManyToOne(cascade={CascadeType.ALL})

此时保存Person对象时会级联保存Group对象。

cascade属性

  • cascade属性设定在持久化时对于关联对象的操作(CUD,R归fetch管),在@ManyToOne、@ManyToMany等表示关联关系的注解中都含有该属性。
  • cascade属性的值是一个CascadeType数组。
  • cascade仅仅是帮我们省了编程的麻烦而已,没有它,也可以先对关联对象进行CUD操作,不要把它的作用看的太大。

测试保存“一”的一方:保存Group对象,测试前先在Group类中@OneToMany注解上加cascade属性:

@Test
public void testSaveGroup(){
	Person p1 = new Person();
	p1.setName("xiaoming");

	Person p2 = new Person();
	p2.setName("MM");

	Group g = new Group();
	g.setName("y11");

	//建立关联
	g.getPersons().add(p1);
	g.getPersons().add(p2);

	//①
	//p1.setGroup(g);
	//p2.setGroup(g);

	session.beginTransaction();
	session.save(g);// ②
	session.getTransaction().commit();
}

查看数据库可以发现p1、p2对象已经被插入了,但问题是persons表的gid字段全部为null。将①处的两行注释去掉,问题解决。说明在保存Group对象时,要想正确地级联保存Person对象,需要设置双向的关联。

小技巧:在进行双向关联关系的CRUD操作时,需在程序中设置双向关联。

2、get

测试:从数据库取Person对象(Group对象)的时候,会不会将关联的Group对象(Person对象)取出来?

@Test
public void testGetPerson(){
	session.beginTransaction();
	Person p1 = (Person)session.get(Person.class, 2);
	session.getTransaction().commit();
}

@Test
public void testGetGroup(){
	session.beginTransaction();
	Group p1 = (Group)session.get(Group.class, 1);
	session.getTransaction().commit();
}

通过控制台打印的SQL语句可以发现,在取Person对象时,会将关联的Group对象一并取出来;但取Group对象时,不会去取Person对象。这和注解上fetch属性的设置有关。

fetch属性(☆)

  • fetch属性管CRUD中的R,即从数据库中读。
  • fetch属性值为FetchType枚举类型,该类型有两个取值:EAGER、LAZY。EAGER表示将关联对象一并取出来,LAZY表示延迟加载,在用到关联对象时才会取。

通过上面testGetPerson和testGetGroup两个测试可以知道,默认情况下:

  • @ManyToOne注解的fetch属性值为EAGER,如果关联的对象是“一”的一方,则将它一并取出来。
  • @OneToMany注解的fetch属性值为LAZY,如果关联的对象是“多”的一方就不取。

这也比较符合实际情况。

最佳实践:

  • 一般情况下不去动fetch属性,保持默认就行。
  • 特殊情况,当一对多且“多”的一方非常有限时,在查询“一”的对象时渴将关联的多的一方也取出来(@OneToMany(fetch=FetchType.EAGER)),如一个查询一个部门时将其子部门也一并查出来。

注:load()、FetchType.LAZY都有延迟加载的作用,当去使用某个对象时才向数据库发送select语句。区别是前者关心的是当前要取的对象,后者关心的是关联的对象。

3、update、delete

关于update的操作同上面的save类似,不作讲解。下面讲解delete操作。

测试删除“多”的一方:取id=2的Person对象,并将其从数据库中删除。删除前将Person、Group类的cascade属性都设置为CascadeType.ALL。注:persons表中Id=1,2字段的gid属性都为1。

@Test
public void testDeletePerson(){
	session.beginTransaction();
	Person p = (Person)session.get(Person.class,2);
	//①
	//p.setGroup(null);

	session.delete(p);
	session.getTransaction().commit();
}

测试后查询数据库,发现关联的Group对象和id=1的Person对象也从数据库中级联删除了。在不改变cascade属性值的前提下,如何避免这种删除时的级联操作?解决方法有两种:

  • 在删除Person对象之前,解除与之关联的Group对象的关联关系,即去掉①处的注释。
  • 写HQL语句执行:delete from Person where id=2

测试删除“一”的一方:也会将关联的“多”的一方从数据库中删除,避免级联删除最简单的方法是使用HQL进行删除。【也可以遍历Group对象关联的persons,将每个Person对象的Group设置为NULL】


留下一个回复

你的email不会被公开。