下面这个案例是一对多和多对一共存的一个案例:
Customer类:
public class Customer {
private Integer id;
private String fname;
private String lname;
private Integer age;
private Date birthday;
private boolean married;
private byte[] photo;
private String description;
//建立从Customer到Order之间的一对多关联关系
/*
* 为何选用Set:
* 1.List:元素可重复,但是我们的需求是订单集合不要重复
* 2.Map:是一个键值对,value是每个不同的订单,那么key呢?如果一个客户对应100张订单,这时候让这个客户作为key,那不是数据冗余了么?
* 3.Set:无序并且唯一。
*/
private Set<Order> orders = new HashSet<Order>();
}
Customer.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="com.xxc.domain.Customer" table="Customers" lazy="false">
<id name="id" column="id" type="integer">
<generator class="native"></generator>
</id>
<property name="name" column="name" type="string" length="20" access="property"/>
<property name="birthday" column="birthday" type="date"/>
<property name="age" column="age" type="integer" length="20"/>
<property name="description" column="description" type="text" />
<property name="married">
<column name="married" sql-type="int(20)"></column>
</property>
<property name="photo">
<!-- property表的column属性和子元素column是互斥的,只能用其一 -->
<column name="photo" sql-type="longblob"></column>
</property>
<!-- 映射一对多关系 name是集合名 并设置级联添加操作-->
<set name="orders" cascade="save-update">
<!-- 这个写的是"多"那一端的外键在数据库中的字段名,因为客户表中的所有属性都是描述自身属性的,没有一个是具有关联关系的-->
<key column="cid"></key>
<!-- 集合中存的"多"那一端的类型 -->
<one-to-many class="com.xxc.domain.Order"/>
</set>
</class>
</hibernate-mapping>
Order类:
public class Order {
private Integer id;
private String orderNumber;
private float price;
/* 建立从Order到Customer之间多对一关联关系 */
private Customer customer ;
}
Order.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="com.xxc.domain.Order" table="Orders" lazy="false">
<id name="id" column="id" type="integer">
<generator class="native"></generator>
</id>
<property name="orderNumber" column="orderNumber" type="string" length="20"/>
<property name="price" column="price" type="float"/>
<!-- 映射从Order到Customer之间多对一关联关系 -->
<many-to-one name="customer" class="com.xxc.domain.Customer" column="cid" cascade="save-update" />
</class>
</hibernate-mapping>
按照下图测试案例1,session.save(o1)
测试:
package com.xxc.app;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.BeforeClass;
import org.junit.Test;
import com.xxc.domain.Customer;
import com.xxc.domain.Order;
public class App {
private static SessionFactory sf = null;
@BeforeClass
public static void initialize(){
Configuration config = new Configuration();
/* 也可以写成这样的形式
* sf = config.addClass(Customer.class).addClass(Order.class).buildSessionFactory();
*/
config.addClass(Customer.class);
config.addClass(Order.class);
sf = config.buildSessionFactory();
}
@Test
public void insertCustomer(){
Customer c = new Customer();
Order o1 = new Order();
Order o2 = new Order();
Order o3 = new Order();
//设置订单和客户的关联关系
c.getOrders().add(o2);
c.getOrders().add(o3);
o1.setCustomer(c);
Session session = sf.openSession();
Transaction t = session.beginTransaction();
session.save(o1);
t.commit();
session.close();
}
/*
* 建立一对多关系的主要目的不是在于插入,而是在于查询
* 下面的例子中,可以发现当查询某个客户的时候,因为客户类设置了一对多关联关系
* hibernate会将此客户中所有的订单表查询出来,封装在客户类中的订单集合中
*/
@Test
public void loadOrders(){
Session session = sf.openSession();
Transaction t= session.beginTransaction();
Customer c = (Customer) session.load(Customer.class, 1);
t.commit();
for(Order o : c.getOrders()){
System.out.println(o.getId());
}
session.close();
}
}
Many方<set>元素的inverse属性:
Customer表:
Orders表:
需求:将Orders表中id为4的数据的cid改成1。利用Hibernate面向对象的方法来实现。说的书面化点就是:修改Order表中指定数据和Customer表中数据的所属关系。
思路:由于Customer类中有存放Order的集合,Order类中又要设置所属的Customer。所以我们要分别获取需要被修改的Order和它新所属的Customer对象。然后分别设置。
测试:
package com.xxc.app;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.BeforeClass;
import org.junit.Test;
import com.xxc.domain.Customer;
import com.xxc.domain.Order;
public class OneToMany {
private static SessionFactory sf = null;
@BeforeClass
public static void initialize(){
Configuration config = new Configuration();
/* 也可以写成这样的形式
* sf = config.addClass(Customer.class).addClass(Order.class).buildSessionFactory();
*/
config.addClass(Customer.class);
config.addClass(Order.class);
sf = config.buildSessionFactory();
}
@Test
public void changeRelation(){//改变所属 关系
Session session = sf.openSession();
Transaction t = session.beginTransaction();
//获取Order表中指定数据的新的所属对象
Customer c = (Customer) session.load(Customer.class, 1);
//获取需要被改变的Order对象
Order o = (Order) session.load(Order.class, 4);
c.getOrders().add(o);
o.setCustomer(c);
//这里是不需要session.save(),原因是从数据库里查询出来的数据状态为持久化状态,在持久化状态的对象上修改属性,只要当事务提交的时候,这个对象会自动会和session缓存里此对象的快照进行对比,如果属性和快照里的不同,那么会自动进行更新数据库操作。
t.commit();
session.close();
}
}
经过以上代码测试,发现控制台打印以下两句SQL。其实两句SQL语句执行的操作是同一个意思,都是将Order表中id为4的数据的Cid字段改成1。书面化点:将id为4的订单数据的所属客户改成所属于id为1的客户。
出现这样的情况势必会导致程序性能下降,那么为什么会出现这样的情况呢?
是因为Session缓存监控到了两个对象的改变,而这两个对象的改变对应的sql操作是同一种结果。
解决办法:
在"one"方的<set>元素中增加inverse=true。Hibernate不会按照Customer对象的属性变化来同步更新数据库。
修改Customer.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="com.xxc.domain.Customer" table="Customers" lazy="false">
<id name="id" column="id" type="integer">
<generator class="native"></generator>
</id>
<property name="name" column="name" type="string" length="20" access="property"/>
<property name="birthday" column="birthday" type="date"/>
<property name="age" column="age" type="integer" length="20"/>
<property name="description" column="description" type="text" />
<property name="married">
<column name="married" sql-type="int(20)"></column>
</property>
<property name="photo">
<column name="photo" sql-type="longblob"></column>
</property>
<set name="orders" cascade="save-update" inverse="true"><!-- 让缓存忽略掉客户类中订单集合的变化 true|false -->
<key column="cid"></key>
<one-to-many class="com.xxc.domain.Order"/>
</set>
</class>
</hibernate-mapping>
注意:在建立两个对象的双向关联时,应该同时修改关联两端的对象的相应属性。这样才会使程序更加健壮,提高业务逻辑层的独立性,使业务逻辑层的程序代码不受Hibernate实现的影响。同理,当解除双向关联的关系时,也应该修改关联两端的对象的相应属性。
解除关联关系:解除id为5的order和id为3的Customer数据的关联关系(就是将多的一方的外键置空而不是删除)
public void removeRelation(){//改变所属 关系
Session session = sf.openSession();
Transaction t = session.beginTransaction();
Customer c = (Customer) session.load(Customer.class, 3);
Order o = (Order) session.load(Order.class, 5);
//在Customer的Orders集合中删除要解除关系的order对象
c.getOrders().remove(o);
//在Order类中将所属的Customer类置空
o.setCustomer(null);
t.commit();
session.close();
}
cascade属性:
all-delete-orphan:
1、当保存或更新customer对象时,级联保存或更新所有关联的order对象,相当于save-update。
2、当删除customer对象时,级联删除所有的order对象,相当于delete。
3、删除不再和customer对象关联的所有order对象。
当关联双方存在父子关系,就可以把父方的cascade属性设为all-delete-orphan。
所谓父子关系:是指由父方来控制子方的持久化生命周期,子方对象必须和一个父方对象关联,而不允许单独存在。如果删除父方对象,应该级联删除所有关联的子方对象;如果一个子方对象不再和一个父方对象关联,应该把这个子方对象删除。
例如:
1、订单和客户是一个父子关系,如a客户有1 2 3三个订单,如果将a客户信息删除,那么1 2 3订单也没有存在的必要了。
2、公司和职工不算父子关系,因为职工换工作后,可以和新的一个公司对象关联。
级联删除:
在one方的hbm.xml中修改如下
<set name="orders" cascade="all" inverse="true"><!-- all表示既可以save-update也可以delete级联删除 如果为默认值none,那么就不会进行级联操作了 -->
<key column="cid"></key>
<one-to-many class="com.xxc.domain.Order"/>
</set>