【发布时间】:2017-11-24 09:09:03
【问题描述】:
我面临的问题是从 spring 可缓存方法返回的具有相同键的两个对象未通过 assertSame 测试。为什么这些对象不共享同一个存储区域?
详情: 我正在使用redis缓存机制在spring boot REST api中实现缓存。 缓存正常工作的方式是它首先从外部提供的源(访问数据库的 JPS 存储库)检索数据,然后对相同缓存键的后续调用从缓存中返回数据。但是,我无法在 JUnit 测试用例中完全模仿这种行为。我的 assertEquals 或 assertSame 在从缓存返回的 2 个对象上失败。
我的代码库如下所示: mvn 依赖:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.7.6.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
Spring 应用配置:
@SpringBootApplication
@EnableCaching
public class Application {
@Value("${redis.host}")
private String redisHost;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory jedisConFactory = new JedisConnectionFactory();
jedisConFactory.setHostName(redisHost);
jedisConFactory.setPort(6379);
return jedisConFactory;
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(jedisConnectionFactory());
return template;
}
@Bean
CacheManager cacheManager() {
return new RedisCacheManager(redisTemplate());
}
服务类:
@Service
public class CIDomainService {
private RedisTemplate<String, Object> redisTemplate;
private CIDomainDAO ciDomainDAO;
@Autowired
public CIDomainService(CIDomainDAO ciDomainDAO, RedisTemplate<String, Object> redisTemplate) {
this.ciDomainDAO = ciDomainDAO;
this.redisTemplate = redisTemplate;
}
@Cacheable(value = "ciDomain", key = "#id")
public CIDomain getCIDomain(int id) {
CIDomain ciDomain = new CIDomain();
ciDomain.setId(id);
ciDomain.setName("SomeName");
return ciDomain;
}
public void clearAllCache() {
redisTemplate.delete("listCIDomains");
redisTemplate.delete("ciDomain");
}
}
上述服务中的ciDomainDAO 只是一个JPS 存储库接口,它使用findAll() 方法从外部数据库或内存数据库中检索数据。我的测试课:
@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles("local")
@SpringBootTest
public class CIDomainServiceIntegrationTest {
@Autowired
CIDomainService ciDomainServiceSpy;
@Before
public void setUp(){
ciDomainServiceSpy.clearAllCache();
}
@Test
public void listCIDomains_ShouldRetrieveCIDomainsWithCachingPluggedIn() {
CIDomain domain1 = ciDomainServiceSpy.getCIDomain(1);
CIDomain domain2 = ciDomainServiceSpy.getCIDomain(2);
CIDomain domain3 = ciDomainServiceSpy.getCIDomain(1);
assertSame(domain1, domain3); //fails
}
我的领域类:
@Entity
@Table(name = "CI_DOMAIN")
public class CIDomain implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;
@Column(name = "name")
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
基于this post 我知道第一次调用时从存储库中检索对象,然后如果提供了相同的“密钥”,稍后调用将从缓存中获取该对象。我在上面的测试用例中做同样的事情,但 assertSame 失败了。 Spring cacheable 必须是在内存中为给定请求获取的缓存对象。为什么每次都为相同的请求密钥发送不同的对象。
我尝试了另一种解决方案,我在服务类上使用了 spy 并根据相同的密钥请求验证方法调用。但是,我在这样做时遇到了不同的问题。在服务类上创建间谍甚至不使用缓存机制,即使提供了相同的密钥,它也会调用服务 getCIDomain 方法。我关注了this、this、this、this、this 和许多其他帖子以进行进一步分析,但无法通过间谍的 assertSame 使其工作。
任何帮助将不胜感激。
【问题讨论】:
-
您正在检查它们是否相同(
assertSame(,它们不是。它们应该相等(assertEqual)。相等和相同是不同的事情。对于后者通过你的域对象中需要一个equals和hashCode方法。 -
@M.Deinum 感谢您的回复。我可以根据序列化和反序列化“相同”(等于通过)java 对象具有两个不同的引用或 ID 的事实来推断您的评论,因此 object1 == object2 将是错误的。但是,使用 equals 代替 assertSame 对我来说有点问题,原因之一是我认为“我需要使用 assertSame,否则代码依赖于 .equals(Object),它尚未显示。它可能已被覆盖比较所有属性,然后此测试将始终通过,即使没有缓存任何内容。"
-
相同不等于。 assertSame 确实
domain1 == domain3不会出现这种情况,因为它是一个不同的对象实例。你真的应该使用equals.而不是别的东西。如前所述,缓存(由于使用 Redis)正在序列化和序列化对象,因此它们永远不会相同。即使您使用 java 序列化,对象也永远不会相同。相信我,您确实必须检查对象是否相等,而不是检查它们是否相同(因为它们永远不会相同)。 -
@M.Deinum 是的,这是有道理的,但如上所述,我对使用 equals 唯一关心的是,我如何确保缓存是否正常工作。假设缓存不起作用 - 在这种情况下,该方法将从原始存储库中获取数据,并且我的 equals 测试仍将通过,因为我正在根据相同的 ID 检索数据。因此,“domain1”和“domain2”都将具有相同的 ID 和 NAME,因此即使缓存没有真正发生,也会通过相等的。这就是我的担忧。
-
我会说你的测试是错误的。您应该测试多个调用,并且只有第一次调用实际方法,而不是随后的缓存结果。那是你应该测试的,而不是方法的结果。