3

I'm trying to implement locking on a JPA/Hibernate application, to detect when a client is trying to push changes on an out-of-date version of an entity.

I've choose to expose dto's (representing a subset of the domain) over REST services. At the moment, I can easily detect concurrent transactions updates, but I can not make it work to detect "old" entities updates. I'll explain:

  • #1 2 concurrent transactions manipulating the same entity, each attached to their entity manager, are correctly protected against dirty reads (the last to commit gets an OptimisticLockingException)
  • #2 2 concurrent users, manipulating the same entity with their frontend and committing two different, non-concurrent transactions, do NOT get any lock exception. Thats because, using DTO's, the update part of the code is something like that:

    • start a transaction
    • get the persisted entity to update from manager
    • copy what is relevant from the dto to that entity
    • commit

... but nothing (JPA, Hibernate or whatever) never checks for the consistency between the dto's version and the entity's one... (ps: trying to set the @Version field with the version given in the dto, as specified by JPA, lead to weird results)

Based on what I've seen and lots of debugging and docs read, I've ended up writing a code like that:

abstract class AbstractBusinessService {
    private static final Logger LOG = LoggerFactory.getLogger(AbstractBusinessService.class);

    protected final void checkEntityVersion(Long givenVersion, VersionedEntity<?> entity) {
        if (givenVersion == null) {
            LOG.warn("no version have been provided. unable to check for concurrent modification");
        } else if (entity == null) {
            LOG.warn("the given entity is null");
        } else if (entity.getVersion() != givenVersion.longValue()) {
            throw new LockingException("The persistent entity " + entity.getClass().getName() + "#" + entity.getId()
                    + " has a newer version than expected (" + givenVersion + " vs. " + entity.getVersion() + ")");
        }
    }
}

... invoked before every "update" operation... It obviously works like a charm, but adds some complexity on the business layer, for a topic that is purely persistence-related and should not be visible on the business layer...

Am I right in the sense that this is a DIY thing and nothing is available out-of-the-box to implement #2? Am I missing something?? How do you address that problem in your development?

Thank you very much for reading,

SP

EDIT : as per indicated in comments, the best way seems to

  1. get a persistent entity from context (A)
  2. create a new, clean (detached/transient) entity (B)
  3. copy every properties of (A) to (B)
  4. set every properties in (B) with what comes from the DTO (conversion may be needed)
  5. merge (B) --> get LockException if @Version mismatch
tharindu_DG
  • 8,900
  • 6
  • 52
  • 64
spi
  • 626
  • 4
  • 19
  • 1
    It would seem to me that "setting the @Version field with the version given in the dto" is the right thing to do ... if you are marshalling and unmarshalling data from a RESTful service, this is what would happen anyway, regardless of whether you use DTOs or the entities themselves. So I'm curious what sort of "weird results" you see? – dcsohl Dec 18 '15 at 18:38
  • You should not be doing this, JPA (Hibernate) should take care of this. You need an extra step before you persist a detached entity. You need to load the latest one from em or db and merge the entity which is coming from the UI and then save. That automatically detect the version. You can read up on saving the detatched entity. – RP- Dec 18 '15 at 18:39
  • Another good reason to avoid the DTO anti-pattern. – duffymo Dec 18 '15 at 18:40
  • @dcsohl JPA 2.0 Final Spec, Section 3.4.2: 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. With the exception noted in section 4.10, only the persistence provider is permitted to set or update the value of the version attribute in the object. – spi Dec 18 '15 at 18:50
  • 1
    Once it is actually a managed entity, that is true. The typical use case here would be 1) Create a new unmanaged entity. 2) Copy all attributes from your DTO to the unmanaged entity, including (most importantly) the @Id field and the @Version field. 3) Call `em.merge(obj)`. 4) Profit! Like I said, even if you weren't using DTOs, this would be happening under the covers of Jackson/JAXB/MOxy/whatever you're using. – dcsohl Dec 18 '15 at 19:00
  • That seem to work (lock exception raise, and consistently), but since my dto's contains only a subset of the properties of the entities, I end up with lots of null... I think I should do 1) create unmanaged entity 2) get the persisted entity 3) copy the properties of the managed entity to the unmanaged 4) copy the dto values to the unamanaged 5) merge? – spi Dec 18 '15 at 19:17
  • Yep that seems much better this way :-) thank you all but btw, how could you not use dto's if you want to control the format & content of your rest resources? I mean, I don't want to show every single field of my persistence to the rest? neither I want to impact the rest api whenever I add/remove/change a column in the db – spi Dec 18 '15 at 19:26

0 Answers0