Skip to content

Commit

Permalink
Merge pull request #11 from joon6093/2.0.x
Browse files Browse the repository at this point in the history
Release v2.0.1
  • Loading branch information
joon6093 authored Sep 19, 2024
2 parents aab9199 + 6748f9b commit cead228
Show file tree
Hide file tree
Showing 13 changed files with 340 additions and 134 deletions.
2 changes: 1 addition & 1 deletion detector/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
}

group = 'io.jeyong'
version = '2.0.0'
version = '2.0.1'

java {
toolchain {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public NPlusOneDetectorConfig(final NPlusOneDetectorProperties nPlusOneDetectorP
public void logInitialization() {
logger.info("N+1 Detector is enabled with threshold: {}, log level: {}",
nPlusOneDetectorProperties.getThreshold(),
nPlusOneDetectorProperties.getLevel().toString());
nPlusOneDetectorProperties.getLevel().toString().toLowerCase());
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.jeyong.detector.context;

import io.jeyong.detector.test.NPlusOneQueryException;

public final class ExceptionContext { // Todo: Consider concurrency issues

private NPlusOneQueryException primaryException;

public void saveException(final NPlusOneQueryException exception) {
if (primaryException != null) {
primaryException.addSuppressed(exception);
} else {
primaryException = exception;
}
}

public NPlusOneQueryException getException() {
return primaryException;
}

public void clearException() {
primaryException = null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.jeyong.detector.template;

import io.jeyong.detector.context.ExceptionContext;
import io.jeyong.detector.test.NPlusOneQueryException;

public final class NPlusOneQueryExceptionCollector extends NPlusOneQueryTemplate {

private final ExceptionContext exceptionContext;

public NPlusOneQueryExceptionCollector(final int queryThreshold, final ExceptionContext exceptionContext) {
super(queryThreshold);
this.exceptionContext = exceptionContext;
}

@Override
protected void handleDetectedNPlusOneIssue(final String query, final Long count) {
exceptionContext.saveException(new NPlusOneQueryException(query, count));
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,31 +1,55 @@
package io.jeyong.detector.test;

import io.jeyong.detector.context.ExceptionContext;
import io.jeyong.detector.context.QueryContextHolder;
import io.jeyong.detector.template.NPlusOneQueryExceptionThrower;
import io.jeyong.detector.template.NPlusOneQueryTemplate;
import java.util.Optional;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringExtension;

public class DetectNPlusOneExtension implements BeforeEachCallback, AfterEachCallback {
public class DetectNPlusOneExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback {

private final NPlusOneQueryTemplate nPlusOneQueryTemplate;
private ExceptionContext exceptionContext;

public DetectNPlusOneExtension() {
this(2);
@Override
public void beforeAll(ExtensionContext extensionContext) {
ApplicationContext applicationContext = SpringExtension.getApplicationContext(extensionContext);
exceptionContext = getExceptionContext(applicationContext);
}

public DetectNPlusOneExtension(int queryThreshold) {
this.nPlusOneQueryTemplate = new NPlusOneQueryExceptionThrower(queryThreshold);
private ExceptionContext getExceptionContext(final ApplicationContext applicationContext) {
try {
return applicationContext.getBean(ExceptionContext.class);
} catch (BeansException e) {
return null;
}
}

@Override
public void beforeEach(ExtensionContext context) throws Exception {
public void beforeEach(final ExtensionContext extensionContext) {
QueryContextHolder.clearContext();
getOptionalExceptionContext().ifPresent(ExceptionContext::clearException);
}

@Override
public void afterEach(ExtensionContext context) throws Exception {
nPlusOneQueryTemplate.handleNPlusOneIssues();
public void afterEach(final ExtensionContext extensionContext) {
getOptionalExceptionContext().ifPresent(context -> {
try {
NPlusOneQueryException primaryException = context.getException();
if (primaryException != null) {
throw primaryException;
}
} finally {
context.clearException();
}
});
}

private Optional<ExceptionContext> getOptionalExceptionContext() {
return Optional.ofNullable(exceptionContext);
}
}
27 changes: 27 additions & 0 deletions detector/src/main/java/io/jeyong/detector/test/NPlusOneTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.jeyong.detector.test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.event.Level;
import org.springframework.context.annotation.Import;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(DetectNPlusOneExtension.class)
@Import(NplusOneTestImportSelector.class)
public @interface NPlusOneTest {

int threshold() default 2;

Level level() default Level.WARN;

Mode mode() default Mode.LOGGING;

enum Mode {
LOGGING,
EXCEPTION
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,45 @@
package io.jeyong.detector.test;

import static org.hibernate.cfg.AvailableSettings.STATEMENT_INSPECTOR;

import io.jeyong.detector.interceptor.QueryStatementInspector;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import io.jeyong.detector.config.NPlusOneDetectorConfig;
import io.jeyong.detector.config.NPlusOneDetectorProperties;
import io.jeyong.detector.context.ExceptionContext;
import io.jeyong.detector.template.NPlusOneQueryExceptionCollector;
import io.jeyong.detector.template.NPlusOneQueryTemplate;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;

@Configuration
@Import(NPlusOneDetectorConfig.class)
@EnableConfigurationProperties(NPlusOneDetectorProperties.class)
public class NPlusOneTestConfig {

@Bean
public QueryStatementInspector queryStatementInspector() {
return new QueryStatementInspector();
private static final Logger logger = LoggerFactory.getLogger(NPlusOneTestConfig.class);
private final NPlusOneDetectorProperties nPlusOneDetectorProperties;

public NPlusOneTestConfig(final NPlusOneDetectorProperties nPlusOneDetectorProperties) {
this.nPlusOneDetectorProperties = nPlusOneDetectorProperties;
}

@PostConstruct
public void logInitialization() {
logger.info("N+1 issues detected will throw exceptions.");
}

@Bean
public ExceptionContext exceptionContext() {
return new ExceptionContext();
}

@Primary
@Bean
public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(
final QueryStatementInspector queryStatementInspector) {
return hibernateProperties -> hibernateProperties.put(STATEMENT_INSPECTOR, queryStatementInspector);
public NPlusOneQueryTemplate nPlusOneQueryExceptionCollector(final ExceptionContext exceptionContext) {
return new NPlusOneQueryExceptionCollector(
nPlusOneDetectorProperties.getThreshold(), exceptionContext);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.jeyong.detector.test;

import io.jeyong.detector.config.NPlusOneDetectorConfig;
import java.util.Map;
import org.slf4j.event.Level;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.NonNull;

public class NplusOneTestImportSelector implements ImportSelector {

@NonNull
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Map<String, Object> attributes = importingClassMetadata
.getAnnotationAttributes(NPlusOneTest.class.getName());

assert attributes != null;
int threshold = (int) attributes.get("threshold");
Level logLevel = (Level) attributes.get("level");
NPlusOneTest.Mode mode = (NPlusOneTest.Mode) attributes.get("mode");

System.setProperty("spring.jpa.properties.hibernate.detector.enabled", "true");
System.setProperty("spring.jpa.properties.hibernate.detector.threshold", String.valueOf(threshold));
System.setProperty("spring.jpa.properties.hibernate.detector.level", logLevel.toString());
return new String[]{
mode == NPlusOneTest.Mode.LOGGING
? NPlusOneDetectorConfig.class.getName()
: NPlusOneTestConfig.class.getName()
};
}
}
6 changes: 3 additions & 3 deletions test/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ spring:
highlight_sql: true
hbm2ddl.auto: create-drop
detector:
enabled: true # N+1 문제 감지 기능 활성화 여부 / 기본 값 : false
threshold: 2 # 쿼리 실행 횟수 기준 설정 (예: 3회 이상 실행된 쿼리에 대해 N+1 문제 감지) / 기본 값 : 2
level: warn # 감지된 N+1 문제에 대한 로깅 레벨 설정 (error, warn, info, debug) / 기본 값 : warn
enabled: true # N+1 문제 감지 기능 활성화 여부 / 기본 값 : false
threshold: 2 # 쿼리 실행 횟수 기준 설정 (예: 3회 이상 실행된 쿼리에 대해 N+1 문제 감지) / 기본 값 : 2
level: warn # 감지된 N+1 문제에 대한 로깅 레벨 설정 (error, warn, info, debug, trace) / 기본 값 : warn
# open-in-view: false
show-sql: true

This file was deleted.

Loading

0 comments on commit cead228

Please sign in to comment.