Spring and hibernate: Lazy loading collections in desktop (swing) applications
So here’s something you can’t find on the internets. Or at least i couldn’t, when i was trying to find a way to do this.
The problem:
Traditionally, spring and hibernate have been mostly used in web applications. In this case, you can easily define your domain objects to have lazy collections because spring/hibernate will keep an open session during the whole handling of each web request. This means that you can access lazy loading objects even in your views (provided you use the open session in view filter
).
Now, in desktop applications you usually don’t go around having open sessions all the time which means that, unfortunately, when you try to get a lazy loading collection, hibernate will give you a big finger in the form of a LazyInitializationException due to the fact of no open sessions existing.
One solution:
My application’s structure is such that no UI classes are being managed by spring (and by this i mean your mileage may vary with this solution), only my services and data access objects (DAOs) are. The UI accesses the services by use of a ServiceLocator pattern (which can be more or less complex, depending on your needs) and the DAOs in the services are autowired. The rest is just spring plumbing.
The way i found more elegant to deal with the lazy loading problem is the following. Take this bean (for the sake of simplicity some annotations or methods might be missing, beware):
@Entity
public class MyBean {
@Column(name = "NAME")
private String name;
@CollectionOfElements(fetch = FetchType.LAZY)
private Set<AnotherBean> children;
public Set<AnotherBean> getChildren() {
return children;
}
}
If you try to access getChildren() hibernate will complain so i changed the method to this:
public Set<AnotherBean> getChildren() {
children = DBUtils.initializeIfNeeded(this, children);
return children;
}
And here’s the definition of that static method:
public static <T> Set<T> initializeIfNeeded(Object obj, Set<T> collection) {
if (!(collection instanceof PersistentCollection)) {
return Sets.newHashSet();
}
if (!((PersistentCollection) collection).wasInitialized()) {
ServiceLocator.get(CompanyDA.class).initializeLazyCollection(obj, collection);
}
return collection != null ? collection : Sets.<T>newHashSet();
}
What we are doing is checking whether this collection is a instance of hibernate’s PersistentCollection and, if not, getting out. If it is, and hasn’t been initialized, we call a special method in the super of all my DAO’s that is responsible for asking hibernate to initialize the collection. Take into account that this method has to be inside a spring managed bean (i.e. non static) otherwise it won’t be able to open a new session and you’re back to square one.
Here’s the definition of that method in the DAO:
public void initializeLazyCollection(Object obj, Collection<? extends Object> collection) {
sessionFactory.getCurrentSession().lock(obj, LockMode.NONE);
new HibernateTemplate(sessionFactory).initialize(collection);
}
Hope this helps anyone and if you know of a better way to do the same thing, please share
Peace out.
Related:
- Spring : inject proxy instead of proxied object Today i found a neat trick. Typically when you inject...
- Java HTTP proxy servlet (with Spring) I always wanted to build my own proxy. I’m kidding....
Categorised as: findings, software development, tips, tutorials
Pedro my english is not good, I'm from argentine, so sorry if i write bad. I just implement your method, but when i call my function recovery, its returns the class and load the collection. My class is category and contain a List<Subcategory>. My mapping is
<class name="Categoria" table="categorias">
<id column="idCategoria" name="id" type="integer">
<generator class="identity" />
</id>
<property column="descripcion" name="descripcion" type="string" />
<bag name="subcategorias" table="subcategorias" lazy="true" cascade="all-delete- orphan">
<key column="idCategoria" />
<one-to-many class="Subcategoria" />
</bag>
</class>
if I change the code
public List<Subcategoria> getSubcategorias() {
return DbUtils.initializeIfNeeded(this, _subcategorias, "ICategoriaDao");
}
for
public List<Subcategoria> getSubcategorias() {
return _subcategorias;
}
if I change the code public List<Subcategoria> getSubcategorias() {
return DbUtils.initializeIfNeeded(this, _subcategorias, "ICategoriaDao");
}
for public List<Subcategoria> getSubcategorias() {
return _subcategorias;
}
again hibernate generate LazyInitializationException.
How I can load the list when just the property category.getSubcategory() is called?
Thanks
Hi Pablo,
I changed the code a little bit in the meantime. I'll update the post in a minute.
Cheers,
Pedro
Hi Pedro, sorry but I can't work your solution. I don't understand why when Hibernate retrieve the class, inmediatly retrieve the collection too. I replaced my code with your last modification and didn't work. Can you show me a test with the circuit complete please? Otherwise I can send you mi code, which is a basic create, update, delete and you see what is the problem.
Thanks
Pablo
Send me you code to pedro @ my domain, i'll take a look.
Hi, could you explain me how you implements the class ServiceLocator? Thanks
Hi Pablo,
here's my current implementation:
public class ServiceLocator {
private ApplicationContext applicationContext;
private static ServiceLocator provider;
private ServiceLocator() throws ExceptionInInitializerError {
try {
this.applicationContext = new ClassPathXmlApplicationContext(new String[] { "context/app-context.xml" });
} catch (Throwable ex) {
// logger.error(
// "Initial ApplicationContext creation failed — "
// + ex.getMessage(), ex);
throw new ExceptionInInitializerError(ex);
}
}
private synchronized static ServiceLocator getInstance() throws ExceptionInInitializerError {
if (provider == null) {
provider = new ServiceLocator();
}
return provider;
}
public static <T> T get(Class<T> clazz) {
return getInstance().applicationContext.getBean(clazz);
}
public static void init() {
getInstance();
}
public static void printAll() {
for (String obj : provider.applicationContext.getBeanDefinitionNames()) {
System.out.println(obj);
}
}
}
So basically every time i need to access a bean i can do it by calling:
ServiceLocator.get(SomeServiceBean.class)
Note that the SomeServiceBean class needs to be declared as a bean inside that context file, or by using annotations.
Hope it helps,
Pedro