아키텍처와 함께

블로그 이미지
by gregorio
  • Total hit
  • Today hit
  • Yesterday hit

Spring Framework에서는 다양한 Annotation을 제공하고 있으며, Annotation을 이용하여 XML을 사용하지 않고 Bean 설정, Property의 값을 읽을 수 있는 Annotation을 제공한다.


Annotation을 이용하여 SQL 수행 시 Binding 변수를 이용하여 SQL을 Log에 저장하는 Custom Annotation 예제이다.


SQL을 출력하기 위해서 Annotation과 AOP 기능을 이용하여 AOP 내에서 SQL 을 출력하도록 개발하였다.


먼저 Custom Annotation을 만든다.


■ BindSQL.java


import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;


@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface BindSQL {


String[] sqlIds();

String paramType() default "";

}

 


BindSQL은 sqlIds와 paramType을 정의하고 있으며, sqlIds는 서비스에서 수행하는 Mybatis의 sqlId를 의미하며, paramType은 Mybatis의 해당 SQL의 paramterType이다.


BindSQL은 메소드 Level에 적용이 가능하며, Runtime 시점에 수행된다.


Method Level에서 수행되기 때문에 AOP를 적용한다.



■ AnnotationAspect.java


mport java.lang.reflect.Method;

import java.util.List;

import java.util.Map;


import javax.annotation.Resource;


import org.apache.ibatis.mapping.BoundSql;

import org.apache.ibatis.mapping.MappedStatement;

import org.apache.ibatis.mapping.ParameterMapping;

import org.apache.ibatis.mapping.ParameterMode;

import org.apache.ibatis.reflection.MetaObject;

import org.apache.ibatis.session.Configuration;

import org.apache.ibatis.session.SqlSessionFactory;

import org.apache.ibatis.type.JdbcType;

import org.apache.ibatis.type.TypeHandlerRegistry;

import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.reflect.MethodSignature;

import org.slf4j.Logger;

import org.springframework.context.annotation.EnableAspectJAutoProxy;

import org.springframework.core.annotation.Order;

import org.springframework.stereotype.Component;

import org.springframework.util.StopWatch;


import dymn.spring.annotation.BindSQL;

import dymn.spring.annotation.Logging;


@Aspect

@Component

@Order(2)

@EnableAspectJAutoProxy(proxyTargetClass=true)

public class AnnotationAspect {

@Resource(name = "sqlSessionFactory")

private SqlSessionFactory sqlSessionFactory;

@Around("@annotation(dymn.spring.annotation.StopWatch)")

private Object stopWatch(ProceedingJoinPoint joinPoint) throws Throwable {

StopWatch stopWatch = new StopWatch();

Object proceed = null;

String className = joinPoint.getSignature().getName();

try {

stopWatch.start();

proceed = joinPoint.proceed();

}

finally {

stopWatch.stop();

LOGGER.info("#{}({}): {} in {}[msec]s", className, joinPoint.getArgs(), proceed, stopWatch.getTotalTimeMillis());

}

return proceed;

}


@Before("@annotation(dymn.spring.annotation.BindSQL)")

public void bindingSql(JoinPoint joinPoint) throws Throwable {


/** Get annotation values **/

    MethodSignature signature = (MethodSignature) joinPoint.getSignature();

    Method method = signature.getMethod();


    BindSQL sqlAnnotation = method.getAnnotation(BindSQL.class);

    String[] sqlIds = sqlAnnotation.sqlIds();

    String paramType = sqlAnnotation.paramType();

    

    /** Get parameter for binding SQL **/

    Object sqlBindParam = null;

Object[] methodParams = joinPoint.getArgs();

if (!"".equals(paramType)) {

for (Object obj : methodParams) {

if ("Map".equals(paramType)) {

if (obj instanceof Map) {

sqlBindParam = obj;

break;

}

}

if ("String".equals(paramType)) {

if (obj instanceof String) {

sqlBindParam = obj;

break;

}

}

if ("int".equals(paramType)) {

if (obj instanceof Integer) {

sqlBindParam = obj;

break;

}

}

}

}

/** Binding SQL with parameter **/

for (String sqlId : sqlIds) {

Configuration configuration = sqlSessionFactory.getConfiguration();

MappedStatement statement = configuration.getMappedStatement(sqlId);

TypeHandlerRegistry typeHandlerRegistry = statement.getConfiguration().getTypeHandlerRegistry();

BoundSql boundSql = statement.getBoundSql(sqlBindParam);

String sql = boundSql.getSql().trim();


/** Binding SQL with parameter **/

List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

if (parameterMappings != null) {

        for (int i = 0; i < parameterMappings.size(); i++) {

            ParameterMapping parameterMapping = parameterMappings.get(i);

            if (parameterMapping.getMode() != ParameterMode.OUT) {

                Object value;

                String propertyName = parameterMapping.getProperty();

                if (boundSql.hasAdditionalParameter(propertyName)) {

                    value = boundSql.getAdditionalParameter(propertyName);

                } else if (sqlBindParam == null) {

                    value = null;

                } else if (typeHandlerRegistry.hasTypeHandler(sqlBindParam.getClass())) {

                    value = sqlBindParam;

                } else {

                    MetaObject metaObject = configuration.newMetaObject(sqlBindParam);

                    value = metaObject.getValue(propertyName);

                }

                JdbcType jdbcType = parameterMapping.getJdbcType();

                if (value == null && jdbcType == null) {

                jdbcType = configuration.getJdbcTypeForNull();

                }

                sql = replaceParameter(sql, value, jdbcType, parameterMapping.getJavaType());

            }

        }

    }

LOGGER.info("Execute SQL :: [{}] :: [{}]", sql, sqlBindParam);

}

}

