Hibernate JPA@Version合并行为不适用于EclipseLink

Hibernate JPA@Version合并行为不适用于EclipseLink,hibernate,jakarta-ee,jpa,eclipselink,Hibernate,Jakarta Ee,Jpa,Eclipselink,当我将一个简单的项目部署到一个默认使用hibernate的WildFly 9.0.1服务器上时,一切都如演示的那样工作。但是,部署到Glassfish 4.1(或Payara)服务器(默认情况下使用EclipseLink)会导致集成测试失败 具体来说,在Glassfish(而不是WildFly)上部署时需要插入时,EntityManager.merge方法会导致版本从0更改为1 我的集成测试为每个测试件调用JAX-RS服务。ToDomainManager中的每个方法都是一个JPA事务,因此如果我

当我将一个简单的项目部署到一个默认使用hibernate的WildFly 9.0.1服务器上时,一切都如演示的那样工作。但是,部署到Glassfish 4.1(或Payara)服务器(默认情况下使用EclipseLink)会导致集成测试失败

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

我的集成测试为每个测试件调用JAX-RS服务。ToDomainManager中的每个方法都是一个JPA事务,因此如果我没有弄错的话,就不需要刷新数据库来进行更新

JAX-RS异常捕获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);
    }
}
@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);
基础持久性:

@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);
    }
}
@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上部署以进行第一次更新尝试时抛出OptimisticLockException,测试应通过时失败。而当部署到WildFly时,第一次更新发生,测试通过,然后第二次测试通过,因为我正在检查409状态代码

测试:

@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);
    }
}
@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上使用这种方法都会非常有帮助。

实际上没有足够的代码来解决问题。这将取决于事务边界和/或刷新数据库的时间。我无法从代码中计算出事务边界是什么。当通过显式刷新或事务提交将对象写入数据库时,版本字段应该增加

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

你可以通过这样做来确保你有一个一致的起始值

@Version 
private int version = 1;

这本应该是显而易见的,但是,我正在关注一个研讨会,研讨会没有包括以下内容


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

我编辑了文章以提供更多代码。类是无状态的,因此
返回this.em.merge(todo)方法应该已经是JPA事务。排除故障时,我将
@Version private long Version=0但没有什么区别。问题仍然存在。请尝试使用1作为初始值,或者在构建JSON对象时传入初始值1。我刚才在实体上和构建JSON对象时都尝试过将其作为初始值。Create方法在Glassfish和Payara上失败,但响应代码是500而不是409。仍然没有回答这个问题。我已经挖得更深了,这似乎是一个日食问题,但无论我如何尝试使用@Version,我都无法更新记录而不获得异常。任何帮助都将不胜感激。您是否碰巧在两台服务器上使用了相同版本的JPA提供商?否则,您观察到的行为可能是特定于提供者的行为(因此,不是特定于容器的)。GlassFish和WildFly ship EclipseLink和Hibernate分别作为默认的JPA实现。没错!野蝇使用冬眠,玻璃鱼使用日食。因此,我将尝试在Glassfish上使用Hibernate,看看我是否能做到这一点。非常感谢。添加hibernate 5.4.0作为Payara和Glassfish的默认提供程序并没有解决该问题。我将继续挖掘,但它似乎不是特定于容器或JPA实现的。我在研讨会上注意到,亚当·边交替使用WildFly和Payara。感谢您提供的任何其他可能的见解。