Entity loads eagerly when it should be lazy


#1

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.


#2

By default, @ManyToOne and @ObeToOne are EAGER. You need to set those to LAZY. And also, parent side one-to-one are also EAGER, unless you use bytecode enhancement.


#3

Thansks for the reply. I read this aswell, and I tried setting it manually for the supervisor. It didn’t change the outcome of the tests however. Do you know if there is a way to make hibernate to always lazy fetch all types?

Update: I checked now an older post that you did on your website and I saw the possibility to use the bytecode enhancement that you mentioned. Is this a good way to always load lazily? Can you still apply hints to individual queries to for example load something eagerly?


#4

Do you know if there is a way to make hibernate to always lazy fetch all types?

As long as you set associations to LAZY, Hibernate should honor that. The mappedBy one-to-one is the exception to the rule. But you can use bytecode enhancement to address that too.

Is this a good way to always load lazily?

Yes.

Can you still apply hints to individual queries to for example load something eagerly?

Yes. You can use JOIN FETCH.


#5

I have been trying to resolve this issue now for a while, but I haven’t managed to get any further. I have tried to get hibernate to throw LazyLoadingExceptions, but no matter what I try to fetch (session closed, lazy association), it seems to successfully load it. When I debug, I see that the proxy that is loaded, has a target (the persistent object I guess?) with all the fields with the actual values. It seems like the proxy just uses the actual object always instead. I have noticed also that when I load an object, the proxy has all the values null, even the ID. I don’t know what this is supposed to mean. Is this normal, or is something strange going on here? If so, what could it be? Any ideas how I could continue debugging this?


#6

Did you enable hibernate.enable_lazy_load_no_trans?

Otherwise, I can’t see how lazy Proxies could be fetched eagerly even if the Session is closed.


#7

No, I haven’t enabled that setting.

I will try to show you how it behaves. So my test is basically this:

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();
            }
            session.close();
        }
    }
    @Test
    public void testLoadFetchProfile() {
        em.clearExtentInformation();

        // Execute query multiple times
//        someAccess(true);
        Employee dave = someAccess(true);

        // 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().getSubordinates();
            // Shouldn't get here
            Assert.fail("Lazy instantion exception not thrown for a property which shouldn't have been fetched");
        } catch (LazyInitializationException e) {
            // Good
        }
    }

As you can see, I traverse some of the entities I created. However, the line dave.getSupervisor().getSupervisor().getSupervisor().getSubordinates() should throw LazyInitializationException since it’s subordinates is a lazy association. Also notice that the variables show that the proxy is completely empty (I am debugging with the debug-pointer at line dave.getSupervisor().getSupervisor().getSupervisor().getSubordinates()). So all in all, session is null, the proxy doesn’t have any values set, but the target has all the values set (is this correct?). I do get a com.sun.jdi.InvocationException when observing the value for associations that uses my custom wrapper set in the debugging view, but I have heard that is only a problem with the toString() method and when debugging.


#8

If you do it in debug, you won’t ever find any Lazy association because the IDE initializes everything in the Code Inspector.


#9

Alright, so in order to see if it actually lazy loads I will have to check for prints in the console from queries or check for lazy load exception (which doesn’t get thrown) so I can assume that all of my entities are already loaded. Could it have to do with that the target of the proxy fully instantiated object with all fields filled in already? I mean, the IDE wouldn’t have known the value of the persisted object if the values werent already fetched and set… Or is my proxying completely broken? I will try to investigate the queries closer to see if I can make something out of it. What is clear is that the traversal of the associations doesn’t need additional queries, so everything is definitely eager fetched. If you have any ideas what might be the cause of this it would be highly appreciated.


#10

Just let the test run without debugging it. If you close the Session, and then initialize the Proxy, you will get the LazyException.

If you do it from the IDE, make sure the breakpoint is added after the Session got closed to avoid forcing it to bee initialized by the Code Inspector.


#11

Thanks for the tips, but I have set the breakpoint after the session is closed and still when I try to get an association that should be lazy fetched, the exception is not thrown. So it does not have to do with the IDE and the force initialization by the code inspector.

So as far as I know it must have to do that the whole object graph is loaded instead (for some weird reason) instead of only the root object of the query, the associations that should be eager fetched and the associations I traverse before the session is closed. But why?

What I’ve noticed is that when I do the call for dave.getName(), I get the following queries:

Hibernate: 
    select
        employee0_.employee_id as employee1_0_0_,
        employee0_.m_city as m_city2_0_0_,
        employee0_.m_state as m_state3_0_0_,
        employee0_.m_street as m_street4_0_0_,
        employee0_.mentor_id as mentor_i6_0_0_,
        employee0_.name as name5_0_0_,
        employee0_.supervisor_id as supervis7_0_0_,
        employee1_.employee_id as employee1_0_1_,
        employee1_.m_city as m_city2_0_1_,
        employee1_.m_state as m_state3_0_1_,
        employee1_.m_street as m_street4_0_1_,
        employee1_.mentor_id as mentor_i6_0_1_,
        employee1_.name as name5_0_1_,
        employee1_.supervisor_id as supervis7_0_1_,
        employee2_.employee_id as employee1_0_2_,
        employee2_.m_city as m_city2_0_2_,
        employee2_.m_state as m_state3_0_2_,
        employee2_.m_street as m_street4_0_2_,
        employee2_.mentor_id as mentor_i6_0_2_,
        employee2_.name as name5_0_2_,
        employee2_.supervisor_id as supervis7_0_2_ 
    from
        Employee employee0_ 
    left outer join
        Employee employee1_ 
            on employee0_.mentor_id=employee1_.employee_id 
    left outer join
        Employee employee2_ 
            on employee1_.supervisor_id=employee2_.employee_id 
    where
        employee0_.employee_id=?
