原文:JPA implementation patterns: Saving (detached) Entities
作者:Vincent Partington
出处:http://blog.xebia.com/2009/03/23/jpa-implementation-patterns-saving-detached-entities/
我们以数据访问对象模式作为探索JPA实施模式的开始,接着讨论了如何管理双向的关联,本周我们谈一谈这个乍一看仿佛微不足道的问题:如何保存实体。
在JPA中保存实体很简单,对吧?只要把想持久的对象传递给EntityManager.persist就可以了,在遇到可怕的“游离的实体被传给了persist方法”这一信息,或者是在用不同于Hibernate EntityManager的JPA提供程序的时候遇到类似的信息之前,这一做法似乎都很有效。
那么信息中提到的游离实体指的是什么呢?一个游离的实体(也叫做游离的对象)是指一个与持久性存储中的一个实体有着相同的ID,但已不再是持久性上下文(EntityManager会话范围)的组成部分的对象。导致这一现象的两个最常见的原因是:
用来检索该对象的EntityManager已经被关闭。
从应用的外部获得该对象,例如,作为提交表单的一部分、Hessian一类的远程协议,或者是通过BlazeDS AMF频道从Flex客户端获得等等。
关于持久的约定(见JPA 1.0规范的3.2.1节)明确指出,当被传入的对象是一个游离对象时,抛出EntityExistsException异常,而在持久性上下文被刷新或者事务被提交时,抛出任何其他的PersistenceException异常。需要注意的是,在一个事务内部两次持久同一个对象并不会产生问题,第二次调用仅是会被忽略掉,但该持久操作可能会被级联到自第一次调用以来所添加的实体的所有关联上。除了后者这一考虑外,对于已经被持久的对象来说,不需要调用EntityManager.persist,因为任何修改都会在做刷新或者提交的时候被自动保存。
saveOrUpdate和merge的比较
那些使用原始的Hibernate来工作的人可能已经非常习惯于使用Session.saveOrUpdate方法来保存实体了,saveOrUpdate方法会弄清楚对象是新的还是在这之前已经被保存过的,在第一种情况下实体被保存,在后一种情况下实体被更新。
在从Hibernate切换到JPA时,许多人都沮丧地发现这一方法不见了,而最接近的替代看起来好像是EntityManager.merge方法,但实际上彼此之间存在着很大的区别,而这些区别则带来了很重要的影响。Session.saveOrUpdate方法以及它的表亲Session.update,把传入的实体依附(attach)到持久性上下文中,而EntityManager.merge方法则是把传入对象的内容拷贝到拥有相同标识符的持久实体中,然后返回一个到该持久实体的引用,而传入的对象并未被依附到持久性上下文中。
这意味着在调用了EntityManager.merge之后,我们必须使用从该方法中返回的实体引用来代替最初传入的对象,这不同于简单地就一个对象调用EntityManager.persist(如前所述甚至是多次调用)来持久它,然后继续使用原来的对象的这样一种做法。即使是在更新的时候,Hibernate的Session.saveOrUpdate的确也像EntityManger.persist(更确切地说是Session.save)一样使用了这种好的做法,但是它有一个比较大的缺点,就是如果我们正在尝试更新一个实体,比如说重新依附(reattach)该实体,而此时另一个与它有着相同的ID的实体已经存在于持久性上下文中的话,则NonUniqueObjectException异常会被抛出。而且,要弄清楚哪段代码持久(或合并或检索)了另一个实体比弄清楚为什么我们会得到“游离的实体被传给persist方法”这样的信息还要难些。
归纳
场景
|
EntityManger.persist
|
EntityManager.merge
|
SessionManager.saveOrUpdate
|
传入的对象从未被持久过
|
1、 把对象作为新实体添加到持久性上下文中
2、 在刷新/提交时把新实体插入到数据库中
|
1、 拷贝内容到新实体中
2、 把新实体添加到持久性上下文中
3、 在刷新/提交时把新实体插入到数据库中
4、 返回新实体
|
1、 把对象作为新的实体添加到持久性上下文中
2、 在刷新/提交时把新实体插入到数据库中
|
对象之前已被持久,但没有被加载到该持久性上下文中
|
1、抛出EntityExistsException异常(或在刷新/提交时抛出PersistenceException异常)
|
1、 加载已存在的实体
2、 拷贝对象的内容到被加载实体中
3、 在刷新/提交时更新被加载实体到数据库中
4、 返回被加载实体
|
1、 把对象添加到持久性上下文中
2、 在刷新/提交时把被加载实体更新到数据库中
|
对象之前已被持久且已经被加载到该持久性上下文中
|
1、抛出EntityExistsException异常(或在刷新/提交时抛出PersistenceException异常)
|
1、 拷贝对象的状态到被加载实体中
2、 在刷新/提交时更新被加载实体到数据库中
3、 返回被加载实体
|
1、抛出NonUniqueObjectException异常
|
通过查看该表就可以开始理解,为什么saveOrUpdate方法不会成为JPA规范的一部分,以及为什么JSR的成员反而会选择merge方法。顺便说一下,你可以在Stevi Deter关于该主题的博客中发现其从不同的角度来讨论这一saveOrUpdate和merge之间的比较问题。
合并的问题
在继续我们的话题之前,我们需要讨论一下EntityManger.merge工作方式中存在的缺陷,那就是该工作方式很容易破环双向的关联。考虑一下使用本系列文章中的上一篇博客提到的Order和OrderLine类作为例子,如果是从web前端(或从Hessian客户端,或是flex应用等)接收到了一个已被更新的OrderLine对象的话,其order域可能被设置为null。如果该对象随后与已加载的实体合并的话,该实体的order域就会被设成null,但它不会被从其曾指向的Order的orderLines集合中去掉,从而破坏了Order的orderLines集合中的每个元素的order域都被设置成指回到该Order的这样一种固定做法。
在这种情况下,或是在其他简单地因EnttiyManager.merge拷贝对象的内容到被加载的实体中而导致问题的情况下,我们可以求助于自助合并模式(DIY merge pattern),通过调用EntityManger.find而不是调用EntityManager.merge来查找已存在的实体,然后由我们自己来拷贝内容,如果EntityManager.find返回null的话,我们可以决定是持久接收到的对象还是抛出异常。适用于Order类的这一模式可以这样来实现:
Order existingOrder = dao.findById(receivedOrder.getId());
if(existingOrder == null) {
dao.persist(receivedOrder);
} else {
existingOrder.setCustomerName(receivedOrder.getCustomerName());
existingOrder.setDate(receivedOrder.getDate());
}
模式
那么,所有的这些情况是否会让我们无从下手呢?我会坚持这样的一些经验法则:
当且只有当创建新的实体时(最好是在这种情况下),才调用EntityManager.persist来持久它,当我们把领域的访问对象视作集合时,这样做最合理。我把这种做法称作新增持久模式(persist-on-new pattern)。
在更新现有的实体时,不调用任何的EntityManager方法,JPA提供程序会在刷新或者提交时自动地更新数据库。
在从应用的外部接收到已存在的简单实体(没有引用其他实体的实体)的更新版本并希望保存新的内容时,调用EntityManager.merge把这些内容拷贝到持久性上下文中。鉴于合并的工作方式,如果不能确定对象是否已经被持久过的话,也可以这样做。
当我们在合并的过程中需要更多的控制权时,使用自助合并模式(DIY merge pattern)。
我希望这篇博客为你提供了一些如何保存实体以及如何处理游离实体的指引,在讨论数据传输对象(Data Transfer Object)的时候我们会重新提到游离实体,不过下周我们会先处理一些常见的实体检索模式,在此期间欢迎你提出意见,你会用到哪些JPA模式呢?
分享到:
相关推荐
Spring Data JPA系列5:让IDEA自动帮你写JPA实体定义代码.doc
jpa入门案例:单表查询,包括分页查询 使用springboot来整合实现
Spring Data JPA系列3:JPA项目中核心场景与进阶用法介绍.doc
Spring Data JPA系列2:SpringBoot集成JPA详细教程,快速在项目中熟练使用JPA.doc
背景如果文本值存储在数据类型为CHAR(n)的 Oracle 列中,则数据库在将该值保存到列之前最多n字符。 后来,尝试使用 Hibernate 或 JPA 等框架搜索具有相同值的列失败,因为搜索词没有填充到n ,导致搜索词与列中存储...
一共有三个分卷。全部下载才能解压。 这本书不错,值得一看。
主要介绍了使用SpringBoot-JPA进行自定义的保存及批量保存功能,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
第一章:Spring Data JPA入门 包括:是什么、能干什么、有什么、HelloWorld等 第二章:JpaRepository基本功能 包括:代码示例JpaRepository提供的CRUD功能,还有翻页、排序等功能 第三章:JpaRepository的查询 ...
1:JPA: : 2:对象数据库: ://www.objectdb.com/ 3:JPA 性能基准: ://www.jpab.org/All/All/All.html 支持功能 支持所有蓝图功能, 除外 支撑 支持 支持 Java 5、6 或 7 支持 JPA 你需要哪一个取决于你想用...
春天数据jpa额外使用jpa的spring数据更舒适我爱spring-data-jpa,她放开我的双手,粗鲁的方法很无聊! 然而,尽管她为我们提供了规范解决方案,但她在动态本机查询上并不完美,而且她的返回类型必须是一个实体,但是...
SpringBoot-JPA-MongoDB:SpringBoot-JPA-MongoDB
Spring Data Jpa常用功能演示配套说明请查看:项目简介本项目采用当前最新版本的2.1.4.RELEASE做基础架构支撑,请参考本项目建议有一定的基础及经验。教程主要针对中文用户,如果您英文良好,建议直接阅读官网帮助...
精通Hibernate:Java对象持久化技术详解.pdf 精通Hibernate:Java对象持久化技术详解.pdf
精通Hibernate:Java对象持久化详解解压后是pdf文档。
笔记_JPA_JSF 使用JSF和JPA实施来重建Notes项目
classpath 'at.schmutterer.oss.gradle:gradle-openjpa:0.2.0' } } apply plugin: 'openjpa' openjpa { //additional configuration if required } 配置属性 这些配置属性可以传递给 openjpa-closure: ...
本文是介绍Spring-data-jpa的PPT的学习笔记,整理了Spring Data JPA相关知识配置和实践源码. 本文介绍知识点有: JPA与Spring的相关配置 JPA 方法名常用查询 JPA 使用@Query注解实现JPQL和本地自定义查询 JPA API 条件...
项目简介如果你喜欢Jpa ...官方地址:主要功能v1.0.0:增加(C): 提供单个实体保存、批量保存功能。查询(R): 提供单/多查询,并支持findBy自定义字段名查询。更新(U): 提供单个实体更新、批量更新功能。删除(D): 提供
PrimeFaces-JPA-Hibernate :green_circle: 续:WebService Cep,JSF和PrimeFaces集成,按需配送。 Imagem do Sistema I 西马克玛二世 Imagem do Sistema III Guia da Uso 1-O Eclipse项目的实现过程2Kongusados就...
spring.jpa.hibernate.ddl-auto: update 根据实体生成表结构,实体属性变动时,更新表结构,开发时建议使用这种策略 spring.jpa.hibernate.ddl-auto: validate 启动时校验实体和表结构是否一致, 数据结构稳定时采用...