JPARepository:deleteAll() 上没有任何反应; deleteAllInBatch() 有效
JPARepository: Nothing Happens on deleteAll(); deleteAllInBatch() Works
在我的 Spring 集成测试中(使用 JUnit 5。我的测试 class 被注释 @SpringBootTest(classes = {SecurityBeanOverrideConfiguration.class, XXXApp.class})
,我试图在我的 [=13] 中调用 repository.deleteAll()
=]方法。
查看日志中的SQL,好像什么都没执行;事实上,在接下来的测试中,无法创建具有相同 ID 的实体,因为它已经存在——这意味着某些东西阻止了数据库。正如其他问题提到的,我玩过不同的事务类型(传播、隔离...),但无济于事。
不过,有趣的是,调用 repository.deleteAllInBatch()
而不是 deleteAll()
确实 有效:所有测试都通过了。
这是怎么回事?
编辑:添加代码。
@Transactional
@SpringBootTest(classes = {SecurityBeanOverrideConfiguration.class, XXXApp.class})
public class DeviceResourceIT {
@Autowired DeviceRepository deviceRepository;
@Autowired DeviceService lotService;
@Autowired private MappingJackson2HttpMessageConverter jacksonMessageConverter;
@Autowired private ExceptionTranslator exceptionTranslator;
private MockMvc mockMvc;
private Logger log = LoggerFactory.getLogger(DeviceResourceIT.class);
@PostConstruct
void setup() {
DeviceResource deviceResource = new DeviceResource(deviceService);
mockMvc = MockMvcBuilders.standaloneSetup(deviceResource)
.setControllerAdvice(exceptionTranslator)
.setConversionService(createFormattingConversionService())
.setMessageConverters(jacksonMessageConverter)
.build();
}
@Test
public void getLot() throws Exception
{
String lotID;
String wrongLotID = "aRandomString";
final List<DeviceRequestDTO> requests = Arrays.asList(
new DeviceRequestDTO("l1", "ble1"),
new DeviceRequestDTO("l2", "ble2"),
new DeviceRequestDTO("l3", "ble3")
);
LotDTO lotDTO = new LotDTO(requests);
MvcResult mvcResult = mockMvc.perform(post("/api/admin/lot")
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("utf-8")
.content(toJsonString(lotDTO)))
.andDo(print())
.andExpect(status().isOk())
.andReturn();
LotDTO returnedLotDTO = convertJsonBytes(mvcResult.getResponse().getContentAsByteArray(), LotDTO.class);
lotID = returnedLotDTO.getId();
log.info("{the lot id is : }" + lotID);
// retrieve the Lot with the right lot ID
mockMvc.perform(get("/api/admin/lot/{lot_id}", lotID))
.andDo(print())
.andExpect(status().isOk());
}
@AfterEach
public void tearDown() {
try {
log.info("{}", deviceRepository.count());
// Returns: 3
deviceRepository.deleteAll();
log.info("{}", deviceRepository.count());
// Returns: 3
// ... but we would expect to see 0, given the call to
// deleteAll() just above...
} catch (Exception e) {
Fail.fail(e.getMessage());
}
}
}
这是一个奇怪的诊断错误。结果我实施了 isNew()
(of Persistable<T>
) 错误,它返回了 true。
因此,对 SimpleJpaRepository#delete(T entity)
的调用会执行此检查:
public void deleteAll(T entity)
// [...]
if (entityInformation.isNew(entity)) {
return;
}
// actually delete...
// [...]
}
我的实体正在返回 isNew() = true
因此,很自然地,存储库只是跳过所有实体,并且从不删除它们。同时,deleteAllInBatch()
不执行该检查。
为了解决这个问题,我已经停止实施 Persistable
并改为制作我的实体 Serializable
。
2020 年 6 月更新:
无需切换到 Serializable 即可工作的最小实现示例:
class XXX implements Persistable<T>
{
@Id private String id;
// (getters, setters, logic, etc...)
// implement the `T getId()` method for the Persistable<T> interface,
// most likely this will simply be a getter for the id field above
// transient fields are not stored but provide information to Hibernate/JPA
// regarding the status of this entity instance
@Transient private Boolean persisted = false;
@PostPersist @PostLoad void setPersisted() {
persisted = true;
}
@Override public boolean isNew() {
return !persisted;
}
}
在我的 Spring 集成测试中(使用 JUnit 5。我的测试 class 被注释 @SpringBootTest(classes = {SecurityBeanOverrideConfiguration.class, XXXApp.class})
,我试图在我的 [=13] 中调用 repository.deleteAll()
=]方法。
查看日志中的SQL,好像什么都没执行;事实上,在接下来的测试中,无法创建具有相同 ID 的实体,因为它已经存在——这意味着某些东西阻止了数据库。正如其他问题提到的,我玩过不同的事务类型(传播、隔离...),但无济于事。
不过,有趣的是,调用 repository.deleteAllInBatch()
而不是 deleteAll()
确实 有效:所有测试都通过了。
这是怎么回事?
编辑:添加代码。
@Transactional
@SpringBootTest(classes = {SecurityBeanOverrideConfiguration.class, XXXApp.class})
public class DeviceResourceIT {
@Autowired DeviceRepository deviceRepository;
@Autowired DeviceService lotService;
@Autowired private MappingJackson2HttpMessageConverter jacksonMessageConverter;
@Autowired private ExceptionTranslator exceptionTranslator;
private MockMvc mockMvc;
private Logger log = LoggerFactory.getLogger(DeviceResourceIT.class);
@PostConstruct
void setup() {
DeviceResource deviceResource = new DeviceResource(deviceService);
mockMvc = MockMvcBuilders.standaloneSetup(deviceResource)
.setControllerAdvice(exceptionTranslator)
.setConversionService(createFormattingConversionService())
.setMessageConverters(jacksonMessageConverter)
.build();
}
@Test
public void getLot() throws Exception
{
String lotID;
String wrongLotID = "aRandomString";
final List<DeviceRequestDTO> requests = Arrays.asList(
new DeviceRequestDTO("l1", "ble1"),
new DeviceRequestDTO("l2", "ble2"),
new DeviceRequestDTO("l3", "ble3")
);
LotDTO lotDTO = new LotDTO(requests);
MvcResult mvcResult = mockMvc.perform(post("/api/admin/lot")
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("utf-8")
.content(toJsonString(lotDTO)))
.andDo(print())
.andExpect(status().isOk())
.andReturn();
LotDTO returnedLotDTO = convertJsonBytes(mvcResult.getResponse().getContentAsByteArray(), LotDTO.class);
lotID = returnedLotDTO.getId();
log.info("{the lot id is : }" + lotID);
// retrieve the Lot with the right lot ID
mockMvc.perform(get("/api/admin/lot/{lot_id}", lotID))
.andDo(print())
.andExpect(status().isOk());
}
@AfterEach
public void tearDown() {
try {
log.info("{}", deviceRepository.count());
// Returns: 3
deviceRepository.deleteAll();
log.info("{}", deviceRepository.count());
// Returns: 3
// ... but we would expect to see 0, given the call to
// deleteAll() just above...
} catch (Exception e) {
Fail.fail(e.getMessage());
}
}
}
这是一个奇怪的诊断错误。结果我实施了 isNew()
(of Persistable<T>
) 错误,它返回了 true。
因此,对 SimpleJpaRepository#delete(T entity)
的调用会执行此检查:
public void deleteAll(T entity)
// [...]
if (entityInformation.isNew(entity)) {
return;
}
// actually delete...
// [...]
}
我的实体正在返回 isNew() = true
因此,很自然地,存储库只是跳过所有实体,并且从不删除它们。同时,deleteAllInBatch()
不执行该检查。
为了解决这个问题,我已经停止实施 Persistable
并改为制作我的实体 Serializable
。
2020 年 6 月更新:
无需切换到 Serializable 即可工作的最小实现示例:
class XXX implements Persistable<T>
{
@Id private String id;
// (getters, setters, logic, etc...)
// implement the `T getId()` method for the Persistable<T> interface,
// most likely this will simply be a getter for the id field above
// transient fields are not stored but provide information to Hibernate/JPA
// regarding the status of this entity instance
@Transient private Boolean persisted = false;
@PostPersist @PostLoad void setPersisted() {
persisted = true;
}
@Override public boolean isNew() {
return !persisted;
}
}