Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Solving LazyInitializationException via ignorance

There are countless questions here, how to solve the "could not initialize proxy" problem via eager fetching, keeping the transaction open, opening another one, OpenEntityManagerInViewFilter, and whatever.

But is it possible to simply tell Hibernate to ignore the problem and pretend the collection is empty? In my case, not fetching it before simply means that I don't care.

This is actually an XY problem with the following Y:

I'm having classes like

class Detail {
    @ManyToOne(optional=false) Master master;
    ...
}

class Master {
    @OneToMany(mappedBy="master") List<Detail> details;
    ...
}

and want to serve two kinds of requests: One returning a single master with all its details and another one returning a list of masters without details. The result gets converted to JSON by Gson.

I've tried session.clear and session.evict(master), but they don't touch the proxy used in place of details. What worked was

 master.setDetails(nullOrSomeCollection)

which feels rather hacky. I'd prefer the "ignorance" as it'd be applicable generally without knowing what parts of what are proxied.

Writing a Gson TypeAdapter ignoring instances of AbstractPersistentCollection with initialized=false could be a way, but this would depend on org.hibernate.collection.internal, which is surely no good thing. Catching the exception in the TypeAdapter doesn't sound much better.

Update after some answers

My goal is not to "get the data loaded instead of the exception", but "how to get null instead of the exception" I

Dragan raises a valid point that forgetting to fetch and returning a wrong data would be much worse than an exception. But there's an easy way around it:

  • do this for collections only
  • never use null for them
  • return null rather than an empty collection as an indication of unfetched data

This way, the result can never be wrongly interpreted. Should I ever forget to fetch something, the response will contain null which is invalid.

like image 543
maaartinus Avatar asked Sep 06 '15 12:09

maaartinus


People also ask

How can LazyInitializationException be prevented?

The right way to fix a LazyInitializationException is to fetch all required associations within your service layer. The best option for that is to load the entity with all required associations in one query.

What is the LazyInitializationException?

Class LazyInitializationExceptionIndicates an attempt to access not-yet-fetched data outside of a session context. For example, when an uninitialized proxy or collection is accessed after the session was closed.


2 Answers

You could utilize Hibernate.isInitialized, which is part of the Hibernate public API.

So, in the TypeAdapter you can add something like this:

if ((value instanceof Collection) && !Hibernate.isInitialized(value)) {
   result = new ArrayList();
}

However, in my modest opinion your approach in general is not the way to go.

"In my case, not fetching it before simply means that I don't care."

Or it means you forgot to fetch it and now you are returning wrong data (worse than getting the exception; the consumer of the service thinks the collection is empty, but it is not).

I would not like to propose "better" solutions (it is not topic of the question and each approach has its own advantages), but the way that I solve issues like these in most use cases (and it is one of the ways commonly adopted) is using DTOs: Simply define a DTO that represents the response of the service, fill it in the transactional context (no LazyInitializationExceptions there) and give it to the framework that will transform it to the service response (json, xml, etc).

like image 122
Dragan Bozanovic Avatar answered Sep 24 '22 03:09

Dragan Bozanovic


What you can try is a solution like the following.

Creating an interface named LazyLoader

@FunctionalInterface // Java 8
public interface LazyLoader<T> {
    void load(T t);
}

And in your Service

public class Service {
    List<Master> getWithDetails(LazyLoader<Master> loader) {
        // Code to get masterList from session
        for(Master master:masterList) {
            loader.load(master);
        }        
    }
}

And call this service like below

Service.getWithDetails(new LazyLoader<Master>() {
    public void load(Master master) {
        for(Detail detail:master.getDetails()) {
            detail.getId(); // This will load detail
        }
    }
});

And in Java 8 you can use Lambda as it is a Single Abstract Method (SAM).

Service.getWithDetails((master) -> {
    for(Detail detail:master.getDetails()) {
        detail.getId(); // This will load detail
    }
});

You can use the solution above with session.clear and session.evict(master)

like image 42
shazin Avatar answered Sep 27 '22 03:09

shazin