ํ•˜๋‚˜์˜ ์š”์ฒญ์— ๋‚˜๊ฐ€๋Š” ์ฟผ๋ฆฌ ๊ฐœ์ˆ˜๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์„ธ์–ด๋ณด์ž (Hibernate StatementInspector, Spring AOP)

1. ๊ฐœ์š”

JPA๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, ์ง€์—ฐ ๋กœ๋”ฉ๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ๋ฌด๋ถ„๋ณ„ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋‹ค๋ณด๋ฉด ์˜๋„ ํ•˜์ง€ ์•Š์€ ์ˆ˜๋งŽ์€ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ€๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค. ํ”ํžˆ ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ N+1 ๋ฌธ์ œ๋ผ๊ณ  ํ•œ๋‹ค. 

 

์ด๋ ‡๋“ฏ ORM์„ ์‚ฌ์šฉํ•˜๋‹ค๋ณด๋ฉด ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๊ฐœ์ˆ˜์˜ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ๋„ ํ•˜๋Š”๋ฐ, ์ด๋ฅผ ํ•œ๋ˆˆ์— ํŒŒ์•…ํ•˜๊ธฐ๋Š” ์‰ฝ์ง€ ์•Š๋‹ค. ๋กœ๊ทธ์— ์ฟผ๋ฆฌ๊ฐ€ ์ถœ๋ ฅ๋˜๊ธด ํ•˜์ง€๋งŒ ํ•˜๋‚˜ํ•˜๋‚˜ ์„ธ๊ณ  ์žˆ์ž๋‹ˆ ์ƒ์‚ฐ์„ฑ์ด ๋„ˆ๋ฌด ๋–จ์–ด์ง„๋‹ค. 

 

๋ฐฉ๋ฒ•์ด ์—†์„๊นŒ ์ฐพ์•„๋ณด๋‹ค ์ฟผ๋ฆฌ ์นด์šดํ„ฐ์— ๋Œ€ํ•ด ์•Œ๊ฒŒ๋๋‹ค. 

 

๋Œ€ํ‘œ์ ์ธ, ๋˜ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์€ Hibernate์—์„œ ์ฟผ๋ฆฌ๋ฌธ์„ ๊ฐ€๋กœ์ฑŒ ์ˆ˜ ์žˆ๋Š” StatementInspector๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋ก  Spring AOP๋ฅผ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค. 

 

๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ํ•˜๋‚˜ํ•˜๋‚˜ ์•Œ์•„๋ณด๋ ค ํ•œ๋‹ค. ๊ฒฐ๋ก ๋ถ€ํ„ฐ ์–˜๊ธฐํ•˜์ž๋ฉด ๋‚˜๋Š” Spring AOP๋ฅผ ํ™œ์šฉํ•œ ์ฟผ๋ฆฌ ์นด์šดํŒ… ๊ธฐ๋Šฅ์„ ๊ฐœ๋ฐœํ–ˆ๋‹ค.  

 

 

 

 

 

 

2. StatementInspector

์ฟผ๋ฆฌ ์นด์šดํ„ฐ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„  ์•„๋ž˜ 2๊ฐ€์ง€ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•ด์•ผ ํ•œ๋‹ค.

 

1. ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ๋ฐ”๋กœ ์ง์ „ queryCount ๊ฐ’์„ ์ฆ๊ฐ€์‹œํ‚ค๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์‹คํ–‰

2. queryCount๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ฐ์ฒด์˜ ์ƒ๋ช…์ฃผ๊ธฐ๋Š” HTTP request์™€ ์ผ์น˜

 

 

2๋ฒˆ์˜ ์กฐ๊ฑด์€ @RequestScope๋ฅผ ํ™œ์šฉํ•˜์—ฌ Http request ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๊ฐ€์ง€๋Š” ๋นˆ์„ ๋งŒ๋“ค์–ด ์ฃผ๋ฉด ๊ฐ„๋‹จํžˆ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. 1๋ฒˆ์˜ ๊ฒฝ์šฐ StatementInspector๊ฐ€ ์ด ์—ญํ• ์„ ํ•ด์ค€๋‹ค.


 

 

StatementInspector๋Š” ํ•˜์ด๋ฒ„๋„ค์ดํŠธ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค. inspect๋ผ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”๋ฐ, HibernateProperty๋กœ StatementInspector ๊ตฌํ˜„์ฒด๋ฅผ ๋“ฑ๋กํ•˜๋ฉด ์ฟผ๋ฆฌ ์‹คํ–‰ ์‹œ์ ์— ์‹คํ–‰๋  ์ฟผ๋ฆฌ๋ฌธ์„ ์ธ์ž๋กœ ๋ฐ›๋Š” inspect ๋ฉ”์„œ๋“œ๊ฐ€ ์‹คํ–‰๋œ๋‹ค. 

 

