My Life/2025년

[긁어쓰는 소스] Mybatis에서 SQL호출시 세션정보를 공통적으로 추가하는 방법

우라질레이터 2025. 2. 14. 16:03

(1) config.xml


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- 
Solme ECM
-->

<configuration>

<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
<setting name="jdbcTypeForNull" value="NULL" />
</settings>
<!-- Mybatis Intercept -->
<plugins>
    <plugin interceptor="solme.ecm.system.MybatisInterceptor" />
</plugins>
 
</configuration>

 

(2) MybatisInterceptor.java

package xxxx.xxxx.xxxx;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.log4j.Logger;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;

/**
 * Mybatis Interceptor
 * - Mybatis sql호출시 intercept해서 세션정보를 공통으로 추가 
 * - intercept method는 "prepare"보다는 "update"/"query" 권고 :  prepare는 결국 update 또는 query를 호출함
 * @author : 황상규
 * @date : 2025.02.14 
 */

@Intercepts({
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})    
    //@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class} )
})
public class MybatisInterceptor implements Interceptor {

private final static Logger logger = Logger.getLogger(MybatisInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        if (args != null && args.length > 1) {
            //--------------------------------------------------------
            // (1) 웹 요청이 아닌 경우(ex 배치프로그램) : 웹 요청 정보(HttpServletRequest, HttpSession) 사용 불가
            if(RequestContextHolder.getRequestAttributes() == null) {
               return invocation.proceed();
            }
            //--------------------------------------------------------
            // (2) 웹 요청 : 세션에서 값 가져오기
            ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
            HttpServletRequest request = attr.getRequest();
            HttpSession session = request.getSession(false); // 세션이 없으면 null 반환

            String ssUserId = null;
            String ssLanguage = null;
            
            if (session != null) {
                UserInfo userInfo = (UserInfo) session.getAttribute("userInfo");
                if (auth != null) {                 
                        ssUserId = userInfo.getUserId();
                        ssLanguage = userInfo.getLanguage();
                }
            }
            // 세션 값 또는 쿠키 값을 공통 파라미터로 추가
            ssLanguage = ssLanguage == null? "KO":ssLanguage;
            
            //---------------------------------------------------------------
            Object parameterObject = null;
            BoundSql boundSql = null;
            String queryID = null;
            String queryString = null;
            String interceptsMethod = "update_query"; // prepare, update or query
            
            switch(interceptsMethod) {
             //---------------------------------------------------
             // (1) method = "prepare"
            case "prepare" : 
            
                // MyBatis의 StatementHandler 가져오기
                StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
                parameterObject = statementHandler.getBoundSql().getParameterObject();
                // MyBatis 내부 객체에 접근하기 위해 MetaObject 사용
                MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
                MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
                // SQL ID 가져오기
                queryID = mappedStatement.getId();
                
                // BoundSql 가져오기 (실제 SQL 쿼리 포함)
                boundSql = statementHandler.getBoundSql();
                queryString = boundSql.getSql(); // 실행될 SQL 문장
                
                // 기존 파라미터 가져오기
                parameterObject = boundSql.getParameterObject();
                
                // 파라미터가 null이면 새로운 HashMap 생성
                if (parameterObject == null) {
                 parameterObject = new HashMap<String, Object>();
                    // MyBatis 내부적으로 parameterObject를 대체할 수 있도록 설정
                    metaObject.setValue("delegate.boundSql.parameterObject", parameterObject);
                }
                
                // 파라미터가 null이면 새로운 HashMap 생성                
                if (parameterObject instanceof Map) {                     
                    Map<String, Object> paramMap = (Map<String, Object>) parameterObject;
                    paramMap.put("ssUserId", ssUserId);
                    paramMap.put("ssLanguage", ssLanguage);     
                }
                else if (parameterObject instanceof Domain) {
                 invokeSetterIfExists(parameterObject, "ssUserId", ssUserId);
                 invokeSetterIfExists(parameterObject, "ssLanguage", ssLanguage);
                }
             break;
             //---------------------------------------------------
            // (2) method = "update" or "query"
            case "update_query" : 
            case "update" : 
            case "query" : 
            default : 
             queryID = ((MappedStatement) invocation.getArgs()[0]).getId();
            
                parameterObject = invocation.getArgs()[1];
                
                if(parameterObject != null) {
                boundSql = ((MappedStatement) invocation.getArgs()[0]).getBoundSql(parameterObject);
                queryString = boundSql.getSql();
                }
                // 파라메터 유형별 설정
                if (parameterObject instanceof Map) {        
                    Map<String, Object> paramMap = (Map<String, Object>) parameterObject;
                    paramMap.put("ssUserId", ssUserId);
                    paramMap.put("ssLanguage", ssLanguage);                
                }                 
                else if (parameterObject instanceof Domain) {
                 invokeSetterIfExists(parameterObject, "ssUserId", ssUserId);
                 invokeSetterIfExists(parameterObject, "ssLanguage", ssLanguage);                               
                }
                else if (parameterObject instanceof String) {
                 Map<String, Object> paramMap = new HashMap<>();
                 paramMap.put("value",  parameterObject);
                    paramMap.put("ssUserId",  ssUserId);
                    paramMap.put("ssLanguage",  ssLanguage); 
                 invocation.getArgs()[1] = paramMap; // 복호화등 
                }
                else if (parameterObject == null) {
                 Map<String, Object> paramMap = new HashMap<String, Object>();
                 paramMap.put("ssUserId", ssUserId);
                    paramMap.put("ssLanguage", ssLanguage); 
                    invocation.getArgs()[1] = paramMap; // 새로운 Map을 매개변수로 설정
                }
                parameterObject = invocation.getArgs()[1];
             break;
            }        
            System.out.println("**********************************************");
            System.out.println("**********************************************");
            System.out.println("***************[START]*******************************");
            System.out.println(queryID);
            if(parameterObject != null) {
             System.out.println(parameterObject.getClass().getName().toLowerCase() + " : " + parameterObject.getClass());
            }            
            System.out.println(parameterObject);
            System.out.println("**************[END]********************************");
            System.out.println("**********************************************");
            
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
    //----------------------------------------
/**
 * domain(Object) setter invoke
 * @param domain Object
 * @param fieldName String
 * @param value Object
 * @return
 * @throws Exception
 */
    private void invokeSetterIfExists(Object domain, String fieldName, Object value) {
        try {
            // setter 메서드명
            String methodName = "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
          // 해당 클래스의 모든 메서드 조회
            Method[] methods = domain.getClass().getMethods();
            for (Method method : methods) {
                // 메서드 이름이 setter 이름과 일치
                if (method.getName().equals(methodName) && method.getParameterCount() == 1) {
                    Class<?> paramType = method.getParameterTypes()[0]; // 첫 번째 파라미터 타입 확인
                    // value가 해당 타입으로 변환 가능하면 setter 호출
                    if (value == null || paramType.isAssignableFrom(value.getClass())) {
                        method.invoke(domain, value);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace(); // 기타 예외 처리 (디버깅 목적)
        }
    }
}

 

(3) 파라메터 유형별 SQL내 변수명

파라메터 Type 내용() 파라메터 변수명
Map selectMap(Map paramMap);  
selectString(@Param("userId") String userId); 설정명 : ex) “userId”
selectList(List var1); list 또는 collection
selectString(String var1, String var2); param1, param2
Domain
(Value Object)
selectDomain(UserInfo userinfo);       
int update( UserInfo  userinfo );  
String selectString(String userId); value 또는 param1
null selectNull();