Programming/Spring

반응형

Custom Mapper with MapStruct

개요

MapStruct를 사용한 사용자 정의 매퍼 방법에 대하여 정리해 봅니다.

현재 실무에서 MapStruct 라이브러리를 활용해 Java Bean 유형 간의(DTO <> Domain) 매핑 구현에 사용하고 있습니다.

유사한 기능의 ModelMapper 라이브러리도 존재하지만 맵핑이 일어나는 시점에 리플렉션 API 방식으로 처리되기 때문에 B2C or 대용량 트래픽을 핸들링하는 사이트에서는 성능 이슈로 사용 하기에는 적절하지 않습니다. 반면에 MapStruct는 컴파일 시점에 구현체가 만들어지는 Code Generated 방식으로 성능상의 월등한 차이로 많이 활용하고 있습니다.

MapStruct 및 Bean Object Patten

일반적으로 Java 애플리케이션에서 POJO를 다른 POJO로 변환하는 로직을 흔히 볼 수 있습니다. 예를 들어, 도메인에서 가져오는 Entity와 클라이언트로 전달하는 DTO간에 일반적인 유형의 변환이 발생하는데, 이것을 손쉽게 해결하는 것이 MapStruct입니다. 뭐, 수동으로 Bean mapper를 만들 수도 있겠지만 시간이 많이 소요되고 노가다성 코드가 발생하게 됩니다. 그러나 MapStructs는 자동으로 Bean mapper Class를 생성할 수 있습니다.

Dependency

maven 에 아래 종속성을 추가해 줍니다.

MapStruct의 안정적인 최신 릴리스 버전은 maven central repository에서 사용할 수 있습니다.

Processor는 컴파일 시점에 구현체를 생성하는 역할을 담당하게 됩니다.

<dependency>
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct</artifactId>
  <version>1.4.1.Final</version>
</dependency>
<dependency>
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct-processor</artifactId>
  <version>1.4.2.Final</version>
</dependency>

MapStruct 활용

Entity 및 DTO 객체를 만들어 보겠습니다.

@Getter @Setter
public class Purchase {
    private String orderName;
    private String orderDescription;
}

@Getter @Setter
public class PurchaseDTO {
    private String orderName;
    private String orderDescription;
}

Mapper Interface

객체 맵핑을 위해 아래와 같은 인터페이스만 생성해 주면 됩니다. 그 이유는 구현체는 MapStruct-Processor 가 알아서 생성해 주기 때문입니다. 다만, 아래와 같은 인터페이스를 별도로 생성해주는 작업은 modelMapper에 비해 약간의 번거로움이 있습니다. 그럼에도 애플리케이션의 성능 향상을 위해서는 필수 요소라고 생각이 됩니다.

@Mapper
public interface PurchaseMapStruct {
  PurchaseMapStruct INSTANCE = Mappers.getMapper(PurchaseMapStruct.class);

  PurchaseDTO toPurchaseDto(Purchase entity);
}

Genernat 된 mapper

Maven clean install을 실행하면 /target/generated-sources/annotations/ 경로에 아래와 같은 구현체가 생성이 됩니다. MapStruct가 자동으로 생성해주는 클래스입니다.

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2022-10-28T16:52:20+0900",
    comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_282 (AdoptOpenJDK)"
)
public class PurchaseMapStructImpl implements PurchaseMapStruct {

    @Override
  public PurchaseDTO toPurchaseDto(Purchase dto) {
      if ( dto == null ) {
        return null;
    }

    PurchaseDTO purchaseDTO = new PurchaseDTO();
    purchaseDTO.setOrderName( dto.getOrderName() );
    purchaseDTO.setOrderDescription( dto.getOrderDescritpion() );

    return purchaseDTO;
    }
}    

Mapping Fields With Different Field Names

Enitity와 DTO 필드의 이름이 다른 경우에도 Java Bean 객체를 자동으로 매핑할 수 있습니다. 아래 예제를 보겠습니다.

@Getter @Setter
public class Purchase {
    private String ordNm;
  private String ordDesc;
  private String ordDate;
  private OrdItem ordItem;
}

@Getter @Setter
public class PurchaseDTO {
    private String orderName;
  private String orderDescription;
  private Date orderDate;
  Private OrderItem orderItem;
}

Mapper Interface 선언

다른 필드 이름을 매핑할 때 @Mapping 애너테이션을 추가하여 source와 target을 정의해 주면 됩니다.

@Mapper
public interface PurchaseMapStruct {
  PurchaseMapStruct INSTANCE = Mappers.getMapper(PurchaseMapStruct.class);

