(八)Hibernate的一对多关联关系
一、概述
1 2 3 4 5 6 7 | 例如,以客户(Customer)和订单(Order)为例,一个客户能有多个订单,一个订单只能有一个客户。 从Customer到Order是一对多关联,在java类中的面向对象设计应该一个Customer对象包含多个Order对象,因此应该定义一个集合,来包含所有的Order对象。 从Order到Customer是多对一关联,在java类中设计每个Order对象需要关联一个Customer对象,因此Order类中应该定义一个Cutomer类型的属性,来引用关联的customer对象。 但是在关系数据库中,只存在主外键参照关系来表达两者的关联。 |
二、实例
(1)先创建两个实体类 Customer和Order
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <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中配置映射文件
1 2 | <mapping resource= "com/cad/domain/Customer.hbm.xml" /> <mapping resource= "com/cad/domain/Order.hbm.xml" /> |
(4)测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | //添加方法 @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 会执行成功么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | @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属性
1 2 3 4 5 6 7 8 9 10 11 | 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属性,用来指定如何操纵与当前对象关联的其他对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 我们先看下面的例子,我们创建一个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)级联保存和更新
1 2 3 4 | 当我们持久化对象时自动持久化所关联的其他对象。 把cascade属性设置为save-update ,这时候我们再执行上面的代码就会自动帮我们保存Customer关联的Order对象。 当cascade属性为save-update时,表明保存或更新当前对象时,会级联保存或更新与它关联的对象。 |
(2)级联删除
1 2 3 4 | 如果我们的cascade属性为delete时,我们删除当前对象,会自动删除与之关联的对象。 慎用这个delete属性。 例如:我们的Order配置了这个属性,Customer也配置了这个属性,我们删除订单时,因为是级联删除 所以会查找Customer,删除Customer,但Customer也配置了级联删除,所以会查找所有关联的订单,最后会删除该客户的所有订单和该客户。 |
(3)孤儿删除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 如果我们的对象和关联的对象解除关系后,希望自动删除不再关联的对象。 需要将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的整合。 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)