1- [ ![ Language grade: Java] ( https://img.shields.io/lgtm/grade/java/g/Lemick/hibernate-spring-sql-query-count.svg?logo=lgtm&logoWidth=18 )] ( https://lgtm.com/projects/g/Lemick/hibernate-spring-sql-query-count/context:java )
21# Hibernate SQL Query Assertions for Spring
32
43Hibernate is a powerful ORM, but you need to have control over the executed SQL queries to avoid ** huge performance problems** (N+1 selects, batch insert not working...)
@@ -15,7 +14,7 @@ A full-working demo of the examples below [is available here](https://github.com
1514
1615* Tested versions* : Hibernate 5 & 6
1716
18- ### Assert SQL statements
17+ ### Assert SQL statements declaratively
1918
2019You just have to add the ` @AssertHibernateSQLCount ` annotation to your test and it will verify the SQL statements (SELECT, UPDATE, INSERT, DELETE) count at the end of the test :
2120
@@ -37,67 +36,93 @@ void create_two_blog_posts() {
3736```
3837If the actual count is different, an exception is thrown with the executed statements:
3938```
40- com.mickaelb.assertions.HibernateAssertCountException:
41- Expected 5 INSERT but got 6:
42- => '/* insert com.lemick.demo.entity.BlogPost */ insert into blog_post (id, title) values (default, ?)'
43- => '/* insert com.lemick.demo.entity.PostComment */ insert into post_comment (id, blog_post_id, content) values (default, ?, ?)'
44- => '/* insert com.lemick.demo.entity.PostComment */ insert into post_comment (id, blog_post_id, content) values (default, ?, ?)'
45- => '/* insert com.lemick.demo.entity.BlogPost */ insert into blog_post (id, title) values (default, ?)'
46- => '/* insert com.lemick.demo.entity.PostComment */ insert into post_comment (id, blog_post_id, content) values (default, ?, ?)'
47- => '/* insert com.lemick.demo.entity.PostComment */ insert into post_comment (id, blog_post_id, content) values (default, ?, ?)'
39+ com.mickaelb.assertions.HibernateAssertCountException:
40+ Expected 5 INSERT but got 6:
41+ => '/* insert com.lemick.demo.entity.BlogPost */ insert into blog_post (id, title) values (default, ?)'
42+ => '/* insert com.lemick.demo.entity.PostComment */ insert into post_comment (id, blog_post_id, content) values (default, ?, ?)'
43+ => '/* insert com.lemick.demo.entity.PostComment */ insert into post_comment (id, blog_post_id, content) values (default, ?, ?)'
44+ => '/* insert com.lemick.demo.entity.BlogPost */ insert into blog_post (id, title) values (default, ?)'
45+ => '/* insert com.lemick.demo.entity.PostComment */ insert into post_comment (id, blog_post_id, content) values (default, ?, ?)'
46+ => '/* insert com.lemick.demo.entity.PostComment */ insert into post_comment (id, blog_post_id, content) values (default, ?, ?)'
4847```
49- ### Assert L2C statistics
48+
49+ ### Assert SQL statements programmatically
50+
51+ You can also assert statements from several transactions in your test using the programmatic API:
52+ ``` java
53+ @Test
54+ void multiple_assertions_using_programmatic_api() {
55+ QueryAssertions . assertInsertCount(2 , () - > {
56+ BlogPost post_1 = new BlogPost (" Blog post 1" );
57+ post_1. addComment(new PostComment (" Good article" ));
58+ blogPostRepository. save(post_1);
59+ });
60+
61+ QueryAssertions . assertSelectCount(1 , () - > blogPostRepository. findById(1L ));
62+
63+ // Or even multiple asserts at once
64+ QueryAssertions . assertStatementCount(Map . of(INSERT , 2 , SELECT , 1 ), () - > {
65+ BlogPost post_1 = new BlogPost (" Blog post 1" );
66+ post_1. addComment(new PostComment (" Good article" ));
67+ blogPostRepository. save(post_1);
68+
69+ blogPostRepository. findById(1L );
70+ });
71+ }
72+ ```
73+
74+ ### Assert L2C statistics declaratively
5075
5176It supports assertions on Hibernate level two cache statistics, useful for checking that your entities are cached correctly and that they will stay forever:
5277``` java
53- @Test
54- @AssertHibernateL2CCount (misses = 1 , puts = 1 , hits = 1 )
55- void create_one_post_and_read_it() {
56- doInTransaction(() - > {
57- BlogPost post_1 = new BlogPost (" Blog post 1" );
58- blogPostRepository. save(post_1);
59- });
60-
61- doInTransaction(() - > {
62- blogPostRepository. findById(1L ); // 1 MISS + 1 PUT
63- });
64-
65- doInTransaction(() - > {
66- blogPostRepository. findById(1L ); // 1 HIT
67- });
68- }
78+ @Test
79+ @AssertHibernateL2CCount (misses = 1 , puts = 1 , hits = 1 )
80+ void create_one_post_and_read_it() {
81+ doInTransaction(() - > {
82+ BlogPost post_1 = new BlogPost (" Blog post 1" );
83+ blogPostRepository. save(post_1);
84+ });
85+
86+ doInTransaction(() - > {
87+ blogPostRepository. findById(1L ); // 1 MISS + 1 PUT
88+ });
89+
90+ doInTransaction(() - > {
91+ blogPostRepository. findById(1L ); // 1 HIT
92+ });
93+ }
6994```
7095## How to integrate
71961 . Import the dependency
72- ```xml
73- <dependency>
74- <groupId>com.mickaelb</groupId>
75- <artifactId>hibernate-query-asserts</artifactId>
76- <version>2.0.0</version>
77- </dependency>
78- ```
97+ ``` xml
98+ <dependency >
99+ <groupId >com.mickaelb</groupId >
100+ <artifactId >hibernate-query-asserts</artifactId >
101+ <version >2.0.0</version >
102+ </dependency >
103+ ```
791042. Register the integration with Hibernate, you just need to add this key in your configuration (here for yml):
80105
81106 spring:
82- jpa:
83- properties:
84- hibernate.session_factory.statement_inspector: com.mickaelb.integration.hibernate.HibernateStatementInspector
107+ jpa:
108+ properties:
109+ hibernate.session_factory.statement_inspector: com.mickaelb.integration.hibernate.HibernateStatementInspector
85110
861113. Register the Spring TestListener that will launch the SQL inspection if the annotation is present:
87112
88113 * By adding the listener on each of your integration test:
89114 ```java
90- @SpringBootTest
91- @TestExecutionListeners(
92- listeners = HibernateAssertTestListener.class,
93- mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
94- )
95- class MySpringIntegrationTest {
96- ...
97- }
115+ @SpringBootTest
116+ @TestExecutionListeners(
117+ listeners = HibernateAssertTestListener.class,
118+ mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
119+ )
120+ class MySpringIntegrationTest {
121+ ...
122+ }
98123 ```
99124
100125 * **OR** by adding a **META-INF/spring.factories** file that contains the definition, that will register the listener for all your tests:
101126 ```
102- org.springframework.test.context.TestExecutionListener=com.mickaelb.integration.spring.HibernateAssertTestListener
127+ org.springframework.test.context.TestExecutionListener=com.mickaelb.integration.spring.HibernateAssertTestListener
103128 ```
0 commit comments