我們正在開發無頭 CMS,我們有一個龐大的系統,其中包含一些我們稱之為EntityItems/ContentItems. 這些基本上是資料庫中可以由用戶建模的物體。他們有一種JSON風格的方法,其中這些模型可以包含數字、字串等。這意味著這些專案是我們系統的核心,在大專案中可以使用很多。
@Entity
@Getter
@Setter
@EqualsAndHashCode(callSuper = true, exclude = {"children", "parent"})
@NoArgsConstructor
@Table(indexes = {
@Index(name = "entityitem_objectState", columnList = "objectState"),
@Index(name = "entityitem_appscope", columnList = "appScope"),
@Index(name = "entityitem_type_id", columnList = "type_id")})
public class EntityItem extends AbstractContentItem {
private static final long serialVersionUID = 1L;
@ManyToOne(fetch = FetchType.EAGER)
private EntityItemType type;
....
@Convert(converter = ObjectMapToJsonConverter.class)
@Column(name = "itemProperties", columnDefinition = "jsonb")
private Map<String, String> itemPropertiesJson;
}
我們還有一個基于事件的系統,我們為用戶提供對洗掉、創建和更新此類EntityItems/ContentItems. 在其中一個事件中,我們有一個名為的屬性changedProperties,用于記錄此事件中發生的更改。
public class ContentItemChangedEvent {
private final long itemId;
private final List<String> changedProperties;
}
最后,我們有一個EntityItemServiceImpl適用于此類的EntityItems. 這有一個容器管理的物體管理器和一個@Transactional保存方法。
@Service
@Log4j
@Validated
public class EntityItemServiceImpl {
@Autowired
private EntityItemRepository entityItemRepository;
@Autowired
@Qualifier("defaultEntityManager")
private EntityManager entityManager;
//...
}
這entityItemRepository是一個基本的 Jpa/Crud 存盤庫。
public interface EntityItemRepository extends CrudRepository<EntityItem, Long>, JpaRepository<EntityItem, Long>, JpaSpecificationExecutor<EntityItem> {
....
}
save呼叫該方法時,我們希望接收該專案,獲取其 JSON 屬性并將它們與資料庫中當前未提交的內容進行比較。
@Transactional
public synchronized EntityItem save(EntityItem item, ...) {
...
if (item.getId() != null) {
var oldItem = this.entityItemRepository.findById(item.getId()).orElse(null);
var changedProperties = this.getChangedProperties(item, oldItem);
}
...
this.entityItemRepository.save(item);
...
}
現在的問題:
有時在保存專案時,它已經由 管理EntityManager,因為它在同一個事務中,因此一級快取啟動并始終回傳專案的相同實體。簡而言之,當呼叫this.entityItemRepository.findById(item.getId())與作為引數傳遞的實體相同的實體時(由于第一級快取,如上所述)。
為了解決這個問題,我們嘗試了以下方法,我們引入了另一種方法fetchOldVersionOfItem(EntityItem item),該方法現在看起來像:
@Transactional
public synchronized EntityItem save(EntityItem item, ...) {
...
if (item.getId() != null) {
var oldItem = this.entityItemRepository.fetchOldVersionOfItem(item).orElse(null);
var changedProperties = this.getChangedProperties(item, oldItem);
}
...
this.entityItemRepository.save(item);
...
}
我們嘗試了以下實作,但由于某種原因都失敗了:1)
private Optional<EntityItem> fetchOldVersionOfItem(EntityItem item) {
this.entityManager.clear();
var oldItemOpt = this.findById(item.getId());
this.entityManager.clear();
return oldItemOpt;
}
Here, two saves were happening very quickly (as this is our core feature in the CMS) and was resulting in a row was updated or deleted error. Replacing this code with the usual findById no longer yielded this error.
2)
private Optional<EntityItem> fetchOldVersionOfItem(EntityItem item) {
if (this.entityManager.contains(item)) {
this.entityManager.detach(item);
var oldItemOpt = this.findById(item.getId());
oldItemOpt.ifPresent(oldItem -> this.entityManager.detach(oldItem));
this.entityManager.merge(item);
return oldItemOpt;
}
return findById(item.getId());
}
This code was producing an Optimistic Lock Exception, due to some succession of saves. Removing it no longer produced the error.
3)
private Optional<EntityItem> fetchOldVersionOfItem(EntityItem item) {
if (this.entityManager.contains(item)) {
this.entityManager.detach(item);
var oldItemOpt = this.findById(item.getId());
oldItemOpt.ifPresent(oldItem -> this.entityManager.detach(oldItem));
this.entityManager.merge(item);
return oldItemOpt;
}
var oldItemOpt = findById(item.getId());
oldItemOpt.ifPresent(oldItem -> this.entityManager.detach(oldItem));
return oldItemOpt;
}
Here it had the same issue as the above one.
Now for the question: What did we do wrong with these two solutions and what is the best way to do something like this?
Note that we tested different variants of the given solutions (introducing and removing .contains checks and etc.) and the results were the same. Also, this functionality of the system will be under a lot of stress as basically everything that happens in our projects is somehow related to saving the items somehow.
Thanks for your time!
uj5u.com熱心網友回復:
將舊狀態保存在瞬態欄位(不受 jpa 管理)中可能是實作此功能的好方法,因為它還避免了額外的 DB 往返需要。
例如,您可以在“@PostLoad”回呼中克隆原始地圖內容(不僅是參考),類似于此處為完整物體所做的操作: Getting object field previous value hibernate JPA
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/384504.html
