hibernate - 如果集合被重新初始化,Hibernate 会触发删除语句
问题描述
我有一个有线问题,即 Hibernate 在更新实体时触发一些删除查询。
实体:
public class Item {
@Id
@GeneratedValue
private Long id;
@ManyToMany(cascade = CascadeType.PERSIST)
@JoinTable(name = "item_tags", joinColumns = @JoinColumn(name = "item_id"), inverseJoinColumns = @JoinColumn(name = "tag_id"))
private Set<Tag> tags = new HashSet<>();
// some more stuff
}
更新实体的代码:
public Item updateItem(ItemUpdateRequest dto, long id) {
Item item = itemDao.findEntity(id);
item = ItemMapper.INSTANCE.mapFromUpdateRequest(item, dto, tagDao);
// save item so it can be returned
item = itemDao.saveEntity(item);
return item;
}
ItemMapper 由 MapStruct 生成,带有自定义方法来加载标签,因为请求只发送标签的 ID:
@AfterMapping
default void loadTags(@MappingTarget Item item, ItemUpdateRequest dto, @Context TagDAO dao) {
Collection<Tag> tags = dao.findEntities(dto.getTags());
item.setTags(new HashSet<>(tags));
}
因此,生成的 MapStruct 映射器如下所示:
@Override
public Item mapFromUpdateRequest(Item item, ItemUpdateRequest dto, TagDAO tagDao) {
if ( dto == null ) {
return null;
}
item.setDescription( dto.getDescription() );
item.setPrice( dto.getPrice() );
item.setTitle( dto.getTitle() );
item.setType( itemTypeMapper.map( dto.getType() ) );
loadTags( item, dto, tagDao );
return item;
}
如果 ItemUpdateRequest 不包含任何标签,Hibernate 会执行以下查询:
1. Hibernate: update public.item set description=?, price=?,
restaurant_id=?, title=?, type=?, visible=? where id=?
2. Hibernate: select tags0_.item_id as item_id1_20_0_, tags0_.tag_id as
tag_id2_20_0_, tag1_.id as id1_41_1_, tag1_.abbreviation as
abbrevia2_41_1_, tag1_.text as text3_41_1_ from public.item_tags
tags0_ inner join public.tag tag1_ on tags0_.tag_id=tag1_.id where
tags0_.item_id=?
3. Hibernate: delete from public.item_tags where item_id=?
我不明白为什么 Hibernate 会发出删除查询。数据库中的项目和 ItemUpdateRequest 都不包含任何标签。我认为 Hibernate 可能会识别更改的集合,因为它是在 loadTags() 方法中再次设置的。
我尝试编写单元测试,但该测试按预期工作,并且没有发出删除语句。
@Test
public void deleteQueryTest() {
Item item = Item.builder().title("Test").build();
item = itemDao.saveEntity(item);
Item reloadedItem = itemDao.findEntity(item.getId());
ItemUpdateRequest updateRequest = ItemUpdateRequest.builder()
.title("Test Changed")
.build();
reloadedItem = ItemMapper.INSTANCE.mapFromUpdateRequest(reloadedItem, updateRequest, tagDao);
reloadedItem = itemDao.saveEntity(reloadedItem);
}
解决方案
您的loadTags
方法总是设置一个新的集合实例,而不是更改支持脏跟踪的现有集合实例。尝试item.getTags().clear(); item.getTags().addAll(tags);
改用。除此之外,我建议您研究Blaze-Persistence Entity Views,它可以通过减少查询来进一步提高事务性能。
我创建了该库以允许在 JPA 模型和自定义接口或抽象类定义模型之间轻松映射,例如 Spring Data Projections on steroids。这个想法是您按照自己喜欢的方式定义目标结构(域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。
使用 Blaze-Persistence Entity-Views 的用例的 DTO 模型可能如下所示:
@EntityView(Item.class)
@UpdatableEntityView
public interface ItemDto {
@IdMapping
Long getId();
String getTitle();
void setTitle(String title);
String getDescription();
void setDescription(String description);
BigDecimal getPrice();
void setPrice(BigDecimal price);
ItemTypeDto getType();
void setType(ItemTypeDto type);
Set<TagDto> getTags();
@EntityView(ItemType.class)
interface ItemTypeDto {
@IdMapping
Long getId();
}
@EntityView(Tag.class)
@UpdatableEntityView
@CreatableEntityView
interface TagDto {
@IdMapping
Long getId();
String getAbbreviation();
void setAbbreviation(String abbreviation);
String getText();
void setText(String text);
}
}
查询是将实体视图应用于查询的问题,最简单的就是通过 id 进行查询。
ItemDto a = entityViewManager.find(entityManager, ItemDto.class, id);
Spring Data 集成允许您几乎像 Spring Data Projections 一样使用它:https ://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<ItemDto> findAll(Pageable pageable);
最好的部分是,它只会获取实际需要的状态!
感谢 Jackson 和 Spring MVC 的集成,您的代码看起来就像这样简单:
public interface ItemDtoRepository extends EntityViewRepository<ItemDto, Long> {
}
@Autowired
private ItemDtoRepository itemRepository;
@RequestMapping(path = "/items/{id}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> updateItem(@EntityViewId("id") @RequestBody ItemDto itemDto) {
itemRepository.save(itemDto);
return ResponseEntity.ok(itemDto.getId().toString());
}
有关更多详细信息,您还可以查看文档:https ://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#entity-view-deserialization
推荐阅读
- java - 如何加载文件以供多种方法访问?
- c# - 当椭圆位于画布边界时,不会触发 MouseUp 事件
- firebase - 如何将数据从 Google Pub/Sub 发送到 Firebase 实时数据库
- python - 从 ardunio 温度传感器数据更新 Kivy 标签
- html - 我可以编写 HTML 脚本并将脚本中的信息传递到 Qubole 上的单元格吗?
- javascript - 如何将多个对象合二为一?
- r - R Data.Table复制组的第一个值
- python - 是否可以为在两个不同应用程序中打开的单个文件创建实时链接?
- node.js - Tokbox:如何断开所有客户端与服务器端的会话?
- java - 在 jsp 文件中显示 servlet 数组列表不起作用