Hi there,
Hibernate: 3.0.5
Spring: 1.2.1
Tomcat: 5.0.27
JVM: 1.4.2_06
OS: Windows XP
I'm having some trouble with lazy initialisation of proxies and the visitor pattern. I'm retrieving a collection of objects and they're initialised as the base-class as you'd expect from the Hibernate docs. I'd like to use the visitor pattern to widen the instance to the correct sub-class and acquire a handle on that instance.
My problem is that although I can get the visitor pattern to execute the expected methods on the sub-class instance, the CGLibLazyInitializer is not returning me the result of my method call, but the CGLib proxy instance instead (as a result of its equivalence test - see below). The exceptions are manifesting in the view layer where I try to access a property on the sub-class:
Code:
javax.servlet.ServletException: Unable to find a value for "homeGame" in object of class "uk.co.mindfruit.cms.domain.Event$$EnhancerByCGLIB$$5e0da2a0" using operator "."
The view is expecting an instance of uk.co.mindfruit.cms.domain.Fixture instead of the base-class, Event. The basic class hierarchy is:
Code:
public class Event {
// simple POJO impl
}
Code:
public class Fixture extends Event {
// etc.
}
Visitor implementationMy EventVisitorAdaptor implementation looks like this:
Code:
public class EventVisitorAdaptor implements EventVisitor {
private Session session;
public EventVisitorAdaptor(Session session) {
super();
this.session = session;
}
public Event visitEvent(Event event) {
// return base class
return event;
}
public Fixture visitFixture(Fixture fixture) {
// makes no difference to the outcome if this is .load() or .get()
return (Fixture) session.load(Fixture.class, fixture.getId());
// I've tried using Criteria and HQL queries to see if it makes any difference to the identity of
// the returned object. As you'd expect, they make no difference at all.
//
// a)
// return (Fixture) session.createCriteria(Fixture.class).add(Expression.eq("id", fixture.getId())).uniqueResult();
//
// b)
// Query hqlQuery = session.createQuery("from Fixture as fixture where fixture.id = :fixtureId");
// hqlQuery.setLong("fixtureId", fixture.getId().longValue());
// return (Fixture)hqlQuery.uniqueResult();
}
}
It's worth pointing out that when in the visitFixture(...) method above, using a debugger it's possible to see that the Fixture object is loaded and returned correctly.
I have added an accept(EventVisitor) method to both Event and Fixture, so that I can make a call on the instance:
Code:
public class Event {
public Event accept(EventVisitor eventVisitor){
return eventVisitor.visitEvent(this);
}
}
Code:
public class Fixture extends Event {
public Event accept(EventVisitor eventVisitor){
return eventVisitor.visitFixture(this);
}
}
The intention of calling .accept(eventVisitor) is so that a "widened" instance of the Fixture sub-class is returned, if appropriate, rather than the base-class proxy obtained from the collection.
My problem comes in the CGLibLazyInitializer, in the method intercept(Object, Method, Object[], MethodProxy). The code snippet of interest is:
Code:
...
public Object intercept(
final Object proxy,
final Method method,
final Object[] args,
final MethodProxy methodProxy)
throws Throwable {
if (constructed) {
Object result = invoke(method, args, proxy);
if (result==INVOKE_IMPLEMENTATION) {
Object target = getImplementation();
final Object returnValue;
if ( ReflectHelper.isPublic(persistentClass, method) ) {
returnValue = methodProxy.invoke(target, args);
}
else {
if ( !method.isAccessible() ) method.setAccessible(true);
returnValue = method.invoke(target, args);
}
// the line below returns me the proxy rather than my return value as
// target == returnValue.
return returnValue==target ? proxy : returnValue;
}
else {
return result;
}
}
else {
//while constructor is running
if( method.getName().equals("getHibernateLazyInitializer") ) {
return this;
}
else {
return methodProxy.invokeSuper(proxy, args);
}
}
}
So really I'm wondering how to get a handle on the object I returned rather than the proxy. Do I have to wrap returnValue in some simple bean class simply to overcome the equivalence test?
I'm also getting the following debug, which seems to be a result of my narrowing conversion, though I'm not sure what's broken!
Code:
WARN org.hibernate.engine.PersistenceContext - Narrowing proxy to class uk.co.mindfruit.cms.domain.Fixture - this operation breaks ==
DEBUG uk.co.mindfruit.coreweb.domain.logic.events.PlayerEventModel - Adding event (pollable.id:11,event.id:11,pollable:true) for date 2005-02-05
WARN org.hibernate.engine.PersistenceContext - Narrowing proxy to class uk.co.mindfruit.cms.domain.Fixture - this operation breaks ==
DEBUG uk.co.mindfruit.coreweb.domain.logic.events.PlayerEventModel - Adding event (pollable.id:14,event.id:12,pollable:false) for date 2005-02-05
Please let me know if any stack-traces or logging would be of assistance. I'm at a bit of a loss what to try next, other than wrapping the return value.
The relevant part of my wrapping document follows; thanks in advance for reading this far. I hope it makes sense.
Best wishes,
Mike
Code:
<class name="Event" table="hbm_events" >
<id name="id" column="event_id" type="java.lang.Long">
<generator class="native"/>
</id>
<property name="type" type="string"/>
<property name="eventDate" type="date" not-null="true">
<column name="event_date" sql-type="date"/>
</property>
<property name="startTime" type="time" not-null="true">
<column name="start_time" sql-type="time"/>
</property>
<property name="endTime" type="time">
<column name="end_time" sql-type="time"/>
</property>
<property name="title" type="string"/>
<property name="description" type="string"/>
<property name="lastUpdated" type="timestamp">
<column name="last_updated" sql-type="timestamp"/>
</property>
<set name="eventGroups" cascade="save-update">
<key column="event_id"/>
<one-to-many class="uk.co.mindfruit.cms.domain.PollableEventGroup"/>
</set>
<many-to-one name="club" class="uk.co.mindfruit.cms.domain.Club" column="club_id" />
<many-to-one name="season" class="uk.co.mindfruit.cms.domain.Season" column="season_id" cascade="save-update" />
<many-to-one name="venue" class="uk.co.mindfruit.cms.domain.Venue" column="venue_id" cascade="save-update" />
<joined-subclass name="Fixture" table="hbm_fixtures" >
<key column="event_id"/>
<property name="meetTime" type="time">
<column name="meet_time" sql-type="time"/>
</property>
<property name="pointsFor" type="integer">
<column name="points_for"/>
</property>
<property name="pointsAgainst" type="integer">
<column name="points_against"/>
</property>
<property name="homeGame" type="boolean">
<column name="home_game"/>
</property>
<many-to-one name="homeTeam" class="uk.co.mindfruit.cms.domain.Team" column="our_team_id" cascade="none"/>
<many-to-one name="opposition" class="uk.co.mindfruit.cms.domain.Team" column="opposition_id" cascade="none"/>
<many-to-one name="fixtureType" class="uk.co.mindfruit.cms.domain.FixtureType" column="type_id" cascade="none"/>
</joined-subclass>
</class>