有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java JPA EclipseLink和@TableGenerator。为什么事务回滚后分配给id的值不为空?

我有以下实体:

@Entity
@Table(name = "sales", schema = "cust_tables")
@Multitenant(MultitenantType.TABLE_PER_TENANT)
@TenantTableDiscriminator(contextProperty = "customer_schema", type = TenantTableDiscriminatorType.SCHEMA)
@XmlRootElement
@XmlAccessorType(XmlAccessType.NONE)
public class Sale implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "Sale")
    @TableGenerator(name = "Sale", schema = "thehub", allocationSize = 5)
    @Column(name = "id", unique = true, nullable = false, updatable = false)
    @XmlElement
    private Long id;

在持久化操作期间,我可能会故意允许事务在乐观锁定失败期间回滚

然而,我对JPA/EclipseLink在这种情况下的行为感到惊讶。正如预期的那样,JPA将序列中的一个值分配给id。但是,在回滚期间,该值不会被“抹掉”。然后下次我尝试持久化时,id的值已经存在了。JPA跳过从序列中提取一个新的数字,并尝试保留该实体

我因SQL完整性约束异常而失败:

Caused by: javax.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130226-e0971b1): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '51' for key 'PRIMARY'
Error Code: 1062

我做错了什么


共 (2) 个答案

  1. # 1 楼答案

    好的,我设法解决了这个问题,我将在这里发布我的结果,以帮助任何可能遇到相同问题的人

    使用调试器,我可以看到EclipseLink确实为实体赋值。id字段,即使在事务回滚期间也是如此。如果你像我一样,你的id字段会被注释,你的id是不可变的,这意味着只有一个getter而不是setter。EclipseLink能够使用反射为字段指定一个值,但是暴露的API可以防止id字段的变异。(这是一件‘好事’)

    EclipseLink似乎是通过设计或bug为ID字段分配一个值,并从ID序列中删除该值。因此,事务回滚将导致序列号出现漏洞(这并不是什么大问题)

    请记住,EclipseLink读取序列号表,在内存中缓存一些序列号,然后在其缓存为空时重新读取,同时提交下一个序列的起始值

    不知何故,通过事务回滚和重新启动服务器的组合,序列号表落后于实际使用的序列号。因此,在事务回滚期间,错误没有分配ID,错误正在丢失序列号。我得到的结果是序列号表的值为50,但数据库中编号最高的实体是51。解决方案是将sequence表更新为allocationSize(5)的两倍,将其设置为60,然后跳转我的服务器

    不幸的是,我无法复制这个错误(我不知道EclipseLink是如何进入畸形状态的

  2. # 2 楼答案

    当JPA事务失败时,所有对象都会分离。您不能重新提交交易

    您需要创建一个新的持久性上下文,或者需要重新创建对象,或者如果您想使用现有的对象,则需要小心地将它们合并到新的持久性上下文中,并清空它们的ID。此外,对于任何更新的对象,都需要还原其版本字段

    JPA没有提供一种简单的方式来重新提交交易。EclipseLink的本机API提供CommittedResumeonFailure(),但这并没有暴露于JPA。也许记录一个增强请求,以获得某种选项,允许在JPA中重新提交失败的事务