JPA合并与持久化[重复]

Posted

技术标签:

【中文标题】JPA合并与持久化[重复]【英文标题】:JPA merge vs. persist [duplicate] 【发布时间】:2012-01-18 04:25:50 【问题描述】:

到目前为止,我的偏好是始终使用 EntityManager 的 merge() 来处理插入和更新。但我也注意到,merge 在更新/插入之前会执行额外的选择查询,以确保数据库中不存在记录。

现在我正在处理一个需要大量(批量)插入数据库的项目。从性能的角度来看,在我绝对知道我总是在创建要持久化的对象的新实例的情况下使用持久化而不是合并是否有意义?

【问题讨论】:

【参考方案1】:

如果您使用分配的生成器,using merge instead of persist can cause a redundant SQL statement,因此会影响性能。

另外,calling merge for managed entities 也是一个错误,因为托管实体由 Hibernate 自动管理,并且它们的状态通过dirty checking mechanism 在flushing the Persistence Context 上与数据库记录同步。

要了解这一切是如何工作的,您首先应该知道 Hibernate 将开发人员的思维方式从 SQL 语句转移到 entity state transitions。

一旦实体由 Hibernate 主动管理,所有更改都将自动传播到数据库。

Hibernate 监控当前连接的实体。但要使实体成为托管实体,它必须处于正确的实体状态。

首先,我们必须定义所有实体状态:

新建(瞬态)

从未与 Hibernate Session (a.k.a Persistence Context) 关联且未映射到任何数据库表行的新创建的对象被视为处于新建(瞬态)状态。

要实现持久化,我们需要显式调用EntityManager#persist 方法或使用传递持久化机制。

持久(托管)

持久性实体已与数据库表行相关联,并由当前运行的持久性上下文管理。对此类实体所做的任何更改都将被检测到并传播到数据库(在会话刷新期间)。 使用 Hibernate,我们不再需要执行 INSERT/UPDATE/DELETE 语句。 Hibernate 采用transactional write-behind 工作方式,并且在当前Session 刷新时间期间的最后负责时刻同步更改。

分离

一旦当前运行的持久性上下文关闭,所有以前管理的实体都将被分离。将不再跟踪连续的更改,也不会发生自动数据库同步。

要将分离的实体关联到活动的 Hibernate Session,您可以选择以下选项之一:

重新连接

Hibernate(但不是 JPA 2.1)支持通过 Session#update 方法重新连接。 Hibernate Session 只能为给定的数据库行关联一个实体对象。这是因为持久性上下文充当内存缓存(一级缓存),并且只有一个值(实体)与给定的键(实体类型和数据库标识符)相关联。 只有在没有其他 JVM 对象(匹配同一数据库行)与当前 Hibernate Session 关联时,才能重新附加实体。

合并

合并会将分离的实体状态(源)复制到托管实体实例(目标)。如果合并实体在当前会话中没有等价物,则将从数据库中获取一个。 即使在合并操作之后,分离的对象实例也将继续保持分离状态。

已删除

虽然 JPA 要求只允许删除托管实体,但 Hibernate 也可以删除分离的实体(但只能通过 Session#delete 方法调用)。 已删除的实体仅计划删除,实际的数据库 DELETE 语句将在 Session 刷新期间执行。

为了更好地理解 JPA 状态转换,您可以可视化下图:

或者,如果您使用 Hibernate 特定的 API:

【讨论】:

【参考方案2】:

persist 足够时使用 merge 不是一个好主意 - merge 做了很多工作。之前的话题是discussed on ***,this article 详细解释了不同之处,用一些漂亮的流程图让事情变得清晰。

【讨论】:

这个“答案”相当于将问题标记为重复,并将文章链接添加到此问题或重复问题。【参考方案3】:

我肯定会选择坚持persist(),如果,如你所说:

(...) 我绝对知道我总是在创建要持久化的对象的新实例 (...)

这就是这个方法的全部意义——它会在实体已经存在的情况下保护你(并且会回滚你的事务)。

【讨论】:

执行以下操作是否是一种好的编码习惯:try em.persist(xxx); catch (RuntimeException re) try xxx = em.merge(xxx); 捕捉(异常 e) 抛出 e; 尽管捕获RuntimeException 绝对不是一个好习惯(但我假设您的意思是EntityExistsException)我不会称其为普遍的良好的编码习惯 .这取决于您的要求。如果要求说对象必须持久化并且在此操作发生之前它必须不存在 - 我绝对不会在捕获异常后尝试进行合并。此外,如果您使用 JTA 实体管理器,此时您的事务将被标记为回滚。 您能否解释一下“此外,如果您使用 JTA 实体管理器,此时您的事务将被标记为回滚”是什么意思?我正在为 EJB3 使用 CMT,并且我能够验证只要我在方法边界内处理异常,就不会回滚任何事务。 @phewataal docs.oracle.com/javaee/6/api/javax/persistence/… 我猜当你在一个已经存在的实体上调用 persist 时,你会得到 EntityExistsException 并且由于 JPA 实现者这个事务将被回滚(不是因为它是一个运行时异常.)。换句话说 - 我认为(不确定)即使您尝试/捕获此异常,tx 仍将被标记为回滚。 merge 永远不会抛出 EntityExistsException

以上是关于JPA合并与持久化[重复]的主要内容,如果未能解决你的问题,请参考以下文章

处理 JPA 合并的最佳方法?

为啥 JPA 重复持久方法不抛出异常?

Spring @Transactional 合并和持久化问题

JPA与EJB3的关系

将 Spring Boot 与 JPA 一起使用时如何持久化

JPA与Hibernate的关系