What I have:
@Entity
public class MyEntity {
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
@JoinColumn(name = "myentiy_id")
private List<Address> addreses;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
@JoinColumn(name = "myentiy_id")
private List<Person> persons;
//....
}
public void handle() {
Session session = createNewSession();
MyEntity entity = (MyEntity) session.get(MyEntity.class, entityId);
proceed(session); // FLUSH, COMMIT, CLOSE session!
Utils.objectToJson(entity); //TROUBLES, because it can't convert to json lazy collections
}
What a problem:
The problem is that I can't pull lazy collection after session has been closed. But I also can't not close a session in proceed method.
What a solution (coarse solution):
a) Before session is closed, force hibernate to pull lazy collections
entity.getAddresses().size();
entity.getPersons().size();
....
b) Maybe more ellegant way is to use @Fetch(FetchMode.SUBSELECT)
annotation
Question:
What is a best practice/common way/more ellegant way to do it? Means convert my object to JSON.
This question is related to
java
hibernate
lazy-loading
Try use Gson
library to convert objects to Json
Example with servlets :
List<Party> parties = bean.getPartiesByIncidentId(incidentId);
String json = "";
try {
json = new Gson().toJson(parties);
} catch (Exception ex) {
ex.printStackTrace();
}
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(json);
Not the best solution, but here is what I got:
1) Annotate getter you want to initialize with this annotation:
@Retention(RetentionPolicy.RUNTIME)
public @interface Lazy {
}
2) Use this method (can be put in a generic class, or you can change T with Object class) on a object after you read it from database:
public <T> void forceLoadLazyCollections(T entity) {
Session session = getSession().openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
session.refresh(entity);
if (entity == null) {
throw new RuntimeException("Entity is null!");
}
for (Method m : entityClass.getMethods()) {
Lazy annotation = m.getAnnotation(Lazy.class);
if (annotation != null) {
m.setAccessible(true);
logger.debug(" method.invoke(obj, arg1, arg2,...); {} field", m.getName());
try {
Hibernate.initialize(m.invoke(entity));
}
catch (Exception e) {
logger.warn("initialization exception", e);
}
}
}
}
finally {
session.close();
}
}
You can traverse over the Getters of the Hibernate object in the same transaction to assure all lazy child objects are fetched eagerly with the following generic helper class:
HibernateUtil.initializeObject(myObject, "my.app.model");
package my.app.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import org.aspectj.org.eclipse.jdt.core.dom.Modifier;
import org.hibernate.Hibernate;
public class HibernateUtil {
public static byte[] hibernateCollectionPackage = "org.hibernate.collection".getBytes();
public static void initializeObject( Object o, String insidePackageName ) {
Set<Object> seenObjects = new HashSet<Object>();
initializeObject( o, seenObjects, insidePackageName.getBytes() );
seenObjects = null;
}
private static void initializeObject( Object o, Set<Object> seenObjects, byte[] insidePackageName ) {
seenObjects.add( o );
Method[] methods = o.getClass().getMethods();
for ( Method method : methods ) {
String methodName = method.getName();
// check Getters exclusively
if ( methodName.length() < 3 || !"get".equals( methodName.substring( 0, 3 ) ) )
continue;
// Getters without parameters
if ( method.getParameterTypes().length > 0 )
continue;
int modifiers = method.getModifiers();
// Getters that are public
if ( !Modifier.isPublic( modifiers ) )
continue;
// but not static
if ( Modifier.isStatic( modifiers ) )
continue;
try {
// Check result of the Getter
Object r = method.invoke( o );
if ( r == null )
continue;
// prevent cycles
if ( seenObjects.contains( r ) )
continue;
// ignore simple types, arrays und anonymous classes
if ( !isIgnoredType( r.getClass() ) && !r.getClass().isPrimitive() && !r.getClass().isArray() && !r.getClass().isAnonymousClass() ) {
// ignore classes out of the given package and out of the hibernate collection
// package
if ( !isClassInPackage( r.getClass(), insidePackageName ) && !isClassInPackage( r.getClass(), hibernateCollectionPackage ) ) {
continue;
}
// initialize child object
Hibernate.initialize( r );
// traverse over the child object
initializeObject( r, seenObjects, insidePackageName );
}
} catch ( InvocationTargetException e ) {
e.printStackTrace();
return;
} catch ( IllegalArgumentException e ) {
e.printStackTrace();
return;
} catch ( IllegalAccessException e ) {
e.printStackTrace();
return;
}
}
}
private static final Set<Class<?>> IGNORED_TYPES = getIgnoredTypes();
private static boolean isIgnoredType( Class<?> clazz ) {
return IGNORED_TYPES.contains( clazz );
}
private static Set<Class<?>> getIgnoredTypes() {
Set<Class<?>> ret = new HashSet<Class<?>>();
ret.add( Boolean.class );
ret.add( Character.class );
ret.add( Byte.class );
ret.add( Short.class );
ret.add( Integer.class );
ret.add( Long.class );
ret.add( Float.class );
ret.add( Double.class );
ret.add( Void.class );
ret.add( String.class );
ret.add( Class.class );
ret.add( Package.class );
return ret;
}
private static Boolean isClassInPackage( Class<?> clazz, byte[] insidePackageName ) {
Package p = clazz.getPackage();
if ( p == null )
return null;
byte[] packageName = p.getName().getBytes();
int lenP = packageName.length;
int lenI = insidePackageName.length;
if ( lenP < lenI )
return false;
for ( int i = 0; i < lenI; i++ ) {
if ( packageName[i] != insidePackageName[i] )
return false;
}
return true;
}
}
When having to fetch multiple collections, you need to:
Hibernate.initialize
for the remaining collections.So, in your case, you need a first JPQL query like this one:
MyEntity entity = session.createQuery("select e from MyEntity e join fetch e.addreses where e.id
= :id", MyEntity.class)
.setParameter("id", entityId)
.getSingleResult();
Hibernate.initialize(entity.persons);
This way, you can achieve your goal with 2 SQL queries and avoid a Cartesian Product.
There are some kind of misunderstanding about lazy collections in JPA-Hibernate. First of all let's clear that why trying to read a lazy collection throws exceptions and not just simply returns NULL for converting or further use cases?.
That's because Null fields in Databases especially in joined columns have meaning and not simply not-presented state, like programming languages. when you're trying to interpret a lazy collection to Null value it means (on Datastore-side) there is no relations between these entities and it's not true. so throwing exception is some kind of best-practice and you have to deal with that not the Hibernate.
So as mentioned above I recommend to :
also as described in other answers there are plenty of approaches(eager fetch, joining etc.) or libraries and methods for doing that, but you have to setting up your view of what's happening before dealing with the problem and solving it.
It's probably not anywhere approaching a best practice, but I usually call a SIZE
on the collection to load the children in the same transaction, like you have suggested. It's clean, immune to any changes in the structure of the child elements, and yields SQL with low overhead.
Place the Utils.objectToJson(entity); call before session closing.
Or you can try to set fetch mode and play with code like this
Session s = ...
DetachedCriteria dc = DetachedCriteria.forClass(MyEntity.class).add(Expression.idEq(id));
dc.setFetchMode("innerTable", FetchMode.EAGER);
Criteria c = dc.getExecutableCriteria(s);
MyEntity a = (MyEntity)c.uniqueResult();
With Hibernate 4.1.6 a new feature is introduced to handle those lazy association problems. When you enable hibernate.enable_lazy_load_no_trans property in hibernate.properties or in hibernate.cfg.xml, you will have no LazyInitializationException any more.
For More refer : https://stackoverflow.com/a/11913404/286588
You can use the @NamedEntityGraph
annotation to your entity to create a loadable query that set which collections you want to load on your query.
The main advantage of this choice is that hibernate makes one single query to retrieve the entity and its collections and only when you choose to use this graph, like this:
Entity configuration
@Entity
@NamedEntityGraph(name = "graph.myEntity.addresesAndPersons",
attributeNodes = {
@NamedAttributeNode(value = "addreses"),
@NamedAttributeNode(value = "persons"
})
Usage
public MyEntity findNamedGraph(Object id, String namedGraph) {
EntityGraph<MyEntity> graph = em.getEntityGraph(namedGraph);
Map<String, Object> properties = new HashMap<>();
properties.put("javax.persistence.loadgraph", graph);
return em.find(MyEntity.class, id, properties);
}
if you using jpa repository, set properties.put("hibernate.enable_lazy_load_no_trans",true); to jpaPropertymap
Source: Stackoverflow.com