Skip to content

Commit b0c9abb

Browse files
authored
Merge pull request #17 from yannbriancon/version-2.0.0
Version 2.0.0
2 parents 90926de + 62aa9c4 commit b0c9abb

File tree

9 files changed

+229
-62
lines changed

9 files changed

+229
-62
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
## [2.0.0]
10+
### Added
11+
- Make autocomplete work for N+1 queries detection properties.
12+
- Add more precise detection to
13+
- Avoid false positive with multiple calls to same method
14+
- Detect undetected N+1 queries caused by eager fetching
15+
- Add a new method to clear Hibernate session state and avoid missed detection because of test setup
16+
- Add new property to enable or disable N+1 queries detection
17+
18+
### Changed
19+
- Rename N+1 queries detection properties.
20+
21+
### Removed
22+
- Remove unused application properties
23+
924
## [1.0.3] - 2020-05-10
1025
### Added
1126
- Add back a more specific detection of N+1 queries due to missing lazy fetching.

CONTRIBUTING.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Contributing
2+
3+
When contributing to this repository, please first discuss the change you wish to make via issue,
4+
email, or any other method with the owners of this repository before making a change.
5+
6+
Please note we have a code of conduct, please follow it in all your interactions with the project.
7+
8+
## Pull Request Process
9+
10+
1. Update the [CHANGELOG][changelog-url] Unreleased section with details of all the changes you made
11+
2. Update the [README][readme-url] with important changes you made, this includes new environment
12+
variables, new feature or changes to the current features.
13+
3. You may merge the Pull Request once you have the sign-off of one of the maintainer, or if you
14+
do not have permission to do that, you may request the reviewer to merge it for you.
15+
16+
## Code of Conduct
17+
18+
### Our Pledge
19+
20+
In the interest of fostering an open and welcoming environment, we as
21+
contributors and maintainers pledge to making participation in our project and
22+
our community a harassment-free experience for everyone, regardless of age, body
23+
size, disability, ethnicity, gender identity and expression, level of experience,
24+
nationality, personal appearance, race, religion, or sexual identity and
25+
orientation.
26+
27+
### Our Standards
28+
29+
Examples of behavior that contributes to creating a positive environment
30+
include:
31+
32+
* Using welcoming and inclusive language
33+
* Being respectful of differing viewpoints and experiences
34+
* Gracefully accepting constructive criticism
35+
* Focusing on what is best for the community
36+
* Showing empathy towards other community members
37+
38+
Examples of unacceptable behavior by participants include:
39+
40+
* The use of sexualized language or imagery and unwelcome sexual attention or
41+
advances
42+
* Trolling, insulting/derogatory comments, and personal or political attacks
43+
* Public or private harassment
44+
* Publishing others' private information, such as a physical or electronic
45+
address, without explicit permission
46+
* Other conduct which could reasonably be considered inappropriate in a
47+
professional setting
48+
49+
### Our Responsibilities
50+
51+
Project maintainers are responsible for clarifying the standards of acceptable
52+
behavior and are expected to take appropriate and fair corrective action in
53+
response to any instances of unacceptable behavior.
54+
55+
Project maintainers have the right and responsibility to remove, edit, or
56+
reject comments, commits, code, wiki edits, issues, and other contributions
57+
that are not aligned to this Code of Conduct, or to ban temporarily or
58+
permanently any contributor for other behaviors that they deem inappropriate,
59+
threatening, offensive, or harmful.
60+
61+
### Scope
62+
63+
This Code of Conduct applies both within project spaces and in public spaces
64+
when an individual is representing the project or its community. Examples of
65+
representing a project or community include using an official project e-mail
66+
address, posting via an official social media account, or acting as an appointed
67+
representative at an online or offline event. Representation of a project may be
68+
further defined and clarified by project maintainers.
69+
70+
### Enforcement
71+
72+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
73+
reported by contacting the project team at [email protected]. All
74+
complaints will be reviewed and investigated and will result in a response that
75+
is deemed necessary and appropriate to the circumstances. The project team is
76+
obligated to maintain confidentiality with regard to the reporter of an incident.
77+
Further details of specific enforcement policies may be posted separately.
78+
79+
Project maintainers who do not follow or enforce the Code of Conduct in good
80+
faith may face temporary or permanent repercussions as determined by other
81+
members of the project's leadership.
82+
83+
### Attribution
84+
85+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
86+
available at [http://contributor-covenant.org/version/1/4][version]
87+
88+
[homepage]: http://contributor-covenant.org
89+
[version]: http://contributor-covenant.org/version/1/4/
90+
[changelog-url]: https://github.com/yannbriancon/spring-hibernate-query-utils/blob/master/CHANGELOG
91+
[readme-url]: https://github.com/yannbriancon/spring-hibernate-query-utils/blob/master/README

README.md

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@
4343
* [Usage](#usage)
4444
* [N+1 Queries Detection](#n1-queries-detection)
4545
* [Detection](#detection)
46+
* [Detection in test with fixtures](#detection-in-test-with-fixtures)
4647
* [Configuration](#configuration)
48+
* [Enable](#enable)
49+
* [Error level](#error-level)
4750
* [Query Count](#query-count)
4851
* [Changelog](#changelog)
4952
* [License](#license)
@@ -82,12 +85,12 @@ This library provides several benefits:
8285
### Installation
8386
##### Maven
8487

85-
Add the dependency to your project inside your `pom.xml` file
88+
Add the dependency to your project inside your `pom.xml` file with the right version
8689
```xml
8790
<dependency>
8891
<groupId>com.yannbriancon</groupId>
8992
<artifactId>spring-hibernate-query-utils</artifactId>
90-
<version>1.0.3</version>
93+
<version>X.X.X</version>
9194
</dependency>
9295
```
9396

@@ -105,9 +108,9 @@ Each time N+1 queries are detected in a transaction, a log of level error will b
105108

106109
Two types of N+1 queries are detected:
107110

108-
- N+1 queries caused by a field needed but not eager fetched on a specific query
111+
- N+1 queries triggered on a getter caused by a field needed but not eager fetched on a specific query
109112

110-
- N+1 queries caused by an entity field not configured to be fetched lazily
113+
- N+1 queries triggered on a query caused by an entity field not configured to be fetched lazily
111114

112115
Here is an example catching the error log for the first type of N+1 queries:
113116

@@ -146,27 +149,71 @@ class NPlusOneQueriesLoggingTest {
146149

147150
LoggingEvent loggingEvent = loggingEventCaptor.getAllValues().get(0);
148151
assertThat(loggingEvent.getMessage())
149-
.isEqualTo("N+1 queries detected on a getter of the entity com.yannbriancon.utils.entity.User\n" +
152+
.contains("N+1 queries detected on a getter of the entity com.yannbriancon.utils.entity.User\n" +
150153
" at com.yannbriancon.interceptor.NPlusOneQueriesLoggingTest." +
151-
"lambda$hibernateQueryInterceptor_isDetectingNPlusOneQueriesWhenMissingEagerFetchingOnQuery$0" +
152-
"(NPlusOneQueriesLoggingTest.java:61)\n" +
153-
" Hint: Missing Eager fetching configuration on the query that fetched the object of type" +
154-
" com.yannbriancon.utils.entity.User\n");
154+
"lambda$hibernateQueryInterceptor_isDetectingNPlusOneQueriesWhenMissingEagerFetchingOnQuery$0");
155155
assertThat(Level.ERROR).isEqualTo(loggingEvent.getLevel());
156156
}
157157
}
158158
```
159159

160+
##### Detection in test with fixtures
161+
162+
If a setup is present in your test to add the data necessary for testing, Hibernate will load all the data in its
163+
state. This will hide potential N+1 queries in the method you test.
164+
165+
To avoid this, a method is available to clear the Hibernate state and the N+1 queries detection state.
166+
167+
Here is an example:
168+
169+
```java
170+
@Test
171+
void nPlusOneQueriesDetection_throwsExceptionWhenSessionIsCleared() {
172+
User author = new User("author");
173+
userRepository.saveAndFlush(author);
174+
Message newMessage = new Message("text", author);
175+
messageRepository.saveAndFlush(newMessage);
176+
177+
// Test a method that should return a N+1 query
178+
// The method does not return an exception because we just created the message so it is loaded in the Session
179+
getMessageAuthorNameWithNPlusOneQuery(newMessage.getId());
180+
181+
// Clear the session to be able to correctly detect the N+1 queries in the tests
182+
hibernateQueryInterceptor.clearNPlusOneQuerySession(entityManager);
183+
184+
try {
185+
// Test a method that should return a N+1 query
186+
// This time the Session is empty and the N+1 query is detected
187+
getMessageAuthorNameWithNPlusOneQuery(newMessage.getId());
188+
assert false;
189+
} catch (NPlusOneQueriesException exception) {
190+
assertThat(exception.getMessage())
191+
.contains("N+1 queries detected on a getter of the entity com.yannbriancon.utils.entity.User\n" +
192+
" at com.yannbriancon.interceptor.NPlusOneQueriesExceptionTest" +
193+
".getMessageAuthorNameWithNPlusOneQuery");
194+
}
195+
}
196+
```
197+
160198
#### Configuration
161199

200+
##### Enable
201+
202+
By default the detection of N+1 queries is enabled for every profile.
203+
204+
To disable it, you can set the property `spring-hibernate-query-utils.n-plus-one-queries-detection.error-level` to false.
205+
206+
207+
##### Error level
208+
162209
By default the detection of N+1 queries logs an error to avoid breaking your code.
163210

164211
However, my advice is to override the default error level to throw exceptions for your test profile.
165212

166213
Now you will easily detect which tests are failing and be able to flag them and set the error level to error logs only on
167214
those tests while you are fixing them.
168215

169-
To do this, you can configure the error level when N+1 queries is detected using the property `hibernate.query.interceptor.error-level`.
216+
To do this, you can configure the error level when N+1 queries is detected using the property `spring-hibernate-query-utils.n-plus-one-queries-detection.error-level`.
170217

171218
4 levels are available to handle the detection of N+1 queries:
172219

@@ -237,6 +284,11 @@ public class NotificationResourceIntTest {
237284

238285
See [`CHANGELOG`][changelog-url] for more information.
239286

287+
<!-- CONTRIBUTING -->
288+
## Contributing
289+
290+
See [`CONTRIBUTING`][contributing] for more information.
291+
240292

241293
<!-- LICENSE -->
242294
## License
@@ -266,3 +318,4 @@ Project Link: [https://github.com/yannbriancon/spring-hibernate-query-utils](htt
266318
[license-shield]: https://img.shields.io/github/license/yannbriancon/spring-hibernate-query-utils.svg?style=flat-square
267319
[license-url]: https://github.com/yannbriancon/spring-hibernate-query-utils/blob/master/LICENSE
268320
[changelog-url]: https://github.com/yannbriancon/spring-hibernate-query-utils/blob/master/CHANGELOG
321+
[contributing]: https://github.com/yannbriancon/spring-hibernate-query-utils/blob/master/CONTRIBUTING

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>com.yannbriancon</groupId>
77
<artifactId>spring-hibernate-query-utils</artifactId>
8-
<version>1.0.3</version>
8+
<version>2.0.0</version>
99
<packaging>jar</packaging>
1010

1111
<name>spring-hibernate-query-utils</name>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.yannbriancon.config;
2+
3+
import org.springframework.boot.context.properties.ConfigurationProperties;
4+
5+
import java.io.Serializable;
6+
7+
@ConfigurationProperties("spring-hibernate-query-utils.n-plus-one-queries-detection")
8+
public class NPlusOneQueriesDetectionProperties implements Serializable {
9+
public enum ErrorLevel {
10+
INFO,
11+
WARN,
12+
ERROR,
13+
EXCEPTION
14+
}
15+
16+
/**
17+
* Error level for the N+1 queries detection
18+
*/
19+
private ErrorLevel errorLevel = ErrorLevel.valueOf("ERROR");
20+
21+
/**
22+
* Boolean allowing to enable or disable the N+1 queries detection
23+
*/
24+
private boolean enabled = true;
25+
26+
public ErrorLevel getErrorLevel() {
27+
return errorLevel;
28+
}
29+
30+
public void setErrorLevel(ErrorLevel errorLevel) {
31+
this.errorLevel = errorLevel;
32+
}
33+
34+
public boolean isEnabled() {
35+
return enabled;
36+
}
37+
38+
public void setEnabled(boolean enabled) {
39+
this.enabled = enabled;
40+
}
41+
}

src/main/java/com/yannbriancon/interceptor/HibernateQueryInterceptor.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.yannbriancon.interceptor;
22

3+
import com.yannbriancon.config.NPlusOneQueriesDetectionProperties;
34
import com.yannbriancon.exception.NPlusOneQueriesException;
45
import org.hibernate.EmptyInterceptor;
56
import org.hibernate.Transaction;
@@ -18,7 +19,7 @@
1819
import java.util.function.Supplier;
1920

2021
@Component
21-
@EnableConfigurationProperties(HibernateQueryInterceptorProperties.class)
22+
@EnableConfigurationProperties(NPlusOneQueriesDetectionProperties.class)
2223
public class HibernateQueryInterceptor extends EmptyInterceptor {
2324

2425
private transient ThreadLocal<Long> threadQueryCount = new ThreadLocal<>();
@@ -31,15 +32,15 @@ public class HibernateQueryInterceptor extends EmptyInterceptor {
3132

3233
private static final Logger LOGGER = LoggerFactory.getLogger(HibernateQueryInterceptor.class);
3334

34-
private final HibernateQueryInterceptorProperties hibernateQueryInterceptorProperties;
35+
private final NPlusOneQueriesDetectionProperties NPlusOneQueriesDetectionProperties;
3536

3637
private static final String HIBERNATE_PROXY_PREFIX = "org.hibernate.proxy";
3738
private static final String PROXY_METHOD_PREFIX = "com.sun.proxy";
3839

3940
public HibernateQueryInterceptor(
40-
HibernateQueryInterceptorProperties hibernateQueryInterceptorProperties
41+
NPlusOneQueriesDetectionProperties NPlusOneQueriesDetectionProperties
4142
) {
42-
this.hibernateQueryInterceptorProperties = hibernateQueryInterceptorProperties;
43+
this.NPlusOneQueriesDetectionProperties = NPlusOneQueriesDetectionProperties;
4344
}
4445

4546
/**
@@ -85,7 +86,7 @@ public Long getQueryCount() {
8586
*/
8687
@Override
8788
public String onPrepareStatement(String sql) {
88-
if (hibernateQueryInterceptorProperties.isnPlusOneDetectionEnabled()) {
89+
if (NPlusOneQueriesDetectionProperties.isEnabled()) {
8990
updateSelectQueriesInfoPerProxyMethod(sql);
9091
}
9192
Long count = threadQueryCount.get();
@@ -114,7 +115,7 @@ public void afterTransactionCompletion(Transaction tx) {
114115
*/
115116
@Override
116117
public Object getEntity(String entityName, Serializable id) {
117-
if (hibernateQueryInterceptorProperties.isnPlusOneDetectionEnabled()) {
118+
if (NPlusOneQueriesDetectionProperties.isEnabled()) {
118119
detectNPlusOneQueriesFromMissingEagerFetchingOnAQuery(entityName, id);
119120
detectNPlusOneQueriesFromClassFieldEagerFetching(entityName);
120121
}
@@ -278,7 +279,7 @@ private Optional<String> getProxyMethodName() {
278279
* @param errorMessage Error message for the N+1 queries detected
279280
*/
280281
private void logDetectedNPlusOneQueries(String errorMessage) {
281-
switch (hibernateQueryInterceptorProperties.getErrorLevel()) {
282+
switch (NPlusOneQueriesDetectionProperties.getErrorLevel()) {
282283
case INFO:
283284
LOGGER.info(errorMessage);
284285
break;
@@ -288,8 +289,10 @@ private void logDetectedNPlusOneQueries(String errorMessage) {
288289
case ERROR:
289290
LOGGER.error(errorMessage);
290291
break;
291-
default:
292+
case EXCEPTION:
292293
throw new NPlusOneQueriesException(errorMessage);
294+
default:
295+
break;
293296
}
294297
}
295298
}

0 commit comments

Comments
 (0)