【问题标题】:JPARepository: Nothing Happens on deleteAll(); deleteAllInBatch() WorksJPARepository:deleteAll() 上没有发生任何事情; deleteAllInBatch() 作品
【发布时间】:2019-10-10 17:24:28
【问题描述】:

在我的 Spring 集成测试中(使用 JUnit 5。我的测试类注释为 @SpringBootTest(classes = {SecurityBeanOverrideConfiguration.class, XXXApp.class}),我试图在我的 @AfterEach 方法中调用 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());
        }
    }

}

【问题讨论】:

  • 一些代码会有所帮助。默认情况下,Spring 测试在每次测试后回滚事务,因此不需要显式删除先前测试插入的数据。
  • 我会做一个最小的例子并尽快提供。
  • @AlanHay:请查看我刚刚进行的编辑。我参加了我的一门测试课程,只留下了一个测试用例并设置/拆除。关键注释在tearDown 中:清除存储库后,执行count() 返回3,即使刚刚调用了deleteAll()。同样奇怪的是,如果我改为调用deleteAllInBatch(),输出是3, 0,正如我所料。

标签: hibernate spring-boot jpa spring-data-jpa junit5


【解决方案1】:

这是一个奇怪的诊断错误。原来我实现了isNew()Persistable&lt;T&gt;)错误,并且它返回了true。

因此,对SimpleJpaRepository#delete(T entity) 的调用会执行此检查:

public void deleteAll(T entity)
   // [...]
   
   if (entityInformation.isNew(entity)) {
      return;
   }

   // actually delete...
   // [...]
}

我的实体返回isNew() = true,因此,存储库自然只是跳过所有实体,从不删除它们。同时,deleteAllInBatch() 不执行该检查。

为了解决这个问题,我已停止实施 Persistable 并改为创建我的实体 Serializable

2020 年 6 月更新:

无需切换到可序列化即可工作的最小实现示例:

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;
    }

}

【讨论】:

  • 更新:我不希望答案给人一种不应该实现持久化的印象,而是应该正确实现它。为此,正确的实现可以使用@PostLoad@PostPersist 注释将@Transient private Boolean persisted = false; 从false 设置为true
猜你喜欢
  • 2011-08-12
  • 2021-05-13
  • 2019-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-16
  • 1970-01-01
相关资源
最近更新 更多