Hibernate: 
    select
        employee0_.employee_id as employee1_0_0_,
        employee0_.m_city as m_city2_0_0_,
        employee0_.m_state as m_state3_0_0_,
        employee0_.m_street as m_street4_0_0_,
        employee0_.mentor_id as mentor_i6_0_0_,
        employee0_.name as name5_0_0_,
        employee0_.supervisor_id as supervis7_0_0_,
        employee1_.employee_id as employee1_0_1_,
        employee1_.m_city as m_city2_0_1_,
        employee1_.m_state as m_state3_0_1_,
        employee1_.m_street as m_street4_0_1_,
        employee1_.mentor_id as mentor_i6_0_1_,
        employee1_.name as name5_0_1_,
        employee1_.supervisor_id as supervis7_0_1_,
        employee2_.employee_id as employee1_0_2_,
        employee2_.m_city as m_city2_0_2_,
        employee2_.m_state as m_state3_0_2_,
        employee2_.m_street as m_street4_0_2_,
        employee2_.mentor_id as mentor_i6_0_2_,
        employee2_.name as name5_0_2_,
        employee2_.supervisor_id as supervis7_0_2_ 
    from
        Employee employee0_ 
    left outer join
        Employee employee1_ 
            on employee0_.mentor_id=employee1_.employee_id 
    left outer join
        Employee employee2_ 
            on employee1_.supervisor_id=employee2_.employee_id 
    where
        employee0_.employee_id=?
Hibernate: 
    select
        employee0_.employee_id as employee1_0_0_,
        employee0_.m_city as m_city2_0_0_,
        employee0_.m_state as m_state3_0_0_,
        employee0_.m_street as m_street4_0_0_,
        employee0_.mentor_id as mentor_i6_0_0_,
        employee0_.name as name5_0_0_,
        employee0_.supervisor_id as supervis7_0_0_,
        employee1_.employee_id as employee1_0_1_,
        employee1_.m_city as m_city2_0_1_,
        employee1_.m_state as m_state3_0_1_,
        employee1_.m_street as m_street4_0_1_,
        employee1_.mentor_id as mentor_i6_0_1_,
        employee1_.name as name5_0_1_,
        employee1_.supervisor_id as supervis7_0_1_,
        employee2_.employee_id as employee1_0_2_,
        employee2_.m_city as m_city2_0_2_,
        employee2_.m_state as m_state3_0_2_,
        employee2_.m_street as m_street4_0_2_,
        employee2_.mentor_id as mentor_i6_0_2_,
        employee2_.name as name5_0_2_,
        employee2_.supervisor_id as supervis7_0_2_ 
    from
        Employee employee0_ 
    left outer join
        Employee employee1_ 
            on employee0_.mentor_id=employee1_.employee_id 
    left outer join
        Employee employee2_ 
            on employee1_.supervisor_id=employee2_.employee_id 
    where
        employee0_.employee_id=?
Hibernate: 
    select
        employee0_.employee_id as employee1_0_0_,
        employee0_.m_city as m_city2_0_0_,
        employee0_.m_state as m_state3_0_0_,
        employee0_.m_street as m_street4_0_0_,
        employee0_.mentor_id as mentor_i6_0_0_,
        employee0_.name as name5_0_0_,
        employee0_.supervisor_id as supervis7_0_0_,
        employee1_.employee_id as employee1_0_1_,
        employee1_.m_city as m_city2_0_1_,
        employee1_.m_state as m_state3_0_1_,
        employee1_.m_street as m_street4_0_1_,
        employee1_.mentor_id as mentor_i6_0_1_,
        employee1_.name as name5_0_1_,
        employee1_.supervisor_id as supervis7_0_1_,
        employee2_.employee_id as employee1_0_2_,
        employee2_.m_city as m_city2_0_2_,
        employee2_.m_state as m_state3_0_2_,
        employee2_.m_street as m_street4_0_2_,
        employee2_.mentor_id as mentor_i6_0_2_,
        employee2_.name as name5_0_2_,
        employee2_.supervisor_id as supervis7_0_2_ 
    from
        Employee employee0_ 
    left outer join
        Employee employee1_ 
            on employee0_.mentor_id=employee1_.employee_id 
    left outer join
        Employee employee2_ 
            on employee1_.supervisor_id=employee2_.employee_id 
    where
        employee0_.employee_id=?
