javajpapersistenceoptimistic-locking

OmniPersistence: usage of VersionedEntity is producing OptimisticLockException


I am experimenting with the library OmniPersistence.

I have a problem using the class org.omnifaces.persistence.model.VersionedEntity. In my code there is a simple entity class City.

@Entity
public class City extends VersionedEntity<Long> {
    @Id
    @GeneratedValue
    private Long id;
    private String postalCode;
    private String name;

    ... (getter + setter)
}

There is a REST-Service that exposes the Entity for some client-applications. But every time I want to update an object a javax.persistence.OptimisticLockException is thrown. The problem is that the version attribute is always null. A look in the code of VersionedEntity revealed that there is no setter method, but a comment

// No setter! JPA takes care of this.

I do understand the intention of the absence of the setter method but that is the reason for the exception.

Question

Is my architecture so poor (exposing the entity class in a web-service) or is it maybe reasonable to add a setter method although JPA should handle the value/manipulation of the @Versioned attribute?


Edit (as requested by the comment)

My update method is basically the one in OmniPersistence' BaseEntityService. My service class looks like the following.

@Stateless
public class CityService extends BaseEntityService<Long, City> {
    public Long count() {
        return super.createLongQuery("select count(c) from City c").getSingleResult();
    }
}

My controller is the REST endpoint.

@Path("city")
public class CityEndpoint {
    @Inject
    private CityService cityService;

    @GET @Produces(MediaType.APPLICATION_JSON)
    public Response getAll() {
        List<City> cities = cityService.list();
        return Response.ok(cities).build();
    }

    @GET @Produces(MediaType.APPLICATION_JSON)
    @Path("{id}")
    public Response get(@PathParam("id") Long id) {
        return Response.ok(cityService.getById(id)).build();
    }

    @POST @Consumes(MediaType.APPLICATION_JSON)
    public Response create(City city) {
        cityService.persist(city);
        return Response.created(URI.create(String.format("city/%s", Objects.toString(city.getId())))).build();
    }

    @POST @Consumes(MediaType.APPLICATION_JSON)
    @Path("update")
    public Response update(City city) {
        System.out.println(city);
        City updated = cityService.update(city);
        return Response.ok(updated).build();
    }

    @GET
    @Path("count")
    public Response count() {
        return Response.ok(cityService.count()).build();
    }
}

Solution

  • The JPA specification document provides an important hint that you must not manipulate the @Version attribute, see section 3.4.2, on page 90

    An entity may access the state of its version field or property or export a method for use by the application to access the version, but must not modify the version value.

    and

    The version attribute is updated by the persistence provider runtime when the object is written to the database.

    So the comment (”No setter! JPA takes care of this.“) you find in VersionedEntity is absolutely reasonable. In essence, you should not change (or null) the @Version attribute from higher application levels.

    In your case, it seems, you must compensate the ”lost“ (=nulled) version effect, eg by introducing a DTO for City. Otherwise, you will always run into an OptimisticLockException.