views:

209

answers:

2

I'm trying to implement a Hibernate persistence layer in a Java application and I'm having some trouble with it. I'm encountering a no proxy error every time I attempt to access a simple, one way association. I haven't implemented Hibernate in quite the right way - I am using the thread method of Session control, which they do suggest you don't use for production. However, they use it in their tutorials. I am still trying to get the basics working, so I figured following the tutorials would be fine. However, I can't get the simple association to work. I have a class that looks something like this:

public class Foo {
    private Long id;
    private String name;
    private Bar bar;

    // Static Handler Methods - I'll flesh these out further down in the question.
    public static List getAllFoo();


    // Convenience methods
    public String getBarName() {
        return bar.getName();
    }

    // Accessor methods
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Bar getBar() {
        return bar;
    }

    public void setBar(Bar bar) {
        this.bar = bar;
    }
}

Foo's Bar class is implemented as a simple one-way association. It's in the .hbm.xml like so:

<many-to-one name="bar" column="bar_id" not-null="true" />

I create the Foo's in a static method inside Foo like so:

public static List getAllFoo() {
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    Transaction t = session.beginTransaction();
    List foos = session.createCriteria(Foo.class).list();
    t.commit();
    return foos;
}

The hibernate instance is configured to use a connection pool of 1 and uses the threading method for session handling like so:

<property name="connection.pool_size">1</property>
<property name="current_session_context_class">thread</property>

I'm encountering a exception every time I try to access the association using the getBarName() function on one of the created objects. It's a proxy error, it looks like this:

org.hibernate.LazyInitializationException: could not initialize proxy - no Session
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:132)
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:174)
    at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
    at Bar_$$_javassist_7.getName(Bar_$$_javassist_7.java)
    at Foo.getBarName(Bar.java:126)

So to make a long story short, this is what I started with and the error I first encountered. Since then I've spent the last several days reading the Hibernate docs and posts here to try and figure out how to make this work.

What I've learned - I think - is that Hibernate objects need to be connected to a Session. Namely, the Session they are created by. When I am creating my objects, in getAllFoo() that is the Session that those objects relate to. In that session the Bar proxy exists and makes sense. However, when I call t.commit() - because I am using the Thread method of Session handling - I am ending that Session. The result of this is that when I go to call bar.getName() later, bar is now an orphaned proxy. It is a proxy who's session has been closed.

I found two possible solutions:

1) don't close the initial session. Leave it open by not calling t.commit() in getAllFoo().

This worked - however, I don't think I can use it. I'm going to have thousands of these objects loaded at once. They are going to need to stay open for an extended period of time while the user has think time - but I need to be able to access the associations freely. And there may, in the future, be concurrency issues with database locks.

2) reattach the object to a new Session before calling on the association.

This didn't work. Maybe I did it wrong - the documentation I found isn't clear. I attempted to start a new session and call session.load(this, this.id) before I accessed the association. Like this:

public Bar getBar() {
    Bar tmp = null;
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    Transaction t = session.beginTransaction();
    session.load(this, this.id);
    tmp = bar;
    t.commit();
    return tmp;
}

I then modified getBarName() to call getBar() instead of accessing bar. This resulted in a new error:

org.hibernate.PersistentObjectException: attempted to load into an instance that was already associated with the session: [Foo#1]

I guess even after days of reading tutorials, posts and the hibernate documentation I still can't wrap my head around this structure. So I ask StackOverflow to shed some light on this.

First, if you can figure it out, what's happening in the code I currently have? Is the session opened or closed? Why does it have a proxy error in the first method, but claim the session is still opened and the object still attached in the second?

Second, what is the correct way to handle Sessions - how do I actually make this work? The way I think I want to use is session per request - that seems to be the most popular and applicable to my situation. But then how is that actually implemented with associations and lazy loading?

Yes, I know I shouldn't be using the thread method - but I have a whole other batch of questions related to JTA vs Thread vs Managed and this question is already too long.

+1  A: 

I believe Hibernate defaults to lazy initialization. So while you loaded the list of Foos in the session, none of their associated Bars are loaded until any attempts to use them. By that time, you've already closed the session, which results in the error.

Some potential solutions:

  • enable eager fetching on Foo-Bar association
  • "join" on the fetch (effectively an eager fetch)
  • try to access the Bar within the session (this will work, but you're calling Foo.getBar() just to get the associated Bar object loaded)

Update from comments:

Idiom for session/transaction management:

Session sess = factory.openSession();
Transaction tx;
  try {
    tx = sess.beginTransaction();
    //do some work
    ...
    tx.commit();
  }
  catch (Exception e) {
    if (tx!=null) tx.rollback();
      throw e;
  }
  finally {
    sess.close();
  }

To reattach the "detached" objects (based on reference docs):

if object has been modified: update the object in new session

// in the first session
Cat cat = (Cat) firstSession.load(Cat.class, catId);
Cat potentialMate = new Cat();
firstSession.save(potentialMate);

// in a higher layer of the application
cat.setMate(potentialMate);

// later, in a new session
secondSession.update(cat);  // update cat
secondSession.update(mate); // update mate

if object is unmodified, can use lock():

//just reassociate:
sess.lock(fritz, LockMode.NONE);
//do a version check, then reassociate:
sess.lock(izi, LockMode.READ);
//do a version check, using SELECT ... FOR UPDATE, then reassociate:
sess.lock(pk, LockMode.UPGRADE);
marklai
Doesn't this, to some degree, break the whole point of lazy loading? I thought the point of having lazy loading was to not load the data until it was needed. If I use eager fetching or any of the related methods I'll be loading my whole database into memory. I thought part of hibernate was to try and avoid that...Is there no way to get lazy loading and session-per-request to play nice together?
Daniel Bingham
True. To keep lazy loading, you can reattach Foo instances to another Hibernate session and then it'll be able to load associated objects. However, I have trouble making this a brain-dead operation for client code. They have to keep in mind what's loaded and what's not...
marklai
What's the correct way to close a session then? And what's the correct way to reattach an object to a newly created session?
Daniel Bingham
Updated answer with some examples from reference docs. Again, the client code cannot simply do [Foo foo = getFoo()] then attempt [Bar bar = foo.getBar()]. It'll need to rely on a dao method like [Bar getBar(Foo f)] that fetches the Bar in a new session.
marklai
A: 
  1. The exception is caused by the object is not detached from the other session. It suggested you didn't close the other session.

  2. The prefer way to handle your problem is to keep the session alive until you finish with the work. Read this for detail.