Hi everyone,
I’m almost done with my migration project for Autofetch. I have run into some problems however regarding the prefetching of the test class entity. Autofetch is supposed to set all fetching to lazy before it does its calculations, so it can determine the optimal fetching strategy for each case during execution. The problem is that it seems like it does load the entity eagerly. This is the entity:
@Entity
@Tuplizer(impl = AutofetchTuplizer.class)
public class Employee {
@Id
@Column(name = "employee_id")
@GeneratedValue(strategy = GenerationType.AUTO)
private Long m_id;
@Column(name = "name")
private String m_name;
@JoinColumn(name = "supervisor_id")
@ManyToOne(cascade = {CascadeType.ALL})
private Employee m_supervisor;
@CollectionType(type="org.autofetch.hibernate.AutofetchSetType")
@JoinColumn(name = "supervisor_id")
@OneToMany(cascade = {CascadeType.ALL})
private Set<Employee> m_subordinates;
@Embedded
private Address m_address;
@JoinColumn(name = "mentor_id")
@ManyToOne(cascade = {CascadeType.ALL})
private Employee m_mentor;
@CollectionType(type="org.autofetch.hibernate.AutofetchSetType")
@ManyToMany(cascade = {CascadeType.ALL})
@JoinTable(name = "friends", joinColumns = {@JoinColumn(name = "friend_id")}, inverseJoinColumns = {@JoinColumn(name = "befriended_id")})
private Set<Employee> m_friends;
This is the test that fails:
@Test
public void testLoadFetchProfile() {
em.clearExtentInformation();
// Execute query multiple times
Employee dave = null;
for (int i = 0; i < 2; i++) {
dave = someAccess(i == 0);
}
// These all should not throw lazy instantiation exception, because
// they should have been fetch eagerly
dave.getSupervisor().getName();
dave.getSupervisor().getSupervisor().getName();
dave.getSupervisor().getSupervisor().getSubordinates().size();
dave.getMentor().getName();
// This should throw a lazy instantiation exception
try {
dave.getSupervisor().getSupervisor().getSupervisor().getName();
// Shouldn't get here
Assert.fail("Lazy instantion exception not thrown for a property which shouldn't have been fetched"); //Fails here
} catch (LazyInitializationException e) {
// Good
}
}
The things it loads in the test:
private Employee someAccess(boolean traverse) {
Session sess;
Transaction tx = null;
Long daveId = createObjectGraph(true);
try {
sess = openSession();
tx = sess.beginTransaction();
Employee dave = (Employee) sess.load(Employee.class, daveId);
dave.getName();
if (traverse) {
dave.getSupervisor().getName();
dave.getMentor().getName();
dave.getSupervisor().getSupervisor().getName();
dave.getSupervisor().getSupervisor().getSubordinates().size();
}
tx.commit();
tx = null;
return dave;
} finally {
if (tx != null) {
tx.rollback();
}
}
}
I have tried setting the fetching strategy manually for Supervisor to lazy, but that does not help either. Since I am quite new with Hibernate and mapping, I suspect I might have done some mistakes when changing the old xml mapping to annotation-based.
This is how the old file looked:
<?xml version="1.0"?>
-<!DOCTYPE hibernate-mapping PUBLIC
- "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
-
-<hibernate-mapping>
- <class name="org.autofetch.test.Employee" table="employee">
- <id name="id" column="employee_id">
- <generator class="native"/>
- </id>
-
- <property name="name" length="255"/>
-
- <set name="subordinates" inverse="true" cascade="all" >
- <key column="supervisor_id"/>
- <one-to-many class="org.autofetch.test.Employee"/>
- </set>
-
- <many-to-one name="supervisor" cascade="all" column="supervisor_id"/>
-
- <many-to-one name="mentor" cascade="all" column="mentor_id"/>
-
- <set name="friends" table="friends" cascade="all">
- <key column="friend_id"/>
- <many-to-many column="befriended_id" class="org.autofetch.test.Employee"/>
- </set>
-
- <component name="address" class="org.autofetch.test.Address" lazy="false">
- <property name="street" length="255"/>
- <property name="city" length="255"/>
- <property name="state" length="255"/>
- </component>
- </class>
-</hibernate-mapping>
The tool comes with its own LazyInitializer:
public class AutofetchLazyInitializer extends BasicLazyInitializer implements MethodHandler {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger(AutofetchLazyInitializer.class);
private EntityTracker entityTracker;
private boolean entityTrackersSet;
private Class[] interfaces;
private boolean constructed;
private AutofetchLazyInitializer(String entityName,
Class persistentClass,
Class[] interfaces,
Serializable id,
Method getIdentifierMethod,
Method setIdentifierMethod,
CompositeType componentIdType,
SessionImplementor session,
Set<Property> persistentProperties,
boolean classOverridesEquals) {
super(entityName, persistentClass, id, getIdentifierMethod, setIdentifierMethod,
componentIdType, session, classOverridesEquals);
this.interfaces = interfaces;
AutofetchService autofetchService = session.getFactory().getServiceRegistry().getService(AutofetchService.class);
this.entityTracker = new EntityTracker(persistentProperties, autofetchService.getExtentManager());
this.entityTrackersSet = false;
}
@Override
public Object invoke(final Object proxy, final Method thisMethod, final Method proceed, final Object[] args) throws Throwable {
if (this.constructed) {
Object result;
try {
result = this.invoke(thisMethod, args, proxy);
} catch (Throwable t) {
throw new Exception(t.getCause());
}
if (result == INVOKE_IMPLEMENTATION) {
if (args.length == 0) {
switch (thisMethod.getName()) {
case "enableTracking":
return handleEnableTracking();
case "disableTracking":
return handleDisableTracking();
case "isAccessed":
return entityTracker.isAccessed();
}
} else if (args.length == 1) {
if (thisMethod.getName().equals("addTracker") && thisMethod.getParameterTypes()[0].equals(Statistics.class)) {
return handleAddTracked(args[0]);
} else if (thisMethod.getName().equals("addTrackers") && thisMethod.getParameterTypes()[0].equals(Set.class)) {
return handleAddTrackers(args[0]);
} else if (thisMethod.getName().equals("removeTracker") && thisMethod.getParameterTypes()[0].equals(Statistics.class)) {
entityTracker.removeTracker((Statistics) args[0]);
return handleRemoveTracker(args);
} else if (thisMethod.getName().equals("extendProfile") && thisMethod.getParameterTypes()[0].equals(Statistics.class)) {
return extendProfile(args);
}
}
final Object target = getImplementation();
final Object returnValue;
try {
if (ReflectHelper.isPublic(persistentClass, thisMethod)) {
if (!thisMethod.getDeclaringClass().isInstance(target)) {
throw new ClassCastException(
target.getClass().getName() + " incompatible with " + thisMethod.getDeclaringClass().getName()
);
}
} else {
thisMethod.setAccessible(true);
}
returnValue = thisMethod.invoke(target, args);
if (returnValue == target) {
if (returnValue.getClass().isInstance(proxy)) {
return proxy;
} else {
LOG.narrowingProxy(returnValue.getClass());
}
}
return returnValue;
} catch (InvocationTargetException ite) {
throw ite.getTargetException();
} finally {
if (!entityTrackersSet && target instanceof Trackable) {
entityTrackersSet = true;
Trackable entity = (Trackable) target;
entity.addTrackers(entityTracker.getTrackers());
if (entityTracker.isTracking()) {
entity.enableTracking();
} else {
entity.disableTracking();
}
}
}
} else {
return result;
}
} else {
// while constructor is running
if (thisMethod.getName().equals("getHibernateLazyInitializer")) {
return this;
} else {
return proceed.invoke(proxy, args);
}
}
}
private Object handleDisableTracking() {
boolean oldValue = entityTracker.isTracking();
this.entityTracker.setTracking(false);
if (!isUninitialized()) {
Object o = getImplementation();
if (o instanceof Trackable) {
Trackable entity = (Trackable) o;
entity.disableTracking();
}
}
return oldValue;
}
private Object handleEnableTracking() {
boolean oldValue = this.entityTracker.isTracking();
this.entityTracker.setTracking(true);
if (!isUninitialized()) {
Object o = getImplementation();
if (o instanceof Trackable) {
Trackable entity = (Trackable) o;
entity.enableTracking();
}
}
return oldValue;
}
private Object extendProfile(Object[] params) {
if (!isUninitialized()) {
Object o = getImplementation();
if (o instanceof TrackableEntity) {
TrackableEntity entity = (TrackableEntity) o;
entity.extendProfile((Statistics) params[0]);
}
} else {
throw new IllegalStateException("Can't call extendProfile on unloaded self.");
}
return null;
}
private Object handleRemoveTracker(Object[] params) {
if (!isUninitialized()) {
Object o = getImplementation();
if (o instanceof Trackable) {
Trackable entity = (Trackable) o;
entity.removeTracker((Statistics) params[0]);
}
}
return null;
}
@SuppressWarnings("unchecked")
private Object handleAddTrackers(Object param) {
Set<Statistics> newTrackers = (Set<Statistics>) param;
this.entityTracker.addTrackers(newTrackers);
if (!isUninitialized()) {
Object o = getImplementation();
if (o instanceof Trackable) {
Trackable entity = (Trackable) o;
entity.addTrackers(newTrackers);
}
}
return null;
}
private Object handleAddTracked(Object param) {
this.entityTracker.addTracker((Statistics) param);
if (!isUninitialized()) {
Object o = getImplementation();
if (o instanceof Trackable) {
Trackable entity = (Trackable) o;
entity.addTracker((Statistics) param);
}
}
return null;
}
@Override
protected Object serializableProxy() {
return new AutofetchSerializableProxy(
getEntityName(),
this.persistentClass,
this.interfaces,
getIdentifier(),
(isReadOnlySettingAvailable() ? Boolean.valueOf(isReadOnly()) : isReadOnlyBeforeAttachedToSession()),
this.getIdentifierMethod,
this.setIdentifierMethod,
this.componentIdType,
this.entityTracker.getPersistentProperties()
);
}
public static HibernateProxy getProxy(
final String entityName,
final Class persistentClass,
final Class[] interfaces,
final Method getIdentifierMethod,
final Method setIdentifierMethod,
final CompositeType componentIdType,
final Serializable id,
final SessionImplementor session,
final Set<Property> persistentProperties) throws HibernateException {
// note: interface is assumed to already contain HibernateProxy.class
try {
final AutofetchLazyInitializer instance = new AutofetchLazyInitializer(
entityName,
persistentClass,
interfaces,
id,
getIdentifierMethod,
setIdentifierMethod,
componentIdType,
session,
persistentProperties,
ReflectHelper.overridesEquals(persistentClass)
);
final ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(interfaces.length == 1 ? persistentClass : null);
factory.setInterfaces(interfaces);
factory.setFilter(FINALIZE_FILTER);
Class cl = factory.createClass();
final HibernateProxy proxy = (HibernateProxy) cl.newInstance();
((Proxy) proxy).setHandler(instance);
instance.constructed = true;
return proxy;
} catch (Throwable t) {
LOG.error(LOG.javassistEnhancementFailed(entityName), t);
throw new HibernateException(LOG.javassistEnhancementFailed(entityName), t);
}
}
public static HibernateProxy getProxy(
final Class factory,
final String entityName,
final Class persistentClass,
final Class[] interfaces,
final Method getIdentifierMethod,
final Method setIdentifierMethod,
final CompositeType componentIdType,
final Serializable id,
final SessionImplementor session,
final Set<Property> persistentProperties) throws HibernateException {
// note: interfaces is assumed to already contain HibernateProxy.class
final AutofetchLazyInitializer instance = new AutofetchLazyInitializer(
entityName,
persistentClass,
interfaces,
id,
getIdentifierMethod,
setIdentifierMethod,
componentIdType,
session,
persistentProperties,
ReflectHelper.overridesEquals(persistentClass)
);
final HibernateProxy proxy;
try {
proxy = (HibernateProxy) factory.newInstance();
} catch (Exception e) {
throw new HibernateException("Javassist Enhancement failed: " + persistentClass.getName(), e);
}
((Proxy) proxy).setHandler(instance);
instance.constructed = true;
return proxy;
}
private static final MethodFilter FINALIZE_FILTER = new MethodFilter() {
@Override
public boolean isHandled(Method m) {
// skip finalize methods
return !(m.getParameterTypes().length == 0 && m.getName().equals("finalize"));
}
};
}
Does anyone have an idea why it has this behaviour? Thanks in advance.