【问题标题】:Changing JPA entity after persist() but before flush() does not work在persist()之后但在flush()之前更改JPA实体不起作用
【发布时间】:2012-10-08 20:15:20
【问题描述】:

我在 Hibernate 版本中使用 JPA 2。 4.1.7.Final 作为 JPA 实现。我也在使用 Spring framework v. 3.1.2.RELEASE 来明确。这是我的问题。

我已经编写了一个方法来添加/更新我的用户实体。

@Override
@Transactional
public void saveUser(UserForm userForm) {
    User user;

    if (userForm.getId() == null) { // new user
        user = new User();
        user.setCreationDate(new Date());
        entityManager.persist(user); // !!!
    } else {
        user = entityManager.find(User.class, userForm.getId());
    }

    user.setFirstName(userForm.getFirstName());
    user.setLastName(userForm.getLastName());
    user.setMiddleName(userForm.getMiddleName());

    user.setEmail(userForm.getEmail());

    user.setRole(entityManager.find(Role.class, 1));//TODO

    user.setLogin(userForm.getLogin());
    user.setPassword(userForm.getPassword1());

    entityManager.flush();
}

我正在测试添加用户 (userForm.getId() == null)。而且上面的代码不起作用,报错:

javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Column 'first_name' cannot be null
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1377)
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1300)
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1306)
at org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:989)
...

但是。如果我在 flush() 之前将调用 persist() 移到最后,一切正常:

@Override
@Transactional
public void saveUser(UserForm userForm) {
    User user;

    if (userForm.getId() == null) { // new user
        user = new User();
        user.setCreationDate(new Date());
    } else {
        user = entityManager.find(User.class, userForm.getId());
    }

    user.setFirstName(userForm.getFirstName());
    user.setLastName(userForm.getLastName());
    user.setMiddleName(userForm.getMiddleName());

    user.setEmail(userForm.getEmail());

    user.setRole(entityManager.find(Role.class, 1));//TODO

    user.setLogin(userForm.getLogin());
    user.setPassword(userForm.getPassword1());

    entityManager.persist(user);// !!!
    entityManager.flush();
}

我认为发生的情况是在第一个(问题)情况下,它尝试存储在持久调用时存在的用户对象数据,尽管在 flush() 期间执行的实际保存(upd. 错误。它试图在persist() 调用时准确地发出sql insert)。让我产生这种想法的原因是,当我尝试调试时,我发现它仍然尝试在内部某处插入正确的 creation_date,但其他值是空值。

但我发誓,在我几年前从事的其他项目中,这工作得很好,尽管后来我使用了 Oracle 而不是 MySQL(我不认为这是原因)和旧版本的框架。

这可能是什么问题?也许 Hibernate 有一些配置选项会影响这个?

或者这是正确的行为和我对 JPA API 的误解?

UPD。我正在使用

@Id
@GeneratedValue
@Column(columnDefinition="INT")
private int id;

用于用户条目中的 id 字段。我相信这意味着生成策略= AUTO,适合mysql auto_increment key。

【问题讨论】:

  • 听起来这可能是使用 IDENTITY 生成 PK 的问题。这就是这里使用的东西吗?
  • 我同意如果 id 生成是 Identity 则需要立即插入。因此,如果可能,请切换到 Sequence 或 TableGenerator。如果不能,那么修改后的代码是正确的,但我会为 persist() 调用添加空检查以避免级联检查。
  • 取决于您是否将hibernate.id.new_generator_mappings 设置为true(为了向后兼容,默认为false)。无论如何,如果@Transactional 真的在做它的事情,那将是 Hibernate 中的一个错误。传统上,基于 IDENTITY 的 id 生成需要立即执行 INSERT 才能知道 id。但是,在这种情况下,Hibernate 应该尝试将 IDENTITY 的插入延迟到刷新(事务性 JPA 使用)。如果您想继续使用 IDENTITY 生成 id,您必须弄清楚原因;或启用hibernate.id.new_generator_mappings
  • 实际上,我收回了这一点,我们只在使用扩展持久性上下文的情况下延迟插入持久化。因此,根据您的解释,这可能不是您在这里所做的。所以插入应该发生在持久 with IDENTITY 上。
  • 这引起了关于 JPA 规范中措辞的争论。它说持久化使实体“持久化”(如果您更喜欢该术语,则可以管理)。在其他地方,它部分地描述了一个实体是“持久的”,因为它引用了它的持久性标识符。在 IDENTITY 生成的情况下,我们作为持久性提供者(JPA 实现者)知道其 ID 的唯一方法是执行插入。恕我直言,这说明了从不将 IDENTITY 与更喜欢 Hibernate 和 JPA 的“事务性写入”的 O/RM 库一起使用的几个原因之一。

标签: java hibernate jpa jpa-2.0


【解决方案1】:

您将用户名设置为 null (user.setFirstName(userForm.getFirstName());),因为 userForm.getFirstName() 返回 null,并且您的错误显示为:Column 'first_name' cannot be null

您应该检查为什么 userForm.getFirstName() 返回 null 或允许用户的名字为 null,因为您当前的实体配置不允许这样做。

也许您可以向我们展示用户实体?

【讨论】:

  • 这完全不真实。 persist 使“持久性上下文”(又名 EntityManger)知道实体。 flush是将内存状态同步到数据库的过程。
  • 是的,我的错。在这种情况下,这不是问题。我删除了不正确的部分。
【解决方案2】:

这是正确的行为。在您第一次调用 persist() 时,用户的 firstName 为空。尽管您在这一点之后设置了名字,但在刷新之前,JPA/Hibernate 准备的命令将类似于,

在持久化时,用户对象的状态可以说,user@version1 从 user@version1 对象插入用户值(id、creationdate、null、null..)

现在存在一个新的用户版本 user@version2

冲洗时,

检查任何挂起的插入/更新.. 将所有对象带到 version1,这是最后一次持久化的时间。

请注意,flush 可能会或可能不会在 DB 中进行物理插入操作,但对象会被带到正确的状态,这意味着非持久性更改将丢失。

不要担心它之前是否可以正常工作,可能是在休眠 3 上,当您迁移到休眠 4 时开始无法正常工作。我有几个问题,休眠 3 没问题,但在没有额外修复的情况下无法在休眠 4 上工作。

【讨论】:

    猜你喜欢
    • 2014-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-02
    • 2012-04-09
    相关资源
    最近更新 更多