๋”ฐ๋ผ์„œ StatementInspector ๊ตฌํ˜„์ฒด๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•˜์—ฌ ์ฟผ๋ฆฌ ๊ฐœ์ˆ˜๋ฅผ ์„ธ๋Š”๋ฐ ํ™œ์šฉํ•˜๋„๋ก ํ•  ๊ฒƒ์ด๋‹ค. 

 

 

๋จผ์ € HTTP request์™€ ์ฃผ๊ธฐ๋ฅผ ๊ฐ™์ด ํ•˜๋Š” ๋นˆ์„ ๋“ฑ๋ก์‹œ์ผœ์ค˜์•ผ ํ•œ๋‹ค. ๋‹จ์ˆœํžˆ ๊ฐœ์ˆ˜๋งŒ ์ €์žฅํ•ด์ฃผ๋Š” ๊ฐ์ฒด์ด๋‹ค.

@Component
@RequestScope
@Getter
public class ApiQueryCounter {

    private int count;

    public void increaseCount() {
        count++;
    }
}

 

 

 

์ด์ œ ์œ„์—์„œ ๋งŒ๋“  ApiQueryCounter์˜ ๊ฐ’์„ ์ฆ๊ฐ€์‹œ์ผœ์ฃผ๋Š” ๋กœ์ง์„ ๊ฐ€์ง„ StatementInspector ๊ตฌํ˜„์ฒด๋ฅผ ์ƒ์„ฑํ•ด์ค€๋‹ค. 

@Component
public class ApiQueryInspector implements StatementInspector {

    private final ApiQueryCounter apiQueryCounter;

    public ApiQueryInspector(final ApiQueryCounter apiQueryCounter) {
        this.apiQueryCounter = apiQueryCounter;
    }

    @Override
    public String inspect(final String sql) {
        if (isInRequestScope()) {
            apiQueryCounter.increaseCount();
        }
        return sql;
    }

    private boolean isInRequestScope() {
        return Objects.nonNull(RequestContextHolder.getRequestAttributes());
    }
}

 

๋นˆ์œผ๋กœ ๋งŒ๋“ค์–ด ์ค€ ๋’ค, ApiQueryCounter์˜ ์˜์กด์„ฑ์„ ์ฃผ์ž…๋ฐ›์•„์ค€๋‹ค.

inspect ๋ฉ”์„œ๋“œ๊ฐ€ ์‹คํ–‰๋˜๋ฉด ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋œ๋‹ค๋Š” ์˜๋ฏธ์ด๋ฏ€๋กœ apiQueryCounter.increaseCount๋ฅผ ํ˜ธ์ถœํ•ด์ฃผ๊ณ  ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์€ ์ฟผ๋ฆฌ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์‹คํ–‰์‹œ์ผœ์ฃผ๋ฉด ๋œ๋‹ค.

 

 

 

 

์ด์ œ HibernateConfig๋ฅผ ์ž‘์„ฑํ•ด์ค€๋‹ค.

์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  ApiQueryInspector๋Š” ์ปค์Šคํ…€ ๋นˆ์ธ ApiQueryCounter๋ฅผ ์ฃผ์ž…๋ฐ›๊ณ  ์žˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค๋ฅธ ๋นˆ๋“ค์ด ๋ชจ๋‘ ์ดˆ๊ธฐํ™”๋˜๊ณ  ๋‚˜์„œ HibernateProperty๋กœ ๋“ฑ๋กํ•˜๋„๋ก ํ•ด์•ผํ•œ๋‹ค. 

@Configuration ์•ˆ์—์„œ @Bean์œผ๋กœ ๋นˆ ๋“ฑ๋ก์„ ํ•ด์ฃผ๊ฒŒ ๋˜๋ฉด ์ปดํฌ๋„ŒํŠธ ์Šค์บ”๋ณด๋‹ค ๋‚˜์ค‘์— ๋นˆ ๋“ฑ๋ก์ด ๋œ๋‹ค๋Š” ์ ์„ ์ด์šฉํ•˜์—ฌ ApiQueryInspector๋ฅผ HibernateProperties์— ๋“ฑ๋กํ•˜๋„๋ก ํ•˜์˜€๋‹ค.

@Configuration
@RequiredArgsConstructor
public class HibernateConfig {

    private final ApiQueryInspector apiQueryInspector;

