使用 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 缓存,可解决后续查询直接返回第一次查询数据的问题。