到目前为止,我的偏好是始终使用EntityManagermerge()
来处理插入和更新。但是我还注意到,合并会在更新/插入之前执行其他选择查询,以确保数据库中不存在记录。
现在,我正在一个需要对数据库进行大量(批量)插入的项目。从性能的角度来看,在我绝对知道我一直在创建要持久化的对象的新实例的情况下,使用持久化而不是合并是否有意义?
Answers:
它不是用一个好主意merge
时,persist
就足够了-merge
做了很多更多的工作。之前已经在StackOverflow上讨论了该主题,并且本文详细解释了它们之间的区别,并提供了一些不错的流程图以使事情变得清晰。
persist()
如您所说,我绝对会坚持下去的:
(...)我绝对知道我总是在创建要持久保存的对象的新实例(...)
这就是此方法的全部内容-在实体已经存在的情况下将为您提供保护(并将回滚您的交易)。
RuntimeException
绝对不是一个好习惯(但是我会假设你的意思EntityExistsException
),但我不会将其称为通用的良好编码习惯。这取决于您的要求。如果要求说必须保留该对象并且该对象在此操作发生之前必须不存在-我绝对不会在捕获异常后尝试合并。此外,如果您使用JTA实体管理器,那么此时您的事务将被标记为回滚。
persist
对一个已经存在的实体进行调用时,您会得到EntityExistsException
并且由于JPA实现器,该事务将被回滚(不是因为它是运行时异常)。换句话说-我认为(不确定)即使您尝试/捕获此异常,仍将tx标记为回滚。该merge
不会抛出EntityExistsException
。
如果使用分配的生成器,则使用merge
代替persist
会导致多余的SQL语句,从而影响性能。
同样,为托管实体调用合并也是一个错误,因为托管实体由Hibernate自动管理,并且在刷新Persistence Context时通过脏检查机制将它们的状态与数据库记录同步。
要了解所有这些工作原理,您首先应该知道Hibernate将开发人员的思维方式从SQL语句转移到实体状态转换。
一旦实体由Hibernate主动管理,所有更改将自动传播到数据库。
Hibernate监视当前连接的实体。但是,要使实体成为受管理实体,它必须处于正确的实体状态。
首先,我们必须定义所有实体状态:
新(瞬态)
从未与Hibernate Session
(aka Persistence Context
)相关联且未映射到任何数据库表行的新创建的对象被视为处于New(瞬态)状态。
为了持久化,我们需要显式调用该EntityManager#persist
方法或使用传递持久性机制。
持久(托管)
持久性实体已与数据库表行关联,并由当前运行的持久性上下文进行管理。对此类实体所做的任何更改都将被检测到,并传播到数据库(在会话刷新期间)。使用Hibernate,我们不再需要执行INSERT / UPDATE / DELETE语句。Hibernate采用事务性的后写式工作方式,并且在当前Session
刷新时间的最后一个负责时刻同步更改。
独立式
一旦当前正在运行的持久性上下文关闭,所有先前管理的实体都将分离。不再跟踪连续的更改,并且不会发生自动数据库同步。
要将分离的实体与活动的Hibernate会话相关联,可以选择以下选项之一:
重新连接
Hibernate(但不支持JPA 2.1)支持通过Session#update方法进行重新连接。休眠会话只能将一个实体对象与给定的数据库行关联。这是因为持久性上下文充当内存中的缓存(第一级缓存),并且只有一个值(实体)与给定的键(实体类型和数据库标识符)相关联。仅当没有其他JVM对象(与同一数据库行匹配)已与当前Hibernate会话关联时,才可以重新附加实体。
合并中
合并将把分离的实体状态(源)复制到托管实体实例(目标)。如果合并实体在当前会话中没有等效项,则将从数据库中获取一个实体。即使在合并操作之后,分离的对象实例仍将继续保持分离状态。
已移除
尽管JPA要求只允许删除托管实体,但是Hibernate也可以删除分离的实体(但只能通过Session#delete方法调用)。仅计划将删除的实体删除,并且实际的数据库DELETE语句将在会话刷新期间执行。
为了更好地了解JPA状态转换,可以将下图可视化:
或者,如果您使用特定于Hibernate的API: