Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPARepository: Nothing Happens on deleteAll(); deleteAllInBatch() Works

In my Spring integration tests (using JUnit 5. My test class is annotated @SpringBootTest(classes = {SecurityBeanOverrideConfiguration.class, XXXApp.class}), I'm trying to call repository.deleteAll() in my @AfterEach method.

Looking at the SQL in the logs, it seems that nothing is executed; and indeed, in the next tests, the entity with the same ID cannot be created because it already exists – meaning something prevented the database. I have played with different transaction types (propagation, isolation...) as other questions mention, but to no avail.

Interestingly, though, calling repository.deleteAllInBatch() instead of deleteAll() does work: all the tests pass.

What is going on?

EDIT: Adding code.

@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());
        }
    }

}
like image 906
Alexandre Cassagne Avatar asked Sep 18 '25 23:09

Alexandre Cassagne


1 Answers

This was a strange error to diagnose. It turned out that I had implemented isNew() (of Persistable<T>) wrong, and that it was returning true.

As a result the call to SimpleJpaRepository#delete(T entity) performs this check:

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

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

My entities were returning isNew() = true so, naturally, the repository was just skipping all the entities, and never deleting them. Meanwhile, deleteAllInBatch() does not perform that check.

To fix this, I have stopped implementing Persistable and made my entities Serializable instead.

Update June 2020:

Example of minimal implementation that would work without switching to 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;
    }

}
like image 60
Alexandre Cassagne Avatar answered Sep 20 '25 18:09

Alexandre Cassagne