Reputation: 9616
How to make the following work ?
public abstract class MyAbstractOne {
@Cacheable(value="myCache")
public MyObject getObject() {
//some code
return object;
}
}
the sub class
public class MySubClass extends MyAbstractOne {
@Cacheable(value="myCache")
public MyOtherObject getObjectConcrete() {
//some code
return object;
}
}
and the user of these objects
//from autowired instance
@Autowired MySubClass subObject;
then somewhere
//first call - may not retrieve cached objects
obj1 = subObject.getMyObject();
//second call - SHOULD retrieve a cached objects
obj2 = subObject.getMyObject();
why this fails
assertTrue(obj1.equals(obj2));
But the same for getMyObjectConcrete does not fail.
Upvotes: 0
Views: 2430
Reputation: 7981
Perhaps you need to check your "equals" (and "hashCode") implementation on your application domain object; making sure they are properly implemented and well formed (see Effective Java, 2nd Edition, Item 8 - Obey the the general contract when overriding equals).
I was able to get a small, simple application working similar to your code snippets above...
package org.spring.cache;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ObjectUtils;
/**
* The CachingWithConcurrentMapTest class is a test suite of test cases testing the contract and functionality
* of @Cacheable inheritance.
*
* @author John Blum
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see org.springframework.cache.annotation.Cacheable
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
* @since 1.0.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@SuppressWarnings("unused")
public class CachingWithConcurrentMapTest {
@Autowired
private FactorialComputeService computeService;
@Test
public void testCachedObject() {
ValueHolder<Long> twoSquared = computeService.squared(2l);
ValueHolder<Long> twoSquaredAgain = computeService.squared(2l);
assertEquals(twoSquared, twoSquaredAgain);
assertSame(twoSquared, twoSquaredAgain);
ValueHolder<Long> fourFactorial = computeService.factorial(4l);
ValueHolder<Long> fourFactorialAgain = computeService.factorial(4l);
assertEquals(fourFactorial, fourFactorialAgain);
assertSame(fourFactorial, fourFactorialAgain);
assertNotSame(twoSquared, fourFactorial);
ValueHolder<Long> eightSquared = computeService.squared(8l);
ValueHolder<Long> eightSquaredAgain = computeService.squared(8l);
assertEquals(eightSquared, eightSquaredAgain);
assertSame(eightSquared, eightSquaredAgain);
assertNotSame(twoSquared, eightSquared);
assertNotSame(fourFactorial, eightSquared);
}
@Service
public static class SquaredComputeService {
@Cacheable("Computations")
public ValueHolder<Long> squared(Long value) {
return new ValueHolder<>(value * value);
}
}
@Service
public static class FactorialComputeService extends SquaredComputeService {
@Cacheable("Computations")
public ValueHolder<Long> factorial(Long value) {
return new ValueHolder<>(computeFactorial(value));
}
protected long computeFactorial(long value) {
long result = value;
while (--value > 0) {
result *= value;
}
return result;
}
}
public static class ValueHolder<T> {
private T value;
public ValueHolder() {
this(null);
}
public ValueHolder(final T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(final T value) {
this.value = value;
}
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ValueHolder)) {
return false;
}
ValueHolder that = (ValueHolder) obj;
return ObjectUtils.nullSafeEquals(this.getValue(), that.getValue());
}
@Override
public int hashCode() {
int hashValue = 17;
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getValue());
return hashValue;
}
@Override
public String toString() {
return String.format("{ @type = %1$s, value = %2$s }", getClass().getName(), getValue());
}
}
}
And the corresponding Spring configuration...
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
">
<context:annotation-config/>
<cache:annotation-driven/>
<bean id="cacheManager" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager"/>
<bean id="service" class="org.spring.cache.CachingWithConcurrentMapTest.FactorialComputeService"/>
</beans>
Keep in mind that some Cache implementations (e.g. Pivotal GemFire) have option to "copy-on-read" or serialize values stored in the Cache. The former is useful for concerns like Transactions while the later is always applicable in a Cache that is also a Distribute Data Grid where (cached) data is partitioned (sharded) across the cluster of nodes.
There could be many factors affecting your results (i.e. equals method construction, read attributes, serialization) to be aware of, so take a look at the settings of your particular caching implementation.
Feel free to follow-up if you have any troubles with my example or our problem still persists. Perhaps you can shed some light on your application domain object types and the caching implementation/configuration in use.
Thanks.
Upvotes: 3