์๋ฅ์๋ฅ์ Spring Data JPA
๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
๊ทธ๋ง์ธ ์ฆ์จ, N+1 ๋ฌธ์ ๋ฅผ ๊ฐ์ ํ๊ณ , ์ฟผ๋ฆฌ ๊ฐ์๋ฅผ ๋ชจ๋ํฐ๋งํ ํ์๊ฐ ์๋ค๋ ๋ป์ด์์ต๋๋ค.
์ด์ ์ ์ ์ด์คํธ๋ ํน์ ์ปค๋ฅ์
์์ ์ฌ์ฉ๋ ์ฟผ๋ฆฌ ๊ฐ์๋ฅผ ์ธ๋ "์ฟผ๋ฆฌ ์นด์ดํฐ"๋ฅผ ๊ฐ๋ฐํ์ต๋๋ค!
sql์ count() ์๋๋๋ค
์ฟผ๋ฆฌ ์นด์ดํฐ๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ผ๋ก ๊ณ ๋ คํ๋ ๊ฒ์ ์ด 2๊ฐ์ง์์ต๋๋ค.
- ํ์ด๋ฒ๋ค์ดํธ์ StatementInspector ์ฌ์ฉํ๊ธฐ
- ์ง์ AOP ๊ธฐ๋ฐ ์ฟผ๋ฆฌ์นด์ดํฐ ๋ง๋ค๊ธฐ
์ ํฌ๋ 2๊ฐ์ง ๋ฐฉ๋ฒ ์ค 2๋ฒ์ ์ ํํ์ต๋๋ค.
1๋ฒ์ด ๊ตฌํํ๊ธฐ์ ๋ ํธ๋ฆฌํฉ๋๋ค.
ํ์ง๋ง ์ดํ ํ์ด๋ฒ๋ค์ดํธ ์ด์ธ์ ๊ตฌํ์ฒด๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ์ฌ์ ์ ์ง์ JdbcTemlate
์ ์ฌ์ฉํ์ ๊ฒฝ์ฐ์๋ ๋ฒ์ฉ์ฑ์๋ ์ฟผ๋ฆฌ ์นด์ดํฐ๋ฅผ ๊ตฌํํ๊ณ ์ถ์ด AOP๋ก ๊ตฌํํ์ต๋๋ค.
๋ค์์ ์ฟผ๋ฆฌ ์นด์ดํฐ ์ธํ ๊ณผ์ ์ ๋๋ค!
์ฐ์ ์ ํฌ ์ฟผ๋ฆฌ์นด์ดํฐ๋ AOP ๊ธฐ๋ฐ์ผ๋ก ๊ตฌํํ์ต๋๋ค.
AOP๋ ๊ธฐ์กด ๊ธฐ๋ฅ์ ๋ด์ ๊ฐ์ฒด(ํ๊ฒ)์ ํ๋ก์์ ๋ถ๊ฐ๊ธฐ๋ฅ(๊ด์ , Aspect)์ ์ถ๊ฐํด์ฃผ๋ ๋ฐฉ์์
๋๋ค.
์คํ๋ง์์๋ ์ด๋
ธํ
์ด์
์ ํตํ ์ฌ์ด AOP ๊ตฌํ์ ์ง์ํฉ๋๋ค.
ํ์ง๋ง ๋ฌธ์ ๊ฐ ์์๋๋ฐ์, ์ ๊ฐ์ฒด๊ฐ Bean์ผ๋ก ๋ฑ๋ก๋ ์ฑ๊ธํด ๊ฐ์ฒด์ฌ์ผ ํ๋ค๋ ์ ์
๋๋ค.
์ ํฌ๊ฐ ํ๊ฒ์ผ๋ก ์ ์ ํ๊ณ ์ถ์ ๊ฑด Connection
์ด๊ณ , ๋น์ฐํ ์ฑ๊ธํด ๊ฐ์ฒด๊ฐ ์๋๋๋ค.
๊ทธ๋ ๋ค๋ฉด ์ ํฌ์ AOP๋ฅผ ํตํ ์ฟผ๋ฆฌ์นด์ดํ
๊ด์ ์ ์ฉ ๊ณํ์ ๋ฌผ๊ฑฐํ์ด ๋ ๊ฑธ๊น์?๐ข
๋คํํ ์๋๋๋ค!๐
DataSource
์์ getConnection()
๋ฉ์๋๋ฅผ ํตํด Connection
์ ์ป๊ฒ ๋๋๋ฐ์.
์ด DataSource
๊ฐ Bean ๋ฑ๋ก ๋์ด์๊ธฐ ๋๋ฌธ์
๋๋ค!
๋ง์ฝ getConnection()
์ด ํธ์ถ๋ ๋๋ง๋ค,
์ง์ง ์ปค๋ฅ์
์ด ์๋
์ด๋ฅผ ๋์์ฑ ์ฟผ๋ฆฌ ์นด์ดํฐ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ํ๋ก์ ์ปค๋ฅ์
๊ฐ์ฒด๋ฅผ ๋ฆฌํดํ๋ค๋ฉด?!
๋ค์์ ํด๋น ์์ด๋์ด๋ฅผ ํตํด ํ๋ก์ ์ปค๋ฅ์
์ ๋ฆฌํดํ๋ ํด๋์ค, PerformanceAspect
์
๋๋ค.
import java.lang.reflect.Proxy;
import java.sql.Connection;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Slf4j
public class PerformanceAspect {
private final ThreadLocal<QueryCounter> queryCounter = new ThreadLocal<>();
@Pointcut("execution(* javax.sql.DataSource.getConnection(..))")
public void performancePointcut() {
}
@Around("performancePointcut()")
public Object start (ProceedingJoinPoint point) throws Throwable {
final Connection connection = (Connection) point.proceed();
queryCounter.set(new QueryCounter());
final QueryCounter counter = this.queryCounter.get();
final Connection proxyConnection = getProxyConnection(connection, counter);
queryCounter.remove();
return proxyConnection;
}
private Connection getProxyConnection(Connection connection, QueryCounter counter) {
return (Connection) Proxy.newProxyInstance(
getClass().getClassLoader(),
new Class[]{Connection.class},
new ConnectionHandler(connection, counter)
);
}
}
PerformanceAspect
ํด๋์ค ์ getProxyConnection
์ ๊ตฌํ์ด
return (Connection) Proxy.newProxyInstance(
getClass().getClassLoader(),
new Class[]{Connection.class},
new ConnectionHandler(connection, counter)
);
๋ค์๊ณผ ๊ฐ์ด ๋์ด์๋๋ฐ์. ๋ค์ด๋๋ฏน ํ๋ก์๋ฅผ ํตํด ์ปค๋ฅ์ ์ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
๋ค์ด๋๋ฏน ํ๋ก์๋, ๋ฐํ์ ์ค์ ์ธํฐํ์ด์ค๊ฐ ๊ตฌํ๋ ์ธ์คํด์ค๋ฅผ ๋์ ์ผ๋ก ๊ตฌํํ ํ๋ก์๋ฅผ ์๋ฏธํฉ๋๋ค.
ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์ง์ ์์ฑํ๊ธฐ ์ํด์๋ ํ๊ฒ์ ๋ชจ๋ ๋ฉ์๋์ ๋ํด ์ง์ ๊ตฌํํด์ค์ผ ํด ๋ถํธํฉ๋๋ค.
ํนํ ์ ํฌ๊ฐ ํ๋ก์๋ก ๋ง๋ค์ด์ผ ํ Connection
์ ๊ฒฝ์ฐ, ์์ญ ๊ฐ์ ๋ฉ์๋๊ฐ ์๋๋ฐ์.
์ด๋ฅผ ๋ชจ๋ ๊ตฌํํ ํ๋ก์ ๊ฐ์ฒด๋ผ๋... ์์๋ง ํด๋ ๊ตฌํํ๊ธฐ ๊ท์ฐฎ๋ค์.
๊ทธ๋์ ๋ค์ด๋๋ฏน ํ๋ก์๋ฅผ ํตํด ํ๋ก์ ์ปค๋ฅ์ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด ์คฌ์ต๋๋ค.
๋ค์ด๋๋ฏน ํ๋ก์๋ Java java.lang.reflect.Proxy package
์์ ์ ๊ณตํด์ฃผ๋ API๋ฅผ ์ด์ฉํ์ฌ ์์ฝ๊ฒ ๋ง๋ค ์ ์์ต๋๋ค.
์ค๋น๋ฌผ์ ์ด ์ธ๊ฐ์ง์
๋๋ค.
ํด๋์ค๋ก๋, ์ ํ๊ฒ ํด๋์ค(์ธํฐํ์ด์ค), ๋์ ์ผ๋ก ํ๋ก์๋ฅผ ์ค์ ํ ConnectionHandler
์
๋๋ค.
์ด์ค ์ง์ ๊ตฌํ์ด ํ์ํ ConnectionHandler
๋ก ๋ค์ด๊ฐ ๋ณด๊ฒ ์ต๋๋ค.
@Slf4j
public class ConnectionHandler implements InvocationHandler {
private final Object target;
private final QueryCounter counter;
public ConnectionHandler(Object target, QueryCounter counter) {
this.target = target;
this.counter = counter;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
countPrepareStatement(method);
logQueryCount(method);
return method.invoke(target, args);
}
private void logQueryCount(Method method) {
if (method.getName().equals("close")) {
warnTooManyQuery();
log.info("\n====== count : {} =======\n", counter.getCount());
}
}
private void countPrepareStatement(Method method) {
if (method.getName().equals("prepareStatement")) {
counter.increase();
}
}
private void warnTooManyQuery() {
if (counter.isWarn()) {
log.warn("\n======= Too Many Query !!!! =======");
}
}
}
ํด๋น ํธ๋ค๋ฌ๋ InvocationHandler
์ธํฐํ์ด์ค์ ๊ตฌํ์ด ๊ผญ ํ์ํฉ๋๋ค.
์ด์ค invoke()
๋ฉ์๋๊ฐ ํต์ฌ์
๋๋ค.
ํด๋น ๋ฉ์๋๋ ๊ตฌํํ ํ๊ฒ ํด๋์ค์ ๋ชจ๋ ๋ฉ์๋์ ์ง์ ์ ๊ทผํ๊ณ , ๋์ ์ผ๋ก ๋ฆฌํด๊ฐ์ ๋ฐ๊พธ๊ฑฐ๋ ๋ถ๊ฐ๊ด์ ์ ์ถ๊ฐํ ์ ์์ต๋๋ค.
์ ํฌ๋ connection
๊ฐ์ฒด๊ฐ prepareStatement()
๋ฉ์๋๋ฅผ ํธ์ถํ ๋๋ง๋ค ์ฟผ๋ฆฌ ์นด์ดํธ๋ฅผ ์ฆ๊ฐ์์ผฐ์ต๋๋ค.
์ปค๋ฅ์
์ ํตํด ์ฟผ๋ฆฌ๊ฐ ์คํ๋ ๋๋ง๋ค ํด๋น ๋ฉ์๋๊ฐ ํ๋ฒ ์ฉ ํธ์ถ๋๋ค๋ ์ ์ ์ฐฉ์ํ์ต๋๋ค.
์ด์ ๋๋ง์ QueryCounter
์
๋๋ค.
๋ณ ๊ตฌํ์ ํ์ ์๊ณ , count ๋ณ์๋ฅผ ๊ฐ์ผ ๊ฐ์ฒด์
๋๋ค.
increase()
๋ฅผ ํตํด ์ฟผ๋ฆฌ ์คํ ์๋ฅผ 1 ์ฆ๊ฐ์ํค๊ณ
isWarn()
์ ํตํด 11๊ฐ ์ด์์ ์ฟผ๋ฆฌ ์นด์ดํธ ์ฌ๋ถ๋ฅผ ๋ฐํํฉ๋๋ค.
public class QueryCounter {
private int count;
public void increase() {
count++;
}
public int getCount() {
return count;
}
public boolean isWarn() {
return count > 10;
}
}
์, ์ด์ ํด๋น ์นด์ดํฐ ๊ฐ์ฒด๊ฐ ์ค๋ ๋๋ณ๋ก ํ ๋น๋๊ฒ ํด์ผ ํฉ๋๋ค.
ThreadLocal
์ ์ด์ฉํ๋ฉด ๋๋๋ฐ์.
์ค๋ ๋ ์์ญ์ ๋ณ์๋ฅผ ์ค์ ํ ์ ์๊ธฐ ๋๋ฌธ์, ํน์ ์ค๋ ๋๊ฐ ์คํํ๋ ๋ชจ๋ ์ฝ๋์์ ๊ทธ ์ฐ๋ ๋์ ์ค์ ๋ ๋ณ์ ๊ฐ์ ์ฌ์ฉํ ์ ์๊ฒ ๋ฉ๋๋ค.
ThreadLocal.set()
: ํ์ฌ ์ฐ๋ ๋์ ๋ก์ปฌ ๋ณ์์ ๊ฐ์ ์ ์ฅํ๋ค.
ThreadLocal.get()
: ํ์ฌ ์ฐ๋ ๋์ ๋ก์ปฌ ๋ณ์ ๊ฐ์ ์ฝ์ด์จ๋ค.
ThreadLocal.remove()
: ํ์ฌ ์ฐ๋ ๋์ ๋ก์ปฌ ๋ณ์ ๊ฐ์ ์ญ์ ํ๋ค.
@Slf4j
public class ConnectionHandler implements InvocationHandler {
...
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
countPrepareStatement(method);
logQueryCount(method);
return method.invoke(target, args);
}
private void logQueryCount(Method method) {
if (method.getName().equals("close")) {
warnTooManyQuery();
log.info("\n====== count : {} =======\n", counter.getCount());
}
}
private void warnTooManyQuery() {
if (counter.isWarn()) {
log.warn("\n======= Too Many Query !!!! =======");
}
}
}
์ ํฌ๋ connection
์ด ๋๋ ๋์ ์ฟผ๋ฆฌ ์นด์ดํธ ๊ฐ์๋ฅผ ๋ก๊ทธ๋ก ๋ณด์ฌ์ฃผ๊ณ ์ถ์์ต๋๋ค.
์ด์ ๋ค์ด๋๋ฏน ํ๋ก์์์,
์ปค๋ฅ์
์ close()
๋ฉ์๋๊ฐ ์คํ๋์์ ๋ ๋ก๊ทธ๊ฐ ์ถ๋ ฅ๋๋๋ก ๊ตฌํํ์ต๋๋ค!
ํ์ฌ ๋๋ฌด ๋ง์ ์ฟผ๋ฆฌ๊ฐ ์นด์ดํธ๋์์ ๋ ๊ฒฝ๊ณ ํด์ฃผ๋ warnTooManyQuery()
๋ฉ์๋์์๋, ํด๋น ์ฟผ๋ฆฌ๋ ๋ฉ์๋ ๊ด๋ จ ์ ๋ณด๊ฐ ์ ๊ณต๋์ง ์๋๋ฐ์.
ํ๋ฒ ๋ ๋ค์ด๋๋ฏน ํ๋ก์๋ฅผ ์ ์ฉํ๋๊ฐ ํด์, ๋ง์ง๋ง ์ฟผ๋ฆฌ ์ํ๋ผ๋ ํ์ธํ ์ ์๊ฒ ๋ก๊น
ํ๋ ๋ฐฉ๋ฒ๊น์ง ๊ณ ๋ คํ๊ณ ์์ต๋๋ค.
ํ์ฌ DEV ์๋ฒ์์ ์ฟผ๋ฆฌ์นด์ดํฐ ๋ก๊ทธ๋ฅผ ๋ณผ ์, ํน์ ์์ฒญ๋ง๋ค 3๋ฒ์ฉ ๋์ผํ ๋ก๊ทธ๊ฐ ์ฐํ๋ ๊ฑธ ํ์ธํ ์ ์์์ต๋๋ค.
์ ํฌ๊ฐ DB์ ๋ฆฌํ๋ฆฌ์ผ์ด์ ์ ์ ์ฉํ๋ฉด์ ์ด 3๊ฐ์ DataSource๊ฐ ์๊ณ , ๊ฐ DataSource๋ง๋ค ๋ก๊ทธ๊ฐ ์ฐํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
๋คํํ ์นด์ดํ
์๋ ๋์ผํ๊ธฐ ๋๋ฌธ์ ํฐ ๋ฌธ์ ๋ ์์ต๋๋ค.
ํฌ์ธํธ์ปท ํํ์์ ์์ ํด ๋ก๊ทธ๊ฐ 1๋ฒ์ฉ ๋๊ฐ๋๋ก ์์ ํ๋ ๊ฒ์ด ๊ณผ์ ์
๋๋ค.
https://likispot.tistory.com/73