spring-data-jpaspring-transactionshibernate-cache

JPA cache behaviour when invoke count() method on Spring Data JPA Repository


I'm writing a transactional junit-based IT test for Spring Data JPA repository. To check number of rows in table I use side JDBCTemplate.

I notice, that in transactional context invoking of org.springframework.data.repository.CrudRepository#save(S) doesn't take effect. SQL insert in not performed, number of rows in table is not increased.

But If I invoke org.springframework.data.repository.CrudRepository#count after the save(S) then SQL insert is performed and number of rows is increased.

I guess this is behavior of JPA cache, but how it works in details?

Code with Spring Boot:

@RunWith(SpringRunner.class)
@SpringBootTest
public class ErrorMessageEntityRepositoryTest {

    @Autowired
    private ErrorMessageEntityRepository errorMessageEntityRepository;
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    @Transactional
    public void save() {
        ErrorMessageEntity errorMessageEntity = aDefaultErrorMessageEntity().withUuid(null).build();
        assertTrue(TestTransaction.isActive());
        int sizeBefore= JdbcTestUtils.countRowsInTable(jdbcTemplate, "error_message");
        ErrorMessageEntity saved = errorMessageEntityRepository.save(errorMessageEntity);
        errorMessageEntityRepository.count(); // [!!!!] if comment this line test will fail
        int sizeAfter= JdbcTestUtils.countRowsInTable(jdbcTemplate, "error_message");
        Assert.assertEquals(sizeBefore+1, sizeAfter);
    }

Entity:

@Entity(name = "error_message")
public class ErrorMessageEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID uuid;
    @NotNull
    private String details;

Repository:

public interface ErrorMessageEntityRepository extends CrudRepository<ErrorMessageEntity, UUID>

Solution

  • You are correct this is a result of how JPA works. JPA tries to delay SQL statement execution as long as possible.

    When saving new instances this means it will only perform an insert if it is required in order to get an id for the entity.

    Only when a flush event occurs will all changes that are stored in the persistence context flushed to the database. There are three triggers for that event to happen:

    1. The closing of the persistence context will flush all the changes. In a typical setup, this is tight to a transaction commit.

    2. Explicitly calling flush on the EntityManager which you might do directly or when using Spring Data JPA via saveAndFlush

    3. Before executing a query. Since you typically want to see your changes in a query.

    Number 3 is the effect you are seeing.

    Note that the details are a little more complicated since you can configure a lot of this stuff. As usual, Vlad Mihalcea has written an excellent post about it.