'Hibernate / Spring Data - get executed sql queries count

I'm working on presentation in which I would like to show difference in number of executed sql queries between deleteByPost() method with and without custom query. I'm expecting method without custom query to execute 10001 delete queries and with it just 2.

I'm aware of Hibernate Statistics object and it's methods. I was expecting one of them, named getQueryExecutionCount(), to return the number of sql queries executed against db, but what I'm getting is always a 0.

If anyone wonders hibernate statistics are enabled for sure because I'm getting correct numbers on other properties like the count of deleted entities.

Below there is a complete example showing what I am trying to accomplish.

Is there a way to get the number of generated and executed queries using Statistics or any other mechanism? Currently I'm looking at logs (hibernate.show_sql) and counting printed queries but it just seems wrong to me.

package example5

import org.hibernate.SessionFactory
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertAll
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.data.jpa.repository.Modifying
import org.springframework.data.jpa.repository.Query
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
import org.springframework.data.repository.PagingAndSortingRepository
import org.springframework.data.repository.query.Param
import org.springframework.stereotype.Repository
import org.springframework.stereotype.Service
import org.springframework.test.context.junit.jupiter.SpringJUnitJupiterConfig
import org.springframework.transaction.annotation.EnableTransactionManagement
import org.springframework.transaction.annotation.Transactional
import javax.persistence.*

// ENTITIES

@Entity
@Table(name = "posts")
class Post(
        @Id
        @Column(name = "id")
        @GeneratedValue(strategy = GenerationType.SEQUENCE)
        var id: Long? = null,

        @Version
        @Column(name = "version")
        var version: Long? = null
)

@Entity
@Table(name = "comments")
class Comment(
        @Id
        @Column(name = "id")
        @GeneratedValue(strategy = GenerationType.SEQUENCE)
        var id: Long? = null,

        @Version
        @Column(name = "version")
        var version: Long? = null,

        @JoinColumn(name = "post_id")
        @ManyToOne(fetch = FetchType.LAZY)
        var post: Post? = null
)


// REPOSITORIES

@Repository
interface PostRepository : PagingAndSortingRepository<Post, Long>

@Repository
interface CommentRepository : PagingAndSortingRepository<Comment, Long> {

    @Modifying
    @Query("delete from Comment c where c.post = :post")
    fun deleteByPost(@Param("post") post: Post)
}


// SERVICES

interface PostService {

    fun delete(post: Post)
}

@Service
open class PostServiceImpl(
        @Autowired
        val postRepository: PostRepository,

        @Autowired
        val commentRepository: CommentRepository
) : PostService {
    @Transactional
    override fun delete(post: Post) {
        commentRepository.deleteByPost(post)
        postRepository.delete(post)
    }

}

// CONFIGURATION

@EnableJpaRepositories(basePackages = ["example5"])
@EnableTransactionManagement
@SpringBootApplication(scanBasePackages = ["example5"])
open class FrameworkApplication


// TESTS

@SpringJUnitJupiterConfig(classes = [FrameworkApplication::class])
class Example5(
        @Autowired
        val postService: PostService,

        @Autowired
        val postRepository: PostRepository,

        @Autowired
        val commentRepository: CommentRepository,

        @Autowired
        val emFactory: EntityManagerFactory
) {

    @AfterEach
    fun cleanUp() {
        commentRepository.deleteAll()
        postRepository.deleteAll()
    }

    @Test
    fun testDelete() {
        //given
        var post = Post()
        post = postRepository.save(post)
        val comments = mutableListOf<Comment>()
        for (i in 1..10000) {
            val comment = Comment()
            comment.post = post
            comments.add(comment)
        }
        commentRepository.save(comments)

        val sessionFactory = emFactory.unwrap(SessionFactory::class.java)
        val statistics = sessionFactory.statistics

        //then
        statistics.clear()
        postService.delete(post)

        val executedQueryCount = statistics.queryExecutionCount


        //then
        assertAll(
                { assertEquals(0, postRepository.count()) },
                { assertEquals(0, commentRepository.count()) },
                { assertEquals(2, executedQueryCount) }
        )
    }

}


Solution 1:[1]

The library Spring Hibernate Query Utils (https://github.com/yannbriancon/spring-hibernate-query-utils) provides a query counter that you can use to check the number of queries generated.

If you prefer to do it yourself, Hibernate provides a class EmptyInterceptor that contains a hook named onPrepareStatement. You can extend this class and add logic in the onPrepareStatement hook to count the queries.

Take a look at the library code to see how to configure the iterator.

Solution 2:[2]

The method onPrepareStatement is now deprecated and is removed in the new Hibernate 6 version. The new way to inspect SQL is to implement a StatementInspector .

I've written a little library (https://github.com/Lemick/hibernate-query-asserts) that can assert the count of SQL queries by type (SELECT, INSERT, ..) generated by Hibernate in your Spring tests, this way, you can be warned whenever the SQL statements change in your tests, and prevent N+1 selects. You can take a look here at the project if you want to know how this is implemented.

A test example that demonstrates the purpose:

@Test
@Transactional
@AssertHibernateSQLCount(inserts = 3)
void create_two_blog_posts() {
    BlogPost post_1 = new BlogPost("Blog post 1");
    post_1.addComment(new PostComment("Good article"));
    post_1.addComment(new PostComment("Very interesting"));
    blogPostRepository.save(post_1);
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Mickaël B.