    @Bean
    public HibernatePropertiesCustomizer hibernatePropertyConfig() {
        return hibernateProperties ->
                hibernateProperties.put(AvailableSettings.STATEMENT_INSPECTOR, apiQueryInspector);
    }
}

 

์ด๋ ‡๊ฒŒ ์„ค์ •์„ ๋๋งˆ์น˜๋ฉด ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋  ๋•Œ๋งˆ๋‹ค inspect ๋ฉ”์†Œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๊ฒŒ ๋œ๋‹ค. 

 

 

 

 

์ด์ œ ๋งˆ์ง€๋ง‰์œผ๋กœ ์ธํ„ฐ์…‰ํ„ฐ์—์„œ ์„ผ ์ฟผ๋ฆฌ ๊ฐœ์ˆ˜๋ฅผ ๋กœ๊ทธ๋กœ ๋‚จ๊ธธ ๊ฒƒ์ด๋‹ค. HandlerInterceptor๋ฅผ implementsํ•ด LoggingInterceptor๋ฅผ ๋งŒ๋“ค์–ด์ฃผ์—ˆ๋‹ค. 

@Slf4j
@Component
@RequiredArgsConstructor
public class LoggingInterceptor implements HandlerInterceptor {

    private static final String QUERY_COUNT_LOG_FORMAT = "STATUS_CODE: {}, METHOD: {}, URL: {}, QUERY_COUNT: {}";
    private static final String QUERY_COUNT_WARNING_LOG_FORMAT = "์ฟผ๋ฆฌ๊ฐ€ {}๋ฒˆ ์ด์ƒ ์‹คํ–‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค.";

    private static final int QUERY_COUNT_WARNING_STANDARD = 10;

    private final ApiQueryCounter apiQueryCounter;
    

    @Override
    public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response,
                                final Object handler, final Exception ex) {
        final int queryCount = apiQueryCounter.getCount();

        log.info(QUERY_COUNT_LOG_FORMAT, response.getStatus(), request.getMethod(), request.getRequestURI(), queryCount);
        
        // 10๊ฐœ ์ด์ƒ์˜ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐˆ ์‹œ warn
        if (queryCount >= QUERY_COUNT_WARNING_STANDARD) {
            log.warn(QUERY_COUNT_WARNING_LOG_FORMAT, QUERY_COUNT_WARNING_STANDARD);
        }
    }
}

 

 

 

 

์ด์ œ WebConfig์— ๋“ฑ๋กํ•ด์ฃผ๋ฉด ๋ชจ๋“  ์„ค์ •์€ ๋์ด๋‚œ๋‹ค. 

@Configuration
public class WebConfig implements WebMvcConfigurer {

    private final LoggingInterceptor loggingInterceptor;

    public WebConfig(LoggingInterceptor loggingInterceptor) {
        this.loggingInterceptor = loggingInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loggingInterceptor);
    }
}

 

์•„๋ž˜์™€ ๊ฐ™์ด ์–ด๋–ค URL์— Query๊ฐ€ ๋ช‡ ๋ฒˆ ๋‚˜๊ฐ€๋Š” ์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

 

 

 

 

 

 

 

 

3. Spring AOP 

StatementInspector์˜ ๊ฐ€์žฅ ํฐ ๋‹จ์ ์€ Hibernate์— ์ข…์†์ ์ด๋ผ๋Š” ๊ฒƒ์ด๋‹ค. 

๊ฐœ๋ฐœํ•˜๋‹ค๋ณด๋ฉด ๋„ค์ดํ‹ฐ๋ธŒ ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ฆฌ๋Š” ๊ฒฝ์šฐ๋„ ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ž๋ฐ”์˜ ํ‘œ์ค€ ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ธฐ์ˆ ์ธ JDBC ๋‹จ์—์„œ ์นด์šดํŠธ๋ฅผ ์„ธ๋Š” ๊ฒŒ ๋” ๋‚˜์€ ๋ฐฉ๋ฒ•์ธ ๊ฒƒ ๊ฐ™๋‹ค. 

 

 

๋จผ์ € Jdbc์— ์ฟผ๋ฆฌ๊ฐ€ ๋‚ ์•„๊ฐ€๋Š” ๊ณผ์ •์„ ์•Œ์•„์•ผํ•œ๋‹ค. 

 

Datasource์—์„œ connection์„ ์–ป๊ณ , ์ด๋ฅผ ์ด์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ ์‹คํ–‰ํ•œ๋‹ค. 

 

 

 

์ด ๊ณผ์ •์—์„œ prepareStatement๊ฐ€ ์‹คํ–‰๋˜๋Š” ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. 

