【问题标题】:JPA @Version merge Behavior not working for EclipseLinkJPA @Version 合并行为不适用于 EclipseLink
【发布时间】:2015-09-02 21:09:03
【问题描述】:

当我将一个简单的项目部署到默认使用休眠的 WildFly 9.0.1 服务器时,一切正常。但是,部署到默认使用 EclipseLink 的 Glassfish 4.1(或 Payara)服务器会导致我的集成测试失败。

具体来说,EntityManager.merge 方法会导致版本从 0 更改为 1,当部署在 Glassfish 上而不是 WildFly 时需要插入。

我的集成测试正在为每个测试件调用 JAX-RS 服务。 ToDoManager 中的每个方法都是一个 JPA 事务,所以如果我没记错的话,我不需要刷新数据库来更新。

JAX-RS ExceptionMapper 捕捉 EJBExeption:

@Provider
public class EJBExceptionMapper implements ExceptionMapper<EJBException> {

@Override
public Response toResponse(EJBException ex) {
    Throwable cause = ex.getCause();
    Response unknownError = Response.serverError().
            header("cause", ex.toString()).build();
    if (cause == null) {
        return unknownError;
    }

    if (cause instanceof OptimisticLockException) {
        return Response.status(Response.Status.CONFLICT).
                header("cause", "conflict occured: " + cause).
                build();
    }

    return unknownError;
}

}

创建和更新请求的保存端点:

@Stateless
@Path("todos")
public class TodosResource {

    @Inject
    ToDoManager manager;

    ... other REST Calls ...

    @POST
    public Response save(@Valid ToDo todo, @Context UriInfo info) {
        ToDo saved = this.manager.save(todo);
        long id = saved.getId();
        URI uri = info.getAbsolutePathBuilder().path("/"+id).build();
        return Response.created(uri).build();
    }
}

底层持久性:

@Stateless
@Interceptors(BoundaryLogger.class)
public class ToDoManager {

    @PersistenceContext
    EntityManager em;

    ... other persistence methods ...

    public ToDo save(ToDo todo) {
        return this.em.merge(todo);
    }
}

当部署到 Glassfish 以进行 第一次 更新尝试时,以下测试将引发 OptimisticLockException,并且测试在应该通过时失败。而当部署到 WildFly 时,会发生第一次更新,测试通过,然后第二次测试通过,因为我正在检查 409 状态代码。

测试:

@Test
public void crud() {
    // build a JSON ToDo
    JsonObjectBuilder todoBuilder = Json.createObjectBuilder();
    JsonObject todoToCreate = todoBuilder.
            add("caption", "implement").
            add("priority", 10).
            build();

    // Run Create Test
    Response postResponse = this.provider.target().request().
            post(Entity.json(todoToCreate));
    assertThat(postResponse.getStatus(), is(201));
    String location = postResponse.getHeaderString("Location");
    System.out.println("location = " + location);

    // Run Find Test
    JsonObject dedicatedTodo = this.provider.client().
            target(location).
            request(MediaType.APPLICATION_JSON).
            get(JsonObject.class);
    assertTrue(dedicatedTodo.getString("caption").contains("implement"));

    // Run Update Test
    JsonObjectBuilder updateBuilder = Json.createObjectBuilder();
    JsonObject updated = updateBuilder.
            add("id", dedicatedTodo.getInt("id")).
            add("caption", "implemented").
            add("priority", 10).
            add("version", dedicatedTodo.getInt("version")).
            build();

    Response updateResponse = this.provider.client().
            target(location).
            request(MediaType.APPLICATION_JSON).
            put(Entity.json(updated));
    assertThat(updateResponse.getStatus(), is(200));

    // Run Update Test Again for Lock Testing
    updateBuilder = Json.createObjectBuilder();
    updated = updateBuilder.
            add("id", dedicatedTodo.getInt("id")).
            add("caption", "implemented").
            add("priority", 8).
            add("version", dedicatedTodo.getInt("version")).
            build();

    updateResponse = this.provider.client().
            target(location).
            request(MediaType.APPLICATION_JSON).
            put(Entity.json(updated));
    assertThat(updateResponse.getStatus(), is(409));
    String conflictInformation = updateResponse.getHeaderString("cause");
    assertNotNull(conflictInformation);
    System.out.println("conflictInformation = " + conflictInformation);

当我在 Glassfish 上创建后查看 @Version private long version 值时,该值为 1 导致第一次更新失败。但是,在 WildFly 上,它仍然是 0,因此允许进行第一次更新。

由于我已经启动并运行了一个生产 Payara 服务器,并且我想在未来的项目中使用这种方法,所以任何帮助解释我如何让它在 Glassfish 上工作都会非常有帮助。

【问题讨论】:

  • 您是否碰巧在两台服务器上使用具有相同版本的 JPA 相同提供程序?否则,您观察到的行为可能是提供者特定的行为(因此,不是容器特定的)。 GlassFish 和 WildFly 分别将 EclipseLink 和 Hibernate 作为默认的 JPA 实现。
  • 没错! WildFly 使用休眠,Glassfish 使用 EclipseLink。所以我会尝试在 Glassfish 上使用 Hibernate,看看我是否可以做到这一点。谢谢!
  • 添加 hibernate 5.4.0 作为 Payara 和 Glassfish 的默认提供程序并没有解决问题。我会继续挖掘,但它似乎不是容器或 JPA 实现特定的。我在研讨会期间注意到,Adam Bien 交替使用 WildFly 和 Payara。感谢您提供任何其他可能的见解。

标签: hibernate jakarta-ee jpa eclipselink


【解决方案1】:

没有足够的代码来弄清楚发生了什么。这将取决于事务边界和/或刷新到数据库的时间。我无法从代码中计算出事务边界是什么。当对象通过显式刷新或事务提交写入数据库时​​,版本字段应递增。

在您的保存方法中,您可以尝试执行 em.flush 以查看两个提供程序之间的行为是否一致。

您可以通过执行以下操作来确保您具有一致的起始值;

@Version 
private int version = 1;

【讨论】:

  • 我已经编辑了帖子以提供更多代码。该类是无状态的,因此 return this.em.merge(todo); 方法应该已经是 JPA 事务。在进行故障排除时,我确实设置了@Version private long version = 0;,但没有任何区别。问题仍然存在。
  • 在构建 JSON 对象时尝试以 1 作为初始值或传入初始值 1。
  • 我尝试将其作为实体的初始值,以及刚刚构建 JSON 对象时。 Glassfish 和 Payara 上的 Create 方法失败,但响应代码为 500 而不是 409。
  • 这个问题仍然没有答案。我已经深入挖掘,这似乎是一个 EclipseLink 问题,但无论我如何尝试使用@Version,我都无法在没有得到 OptimisiticLockException 的情况下更新记录。任何帮助将不胜感激。
【解决方案2】:

这应该很明显,但是,我正在参加一个研讨会,而该研讨会没有包括以下内容。

测试期间的更新调用需要正在编辑的对象的 ID 和版本。添加此信息后,第一次更新成功,第二次更新失败并出现预期的 OptimisticLockException,因此通过了测试。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-07-12
    • 2013-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-15
    • 1970-01-01
    相关资源
    最近更新 更多