Wednesday, January 20, 2010

Bag vs. List


public class FilterGroup {
private List<Fitler> filters = new ArrayList<Fitler>();
...
}








I mapped List as above but got bizarre result. Although there are only 8 items in the list, it returned a list with over 1000 item and most of them are null. It turns out this is because the column "orderPos" used for list index contains a big number 1025+.

I do want a sorted list. So I change to use bag mapping with order-by. It works well. In Hibernate document, it said bag is mapped for Collection. Internally it is implemented as ArrayList but ignores index (Bag is different from set in that it allows duplicates). So I can map it to List as well and add a order-by to sort it in database level. (set, map, bag all can have order-by)






Also, note that we need "delete-orphan" cascade for collection. So when we do a update on FilterGroup, the filters removed from the collection will be deleted from database automatically. Otherwise, it will still remain.

Monday, January 11, 2010

Session.get() return proxy?

Surprising, the object returned by session.get() is a proxy rather than the real object. What is going on?

Hibernate API said Session.load(id) return a proxy and Session.get(id) return a persistent object. But actually get() may return proxy if previously it is lazily loaded in the same session, e.g., as a property of other object. A proxy is a proxy even you have called its methods.

Typecasting and instanceof work well for proxy object, however, there is a pitfall, for super-class or interface, it will not give you the implementation class. e.g., it is instanceof Template, but not instanceof VnsTemplate.

To ensure we always get the real persistent object, I implement read() in Dao as follows:


public T read(int id) {
T obj = (T)getSession().get(entityClass, id);

if (obj instanceof HibernateProxy) {
return(T) ((HibernateProxy)obj).getHibernateLazyInitializer().getImplementation();
}

return obj;
}

Alternatively, HibernateProxyHelper.getClassWithoutInitializingProxy(o) could be used to get class info.

Note that we couldn't use session.evict() to detach old proxy object and call get() to retrieve the real object. The old proxy object will still be referenced by other object, and access to its member may throw LazyInitializationException even inside the transaction since it has been detached.

Friday, December 18, 2009

Spring RMI callback

One open issue of Spring remoting via RMI is that it hasn't supported RMI callback as discussed in here.The conventional RMI callback requires the interface implements Remote and the methods throw RemoteException. Hereby it is not very nice.

WlcfgRmiCallbackProxyFactory is implemented to provide such support by customizing Spring remoting code. It provides a remote proxy which intercepts method invocation on callback interface (non-RMI). It uses wlcfgRmiCallbackExporter which wraps a non-RMI service into remote object via RmiInvocationHandler and export it. Note that it doesn't bind to RMI registry.


public class WlcfgRmiCallbackProxyFactory extends RemoteInvocationBasedAccessor
implements MethodInterceptor, Serializable {

private static final long serialVersionUID = 5685339356048366732L;

private Class callbackInterface;
private RmiInvocationHandler invocationHandler;

public WlcfgRmiCallbackProxyFactory(Object callback, Class callbackInterface) {
this.callbackInterface = callbackInterface;

WlcfgRmiCallbackExporter exporter = new WlcfgRmiCallbackExporter();
this.invocationHandler = exporter.getRmiInvocationHandler(callback,
callbackInterface);
}

public Proxy getProxy() {
Proxy proxy = (Proxy) ProxyFactory.getProxy(callbackInterface, this);
return proxy;
}

@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
return invocationHandler.invoke(createRemoteInvocation(invocation));
} catch (RemoteException re) {
throw RmiClientInterceptorUtils.convertRmiAccessException(
invocation.getMethod(), re, RmiClientInterceptorUtils.isConnectFailure(re), ClassUtils.getShortName(callbackInterface));
} catch (InvocationTargetException ex) {
Throwable targetEx = ex.getTargetException();
RemoteInvocationUtils.fillInClientStackTraceIfPossible(targetEx);
throw targetEx;
}
}
}

public class WlcfgRmiCallbackExporter extends RmiBasedExporter {

private static Log log = LogFactory.getLog(WlcfgRmiCallbackExporter.class);

public RmiInvocationHandler getRmiInvocationHandler(Object callback, Class callbackInterface) {
this.setService(callback);
this.setServiceInterface(callbackInterface);
this.setRegisterTraceInterceptor(false);

// wrap non-RMI service into a remote object
Remote exportedObject = getObjectToExport();

try {
UnicastRemoteObject.exportObject(exportedObject, 0);
} catch(RemoteException e) {
log.error("Failed to export " + getServiceInterface().getName(), e);
}

log.info("Export object " + getServiceInterface().getName());
return (RmiInvocationHandler)exportedObject;
}
}


There are two types of proxy: JDK dynamic proxies or CGLIB. Spring can use both for creating proxies at runtime. One is based on interface, the other for concrete class as discussed in here. With the invocation of ProxyFactory.getProxy(), a Proxy class is generated at runtime which implements all of the supplied interfaces. It is fancy that the returned proxy is an instance of the interface and can be casted as:

WlcfgRmiCallbackProxyFactory proxyFactory = new WlcfgRmiCallbackProxyFactory(this, IHostSessionListener.class);
hostService.registerForNofitications((IHostSessionListener)proxyFactory.getProxy());

Two common usages of proxies are demonstrated above:
- Line 18, add interceptor so that a normal method call becomes remote call.
- Line 47, limit method access to target source object - only methods defined in serviceInterface are exposed. Otherwise, with reflection method call, all methods would be exposed.

Thursday, November 19, 2009

Join (association) Table

one-to-many can be mapped with join table as many-to-many (unique="true"), it gives some flexibility that many side doesn't need to have FK to one side, hence they are more loosely coupled. The relation is kept in a separated table. E.g., both Template and Task have one-to-many relationship with DeployWlc. Then we can keep DeployWlc table intact and have separated join tables ce_template_deployWlc, and ce_task_deployWlc.


<class name="Person">
<set name="addresses">
<key column="personId" not-null="true"/>
<one-to-many class="Address"/>
</set>
</class>

<class name="Person">
<set name="addresses" table="PersonAddress">
<key column="personId"/>
<many-to-many column="addressId"
unique="true"
class="Address"/>
</set>
</class>

For many-to-one, we can also have join table, so that on database table, many side doesn't have FK column, but on java object, it has the reference. If both side defines the join table, it creates a bidirectional one-to-many/many-to-one association using a join table.

<many-to-one name="person" class="Person" column="personId"/>


<key column="addressId" unique="true"/>
<many-to-one name="person" class="Person" column="personId"/>


A cascade myth: the CASCADE on collection is not for join(association) table, but for the base table on the other side. The data entries on association table are updated without cascade setting.

The association could be strong or weak. For strong association, define Hibernate cascade "all, delete-orphan" will do it; For weak association, we may run into some issues. For example, the bidirectional association table, the *inverse* side may have FK constraint violation issue. E.g., ApGroup(inverse) <--> Ap, the deletion of ApGroup will fail since it is referenced by association table. It is OK for Ap deletion which is taken care of by Hibernate. So the solution is add database level "on delete cascade" for FK constraint tbl_apGroup_ap -> apGroup.

alter table tbl_apGroup_ap add index FK4F9906CCF1F23ED3 (apGroupId), add constraint FK4F9906CCF1F23ED3 foreign key (apGroupId) references tbl_apGroup (id) on delete cascade;

It means the deletion on parent table (pointed) tbl_apGroup will cascade the deletion on child table tbl_apGroup_ap.

one-to-one is mapped with many-to-one (unique="true") with foreign key association. If using one-to-one, it is using primary key association.