Monday, June 8, 2015

GWT 2.7.0 RequestFactory + Spring + JPA + Hibernate optimistic locking implementation


As all you know RequestFactory does not support optimistic locking.

see details https://code.google.com/p/google-web-toolkit/issues/detail?id=6046

I tried to implement it для RequestFactory + Spring + JPA + Hibernate

I took as a basis the idea proposed by Thomas (http://stackoverflow.com/questions/7696764/does-gwt-requestfactory-support-implementation-of-optimistic-concurrency-control/7697307#7697307).

As he wrote his idea - pure theory.

@ProxyFor(MyEntity.class)
interface MyEntityProxy extends EntityProxy {
   String getServerVersion();
   String getClientVersion();
   void setClientVersion(String clientVersion);
   …
}

@Entity
class MyEntity {
   private String clientVersion;
   @Version private String serverVersion;

   public String getServerVersion() { return serverVersion; }
   public String getClientVersion() { return null; }
   public void setClientVersion(String clientVersion) {
      this.clientVersion = clientVersion;
   }
   
   public void patchVersion() {
      serverVersion = clientVersion;
   }

   public void shouldPatchVersion() {
      return Objects.equal(serverVersion, clientVersion);
   }
}

On the server-side we need to use somthing like this to edit MyEntityProxy 
public <P extends BaseProxy> P edit(P proxy, RequestContext request)
{
  P mutableProxy = request.edit(proxy);
  
  if (mutableProxy instanceof MyEntityProxy)
  {
    MyEntityProxy myProxy = (MyEntityProxy)mutableProxy;
    myProxy.setClientVersion(myProxy.getServerVersion());
  }
}

On the server-side we need to handle case when clientVersion not equals to serverVersion.

I think that if we use EntityManager, we do not have to manually throw an exception when the versions are not equal.
It has to do EntityManager when it tries to save domain object in the database. Otherwise a situation may arise when 
an object has been checked, but has not been saved to the database, and someone else has save the same object.

I think a good place to make check and patch server version right before validating the domain object.
ServiceLayerDecorator
public <T> Set<ConstraintViolation<T>> validate(T domainObject)

Unfortunately, not enough simply call MyEntity.patchVersion()
JPA provider uses internal structures to keep current version of maanged object.
And value of serverVersion does not actually play any role.

We need to use specific JPA provider's API to change a version of managed object.

In my case it's Hibernate:

@Override
public <T> Set<ConstraintViolation<T>> validate(T domainObject)
{
  
  if (domainObject != null && domainObject instanceof HasVersion<?>)
  {
    MyEntity version = (MyEntity)domainObject;
    
    if (!version.shouldPatchVersion())
    {
      ApplicationContext context = ApplicationContextHolderLocator.getHolder().getApplicationContext();
      EntityManager entityManager = context.getBean(EntityManager.class);
      
      if (entityManager.getDelegate() instanceof SessionImplementor)
          {
            SessionImplementor sess = (SessionImplementor)entityManager.getDelegate();
            EntityEntry entry = sess.getPersistenceContext().getEntry(domainObject);
            if (entry != null)
            {
              version.patchVersion();
              LockMode lockMode = entry.getLockMode();
              entry.forceLocked(domainObject, version.getServerVersion());
              entry.setLockMode(lockMode);
            }
          }
    }
  }
  
  return super.validate(domainObject);
}

after this manipulation Hibernate will throws StaleObjectStateException as expected if client has edited old version of domain object.


There is another way to patch version in domain object:

in the method T find(Class<? extends T> clazz, I id); of entities locator detach from persistent context the found domain object
in the method validate(T domainObject) call method patchVersion() and try to attach domain object to the persistent context by calling
entityManger.merge.
No hibernate dependency, but one more database hit while merging object and exceptions may be thrown.


Please express an opinion on this implementation.

What are the disadvantages?

--
You received this message because you are subscribed to the Google Groups "Google Web Toolkit" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit+unsubscribe@googlegroups.com.
To post to this group, send email to google-web-toolkit@googlegroups.com.
Visit this group at http://groups.google.com/group/google-web-toolkit.
For more options, visit https://groups.google.com/d/optout.

No comments:

Post a Comment