Hibernate: 
    select
        employee0_.employee_id as employee1_0_0_,
        employee0_.m_city as m_city2_0_0_,
        employee0_.m_state as m_state3_0_0_,
        employee0_.m_street as m_street4_0_0_,
        employee0_.mentor_id as mentor_i6_0_0_,
        employee0_.name as name5_0_0_,
        employee0_.supervisor_id as supervis7_0_0_,
        employee1_.employee_id as employee1_0_1_,
        employee1_.m_city as m_city2_0_1_,
        employee1_.m_state as m_state3_0_1_,
        employee1_.m_street as m_street4_0_1_,
        employee1_.mentor_id as mentor_i6_0_1_,
        employee1_.name as name5_0_1_,
        employee1_.supervisor_id as supervis7_0_1_,
        employee2_.employee_id as employee1_0_2_,
        employee2_.m_city as m_city2_0_2_,
        employee2_.m_state as m_state3_0_2_,
        employee2_.m_street as m_street4_0_2_,
        employee2_.mentor_id as mentor_i6_0_2_,
        employee2_.name as name5_0_2_,
        employee2_.supervisor_id as supervis7_0_2_ 
    from
        Employee employee0_ 
    left outer join
        Employee employee1_ 
            on employee0_.mentor_id=employee1_.employee_id 
    left outer join
        Employee employee2_ 
            on employee1_.supervisor_id=employee2_.employee_id 
    where
        employee0_.employee_id=?

Seems a bit odd to me, any ideas?


#12

And the mappings are lazy? Maybe they are set to eager. By default, @OneToOne and @ManyToOne are EAGER, unless you set them to LAZY explicitly.


#13

Some of them are lazy, some of them are eager. Mentor and supervisor are eager, and subordinates and friends are lazy.

@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}, fetch = FetchType.LAZY)
    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;

    /**
     * Default constructor. Needed by Hibernate
     */
    public Employee() {
        // Nothing
    }

    /**
     * Creates employee with name and supervisor and no subordinates
     *
     * @param name       should be unique and not null
     * @param supervisor employee supervisor, null indicates no supervisor.
     */
    public Employee(String name, Employee supervisor, Employee mentor, Address addr) {
        m_name = name;
        m_supervisor = supervisor;
        m_subordinates = new HashSet<>();
        m_friends = new HashSet<>();
        m_mentor = mentor;
        m_address = addr;
    }

    public String getName() {
        return m_name;
    }

    public void setName(String name) {
        m_name = name;
    }

    public Set<Employee> getSubordinates() {
        return m_subordinates;
    }

    public void setSubordinates(Set<Employee> subordinates) {
        m_subordinates = subordinates;
    }

    public void addSubordinate(Employee e) {
        m_subordinates.add(e);
    }

    public Employee getSupervisor() {
        return m_supervisor;
    }

    public void setSupervisor(Employee supervisor) {
        m_supervisor = supervisor;
    }

    public Long getId() {
        return m_id;
    }

    public void setId(Long id) {
        m_id = id;
    }

    public Address getAddress() {
        return m_address;
    }

    public void setAddress(Address address) {
        m_address = address;
    }

    public Employee getMentor() {
        return m_mentor;
    }

    public void setMentor(Employee mentor) {
        m_mentor = mentor;
    }

    public Set<Employee> getFriends() {
        return m_friends;
    }

    public void setFriends(Set<Employee> friends) {
        m_friends = friends;
    }

    public void addFriend(Employee friend) {
        m_friends.add(friend);
    }

    @Override
    public int hashCode() {
        return m_name.hashCode();
    }

    @Override
    public boolean equals(Object other) {
        return (other instanceof Employee)
                && ((Employee) other).m_name.equals(m_name);
    }

    @Override
    public String toString() {
        return m_name;
    }
}

So when I do the following when the session is open, these entities should be available when closing the session:

            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();

Then when I do the following when the session is closed the first four lines should be okay. However, after that we should throw an exception, but for some reason it is already loaded, even though subordinates is lazy.

 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().getSubordinates();
            // Shouldn't get here
            Assert.fail("Lazy instantion exception not thrown for a property which shouldn't have been fetched");
        } catch (LazyInitializationException e) {
            // Good
        }

Am I missing something here?


#14

It might be the Tuplizer. I only know how Hibernate works by default, but since you’re customizing it, I’m not really sure what happens in your application.


#15

Thanks for all the suggestions. I have tried loading the entities without the custom tuplizer, and the same error persists. This leaves me thinking that it is a mapping error, and not a bug in the tool itself. Does that seem reasonable? Anyway, that’s all I can think about right know atleast.


#16

Follow-ups:


#17

Thanks for the links, but I’m not sure how these are relevant in my case? Can you explain a little bit about this?


#18

Outch, sorry, my mistake, they are totally unrelated to this post :).


#19

One more thing Vlad. I tried to run the tests against the normal Configuration and with the normal proxies, i.e. no modification of hibernate and it still gives me the same result, namely no LazyInstantiationException thrown. Strange…


#20

If you run the LazyLoadingNotFoundTest in hibernate-core, you’ll see that the LazyInstantiationException is thrown.