Connection์— AOP๋ฅผ ์ ์šฉํ•ด prepareStatement๊ฐ€ ์‹คํ–‰๋  ๋•Œ ์žก์•„์„œ ์นด์šดํŠธ๋ฅผ ํ•˜๋ฉด ๋œ๋‹ค. 

 

ํ•˜์ง€๋งŒ ํ•œ๊ฐ€์ง€ ๋ฌธ์ œ์ ์ด ์žˆ๋‹ค. 

AOP๋Š” ์Šคํ”„๋ง ๋นˆ์—๋งŒ ์ ์šฉํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— Connection์— ์ง์ ‘์ ์œผ๋กœ ์ ์šฉํ•˜์ง„ ๋ชปํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ์Šคํ”„๋ง ๋นˆ์œผ๋กœ ๋“ฑ๋ก๋œ DataSource์— AOP๋ฅผ ์ ์šฉํ•˜๊ณ  ์ฟผ๋ฆฌ ์นด์šดํŠธ ๊ธฐ๋Šฅ์„ ๊ฐ€์ง„ Connection์„ ํ”„๋ก์‹œ ๊ฐ์ฒด๋กœ ๋งŒ๋“ค์–ด์•ผํ•œ๋‹ค. 

 

 

ํ”„๋ก์‹œ๋Š” ํƒ€๊ฒŸ์˜ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜๊ฑฐ๋‚˜ ํƒ€๊ฒŸ์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•œ ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ํด๋ž˜์Šค๋ฅผ ๋งํ•œ๋‹ค. ๊ทผ๋ฐ Connection์„ ํ”„๋ก์‹œ๋กœ ๊ตฌํ˜„์„ ํ•˜๋ ค๋ฉด Connection์˜ ๋ชจ๋“  ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด ์ค˜์•ผ ๋˜๋Š”๋ฐ ์ด๊ฑธ ์ „๋ถ€ ๊ตฌํ˜„ํ•˜๋Š” ๊ฑด..

๋‹ค์ด๋‚˜๋ฏน ํ”„๋ก์‹œ๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ”„๋ก์‹œ๋ฅผ ์ƒ์„ฑํ•ด ์ฃผ์ž.

 

๋‹ค์ด๋‚˜๋ฏน ํ”„๋ก์‹œ๋ž€?
๋™์ ์ธ ์‹œ์ ์— ํ”„๋ก์‹œ๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด ์ ์šฉํ•˜๋Š” ๊ธฐ์ˆ ์„ ๋งํ•จ

 

 

QueryCounter ์ƒ์„ฑ

@Getter
@Component
@RequestScope
public class QueryCounter {

    private int count;

    public void increaseCount() {
        count++;
    }

}

์นด์šดํŠธ ์—ญํ• ์„ ํ•˜๋Š” ์ด ๊ฐ์ฒด์˜ ์ƒ๋ช…์ฃผ๊ธฐ๋Š” ํŠน์ • ์Šค๋ ˆ๋“œ์˜ ์š”์ฒญ ๋™์•ˆ๋งŒ ์‚ฌ์šฉ๋œ๋‹ค. ThreadLocal์„ ์จ์„œ ์ €์žฅ, ์‚ญ์ œํ•ด ์ค„ ์ˆ˜๋„ ์žˆ๊ฒ ์ง€๋งŒ Spring์—์„œ RequstScope๋ฅผ ์ง€์›ํ•ด ์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ํ™œ์šฉํ•ด ์ค€๋‹ค.

 

 

ConnectionProxyHandler ์ƒ์„ฑ

Connection ๋‹ค์ด๋‚˜๋ฏน ํ”„๋ก์‹œ ๊ตฌํ˜„์„ ์œ„ํ•ด handler๋ฅผ ๋จผ์ € ๋งŒ๋“ค์–ด์•ผ ๋œ๋‹ค. ์›ํ•˜๋Š” ๋™์ž‘(์นด์šดํŠธ)์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก InvocationHandler๋ฅผ ๊ตฌํ˜„ํ•ด ์ค€๋‹ค.

public class ConnectionProxyHandler implements InvocationHandler {

    private static final String QUERY_PREPARE_STATEMENT = "prepareStatement";

    private final Object connection;
    private final QueryCounter queryCounter;

    public ConnectionProxyHandler(Object connection, QueryCounter queryCounter) {
        this.connection = connection;
        this.queryCounter = queryCounter;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        countQuery(method);
        return method.invoke(connection, args);
    }

