如果重新初始化集合,Hibernate 会触发 delete 语句
Hibernate fires delete statement if collection is reinitialized
我有一个有线问题,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 模型和自定义接口或抽象 class 定义的模型之间轻松映射,类似于 Spring 类固醇数据投影。这个想法是您按照自己喜欢的方式定义目标结构(领域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。
您的用例的 DTO 模型与 Blaze-Persistence 实体视图如下所示:
@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 数据集成让您几乎可以像 Spring 数据投影一样使用它: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
我有一个有线问题,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 模型和自定义接口或抽象 class 定义的模型之间轻松映射,类似于 Spring 类固醇数据投影。这个想法是您按照自己喜欢的方式定义目标结构(领域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。
您的用例的 DTO 模型与 Blaze-Persistence 实体视图如下所示:
@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 数据集成让您几乎可以像 Spring 数据投影一样使用它: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