记一次jpa缓存引起的数据不一致问题
问题描述:有一个查询数据字典类型的接口,此接口功能为根据数据字典类型ID,查询数据字典类型及其下面所有的数据字典。
一.重现问题
- 首先我们利用swagger请求此接口
这是该接口的service层
@Transactional(readOnly = true)
public Optional<DataDictionaryTypeVo> findByTypeId(Long typeId) {
return dataDictionaryTypeRepository.findById(typeId)
.map(dataDictionaryTypeVoMapper::toDto);
}
得到的相应json
{
"requestId": "5049dca4-3171-4e6a-be88-7a6dae635b77",
"success": true,
"data": {
"createdBy": "system",
"createdDate": "2019-09-09T03:38:38Z",
"lastModifiedBy": "system",
"lastModifiedDate": "2019-09-09T03:38:38Z",
"id": 152,
"name": "性别",
"dataDictionaries": [
{
"createdBy": "admin",
"createdDate": "2020-07-23T06:36:56Z",
"lastModifiedBy": "admin",
"lastModifiedDate": "2020-07-23T06:36:56Z",
"id": 37,
"name": "男",
"code": null,
"disabled": false,
"desc": null,
"level": 1,
"parentId": null,
"typeId": 152,
"typeName": "性别",
"children": []
},
{
"createdBy": "admin",
"createdDate": "2020-07-23T06:37:33Z",
"lastModifiedBy": "admin",
"lastModifiedDate": "2020-07-23T06:37:33Z",
"id": 38,
"name": "女",
"code": null,
"disabled": false,
"desc": null,
"level": 1,
"parentId": null,
"typeId": 152,
"typeName": "性别",
"children": []
}
]
}
...
...
}
可以看出,该接口正常得到数据字典类型和数据字典
-
添加一个类型为
性别
的数据字典
-
添加成功后,再次请求
1
步骤中的接口
重点来了,此时得到的还是1
中的结果,性别
数据字典类型中没有查出刚添加的不详
为了验证数据库中是否已经添加了改数据字典,我们请求另一接口
根据数据字典类型查询数据字典
可以看出能查出2
步骤添加的内容
二、发现并解决问题
经过查资料看日志,得到了jpa查询的话某些情况下会直接查询缓存的结论。我们
“1”
步骤中的那个接口是根据数据字典类型id,查询数据字典类型,并且带出数据字典类型下的所有数据字典类型,这种情况下如果没有在添加或者删除数据字典的时候操作jpa缓存,就会造成数据不一致的情况。当然我们也可以在日志中看到
解读:黄色框框起来的是步骤"2"
添加成功的日志,下面红色框框起来的是步骤"3"
中请求数据的接口,可以看出来,这边并没有打印sql语句,我们可以与下面的图对比一下
我们先来看下数据字典添加内容的service
public DataDictionaryDTO save(DataDictionaryDTO dataDictionaryDTO) throws Exception {
DataDictionary dataDictionary = dataDictionaryMapper.toEntity(dataDictionaryDTO);
dataDictionary = dataDictionaryRepository.save(dataDictionary);
DataDictionaryDTO result = dataDictionaryMapper.toDto(dataDictionary);
return result;
}
这种写法是没有操作缓存的,我们会遇到数据不一致的情况,接下来贴出符合我们业务场景的代码
public DataDictionaryDTO save(DataDictionaryDTO dataDictionaryDTO) throws Exception {
DataDictionary dataDictionary = dataDictionaryMapper.toEntity(dataDictionaryDTO);
dataDictionary = dataDictionaryRepository.save(dataDictionary);
DataDictionaryDTO result = dataDictionaryMapper.toDto(dataDictionary);
// 添加的情况,修改不需要进入if
if (dataDictionaryDTO.getId() == null) {
DataDictionary finalDataDictionary = dataDictionary;
Optional.ofNullable(dataDictionaryDTO.getTypeId())
.flatMap(dataDictionaryTypeRepository::findById)
.ifPresent(type -> type.addDataDictionary(finalDataDictionary));
}
return result;
}
这边贴一下DataDictionaryType类
public class DataDictionaryType{
...
...
@OneToMany(mappedBy = "type")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<DataDictionary> dataDictionaries = new HashSet<>();
...
...
public DataDictionaryType addDataDictionary(DataDictionary dataDictionary) {
this.dataDictionaries.add(dataDictionary);
dataDictionary.setType(this);
return this;
}
public DataDictionaryType removeDataDictionary(DataDictionary dataDictionary) {
this.dataDictionaries.remove(dataDictionary);
dataDictionary.setType(null);
return this;
}
...
...
}
这样我们就能正常使用符合我们业务场景的接口了
与上面的比较,可以把sql日志打印了,说明没有走jpa缓存