    private void countQuery(Method method) {
        if (isPrepareStatement(method) && isRequest()) {
            queryCounter.increaseCount();
        }
    }

    private boolean isPrepareStatement(Method method) {
        return method.getName().equals(QUERY_PREPARE_STATEMENT);
    }

    private boolean isRequest() {
        return RequestContextHolder.getRequestAttributes() != null;
    }
}

 

invoke ๋ฉ”์„œ๋“œ๊ฐ€ ์‹ค์ œ target์˜ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์„ ๊ฐ€๋กœ์ฑ„๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋ถ€๋ถ„์— ์ถ”๊ฐ€์ ์œผ๋กœ ์ ์šฉํ•  ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค. ์‹คํ–‰๋˜๋Š” ๋ฉ”์†Œ๋“œ์˜ ๋ช…์ด "prepareStatement" ๋ผ๋ฉด countํ•ด์ฃผ์—ˆ๋‹ค. 

 

ํ˜„์žฌ QueryCounter๊ฐ€ RequestScope๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— isRequest ๋ถ„๊ธฐ๊ฐ€ ์—†์œผ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋‹ˆ ๊ผญ ์ถ”๊ฐ€ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

Scope ‘request’ is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton;

 

 

AOP ์ ์šฉ

DataSource๊ฐ€ getConnection() ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ Connection์„ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์ด ์žฅ์ฐฉ๋œ ํ”„๋ก์‹œ ๊ฐ์ฒด๋กœ ๋ฎ์–ด์”Œ์›Œ์ค˜์•ผ ๋œ๋‹ค. ๋ฐฉ๊ธˆ ๋งŒ๋“  Handler๋ฅผ ์ด์šฉํ•˜์—ฌ Connection ๋‹ค์ด๋‚˜๋ฏน ํ”„๋ก์‹œ๋ฅผ ๊ตฌํ˜„ํ•ด ๋ณด์ž.

 

์šฐ์„  ์ž๋ฐ”์—์„œ ๋‹ค์ด๋‚˜๋ฏน ํ”„๋ก์‹œ๋Š” Java.lang.reflect.Proxy ํด๋ž˜์Šค์˜ newProxyInstance() ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•˜์—ฌ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

 

Proxy.newProxyInstance(
        ํ”„๋ก์‹œ ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•˜๋Š” ํด๋ž˜์Šค ๋กœ๋”,
        ๊ตฌํ˜„ํ•  ํ”„๋ก์‹œ ํด๋ž˜์Šค์˜ ์ธํ„ฐํŽ˜์ด์Šค ๋ชฉ๋ก,
        ๋ฉ”์†Œ๋“œ ํ˜ธ์ถœ์„ ์ „๋‹ฌํ•˜๋Š” ํ˜ธ์ถœ ํ•ธ๋“ค๋Ÿฌ
);

 

@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class QueryCountAspect {

    private final QueryCounter queryCounter;

    @Around("execution(* javax.sql.DataSource.getConnection(..))")
    public Object getConnection(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object connection = proceedingJoinPoint.proceed();
        return Proxy.newProxyInstance(
                connection.getClass().getClassLoader(),
                connection.getClass().getInterfaces(),
                new ConnectionHandler(connection, queryCounter)
        );
    }
}

 

 

์ฟผ๋ฆฌ ์นด์šดํŠธ ๋กœ๊น… ์ธํ„ฐ์…‰ํ„ฐ ์ƒ์„ฑ

@Slf4j
@Component
@RequiredArgsConstructor
public class LoggingInterceptor implements HandlerInterceptor {

    private static final String QUERY_COUNT_LOG = "METHOD: {}, URL: {}, STATUS_CODE: {}, QUERY_COUNT: {}";
    private static final String QUERY_COUNT_WARN_LOG = "์ฟผ๋ฆฌ๊ฐ€ {}๋ฒˆ ์ด์ƒ ์‹คํ–‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค!!!";
    private static final int WARN_QUERY_COUNT= 8;

    private final QueryCounter queryCounter;

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) {
        int queryCount = queryCounter.getCount();
        log.info(QUERY_COUNT_LOG, request.getMethod(), request.getRequestURI(), response.getStatus(), queryCount);
        if (queryCount >= WARN_QUERY_COUNT) {
            log.warn(QUERY_COUNT_WARN_LOG, WARN_QUERY_COUNT);
        }
    }

}

 

 

 

AOP๋ฅผ ์ ์šฉํ•ด๋„ ์•„๊นŒ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ฟผ๋ฆฌ ์นด์šดํŒ…์ด ์ž˜ ๋˜๋Š”๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ๐Ÿ˜Š