Reputation: 1717
Can anyone help me with a JPA issue that I thought would have been simple. I'm trying to write a general-purpose lazy load manager in my JPA persistence framework, so layers higher up the application stack can access lazy-loaded data without having to deal with the specifics.
I have a lazy load manager:
public class JpaLazyLoader extends AbstractJpaDAO<Void> implements LazyLoader
{
public JpaLazyLoader()
{
super( void.class );
}
@Transactional(readOnly=true)
public <T,E> T get( ILazyGetter<T,E> p_getter ) throws Exception {
// reattach the object to the session
E l_entity = getEntityManager().merge( p_getter.getEntity() );
// return the getter data
return p_getter.get( l_entity );
}
}
The lazy getter is like this:
public interface ILazyGetter<T,E> extends Serializable
{
public E getEntity();
public T get( E p_entity ) throws Exception;
}
And the idea is that it would be used like this:
return m_lazyLoader.get( new ILazyGetter<Collection<Child>, Parent>() {
private static final long serialVersionUID = 1L;
public Parent getEntity() {
return getValue(); // get the parent object from somewhere
}
public Collection<Child> get( Parent p_entity ) throws Exception {
// children are a lazy-loaded Set<Child>
return p_entity.getChildren();
}
} );
The annotations in Parent are like this:
@Entity(name="parent")
public class Parent implements Serializable {
private static final long serialVersionUID = 1L;
....
@Id
@Column(name="id")
@GeneratedValue(strategy=GenerationType.AUTO)
protected Long id;
@OneToMany
@JoinTable
(
name="parent_child_associations",
joinColumns={ @JoinColumn(name="parent_id", referencedColumnName="id") },
inverseJoinColumns={ @JoinColumn(name="child_id", referencedColumnName="id", unique=true) }
)
protected Set<Child> children;
}
The parent object was loaded in a different transaction and then detached. I hoped it would be a trivial thing to reattach the parent to another session (within the @transactional bit) but I can't get it to work. I've tried optimistic/pessimistic/none locking before and after the merge but none seem to work.
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: test.Parent.children, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:566)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:186)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:545)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:124)
at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:180)
Is this just the wrong way to go about things? If so, what is the 'correct' way? If not, what am I doing wrong?
Thanks for any help
Upvotes: 1
Views: 3390
Reputation: 1717
OK well thanks to Andre I've got it working.
I simply hadn't initialised the collection object, instead I was just calling parent.getChildren(), which only returned the proxy object rather than forcing the fetch.
The updated get method is below for anyone who has a similar need:
@Transactional(readOnly=true)
public <T,E> T get( ILazyGetter<T,E> p_getter ) throws Exception {
// reattach the object to the session
E l_entity = getEntityManager().merge( p_getter.getEntity() );
T l_lazyData = p_getter.get( l_entity );
// Just getting the data doesn't necessarily initialize it -
// explicitly initialize it depending on the data type.
intializeEntity( l_lazyData );
// return the getter data
return l_lazyData;
}
/**
* Attempt to initialize the entity so it is fully lazy loaded while in the transaction
* @param p_entity
*/
protected void intializeEntity( Object p_entity ) {
if( p_entity != null && p_entity instanceof Collection ) {
Collection<?> l_entity = (Collection<?>)p_entity;
l_entity.size(); // force the collection to load
}
}
Upvotes: 1