0

Hi I am testing a record delete-insert-delete operation in MySQL with Spring Boot hibernate, and I have noticed an issue that it seems the records are not deleted entirely when the insert occurs.

3 initial records are created on hibernate kickstart (through data.sql in classpath)

insert into car(id, name, color) values (1, 'Toyota Camry', 'blue');
insert into car(id, name, color) values (2, 'Suzuki Swift', 'yellow');
insert into car(id, name, color) values (3, 'Nissan Qashqai', 'red');

The operation is
(1) Initial records created
(2) Delete all records
(3) Add a new record
(4) Delete all records

When assertion is performed, it is found that the result list still contains the last record, hence failing the assertion.

enter image description here

I am wondering if this is caused by the delete operation not completed before the insert occurs.

Appreciate if you provide some insight to this, thank you very much.

Model (Car.java):

package com.example.demo.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

@ApiModel(value = "Car", description = "The model for car")
@Schema
@Entity
@Data
public class Car {
    @ApiModelProperty(notes = "Car ID.", example = "12345", required = false, position = 0)
    @Id
    @GeneratedValue
    private Long id;

    @ApiModelProperty(notes = "Car name.", example = "Suzuki Swift 2020", required = true, position = 1)
    @NotNull
    @Size(min = 1, max = 30, message = "Name must have length between 1 and 30")
    private String name;

    @ApiModelProperty(notes = "Car color.", example = "blue", required = true, position = 2)
    @NotNull
    @Size(min = 1, max = 30, message = "Color must have length between 1 and 30")
    private String color;
}

Repository (CarReposiroty.java)

package com.example.demo.repo;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.example.demo.model.Car;

@Repository
public interface CarRepository extends JpaRepository<Car, Long> {

}

Service (TestService.java)

package com.example.demo.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.example.demo.model.Car;
import com.example.demo.repo.CarRepository;

@Service
public class TestService {

    @Autowired
    private CarRepository carRepository;

    public List<Car> getAllCars() {
        return carRepository.findAll();
    }

    @Transactional
    public void addNewCar(Car car) {
        carRepository.save(car);
    }

    @Transactional
    public void deleteAllCars() {
        carRepository.deleteAll();
    }
}

JUnit

...
......
@Test
public void shouldBeAbleToDeleteCarAndAddCarAndGetAllCars() {
    testService.deleteAllCars();

    Car car = new Car();
    car.setName("123456789012345678901234567890");
    car.setColor("123456789012345678901234567890");

    testService.addNewCar(car);

    List<Car> carList = testService.getAllCars();

    assertEquals(1, carList.size());

    testService.deleteAllCars();
}
...
......

Spring Boot v2.3.1

pom.xml

    ...
    .....
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    ...
    ......

Spring hibernate config (application-test.yml)

spring.datasource.url: jdbc:mysql://[the host url]:3306/test
spring.datasource.username: [the username]
spring.datasource.password: [the password]
spring.jpa.hibernate.ddl-auto: create-drop
spring.datasource.initialization-mode: always

MySQL 8.0.17

Patrick C.
  • 1,339
  • 3
  • 16
  • 31
  • After delete there should be no records remaining. Then an update is performed to add 1 new record. The assertion asserts that there should be only 1 record. However, the actual result is 2 records are there (1) the last of the initial ones (the first 2 were deleted as expected) (2) our newly added one – Patrick C. Aug 04 '20 at 05:15

1 Answers1

0

This is because deleteAllCars() is not committing the change to the DB and all the actions inside the test method is wrapped in a single transaction because transaction is being propagated. Add below annotation on the test class

@Transactional(propagation = Propagation.NEVER)

Check this related question as well How to flush data into db inside active spring transaction?

Arghya Sadhu
  • 41,002
  • 9
  • 78
  • 107
  • Hi @Arghya Sadhu, thanks. In fact I tried adding Transaction annotation to the JUnit test method and it could complete as expected. So I am wondering the issue is actually the operations in the test method not being wrapped in the same transaction, i.e. allowing the read to go ahead before the delete completes? – Patrick C. Aug 04 '20 at 10:44
  • This should give you some clue http://www.javacodegeeks.com/2011/12/spring-pitfalls-transactional-tests.html – Arghya Sadhu Aug 04 '20 at 10:59