  @Mapping(target="orderName", source="entity.ordNm")
  @Mapping(target="orderDescription", source="entity.ordDesc")
  PurchaseDTO toPurchaseDto(Purchase entity);
}

Mapping With Type And Value Conversion

String Type의 날짜를 Date 객체로 변환할 수도 있습니다.

@Mapping(target="orderDate", source="entity.ordDate", dateFormat="yyyy-MM-dd HH:mm:ss")
PurchaseDTO toPurchaseDto(Purchase entity);

때로는 Value에 대한 커스터 마이징이 필요할 경우도 있습니다. Interface 내에 default method를 정의 하여 다른 빈에 대한 참조로 새로운 맵핑을 할 수도 있습니다.

@Mapping(target="orderDate", source="entity.orderDate", qualifiedByName="toOrderYear")
@Mapping(target="orderItem", source="entity.ordItem", qualifiedByName="toOrderItem")
PurchaseDTO toPurchaseDto(Purchase entity);

@Named("toOtderYear")
default String toOrderYear(String ordDate) {
    if(ordDate == null){
      return null;
  }

  return ordDate.substring(0,4);
}

@Named("toOrderItem")
default OrderItem toOrderItem(OrdItem item){
    if(item == null){
      return null;
  }

  OrderItem orderItem = new OrderItem();
  orderItem.setItemName(item.getItemNm());
  orderItem.setOrderPrice(item.getPrice());
  return orderItem;
}    

Service Layer 호출

Stream에서 map과 함께 entity to DTO 변환에 아래와 같이 적용 할 수 있습니다.

public List<PurchaseDTO> getPurchases() {
    return purchaseComponent.getPurchases(purchaseReq)
                          .stream()
                          .map(PurchaseMapStruct.INSTANCE::toPurchaseDto)
                          .collect(Collectors.toList());
}  

결론

MapStruct를 활용한 Java Bean 객체간의 전환에 대하여 기본적인 표현식과 다른 객체를 맵핑하는 방법에 대하여 소개하였습니다.

반응형
반응형
@Componenet, @Configuration 차이에 대한 정리

@Componenet

  • 개발자가 직접 작성한 클래스를 Bean으로 등록하고자 할 경우 사용
  • @Controller, @Service, @Repository 등의 어노테이션에서 상속
// 다른 클래스에서 Bean으로 불러 사용이 가능함
@Component
public class SpringComponent{
  // do something
}

@Configuration

  • 외부 라이브러리 또는 내장 클래스를 Bean으로 등록하고자 할 경우 사용(개발자가 직접 제어가 불가능한 클래스)
  • 1개 이상의 @Bean을 제공하는 클래스의 경우 반드시 @Configuration을 사용한다. 즉, 해당 클래스에서 한 개 이상의 Bean을 생성하고 있을 때 선언해 주어야 함
@Configuration
public class FeignRetryConfiguration {
​
  @Bean
  public FeignRetryAspect FeignRetryService() {
    return new FeignRetryAspect();
  }
}
반응형
반응형

Spring InterCeptor 를 이용한 Session Login Check

InterCeptor에서 제공하는 3가지 메소드가 각각 실행되는 시점.

  • preHandle: Controller 실행 요청 전
  • postHandle: view(jsp)로 forward 되기 전에
  • afterCompletion: 요청이 끝난 뒤

session check 시점은 dispatcher -> controller 로 요청이 오기 전에 실행..되야 하므로 preHandle 메소드 시점에 처리.

  • CommonInterceptor.java
public class CommonInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
        log.debug("===============================preHandler===============================");
        if (SessionUtils.getSessionVO() == null) {
            //14.11.25 jp add.. 
            //session validation check order & mypage * method type : submit, ajax
            if ((request.getRequestURI().indexOf("/biz/ord/order/") > -1) || (request.getRequestURI().indexOf("/biz/mpg/") > -1)) {
                String ajaxCall = (String) request.getHeader("AJAX");

                if ("true".equals(ajaxCall)) {
                    response.sendError(901);
                } else {
                    response.sendRedirect(Globals.PROTOCOL_HTTPS + Globals.DOMAIN + "/base/login/loginForm.do");
                    response.flushBuffer();
                }
                return false;
            }
        }
        return true;
    }
}
  • Sevlet-interceptor.xml

submit에 경우는 sendRedirect 로 리다이렉트가 가능하지만, ajax Call에 경우는 sendRedirect가 호출 되더라도 Callback 처리 되기때문에 추가해줘야 한다.

ajax 공통 함수에 jqXHR.status = 901로 리턴 될 경우 로그인 페이지로 핸들링 처리를 추가해준다.

  • ajax 호출시 beforeSend 에 ajax 호출을 Header에 기록.
