7

I have an instance of a class that I got from a Hibernate session. That session is long gone. Now, I'm calling toString() and I'm getting the expected LazyInitializationException: could not initialize proxy - no Session since I'm trying to access a reference which Hibernate didn't resolve during loading of the instance (lazy loading).

I don't really want to make the loading eager since it would change the query from about 120 characters to over 4KB (with eight joins). And I don't have to: All I want to display in toString() is the ID of the referenced object; i.e. something that Hibernate needs to know at this point in time (or it couldn't do the lazy loading).

So my question: How do you handle this case? Never try to use references in toString()? Or do you call toString() in the loading code just in case? Or is there some utility function in Hibernate which will return something useful when I pass it a reference which might be lazy? Or do you avoid references in toString() altogether?

Aaron Digulla
  • 297,790
  • 101
  • 558
  • 777
  • If Java had closure you could do: String x = lazyToString({ => this.getY() }) + lazyToString({ => this.getZ() }); and catch the expection in the lazyToString method. The overhead with inner classes (or try/catch) is too high to do this. – Thomas Jung Nov 12 '09 at 13:10
  • Yeah, but that wouldn't give me a session, either. – Aaron Digulla Nov 12 '09 at 14:02
  • That's right. You can than only print that the value is not loaded. I thought that was the intent. You won't be able to start a session and associate the object on the toString method call. – Thomas Jung Nov 12 '09 at 14:53
  • My point is that this information must be available somewhere (see my answer below) :) – Aaron Digulla Nov 12 '09 at 15:36

4 Answers4

6

It's possible to do this by setting the accesstype of the ID field to "property". like:

@Entity
public class Foo {
    // the id field is set to be property accessed
    @Id @GeneratedValue @AccessType("property")
    private long id;
    // other fields can use the field access type
    @Column private String stuff;
    public long getId() { return id; }
    public void setId(long id) { this.id = id; }
    String getStuff() { return stuff; }
    // NOTE: we don't need a setStuff method
}

It's explained here. This way the id field is allways populated when a proxy is created.

Gray
  • 108,756
  • 21
  • 270
  • 333
EJB
  • 2,338
  • 13
  • 15
  • +1 I like it; there is just a minor catch: I'm using DSL syntax, so my getter is called "id()", not "getId()". I guess I could add a second getter for this special case but maybe it's possible to tell Hibernate the name of the getter? – Aaron Digulla Nov 12 '09 at 14:36
  • Well, it's possible by creating your own implementation of org.hibernate.property.PropertyAccessor, and declare the fully qualified name as the value for @AccessType. On the other hand, you could create the setter (you'll need that too) and getter and make them private, so you won't see them from the rest of your app. – EJB Nov 12 '09 at 15:20
  • @EJB :Did it really work? I have a situation where class A -----> (has a one to many rel) with class B. Both A and B have multiple properties. So when I do a toString() method call for class A it fails with LazyInitialization exception (same as above) despite setting the @AccessType("property") for the Id field of class A. – raikumardipak Dec 16 '16 at 09:31
1

I've found a workaround:

public static String getId (DBObject dbo)
{
    if (dbo == null)
        return "null";

    if (dbo instanceof HibernateProxy)
    {
        HibernateProxy proxy = (HibernateProxy)dbo;
        LazyInitializer li = proxy.getHibernateLazyInitializer();
        return li.getIdentifier ().toString ();
    }

    try
    {
        return Long.toString (dbo.id ());
    }
    catch (RuntimeException e)
    {
        return "???";
    }
}

So what this code does is it fetches the ID (a 64bit number) from the object. DBObject is an interface which defines long id(). If the object is a Hibernate proxy, then I talk to its LazyInitializer to get the ID. Otherwise, I call id(). Usage:

class Parent {
    DBObject child;
    public String toString () {
        return "Parent (id=..., child=" + getId(child)+")");
    }
}
Aaron Digulla
  • 297,790
  • 101
  • 558
  • 777
1

I've found the way that best fits good practice is a modification of the solution found on this blog: http://www.nonfunc.com/2016/02/05/jpa-performance-gotcha-tostring-really/. It needed modification for nullable fields and for Collections objects.

public toString() {
  return String.format("FuBar [id=%d" +  
      + ", fu=%s" // fu is a lazy non-nullable field
      + ", bar=%s" // bar is a nullable lazy field
      + ", borks=%s]", // borks is a lazy collection of Bork objects
      id,
      fu instanceof HibernateProxy ? "[null]" : bar.toString(),
      bar == null || bar instanceof HibernateProxy ? "[null]" : bar.toString(),
      borks instanceof PersistentSet ? "[null]" : borks.toString());
}
Nielsvh
  • 1,027
  • 1
  • 17
  • 30
  • 1
    I see what you're doing there. Some comments: Mixing `String.format()` and `+` is bad for performance. You should also replace `[null]` with `` because the proxy isn't null - it's just not loaded. An even better approach would be to return `` where `TYPE` is the entity type and `ID` is the primary key. – Aaron Digulla Jun 06 '17 at 12:32
  • Edited code for performance. The example wasn't exactly what I have and the concatenation of a variable and string does effect performance (the format string should be compiled into a static string), but I'd think that sweating over a toString's performance isn't super important, since it would be used for debugging or error reporting and shouldn't be called super frequently in production situations. I like the suggestion of specifying lazy vs null, though. – Nielsvh Jun 06 '17 at 18:46
0

If all you want to return is the ID of the object, I imagine calling getID(), then parse the int/long as a String value at the moment you want it displayed would work just fine. At least that's how it seems based on the question.

EDIT

How to solve the LazyInitializationException using JPA and Hibernate

After viewing the comment and doing some searching I believe this may be most beneficial to your scenario.

Community
  • 1
  • 1
Woot4Moo
  • 22,887
  • 13
  • 86
  • 143
  • That will throw the LazyInitializationException because the reference hasn't been resolved, yet. – Aaron Digulla Nov 12 '09 at 13:26
  • Aaron, after reading this comment I edited my post. Please see the new information and let me know if this resolves the issue. – Woot4Moo Nov 12 '09 at 13:40
  • @Woot - The answers won't help. The lazy initialized values will never be read. The tx is committed. The connection closed. – Thomas Jung Nov 12 '09 at 13:47
  • Yes I see that the transaction is already committed. However, in the post it is a possibility to refactor the existing code to use the OpenSessionInView pattern found here: http://www.hibernate.org/43.html If that is not doable in Aaron's environment than so be it. – Woot4Moo Nov 12 '09 at 13:58
  • @Woot: I have to agree with Thomas. – Aaron Digulla Nov 12 '09 at 13:58
  • At the time I call toString(), I have no session and I don't want to create one. The question is really: How can I make toString() work inside and outside of a session? – Aaron Digulla Nov 12 '09 at 14:00