集成 Spring JPA Data 和 Spring Cache 时的奇怪行为

Posted

技术标签:

【中文标题】集成 Spring JPA Data 和 Spring Cache 时的奇怪行为【英文标题】:Strange behaviour when integrate Spring JPA Data and Spring Cache 【发布时间】:2016-04-14 20:58:57 【问题描述】:

当我集成 Spring JPA Data 和 Spring Cache 时,出现了一个我无法解释的奇怪行为。

我正在使用 Spring Boot 来设置我的演示项目。代码如下。

我的配置 bean:

@Configuration
public class AppConfig 

    @Bean
    public CacheManager cacheManager() 
        return new ConcurrentMapCacheManager("Person");
    

我的实体 bean。

@Entity
public class Person implements Serializable 

    private static final long serialVersionUID = 2263555241909370718L;

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    public Long getId() 
        return id;
    

    public void setId(Long id) 
        this.id = id;
    

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    


我的 JPA 界面。我覆盖了 JpaRepository 中的一些方法并添加了@cachable 注释。

public interface PersonRepository extends JpaRepository<Person, Long> 

    @Override
    @CacheEvict(value = "Person", allEntries = true)
    public void delete(Long id);

    @Cacheable("Person")
    public Person findByName(String name);

    @Override
    @Query("select p from Person p where p.id = :id + 1L")
    @Cacheable("Person")
    public Person findOne(@Param("id") Long id);

我的单元测试类

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringDataDemoApplication.class)
public class SpringDataDemoApplicationTests 

    @Autowired
    private PersonRepository personRepository;

    @Autowired
    private CacheManager cacheManager;

    @Before
    public void setup() 
        Person p1 = new Person();
        p1.setName("Chris");
        personRepository.save(p1);
    

    @Test
    public void test2() 

        JpaRepository<Person, Long> jpaRepository = personRepository;

        Person p = personRepository.findOne(0L);
        Assert.assertNotNull(p);

        p = personRepository.findOne(0L);
        Assert.assertNotNull(p);

        System.err.println("---------------------------------------");

        p = jpaRepository.findOne(0L);
        Assert.assertNotNull(p);

        p = jpaRepository.findOne(0L);
        Assert.assertNotNull(p);
    


输出很奇怪。

Hibernate: insert into person (id, name) values (default, ?)
Hibernate: select person0_.id as id1_0_, person0_.name as name2_0_ from person person0_ where person0_.id=?+1
---------------------------------------
Hibernate: select person0_.id as id1_0_, person0_.name as name2_0_ from person person0_ where person0_.id=?+1
Hibernate: select person0_.id as id1_0_, person0_.name as name2_0_ from person person0_ where person0_.id=?+1

它应该只打印出我期望的一条 sql 语句。 jpaRepository.findOne(0L) 不使用缓存对象。

在我将 PersonRepository 接口分配给其父接口 JpaRepository 后,缓存注释不起作用。

这两个变量完全指向同一个引用,即使它是一个代理对象。为什么调用同一个引用的方法导致两个不同的结果?

我还注意到@Query 注释运行良好。 JpaRepository 和 PersonRepository 引用都使用定制的 SQL。

我想 Spring Cache 和 Spring JPA Data 如何生成代理顾问之间可能存在一些差异。这可能是一个错误吗?

【问题讨论】:

正在遍历实际的方法签名以确定。第一个是PersonRepository.findOne,另一个是JpaRepository.findOne。使用 Spring Data 时已经有很多代理,并且添加缓存不会使其更清晰。您是否真的想要 Spring Caching 或者您是否真的想要由 JPA 提供程序管理的二级缓存。缓存的用途完全不同。 【参考方案1】:

@EnableCaching 添加到您的配置中:

@EnableCaching
@Configuration
public class AppConfig 

    @Bean
    public CacheManager cacheManager() 
        return new ConcurrentMapCacheManager("Person");
    

声明缓存注释本身不会自动触发它们的动作,您应该使用EnableCaching 注释以声明方式启用Caching 行为。这种方法的优点之一是您可以通过仅删除一个配置行而不是代码中的所有注释来禁用它。

【讨论】:

我将@EnableCahin 注释添加到我的引导类中,并且没有将该类粘贴到问题中。这不是这个问题的根本原因。【参考方案2】:

我想我找到了发生这种情况的原因。我添加了一些 aop 代码来跟踪存储库中调用了什么方法。

@Aspect
@Configuration
public class AppConfig 

    @Bean
    public CacheManager cacheManager() 
        return new ConcurrentMapCacheManager();
    

    @AfterReturning("execution(* org..*Repository.*(..))")
    public void logServiceAccess(JoinPoint joinPoint) 

        Arrays.asList(joinPoint.getTarget().getClass().getMethods()) //
                .stream() //
                .filter(m -> m.getName().startsWith("findOne")) //
                .forEach(m -> System.err.println(m));

        System.err.println("Completed: " + joinPoint);
    

输出是

public final java.lang.Object com.sun.proxy.$Proxy66.findOne(java.io.Serializable)
public final org.chris.demo.domain.Person com.sun.proxy.$Proxy66.findOne(java.lang.Long)

Spring 容器代理 2 findOne 具有不同参数的方法。我认为这是由通用代码引起的,因为所有通用代码都会在编译后被删除。

当我使用父接口调用方法时,它调用public final java.lang.Object com.sun.proxy.$Proxy66.findOne(java.io.Serializable) 方法。而且在那个方法上,没有办法添加@cacheable注解。

不知道有没有办法聚焦Spring容器在有子接口的情况下只生成一个findOne方法被泛型编程覆盖。

【讨论】:

以上是关于集成 Spring JPA Data 和 Spring Cache 时的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

集成 Spring JPA Data 和 Spring Cache 时的奇怪行为

spring boot + spring data jpa

spring-data详解之spring-data-jpa:简单三步快速上手spring-data-jpa开发

spring boot 1.5.4 集成spring-Data-JPA

将 Spring Data JPA 与 GCP Spanner 集成

spring-boot与spring-data-JPA的简单集成使用