$.ajax({ 
    beforeSend: function (xmlHttpRequest) { 
        xmlHttpRequest.setRequestHeader("AJAX", "true"); 
    } 
});
반응형
반응형

Request method 'HEAD' not supported 오류에 대한 대처 방법.

프로젝트 막바지에 위와 같은 오류에 직면 했다..ㅜ 첨 보는 오류..

그동안 너무 쉬엄쉬엄 했나..기본이 부족..

HTTP method GET, POST 이외에...PUT, DELETE...등 그 중 HEAD....

원리는 간단하다. web.xml 전 단계에서 doFilter 를 이용해 우회 처리를..

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        logger.debug("========== isHttpHead(httpServletRequest) : {}", isHttpHead(httpServletRequest));
        if (isHttpHead(httpServletRequest)) {
            chain.doFilter(new ForceGetRequestWrapper(httpServletRequest), response);
        } else {
            chain.doFilter(new FilteredRequest(request), response);
        }
    }

    private boolean isHttpHead(HttpServletRequest request) {
        return "HEAD".equals(request.getMethod());
    }

    private class ForceGetRequestWrapper extends HttpServletRequestWrapper {
        public ForceGetRequestWrapper(HttpServletRequest request) {
            super(request);
        }

        public String getMethod() {
            return "GET";
        }
    }

    public void destroy() {
    }
}

doFilter에 분기문을 추가하여 HEAD 요청시 GET으로 Method 를 변경한다.

http://axelfontaine.com/blog/http-head.html http://forum.spring.io/forum/spring-projects/web/89981-request-method-head-not-supported

 

Legacy forums will be shutdown February 28

In 2014, we announced the retirement of our legacy forum, forum.spring.io, in favor of providing an improved community experience on stackoverflow.com. As part of that announcement, we put our forum into read-only mode, preserving forum posts that were ref

spring.io

 

반응형

mybatis cache 설정

2014. 5. 14. 12:28
반응형
Mybatis cache 적용해 보기
  • 테스트 개발 환경
    • spring3.2.8
    • maven
    • mybatis 3.2.6
        

특별한 설정은 없는거 같다.

mybatisConfig.xml 파일에 setting cacheEnabled true 설정

  • MybatisConfig.xml
<configuration>
    <settings>
        <setting name="cacheEnabled" value="true"></setting>
        <setting name="defaultExecutorType" value="REUSE"></setting>
    </settings>
</configuration>

userMapper.xml 파일에 캐시 사용을 위한 태그 작성하고 태그 속성을 추가한다.

  • UserMapper.xml
<mapper namespace="userDao"> 
	<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> 
    <select id="getUser" parametertype="java.util.Map" usecache="true" resulttype="String"> 
    	SELECT * FROM MEMBER WHERE ID = #{id} 
    </select>
</mapper>
  • eviction: 캐시 알고리즘 속성으로 기본은 LRU(사용빈도가 낮은것)이고 그 외에 3가지가 있음. FIFO(선입선출)
  • flushInterval: 캐시 유지 설정1분 뒤 캐시 비움.(단, 특정 시각을 정하지는 못함)
  • size: 디폴트 값은 1024이며, 메모리 여부를 확인 후 사용해야 할 듯.
  • readOnly: 캐시 데이터를 읽기만 가능하게 할지 설정. (읽기/쓰기 모두 가능한 경우에는 반환된 캐시 데이터에 대한 변경이 가능하며, 캐시 복사본을 반환한다고 함)
  • 캐시를 적용할 쿼리 속성에 userCache="true" 를 선언하면 정상적으로 작동

설정은 간단하게 마무리 됐고, 서버를 재기동 하고 로그를 확인하면 아래와 같은 로그를 확인할 수 있다.

주황색 라인에 캐시 적중률(cache Hit Ratio)을 보면 최초 0.0 에서 2번째 호출시 0.5로 올라가고 결과는 동일한 값이 노출된다. 

mybatis cache는 잘 사용을 안하는거 같고, 이외에도 Ehcache, OScache 등 좋은 오픈소스가 존재하는거 같다. 

 [참고]
http://mybatis.github.io/mybatis-3/ko/sqlmap-xml.html#cache

 

MyBatis – 마이바티스 3 | 매퍼 XML 파일

Mapper XML 파일 마이바티스의 가장 큰 장점은 매핑구문이다. 이건 간혹 마법을 부리는 것처럼 보일 수 있다. SQL Map XML 파일은 상대적으로 간단하다. 더군다나 동일한 기능의 JDBC 코드와 비교하면

mybatis.org

 

반응형

+ Recent posts

반응형