(八)Hibernate的一对多关联关系
一、概述
例如,以客户(Customer)和订单(Order)为例,一个客户能有多个订单,一个订单只能有一个客户。 从Customer到Order是一对多关联,在java类中的面向对象设计应该一个Customer对象包含多个Order对象,因此应该定义一个集合,来包含所有的Order对象。 从Order到Customer是多对一关联,在java类中设计每个Order对象需要关联一个Customer对象,因此Order类中应该定义一个Cutomer类型的属性,来引用关联的customer对象。 但是在关系数据库中,只存在主外键参照关系来表达两者的关联。
二、实例
(1)先创建两个实体类 Customer和Order
public class Customer { private Integer id; private String name; private Set <Order> orders=new HashSet<Order>(); public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Set getOrders() { return orders; } public void setOrders(Set orders) { this.orders = orders; } } public class Order { private Integer id; private String name; private Customer customer; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Customer getCustomer() { return customer; } public void setCustomer(Customer customer) { this.customer = customer; } }
(2)实体类写好了,使用Hibernate映射文件来映射关系
创建Customer.hbm.xml:
<hibernate-mapping > <!--指定实体类和表的映射关系--> <class name="com.cad.domain.Customer" table="customer"> <id name="id" column="id"> <generator class="native"></generator> </id> <property name="name" column="name"></property> <!--使用<set>元素来映射set集合类型--> <!-- name:持久化类中的属性名 --> <set name="orders"> <!--<key>元素设定所关联的持久化类对应的表的外键--> <!--<one-to-many>元素设定关联的持久化类--> <key column="cid"/> <one-to-many class="com.cad.domain.Order"/> </set> </class> </hibernate-mapping> Hibernate根据映射文件获得以下信息 -<set>元素表明Customer类中的orders属性为java.util.Set集合 -<one-to-many>元素表明orders集合中存放的是一组order对象 -<key>元素表明orders表通过外键cid关联customer表
创建Order.hbm.xml
<hibernate-mapping > <class name="com.cad.domain.Order" table="order"> <id name="id" column="id"> <generator class="native"></generator> </id> <property name="name" column="name"></property> <!--<many-to-one>元素建立了customer属性和对应表中外键的映射--> <!-- name:持久化类中的属性名 column:表中的外键 class:关键的Customer对象实现类 --> <many-to-one name="customer" column="cid" class="com.cad.domain.Customer"></many-to-one> </class> </hibernate-mapping>
(3)在hibernate.cfg.xml中配置映射文件
<mapping resource="com/cad/domain/Customer.hbm.xml"/> <mapping resource="com/cad/domain/Order.hbm.xml"/>
(4)测试
//添加方法 @Test public void test() { //读取配置文件 Configuration conf=new Configuration().configure(); //根据配置创建factory SessionFactory sessionfactory=conf.buildSessionFactory(); session = sessionfactory.openSession(); Transaction ts=session.beginTransaction(); //创建Customer Customer c=new Customer(); c.setName("张三"); //创建订单 Order o1=new Order(); o1.setName("矿泉水"); Order o2=new Order(); o2.setName("方便面"); //双向关联 c.getOrders().add(o1); c.getOrders().add(o2); o1.setCustomer(c); o2.setCustomer(c); //保存 session.save(c); session.save(o1); session.save(o2); ts.commit(); session.close(); sessionfactory.close(); } 然后执行,控制台会打印如下语句 Hibernate: insert into customer (name) values (?) Hibernate: insert into orders (name, cid) values (?, ?) Hibernate: insert into orders (name, cid) values (?, ?) Hibernate: update orders set cid=? where id=? Hibernate: update orders set cid=? where id=?
我们来执行删除操作,直接删除Customer,但由于还有Order关联着Customer 会执行成功么?
@Test public void test() { //读取配置文件 Configuration conf=new Configuration().configure(); //根据配置创建factory SessionFactory sessionfactory=conf.buildSessionFactory(); session = sessionfactory.openSession(); Transaction ts=session.beginTransaction(); Customer c=session.get(Customer.class, 2); //删除Customer session.delete(c); ts.commit(); session.close(); sessionfactory.close(); } 执行,打印如下语句 Hibernate: select customer0_.id as id1_0_0_, customer0_.name as name2_0_0_ from customer customer0_ where customer0_.id=? Hibernate: update orders set cid=null where cid=? Hibernate: delete from customer where id=? 我们会发现Hibernate会自动将Order中的cid设置为null,然后执行删除操作 我们发现Hibernate还是挺智能的,但这是由inverse属性操控的。
三、< set >元素的inverse属性
inverse所描述的是对象之间关联关系的维护方式。 inverse属性指定由哪方来维护关联关系。 inverse默认为false,即关联关系由自己控制,若为true,则反转,关联关系由对方控制. Inverse属性的作用是:是否将对集合对象的修改反映到数据库中。 在映射一对多的双向关联关系中,应该在"一"方把inverse属性设为true,由对方来维护主键关联关系. 所以上述例子中,inverse默认是false.即Customer维护关联关系,所以Customer会执行两条更新语句来更新Order的cid. 但是我们Order在插入的时候已经插入cid,所以这样会影响性能,我们只需要将Customer的<set>元素的inverse属性改为true即可。 我们的删除案例中也是,Customer执行删除时,会先去把Order的主键约束解除,然后删除。 我们只需要将Customer的inverse设置为true,然后由对方维护关联关系,我们再进行删除时,就会出现异常,因为有主键约束,我们Customer不再维护关联关系。
四、级联操纵
在实际应用中,对象和对象之间是相互关联的。例如我们的一对多关联关系。
在关系-对象映射文件中,用于映射持久化类之间关联关系的元素,如 < set>,< many-to-one>,< one-to-many>,都有一个cascade属性,用来指定如何操纵与当前对象关联的其他对象。
我们先看下面的例子,我们创建一个Customer,再创建两个Order,然后关联 我们只保存Customer,会抛出org.hibernate.TransientObjectException异常,这是为什么呢? 这是因为我们的Customer的inverse为false,关联关系由Customer维护。我们保存Customer时, 会维护Customer中orders中的所有Order的主键,但是Order是临时对象,并没有转变为持久状态,这时候就会抛出异常。 @Test public void test() { //读取配置文件 Configuration conf=new Configuration().configure(); //根据配置创建factory SessionFactory sessionfactory=conf.buildSessionFactory(); session = sessionfactory.openSession(); Transaction ts=session.beginTransaction(); Customer c=new Customer(); c.setName("jack"); Order o1=new Order(); o1.setName("苹果"); Order o2=new Order(); o2.setName("香蕉"); c.getOrders().add(o1); c.getOrders().add(o2); o1.setCustomer(c); o2.setCustomer(c); session.save(c); ts.commit(); session.close(); sessionfactory.close(); }
当Hibernate持久化一个临时对象时,并不会自动持久化所关联的其他临时对象,所以会抛出异常。
如果我们希望Hibernate持久化对象时自动持久化所关联的其他对象,那么就需要指定cascade属性
(1)级联保存和更新
当我们持久化对象时自动持久化所关联的其他对象。 把cascade属性设置为save-update ,这时候我们再执行上面的代码就会自动帮我们保存Customer关联的Order对象。 当cascade属性为save-update时,表明保存或更新当前对象时,会级联保存或更新与它关联的对象。
(2)级联删除
如果我们的cascade属性为delete时,我们删除当前对象,会自动删除与之关联的对象。 慎用这个delete属性。 例如:我们的Order配置了这个属性,Customer也配置了这个属性,我们删除订单时,因为是级联删除 所以会查找Customer,删除Customer,但Customer也配置了级联删除,所以会查找所有关联的订单,最后会删除该客户的所有订单和该客户。
(3)孤儿删除
如果我们的对象和关联的对象解除关系后,希望自动删除不再关联的对象。 需要将cascade设置为delete-orphan. 例如 ,我们设置cascade="delete-orphan" Transaction ts=session.beginTransaction(); Customer c=session.get(Customer.class, 7); Order order=(Order) c.getOrders().iterator().next(); c.getOrders().remove(order); order.setCustomer(null); ts.commit(); 我们解除Customer和Order的关系,Hibernate就会自动删除Order。 当cascade的值为all时,是save-update和delete的整合。 当cascade的值为all-dalete-orphan时,是all和delete-orphan的整合。