/ Java

使用 EntityManager 取消 JPA 一级缓存

JPA 默认开启一级缓存(底层实现是在 EntityManager 层)。

当不同的查询结果映射到同样的 entity 时,一级缓存可能会导致返回数据不符合预期(只查询了一次,后续查询直接返回第一次查询的结果)。

一个例子

一个展示系统的后台,需要分别查询不同指标的月度趋势,查询结果复用同一个 Entity。

@Entity
public class MonthlyTrendEntity implements Serializable {

    private long id; // @Id

    private Integer year;
    private Integer month;

    private Integer value;
    
    // ...
}

某数据接口中,先后查询了两个不同数据指标的月度趋势。

// ...

// 流动资产趋势
            List<MonthlyTrendEntity> assetLiquidTrend = financialService.selectAssetLiquidTrend();
// 固定资产趋势
            List<MonthlyTrendEntity> assetFixedTrend = financialService.selectAssetFixedTrend;
            
// ...

调用该接口,查看接口返回结果,发现固定资产趋势的数值和流动资产数值相同。但数据库中的数值并不相同。

问题原因是 JPA 默认开启一级缓存(并且一级缓存无法直接通过配置关闭)。两次不同查询返回的月度趋势,如果恰好 Entity 的主键相同,第二次查询会直接利用第一次查询的结果,而非再次查询数据库。

解决方案:使用 EntityManager 消除缓存

1 自定义 Repository 接口类

public interface FinancialTrendRepository {

    List<MonthlyTrendEntity> selectAssetLiquidTrend();
    List<MonthlyTrendEntity> selectAssetFixedTrend();
    
}

2 自定义 Repository 实现类

@Repository
public class FinancialTrendRepositoryImpl implements FinancialTrendRepository {

    @PersistenceContext
    private EntityManager em;

    @Override
    public List<MonthlyTrendEntity> selectAssetLiquidTrend() {
        String sql = "select id, year, month, value from ..."; // 这里省略具体的查询逻辑

        Query query = em.createNativeQuery(sql, MonthlyTrendEntity.class); // 创建自定义查询,并将查询结果映射到 MonthlyTrendEntity
        em.clear(); // 清除缓存
        query.unwrap(NativeQueryImpl.class);
        return query.getResultList();
    }
    
    @Override
    public List<MonthlyTrendEntity> selectAssetFixedTrend() {
        String sql = "select id, year, month, value from ..."; // 这里省略具体的查询逻辑

        Query query = em.createNativeQuery(sql, MonthlyTrendEntity.class); // 创建自定义查询,并将查询结果映射到 MonthlyTrendEntity
        em.clear(); // 清除缓存
        query.unwrap(NativeQueryImpl.class);
        return query.getResultList();
    }
}

上述代码通过 em.clear();,在每次查询前清除 JPA 缓存,可解决后续查询直接返回第一次查询数据的问题。