Skip to content

Commit

Permalink
Add QueryStackTraceLogger that allows you to locate the source of an …
Browse files Browse the repository at this point in the history
…SQL query executed by Hibernate #653
  • Loading branch information
vladmihalcea committed Sep 19, 2023
1 parent 9a397d9 commit 19096d6
Show file tree
Hide file tree
Showing 15 changed files with 1,178 additions and 44 deletions.
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;
}
}
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@

public abstract class AbstractTest {

static {
Thread.currentThread().setName("Alice");
}

protected final ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Expand Down
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;
}
}
Loading

0 comments on commit 19096d6

Please sign in to comment.