Declarative caching and Hibernate
We successfully use the Caching SpringModule. In short, this allows us to declaratively cache values returned from method calls. The Caching module supports many different cache implementations; we have picked OsCache for development. To get started, let’s take a look at the MemberService and its implementation.
public interface MemberService {
@Nullable
Member complicatedLookup(@NotNull ComplexArgument argument);
}
public class DefaultMemberService implements MemberService {
@Nullable
@Cacheable(modelId = "Member")
public Member complicatedLookup(@NotNull ComplexArgument argument) {
// some complex stuff
}
}
This code works perfectly well: when you call the DefaultMemberService.complicatedLookup(...) method more than once with the same (read equal(Object)) ComplexArgument object, the caching framework intercepts the call in and only the first call will go to do the // some complex stuff.
The problem is when you return an object from a Hibernate DAO call and that object is enhanced by CGLIB. Now the cache has a reference to an object that references an invalid Session.
We found this when our integration tests worked fine on their own, but started failing when we ran them as part of our automatic build. The prospect of going through every possible code path to find out if there is a cached CGLIB-enhanced object was daunting. Luckily, Spring AOP was quick to rescue. We wrote a simple little aspect that checks that any method with the @Cacheable annotation does not return a CGLIB-enahanced object.
@Aspect
public class CacheManagerAspect {
@AfterReturning(pointcut =
"@annotation(org.springmodules.cache.annotations.Cacheable)",
returning = "ret",
argNames = "jp, ret")
public void indicateLazilyLoaded(JoinPoint jp, Object ret) throws Exception {
checkNotCGLIBEnhanced(jp, ret);
}
private void checkNotCGLIBEnhanced(JoinPoint jp, Object o)
throws IllegalAccessException, InvocationTargetException {
if (o == null) return;
String name = o.getClass().getName();
if (!name.startsWith("uk.co.package")) return;
if (name.contains("$$Enhancer"))
throw new RuntimeException(
"You cannot cache a CGLIB-Enhanced object at " + jp);
PropertyDescriptor[] descriptors =
BeanUtils.getPropertyDescriptors(o.getClass());
for (PropertyDescriptor descriptor : descriptors) {
Object value = descriptor.getReadMethod().invoke(o);
checkNotCGLIBEnhanced(jp, value);
}
}
}
Simple, but powerful stuff from SpringModules and Spring AOP!
Tags: Caching, Hibernate, Spring AOP