    private static String replaceParameter(String sql, Object value, JdbcType jdbcType, Class javaType) {

        String strValue = String.valueOf(value);

        if (jdbcType != null) {

            switch (jdbcType) {

                case BIT:

                case TINYINT:

                case SMALLINT:

                case INTEGER:

                case BIGINT:

                case FLOAT:

                case REAL:

                case DOUBLE:

                case NUMERIC:

                case DECIMAL:

                    break;

                case DATE:

                case TIME:

                case TIMESTAMP:

                default:

                    strValue = "'" + strValue + "'";

            }

        } else if (Number.class.isAssignableFrom(javaType)) {

        } else {

            strValue = "'" + strValue + "'";

        }

        

        return sql.replaceFirst("\\?", strValue);

    }

}

 


AnnotationAspect에서 @BindSQL Annoation을 사용한 Method가 실행하기 전에 bindSql Mehtod를 호출한다.


bindSql Method에서는 Annotation에서 정의한 sqlIds 값과 paramType 값을 찾는다.


SQL을 출력하기 위해 Mybatis의 MappedStatement에서 BoundSql Method에 입력된 Parameter를 이용하여 SQL을 생성한다.


SQL을 출력하면 SQL에 Bind 변수에 Parameter값이 Binding되어 있지 않기 때문에 replaceParamter Method를 호출하여 Binding을 한다.



서비스에서 @BindSQL Annotation을 사용한 예제이다.


  @BindSQL(sqlIds ={"deploy.target.selSystemNameList", "deploy.target.selSubSystemList"})

@StopWatch

public Map<String, Object> selCodeList() throws Exception {

Map<String, Object> resultMap = new HashMap<String, Object>();

List<Map<String, Object>> systemNameList = baseDao.selectList("deploy.target.selSystemNameList", null);

List<Map<String, Object>> subSystemList = baseDao.selectList("deploy.target.selSubSystemList", null);

resultMap.put("systemNameList", systemNameList);

resultMap.put("subSystemList", subSystemList);

return resultMap;

}


selCodeList Method에 @BindSQL sqlIds 값을 서비스에서 사용하는 sqlId 값을 정의한다.


Method Level의 Annotation은 AOP와 연계하여 다양한 Annotation을 작성할 수 있다.


일반적으로 Service와 ServiceImpl을 적용하여 서비스를 개발하는 경우에는 Service가 JoinPoint에서 수행되는 클래스는 Service로 Annotation이 동작하지 않는다.


이를 해결하기 위해  @EnableAspectJAutoProxy(proxyTargetClass=true) Annotation을 이용하면 해결이 가능하다.



AND

ARTICLE CATEGORY

분류 전체보기 (56)
Spring Framrwork (33)
Linux (1)
APM (1)
Java (8)
python (0)
ant (1)
chart (1)
OS (1)
tomcat (1)
apache (1)
database (0)

RECENT ARTICLE

RECENT COMMENT

CALENDAR

«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

ARCHIVE

LINK