对多关系定义到对象集合引用由于用例通常需要从父对象到子对象遍历而可能需要(也可能不需要)从子对象到父对象遍历所以对多关系是对象模型中最常见关系类型;这意味着单向对多关系可以满足大多数情况
也就是说如果用例需要从子对象到父对象遍历则可以在子类中方便地添加多对关系使的成为双向关系
声明对多关系实体是父对象(并且是非所有者)此实体表定义主键但是它没有外键(外键在子对象中)
此对多关系引用对象是子对象和关系所有者子对象具有外键并引用父对象主键
在 Hibernate 中对多关系映射通常是通过将列添加到外键子表完成但映射详细内容是区别具体取决于是单向对多关系还是双向对多关系
在单向关系中子表中外键列不会映射到子对象中属性它在数据模型中而不是在对象模型中由于是单向所以仅在父对象中有属性而子对象中没有此外必须将外键列定义为可以为空 Hibernate 将首先插入子行(使用 NULL 外键)并在以后更新它
在双向关系中对象关系映射比较好子对象中有个用于外键列属性在数据库中该列不必为空但是结果对象模型在对象的间有循环依赖关系和更紧密耦合关系并需要其他编程来设置关系两端
可以看出有关对多关系定义有多个要考虑权衡原因但是通常建议使用单向关系除非用例指示需要用两个方向导航
对象模型
映射 13. 对多关系 (POJO)
// Address (parent) entity
public Address implements Serializable {
private Long addressId;
private Set phones = HashSet;
...
}
// Phone (child) entity
public Phone implements Serializable {
...
}
Hibernate 约定
在 Hibernate 中对多(单向)关系按照以下方式进行映射:
在父类中将设置、包或列表和对多子元素起使用
如果关系是单向则在表示子类表中创建外键;否则使用双向关系多对关系
映射 14. 对多关系(Hibernate XML 映射)
<!-- Address (parent) -->
< name="Address" table="T_ADDRESS">
<id name="addressId" column="ADDR_TID"/>
<
name="phones"
table="T_PHONE"
cascade="all-delete-orphan">
<key column="ADDR_TID"/>
<one-to-many ="Phone">
</>
</>
<!-- Phone (child) -->
< name="Phone" table="T_PHONE">
...
</>
务必注意 Hibernate 中专有 cascade="all-delete-orphan" 功能(请参见映射 14)使用此属性使您能够从数据库删除子对象思路方法是直接从父集合中移除它然后将父对象保存到数据库使用此专有功能不在代码中显式删除子对象尽管在标准 JPA 中没有任何等效功能但 OpenJPA 中专有 @ElementDependent 注释提供了自动孤立项清除功能但是此功能不可移植到其他持久性提供并且在取读代码时可导致混淆接下来将详细讨论 all-delete-orphan 功能
OpenJPA 约定
在 OpenJPA 中您无法使用外键映射对多单向关系您必须使用连接表映射它也就是说如果关系是双向则可以使用外键映射这样有两个选项可以从 Hibernate 映射对多单向关系:
使用连接表映射并将连接表添加到数据库或
使用外键映射并将关系从单向转换为双向将反向属性添加到对象模型并更改代码以便将关系设置为两个方向
建议将单向关系转换为双向关系和改变现有数据库模式和关联行相比此思路方法通常更容易修改遗留代码
在 OpenJPA 中对多(双向)关系按照以下方式进行映射:
在父对象中使用对多元素定义子对象集合并使用 id 元素为父对象定义主键
在子类中使用多对元素定义父对象并使用嵌套连接列元素定义子对象中外键并指定如何连接父对象和子对象
映射 15 对多关系(OpenJPA XML 映射)
<!-- Address (parent) -->
<entity ="Address">
<table name="T_ADDRESS"/>
<attributes>
<id name="addressId">
<column name="ADDR_TID"/>
</id>
<one-to-many name="phones" mapped-by="address">
<cascade>
<cascade-all/>
</cascade>
</one-to-many>
...
</attributes>
</entity>
<!-- Phone (child) -->
<entity ="Phone">
<table name="T_PHONE"/>
<attributes>
<many-to-one name="address">
<cascade>
<cascade-all/>
</cascade>
<join-column name="ADDR_TID"/>
</many-to-one>
...
</attributes>
</entity>
在定义对多关系时通常使用些其他功能您需要知道如何迁移这些功能:
Hibernate
inverse="true"
在 Hibernate 中您可能会遇到在定义双向关系时使用 inverse="true" 属性如果是这样请不要担心此功能等效于 OpenJPA 指定关系非所有者(它表没有外键)类似地Hibernate inverse="false" 属性等效于 OpenJPA 关系所有者(它表有外键)
通常这些概念是致但使用 inverse="true" 设置在双向关系多对端定义 Hibernate 映射这情况除外如果发现此类映射则应在执行迁移的前更改它它在 Hibernate 中不是最佳实战并且不能生成最佳 SQL例如如果将多对端设置为 inverse="true"则每次创建子对象时Hibernate 将执行两个 SQL 语句个创建子对象个使用父对象外键更新子对象在多对端将其更改为 inverse="false" 并在对多端设置 inverse="true" 可以解决这问题并使它和 OpenJPA 保持致当然进行该更改后您应重新测试应用
cascade="all-delete-orphan"
JPA 规范标准中没有和此 Hibernate 功能等效功能但是 OpenJPA 持久性提供中专有 @ElementDependent 注释完全可以执行 Hibernate all-delete-orphan 功能执行任务如果需要使用该功能请参阅 OpenJPA 用户指南尽管它是专有功能但是如果使用它迁移 all-delete-orphan 特性则不需要对应用源代码进行任何更改
若要以符合标准方式迁移 all-delete-orphan 功能您可以使用 _disibledevent="ADDR_TID"/>
</many-to-one>
...
</attributes>
</entity>
还值得提是Hibernate 和 OpenJPA 在从分离对象访问延迟加载集合上是区别在 Hibernate 中如果员尝试访问分离对象上延迟加载集合则会引发异常;而 OpenJPA 将返回空值而不是异常
此差异原因是 JPA 规范标准没有指定如何处理在分离对象上访问延迟加载集合每个 JPA 供应商可以决定如何处理此条件它们会引发异常或者将其保留为未化状态甚至返回具有零个元素集合
因此如果遗留 Hibernate 应用正在使用异常检测对分离对象延迟加载集合访问您可以使用 OpenJPA 通过测试空集合执行相同操作不过需要记住是:JPA 规范标准没有介绍说明是引发异常还是返回空值因此依赖于此行为不可移植并且随时会更改甚至可能在以后版本中中断您应用
此外还要务必注意您是获取异常(在 Hibernate 中获取)还是不获取异常(在 OpenJPA 中不获取)异常可以指示在测试过程中需要检测和修复应用也就是说问题在于您是否获取异常所以在 JPA 规范标准中强制引发异常和否不能解决问题:为了使用分离对象应用需要某些体系结构指导原则
有以下 3个解决方案使用分离对象:
在返回视图所需所有集合的前请显式化它们;即您应确定延迟加载是否适用于这些特定关系
保持实体管理器处于打开状态直到完成呈现视图(即在视图中打开和关闭实体管理器)这可以全部避免使用分离实体
和第 2个解决方案类似EJB 3.0 第 3个可能解决方案使用扩展持久上下文以保持实体管理器在事务的间处于活动状态
如果将实体从 EJB 组件传递到 Web 层则应遵循第个解决方案它使您能够保持 EJB(会话 facade)中事务逻辑而不是需要 Web 层管理该事务此外Web 层难于管理事务它需要提交事务并在呈现视图后关闭实体管理器(可能在 ServletFilter 中)
最新评论