原文: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模式呢?
分享到:
相关推荐
此外,IDEA还提供了自动生成JPA实体类的功能,这对于创建与数据库表对应的实体模型非常方便。通过"File -> Project Structure -> Modules -> JPA"的路径设置JPA支持,选择Hibernate作为默认提供者。然后,在...
**JPA(Java Persistence API)**是Java平台上的一个标准,用于管理关系数据库中的数据,它简化了数据库操作,提供了一种面向对象的方式来处理数据库事务。JPA通过ORM(Object-Relational Mapping)映射机制将Java...
使用SpringBoot-JPA进行自定义保存及批量保存功能 使用SpringBoot-JPA进行自定义保存及批量保存功能是指在Spring Boot应用程序中使用JPA(Java Persistence API)来实现自定义的保存和批量保存功能。JPA是一个Java ...
这些注解可以用来指定扫描的表映射实体 Entity 的目录、表 repository 目录、开启 JPA 审核能力等。 在 application.properties 文件中,可以配置一些数据库连接信息,例如数据库 URL、用户名、密码等。同时,也...
在处理关联关系时,Spring Data JPA提供了懒加载和急加载(Eager vs Lazy Fetching)机制,通过配置实体类的属性映射,可以在需要时按需加载关联的数据,避免了N+1查询问题。同时,它还支持级联操作(Cascade ...
背景如果文本值存储在数据类型为CHAR(n)的 Oracle 列中,则数据库在将该值保存到列之前最多n字符。 后来,尝试使用 Hibernate 或 JPA 等框架搜索具有相同值的列失败,因为搜索词没有填充到n ,导致搜索词与列中存储...
一共有三个分卷。全部下载才能解压。 这本书不错,值得一看。
1:JPA: : 2:对象数据库: ://www.objectdb.com/ 3:JPA 性能基准: ://www.jpab.org/All/All/All.html 支持功能 支持所有蓝图功能, 除外 支撑 支持 支持 Java 5、6 或 7 支持 JPA 你需要哪一个取决于你想用...
例如,一个JSF页面可能有一个表单用于创建新的笔记,用户输入内容后,提交表单时触发一个后台方法,该方法使用JPA的EntityManager保存新的Note实体到数据库。 项目文件"notes_JPA_JSF-master"很可能是该项目的源...
### JPA实体对象状态 #### 一、实体对象的状态分类 在Java Persistence API (JPA) 中,实体对象的状态管理是实现数据持久化的基础之一。根据实体对象与实体管理器(EntityManager)之间的交互关系,实体对象可以...
JPA没有内置的批量保存方法,但我们可以利用`saveAll()`方法来实现批量插入或更新。例如,我们有一个用户列表,想要一次性保存到数据库: ```java List<User> userList = ... // 初始化用户列表 userRepository....
春天数据jpa额外使用jpa的spring数据更舒适我爱spring-data-jpa,她放开我的双手,粗鲁的方法很无聊! 然而,尽管她为我们提供了规范解决方案,但她在动态本机查询上并不完美,而且她的返回类型必须是一个实体,但是...
Spring Data Jpa常用功能演示配套说明请查看:项目简介本项目采用当前最新版本的2.1.4.RELEASE做基础架构支撑,请参考本项目建议有一定的基础及经验。教程主要针对中文用户,如果您英文良好,建议直接阅读官网帮助...
开发者通过它来保存、查询和删除实体。 3. **实体管理工厂(EntityManagerFactory)**: 创建并管理EntityManager实例,它是线程安全的,一般在应用启动时创建,全局使用。 4. **持久化上下文(Persistence Context...
- **Session vs EntityManager**:`org.hibernate.Session` 提供了对数据库的操作,如加载、保存实体等;而在JPA中,这些操作则由 `javax.persistence.EntityManager` 完成。 **2. 运行时配置** - **Hibernate**:...
classpath 'at.schmutterer.oss.gradle:gradle-openjpa:0.2.0' } } apply plugin: 'openjpa' openjpa { //additional configuration if required } 配置属性 这些配置属性可以传递给 openjpa-closure: ...