One way to have more control over updates is to create proxies for all objects retrieved through the ORM layer which intercept all calls to the entity object. By intercepting these we can know exactly what data is accessed and what data is changed, and this could help with easier models.

Making the proxies themselves is not hard. DomUI already uses the QDataContext/QCriteria framework for all database access, so it is very easy to layer a "special" QDataContext above the real one. The special one acts as a delegate to the original but creates suitable proxies for all entity instances retrieved. These proxies are then returned instead of the originals.

Making the proxies themselves requires the help of a bytecode library like cglib (old), javassist or bytebuddy. There is a Proxy class in the JRE which can make dynamic proxies but as usual it's severely handicapped: it can only create proxies for interfaces, not for concrete classes - so it is useless for this task.

Why proxies?

We want proxies so that we can do one or more of the following:

Most of these are not too hard. The last one, however, needs a bit of further thought.

Using proxies to detect changes for "subtransactions"

We could use a proxy to implement "subtransactions", the idea that the original objects are only changed when the subtransaction "commits". These original objects can then be saved by some other (real) transaction - or even another subtransaction. This allows for layered screens where for instance a Dialog can be used to edit some thingy without those changes becoming permanent even when the dialog is cancelled.

There are two ways to do this:

The latter one is undesirable for the following reasons:

So we get rule 1: data is kept in the proxy and only copied back to the original at special times

Handling proxy data

Key concept: intercept all getters and setters, and where needed replace the data gotten with different copies. We can use this to replace lists with ObservableLists and ManyToOne relation properties to a proxy of that parent.

Replacing OneToMany Lists with ObservableLists

Take for example the wish to wrap all List properties into an Observable list, so that any change made to it propagates to the UI immediately. This can be done relatively easily:

We can do something similar when the property's setter is called: if it is called with a non-observable list we can wrap it and put that into the property. This will have side effects though: if the original list is changed after the observable is created this will not send events (as the observable list does not see the changes since they are not made through it).  This however should not be an issue in most cases: setting a whole new list will cause all bindings to refresh completely, and this is done after the logic has made the changes; hence no change events are needed for this case.

Replacing ManyToOne (parent) property values

We want to make sure that all entities reachable from the proxy are themselves proxies. We register these with the delegate context so that each entity instance is associated with only one proxy. This creates an "access map" of data: all data accessed through the context will become known, and this limits the data we need to check for changes at commit time.

Problems caused by using proxies

As long as the Entity class contains only getters and setters directly mirrored by fields this approach should work fine. But it goes south very quickly when the entity object contains other methods. Take for instance an entity class that has a toString() method which renders the values of its fields as a string. There are two ways to handle this:

The only way around this seems to be the following:

The latter solves a lot of issues. We can now store the proxy data inside the real fields of the proxy, and this means that any method inside the proxy that uses those fields will now work and use the correct (replaced) data. This also saves us from the need to create separate storage for the fields in the 

What needs to be generated as a proxy

For a class Orig we need to generate a class OrigNNNN extends Orig. We should abort if Orig is final - or if it contains final members (other than the finals from Object).

The OrigNNNN class will have extra fields generated to keep data:

FieldTypeDescription
__handlerIQProxyDataHandlerThe data handler for the proxy which receives all delegated calls, and which contains a reference to the QDataContext and other related data.
__originalOrigThe pointer to the original, wrapped instance.

In addition the proxy contains all fields from Orig because it extends Orig. So the start of the class looks like this:

public class Orig$sdads1212 extends Orig {
  private IQProxyHandler __handler;
  private Orig __original;


Getters and setters

For each getter  T getXxxx() we generate a new accessor method as follows:

public T __getXxxx() {
  return super.getXxxx();
}
static private Method __getXxxxM = (initialized to the above method)

We do the same for all setters: void setXxxx(T value) generates:

public void __setXxxx(T value) {
  super.setXxxx(value);
}
static private final Method __setXxxxM = (initialized to the above method)

These are generated in the proxy, and they are needed so that we can actually change the backing fields of the proxy when we need to.

For each of these getters and setters we will also generate static private fields containing Method references to these getters and setters: the __setXxxxM and __getXxxxM fields.

The new getter method delegates immediately to the handler:

static private final __getXxxxO = (initialized to Orig.getXxxx);
public T getXxxx() {
  return __handler.onGet(this, __original, __getXxxxO, __getXxxM);
}

The values passed to the handler allow access to both the proxy and the original, and using the two method references you can get data from both the original (using __getXxxxO method reference) or from the proxy (using __getXxxxM, which uses the "redirected" getter accessing super.getXxxx()).

Something sneakily similar is generated for the getter:

static private final __setXxxxO = (initialized to Orig.setXxxx)
public void setXxxx(T value) {
  __handler.onSet(this, __original, __setXxxxO, __setXxxxM, value);
}