-
Notifications
You must be signed in to change notification settings - Fork 372
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add QueryStackTraceLogger that allows you to locate the source of an …
…SQL query executed by Hibernate #653
- Loading branch information
1 parent
9a397d9
commit 19096d6
Showing
15 changed files
with
1,178 additions
and
44 deletions.
There are no files selected for viewing
82 changes: 82 additions & 0 deletions
82
...bernate-5/src/main/java/io/hypersistence/utils/hibernate/query/QueryStackTraceLogger.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package io.hypersistence.utils.hibernate.query; | ||
|
||
import org.hibernate.resource.jdbc.spi.StatementInspector; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
/** | ||
* The {@link QueryStackTraceLogger} allows you to log the stack trace that | ||
* executed a given SQL query. | ||
* | ||
* @author Vlad Mihalcea | ||
* @since 3.5.3 | ||
*/ | ||
public class QueryStackTraceLogger implements StatementInspector { | ||
|
||
public static final String ORG_HIBERNATE ="org.hibernate"; | ||
|
||
public static String TAB = "\t"; | ||
public static String NEW_LINE = System.getProperty("line.separator"); | ||
|
||
private static final Logger LOGGER = LoggerFactory.getLogger(QueryStackTraceLogger.class); | ||
|
||
private final String packageNamePrefix; | ||
|
||
public QueryStackTraceLogger(String packageNamePrefix) { | ||
this.packageNamePrefix = packageNamePrefix; | ||
} | ||
|
||
@Override | ||
public String inspect(String sql) { | ||
LOGGER.debug( | ||
"This SQL query: [\n\t{}\n]\nwas generated by Hibernate like this: [\n{}\n]", | ||
sql, | ||
String.join( | ||
NEW_LINE, | ||
stackTraceElementsUpTo(packageNamePrefix) | ||
.stream() | ||
.map(e -> TAB + e.toString()) | ||
.collect(Collectors.toList()) | ||
) | ||
); | ||
return null; | ||
} | ||
|
||
/** | ||
* Filter the stack trace based up to the provided package name prefix | ||
* | ||
* @param endPackageNamePrefix package name to match the {@link StackTraceElement} | ||
* @return the {@link StackTraceElement} up to the matching the provided package name | ||
*/ | ||
private List<StackTraceElement> stackTraceElementsUpTo(String endPackageNamePrefix) { | ||
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); | ||
List<StackTraceElement> filteredStackTraceElements = new ArrayList<>(); | ||
boolean startPackageMatched = false; | ||
boolean endPackageMatched = false; | ||
for (StackTraceElement stackTraceElement : stackTraceElements) { | ||
String className = stackTraceElement.getClassName(); | ||
if(!startPackageMatched) { | ||
if(className.startsWith(ORG_HIBERNATE)) { | ||
startPackageMatched = true; | ||
} else { | ||
continue; | ||
} | ||
} | ||
if(!className.contains(endPackageNamePrefix)) { | ||
if(!endPackageMatched) { | ||
filteredStackTraceElements.add(stackTraceElement); | ||
} else { | ||
break; | ||
} | ||
} else if(!endPackageMatched) { | ||
endPackageMatched = true; | ||
filteredStackTraceElements.add(stackTraceElement); | ||
} | ||
} | ||
return filteredStackTraceElements; | ||
} | ||
} |
160 changes: 160 additions & 0 deletions
160
...ate-5/src/test/java/io/hypersistence/utils/hibernate/query/QueryStackTraceLoggerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
package io.hypersistence.utils.hibernate.query; | ||
|
||
import io.hypersistence.utils.hibernate.util.AbstractPostgreSQLIntegrationTest; | ||
import io.hypersistence.utils.hibernate.util.transaction.JPATransactionFunction; | ||
import org.junit.Test; | ||
|
||
import javax.persistence.*; | ||
import javax.persistence.criteria.CriteriaBuilder; | ||
import javax.persistence.criteria.CriteriaQuery; | ||
import javax.persistence.criteria.Join; | ||
import javax.persistence.criteria.Root; | ||
import java.time.LocalDate; | ||
import java.util.Properties; | ||
|
||
/** | ||
* @author Vlad Mihalcea | ||
*/ | ||
public class QueryStackTraceLoggerTest extends AbstractPostgreSQLIntegrationTest { | ||
|
||
@Override | ||
protected Class<?>[] entities() { | ||
return new Class<?>[]{ | ||
Post.class, | ||
PostComment.class | ||
}; | ||
} | ||
|
||
@Override | ||
protected void additionalProperties(Properties properties) { | ||
properties.put( | ||
"hibernate.session_factory.statement_inspector", | ||
new QueryStackTraceLogger("io.hypersistence.utils.hibernate.query") | ||
); | ||
} | ||
|
||
@Test | ||
public void testJPQL() { | ||
doInJPA(new JPATransactionFunction<Void>() { | ||
|
||
@Override | ||
public Void apply(EntityManager entityManager) { | ||
entityManager.createQuery( | ||
"select " + | ||
" count(p) as postCount " + | ||
"from " + | ||
" Post p ", Tuple.class) | ||
.getResultList(); | ||
|
||
return null; | ||
} | ||
}); | ||
} | ||
|
||
@Test | ||
public void testCriteriaAPI() { | ||
doInJPA(new JPATransactionFunction<Void>() { | ||
|
||
@Override | ||
public Void apply(EntityManager entityManager) { | ||
CriteriaBuilder builder = entityManager.getCriteriaBuilder(); | ||
|
||
CriteriaQuery<PostComment> criteria = builder.createQuery(PostComment.class); | ||
|
||
Root<PostComment> postComment = criteria.from(PostComment.class); | ||
Join<PostComment, Post> post = postComment.join("post"); | ||
|
||
criteria.where( | ||
builder.like(post.get("title"), "%Java%") | ||
); | ||
|
||
criteria.orderBy( | ||
builder.asc(postComment.get("id")) | ||
); | ||
|
||
entityManager.createQuery(criteria).getResultList(); | ||
return null; | ||
} | ||
}); | ||
} | ||
|
||
@Entity(name = "Post") | ||
@Table(name = "post") | ||
public static class Post { | ||
|
||
@Id | ||
private Long id; | ||
|
||
private String title; | ||
|
||
@Column(name = "created_on") | ||
private LocalDate createdOn; | ||
|
||
public Long getId() { | ||
return id; | ||
} | ||
|
||
public Post setId(Long id) { | ||
this.id = id; | ||
return this; | ||
} | ||
|
||
public String getTitle() { | ||
return title; | ||
} | ||
|
||
public Post setTitle(String title) { | ||
this.title = title; | ||
return this; | ||
} | ||
|
||
public LocalDate getCreatedOn() { | ||
return createdOn; | ||
} | ||
|
||
public Post setCreatedOn(LocalDate createdOn) { | ||
this.createdOn = createdOn; | ||
return this; | ||
} | ||
} | ||
|
||
@Entity(name = "PostComment") | ||
@Table(name = "post_comment") | ||
public static class PostComment { | ||
|
||
@Id | ||
private Long id; | ||
|
||
@ManyToOne(fetch = FetchType.LAZY) | ||
private Post post; | ||
|
||
private String review; | ||
|
||
public Long getId() { | ||
return id; | ||
} | ||
|
||
public PostComment setId(Long id) { | ||
this.id = id; | ||
return this; | ||
} | ||
|
||
public Post getPost() { | ||
return post; | ||
} | ||
|
||
public PostComment setPost(Post post) { | ||
this.post = post; | ||
return this; | ||
} | ||
|
||
public String getReview() { | ||
return review; | ||
} | ||
|
||
public PostComment setReview(String review) { | ||
this.review = review; | ||
return this; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
...ernate-52/src/main/java/io/hypersistence/utils/hibernate/query/QueryStackTraceLogger.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package io.hypersistence.utils.hibernate.query; | ||
|
||
import org.hibernate.resource.jdbc.spi.StatementInspector; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
/** | ||
* The {@link QueryStackTraceLogger} allows you to log the stack trace that | ||
* executed a given SQL query. | ||
* | ||
* @author Vlad Mihalcea | ||
* @since 3.5.3 | ||
*/ | ||
public class QueryStackTraceLogger implements StatementInspector { | ||
|
||
public static final String ORG_HIBERNATE ="org.hibernate"; | ||
|
||
public static String TAB = "\t"; | ||
public static String NEW_LINE = System.getProperty("line.separator"); | ||
|
||
private static final Logger LOGGER = LoggerFactory.getLogger(QueryStackTraceLogger.class); | ||
|
||
private final String packageNamePrefix; | ||
|
||
public QueryStackTraceLogger(String packageNamePrefix) { | ||
this.packageNamePrefix = packageNamePrefix; | ||
} | ||
|
||
@Override | ||
public String inspect(String sql) { | ||
LOGGER.debug( | ||
"This SQL query: [\n\t{}\n]\nwas generated by Hibernate like this: [\n{}\n]", | ||
sql, | ||
String.join( | ||
NEW_LINE, | ||
stackTraceElementsUpTo(packageNamePrefix) | ||
.stream() | ||
.map(e -> TAB + e.toString()) | ||
.collect(Collectors.toList()) | ||
) | ||
); | ||
return null; | ||
} | ||
|
||
/** | ||
* Filter the stack trace based up to the provided package name prefix | ||
* | ||
* @param endPackageNamePrefix package name to match the {@link StackTraceElement} | ||
* @return the {@link StackTraceElement} up to the matching the provided package name | ||
*/ | ||
private List<StackTraceElement> stackTraceElementsUpTo(String endPackageNamePrefix) { | ||
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); | ||
List<StackTraceElement> filteredStackTraceElements = new ArrayList<>(); | ||
boolean startPackageMatched = false; | ||
boolean endPackageMatched = false; | ||
for (StackTraceElement stackTraceElement : stackTraceElements) { | ||
String className = stackTraceElement.getClassName(); | ||
if(!startPackageMatched) { | ||
if(className.startsWith(ORG_HIBERNATE)) { | ||
startPackageMatched = true; | ||
} else { | ||
continue; | ||
} | ||
} | ||
if(!className.contains(endPackageNamePrefix)) { | ||
if(!endPackageMatched) { | ||
filteredStackTraceElements.add(stackTraceElement); | ||
} else { | ||
break; | ||
} | ||
} else if(!endPackageMatched) { | ||
endPackageMatched = true; | ||
filteredStackTraceElements.add(stackTraceElement); | ||
} | ||
} | ||
return filteredStackTraceElements; | ||
} | ||
} |
Oops, something went wrong.