반응형

JPA ( Java Persistent API )와 ORM ( Object Relational Mapping )

JPA란 자바 ORM 기술에 대한 API 표준 명세를 의미합니다.

JPA는 ORM을 사용하기 위한 인터페이스를 모아둔 것이며, JPA를 사용하기 위해서는 JPA를 구현한 Hibernate, EclipseLink, DataNucleus 같은 ORM 프레임워크를 사용해야 합니다.

그렇다면 ORM은 무엇일까요?

ORM이란 객체와 DB의 테이블이 매핑을 이루는 것을 말합니다.

 객체가 테이블이 되도록 매핑 시켜주는 것을 말합니다.

ORM을 이용하면 SQL Query가 아닌 직관적인 코드(메서드)로서 데이터를 조작할 수 있습니다.

 

예를들어, User 테이블의 데이터를 출력하기 위해서 mysql에서는 SELECT * FROM user; 라는 query를 실행해야 하지만,

ORM을 사용하면 User 테이블과 매핑된 객체를 user라 할 때, user.findAll() 라는 메서드 호출로 데이터 조회가 가능합니다.

 

query를 직접 작성하지 않고 메서드 호출만으로 query가 수행되다 보니, ORM을 사용하면 생산성이 매우 높아집니다.

그러나 query가 복잡해지면 ORM으로 표현하는데 한계가 있고, 성능이 raw query에 비해 느리다는 단점이 있습니다.

 

 

 

 

Mybatis vs Hibernate

JPA를 구현한 여러 프레임워크가 있지만 Hibernate가 JPA를 주도하고 있기 때문에 JPA를 Hibernate라 생각하고 혼용해서 사용하도록 하겠습니다.

 

JDBC를 직접 사용하는 것보다 Mybatis를 사용했을 때 코드가 간결해지고 유지보수가 편했다는 것을 느꼈습니다.

Hibernate를 배우게 되면 Mybatis보다 코드가 더 간결하며, 더 객체 지향적이고 생산성이 매우 높다는 것을 느끼게 될 것입니다.

 

먼저 전 세계 개발자들은 Mybatis와 Hibernate 중 어떤 것을 더 많이 사용하는지 알아보겠습니다.

 

[  구글 트렌드 ( 링크 )  ]

 

동아시아를 제외하고 대부분 나라에서는 Hibernate를 압도적으로 많이 사용하고 있다는 것을 알 수 있습니다.

그만큼 JDBC를 직접 사용하는 Mybatis보다 JDBC를 노출하지 않고 ORM 기술을 사용하는 JPA가 좋은 방식일 수 있다는 것을 알 수 있습니다.

( JPA도 분명 단점이 존재하기 때문에 서비스에 따라 Mybatis를 사용할 지 JPA를 사용할 지 결정해야 합니다. )

 

이제 Hibernate가 이렇게 인기가 많아지게 된 배경을 살펴보기로 하겠습니다.

 

 

 

 

JPA 탄생 배경

Mybatis에서는 테이블 마다 비슷한 CRUD SQL을 계속 반복적으로 사용했었습니다.

소규모라도 Mybatis로 애플리케이션을 만들어 보셨다면, DAO 개발이 매우 반복되는 작업이며, 이 작업이 매우 귀찮다는 경험을 해보았을 것입니다.

 

또한 테이블에 칼럼이 하나 추가된다면 이와 관련된 모든 DAO의 SQL문을 수정해야 합니다.

 DAO와 테이블은 강한 의존성을 갖고 있습니다.

 

이러한 이유로 SQL을 자동으로 생성해주는 툴이 개발 되기도 했지만 반복 작업은 마찬가지였고, 큰 효과를 얻지 못했던 것 같습니다.

그 이유는 객체 모델링보다 데이터 중심 모델링(테이블 설계)을 우선시 했으며, 객체지향의 장점(추상화, 캡슐화, 정보은닉, 상속, 다형성)을 사용하지 않고 객체를 단순히 데이터 전달 목적( VO, DTO )에만 사용했기 때문입니다.

다시 말하면 객체지향 개발자들이 개발하고 있는 방법이 전혀 객체 지향적이지 않다는 것을 느끼게 되었습니다.

 

아래의 모델링은 Book과 Album 테이블의 공통된 칼럼인 name과 price를 Item이라는 테이블에 정의하여 상속받도록 한 객체 지향 모델링입니다.

ERD에서 사용하는 데이터 모델링과 달리 상속이라는 개념이 있습니다.

모델링을 위와 같이 객체 지향적으로 설계 했을 때, SQL을 작성해야 하는 JDBC로 사용하기 위해서는 ERD로 바꿔야 합니다.

그런데 ERD에서는 상속 관계를 표현하기가 까다롭습니다.

 

그런데 객체 지향 설계에 대해서, 상속 관계를 잘 표현 해주는 데이터 모델링으로 바꿔주는 기술이 있다면?

그리고 상속 관계에 있는 테이블에 대해 join도 알아서 해결 해준다면 객체 지향적인 설계가 가능할 것입니다.

 

 

정리하자면 JDBC API를 사용했을 때의 문제는 다음과 같습니다.

1. 유사한 CURD SQL 반복 작업

2. 객체를 단순히 데이터 전달 목적으로 사용할 뿐, 객체 지향적이지 못함 ( 페러다임 불일치 )

 

그래서 객체와 테이블을 매핑 시켜주는 ORM이 주목 받기 시작했고, 자바 진영에서는 JPA라는 표준 스펙이 정의 되었습니다.

 

 

 

 

Hibernate의 특징

Hibernate를 사용하면 위의 문제들을 해결할 수 있습니다.

그리고 항상 완벽한 기술은 없듯이 Hibernate에도 단점이 존재하는데, 지금부터 Hibernate의 장단점을 알아보겠습니다.

 

장점

1) 생산성

Hibernate는 SQL를 직접 사용하지 않고, 메서드 호출만으로 쿼리가 수행됩니다.

즉 SQL 반복 작업을 하지 않으므로 생산성이 매우 높아집니다.

 

그런데 SQL을 직접 사용하지 않는다고 해서 SQL을 몰라도 된다는 것은 아닙니다.

Hibernate가 수행한 쿼리를 콘솔로 출력하도록 설정을 할 수 있는데, 쿼리를 보면서 의도한 대로 쿼리가 짜여졌는지, 성능은 어떠한지에 대한 모니터링이 필요하기 때문에 SQL을 잘 알아야 합니다.

 

 

2) 유지보수

Hibernate 사용의 또 다른 장점은 테이블 칼럼이 하나 변경되었을 경우, Mybatis에서는 관련 DAO의 파라미터, 결과, SQL 등을 모두 확인하여 수정해야 하지만, JPA를 사용하면 JPA가 이런 일들을 대신해주기 때문에 유지보수 측면에서 좋습니다.

 

 

3) 특정 벤더에 종속적이지 않음

여러 DB 벤더마다 쿼리 사용이 조금씩 다르기 때문에 애플리케이션 개발 시 처음 선택한 DB를 나중에 바꾸는 것은 매우 어렵습니다.

 

그런데 JPA는 추상화된 데이터 접근 계층을 제공하기 때문에 특정 벤더에 종속적이지 않습니다.

즉 설정 파일에서 JPA에게 어떤 DB를 사용하고 있는지 알려주기만 하면 얼마든지 DB를 바꿀 수가 있습니다.

 

 

 

단점

1) 성능

물론 SQL을 직접 작성하는 것보다 메서드 호출만으로 쿼리를 수행한다는 것은 성능이 떨어질 수 있습니다.

실제로 초기의 ORM은 쿼리가 제대로 수행되지 않았고, 성능도 좋지 못했다고 합니다.

그러나 지금은 많이 발전하여, 좋은 성능을 보여주고 있고 계속 발전하고 있습니다.

 

 

2) 세밀함

메서드 호출로 DB 데이터를 조작 하기 때문에 세밀함이 떨어집니다.

복잡한 통계 분석 쿼리를 메서드만으로 해결하는 것은 힘든 일입니다.

이것을 보완하기 위해 SQL과 유사한 기술인 JPQL을 지원합니다.

물론 SQL 자체 쿼리를 작성할 수 있도록 지원하고 있습니다.

 

 

3) 러닝커브

JPA를 잘 사용하기 위해서는 알아야 할 것이 많습니다.

즉 러닝커브가 높습니다.

 

 

 

 

정리

처음에 살펴본 구글 트렌드를 볼 때 우리나라는 대부분 Mybatis를 사용하고 있는데, 그 이유는 우리나라 시장 대부분이 SI, 금융 시장이기 때문입니다.

비즈니스가 매우 복잡하고, 안정성을 중요시 하는 서비스일 경우에는 JPA보다 SQL을 작성하는 것이 좋을 것이라는 의도일 것입니다.

그리고 이미 SQL을 사용하여 개발된 애플리케이션이라면 JPA로 바꾸는 일도 쉽지 않을 것입니다.

이러한 이유로 우리나라에서는 Hibernate가 뜨지 못하는 것 같습니다.

 

JPA는 통계 쿼리처럼 복잡한 SQL을 수행하기 힘들기 때문에 비즈니스에 따라 Mybatis를 사용할 지 Hibernate를 사용할 지 상황에 맞는 선택이 중요할 것입니다.

 

Hibernate가 SQL을 직접 사용하지 않는다고 해서 JDBC API를 사용하지 않는다는 것은 아닙니다.

Hibernate가 지원하는 메서드 내부에서는 JDBC API가 동작하고 있으며, 단지 개발자가 직접 SQL을 직접 작성하지 않을 뿐입니다.

 

 

ORM은 꾸준히 발전하고 있는 기술이며, 저는 높은 생산성을 자랑하는 ORM을 매우 좋아합니다.

처음 접한 프레임워크가 Ruby on Rails 였는데, ROR에서는 ORM이 내장되어 있습니다.

그 영향인지는 몰라도 Node.js 공부할 때도 ORM이 있는지 먼저 찾았었네요.

역시나 자바에서도 Hibernate라는 멋진 기술이 있었습니다.

 

출처: https://victorydntmd.tistory.com/195

반응형

RestTemplate (정의, 특징, URLConnection, HttpClient, 동작원리, 사용법, connection pool 적용)

빨간색소년 2018.02.26 21:45

참조문서 : https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html

1. RestTemplate이란?

spring 3.0 부터 지원한다. 스프링에서 제공하는 http 통신에 유용하게 쓸 수 있는 템플릿이며, HTTP 서버와의 통신을 단순화하고 RESTful 원칙을 지킨다. jdbcTemplate 처럼 RestTemplate 도 기계적이고 반복적인 코드들을 깔끔하게 정리해준다. 요청보내고 요청받는데 몇줄 안될 정도..

특징

  • 기계적이고 반복적인 코드를 최대한 줄여줌
  • RESTful형식에 맞춤
  • json, xml 를 쉽게 응답받음

2. HTTP 서버와의 다양한 통신방법

2-1. URLConnection

jdk 1.2 부터 내장되어 있으며, java.net 패키지에 있다. URL의 내용을 읽어오거나, URL 주소에 GET, POST로 데이터를 전달 할 때 사용한다. 또한 http 프로토콜 이외에도 가능하다.(file 등) 보통 아래와 같이 사용한다.

  1. new URL("http:// ....")
  2. openConnection()
  3. URLConnection
  4. getInputStream, getOutputStream
  5. InputStream, OutputStream 처리

2-1-1. 문제점

  • 응답코드가 4xx 거나 5xx 면 IOException 이 터진다.
  • 타임아웃을 설정할 수 없다.
  • 쿠키 제어가 불가

2-2. HttpClient

3.x일 땐 apache commons 의 프로젝트였다가 승급해서, 아파치 탑 프로젝트가 되었다. 4.x부터는 Apache HttpComponents 로 불린다. maven dependency 를 설정하거나, http://hc.apache.org/downloads.cgi 에서 다운로드할 수 있다. org.apache.http 패키지에 있다.보통 아래처럼 사용한다.

  1. CloseableHttpClient httpclient = HttpClients.createDefault();
  2. 메소드에 따라 new HttpGet("http:// ....");
  3. CloseableHttpResponse response = httpclient.execute(httpget);
  4. HttpEntity entity = response.getEntity();
  5. Stream으로 entity.getContent() 처리 등

2-2-1. URLConnection 와 비교하였을 때 장점

  • 모든 응답코드를 읽을 수 있다. httpResponse.getStatusLine().getStatusCode()
  • 타임아웃 설정 가능
  • 쿠키 제어가 가능

2-2-2. 문제점

  • URLConnection 을 이용한 방식보다 코드가 간결해졌지만, 여전히 반복적이고 코드들이 길다.
  • 스트림 처리 로직을 별도로 짜야한다. (EntityUtils 를 쓰면 되는거 같긴하지만)
  • 응답의 컨텐츠타입에 따라 별도 로직이 필요하다. (RestTemplate 가 이때 유용!!)

3. RestTemplate 의 동작원리

org.springframework.http.client 패키지에 있다. HttpClient는 HTTP를 사용하여 통신하는 범용 라이브러리이고, RestTemplate은 HttpClient 를 추상화(HttpEntity의 json, xml 등)해서 제공해준다. 따라서 내부 통신(HTTP 커넥션)에 있어서는 Apache HttpComponents 를 사용한다. 만약 RestTemplate 가 없었다면, 직접 json, xml 라이브러리를 사용해서 변환해야 했을 것이다.

  1. 어플리케이션이 RestTemplate를 생성하고, URI, HTTP메소드 등의 헤더를 담아 요청한다.
  2. RestTemplate 는 HttpMessageConverter 를 사용하여 requestEntity 를 요청메세지로 변환한다.
  3. RestTemplate 는 ClientHttpRequestFactory 로 부터 ClientHttpRequest 를 가져와서 요청을 보낸다.
  4. ClientHttpRequest 는 요청메세지를 만들어 HTTP 프로토콜을 통해 서버와 통신한다.
  5. RestTemplate 는 ResponseErrorHandler 로 오류를 확인하고 있다면 처리로직을 태운다.
  6. ResponseErrorHandler 는 오류가 있다면 ClientHttpResponse 에서 응답데이터를 가져와서 처리한다.
  7. RestTemplate 는 HttpMessageConverter 를 이용해서 응답메세지를 java object(Class responseType) 로 변환한다.
  8. 어플리케이션에 반환된다.

4. RestTemplate 를 써보자

4-1. 생성하기

  • 기본 생성 : RestTemplate restTemplate = getRestTempalte();

  • 설정 생성 :

      HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
      factory.setReadTimeout(5000);  // 읽기시간초과, ms
      factory.setConnectTimeout(3000); // 연결시간초과, ms
      HttpClient httpClient = HttpClientBuilder.create()
          .setMaxConnTotal(100) // connection pool 적용
          .setMaxConnPerRoute(5) // connection pool 적용
          .build();
      factory.setHttpClient(httpClient); // 동기실행에 사용될 HttpClient 세팅 
      RestTemplate restTemplate = new RestTemplate(factory);
    

connection pool 적용

참조문서 : https://stackoverflow.com/questions/31869193/using-spring-rest-template-either-creating-too-many-connections-or-slow/

언젠가 만날 것 같아서 기록.. RestTemplate 은 기본적으로 connection pool 을 사용하지 않는다. 따라서 연결할 때 마다, 로컬 포트를 열고 tcp connection 을 맺는다. 이때 문제는 close() 이후에 사용된 소켓은 TIME_WAIT 상태가 되는데, 요청량이 많다면 이런 소켓들을 재사용하지 못하고 소켓이 오링나서 응답이 지연될 것이다.

이런 경우 connection pool 을 사용해서 해결할 수 있는데, DBCP마냥 소켓의 갯수를 정해서 재사용하는 것이다. RestTemplate 에서 connection pool 을 적용하려면, 위와 같이 HttpClient 를 만들고 setHttpClient() 를 해야한다.

  • setMaxConnPerRoute : IP,포트 1쌍에 대해 수행 할 연결 수를 제한한다.
  • setMaxConnTotal : 최대 오픈되는 커넥션 수를 제한한다.

3-2. 사용하기

주요 메소드

RestTemplate MethodHTTP Method설명
executeAny
exchangeAny헤더세팅해서 HTTP Method로 요청보내고 ResponseEntity로 반환받음
getForObjectGETget 요청을 보내고 java object로 매핑받아서 반환받음
getForEntityGETget 요청을 보내고 ResponseEntity로 반환받음
postForLocationPOSTpost 요청을 보내고 java.net.URI 로 반환받음
postForObjectPOSTpost 요청을 보내고 ResponseEntity로 반환받음
putPUT
deleteDELETE
headForHeadersHEAD
optionsForAllowOPTIONS

요청할 URL

다양한 방법이 있다.

  • UriComponentsBuilder 로 파라미터를 붙이거나 String.format 로 붙이거나 등등
  • (/user/{id}, ... , "redboy") 처럼 rest하게 넘길 수도 있다.
  • map 을 이용해서 더 깔끔하게 할 수도 있다.

Object 로 받기

ForObject 를 사용할때, 응답 xml이나 json 에 맞는 java object(Class responseType)가 필요하다. @XmlElement 를 사용하거나 @JsonProperty 등을 사용하여 매핑해줘야한다.

에러 처리

DefaultResponseErrorHandler를 사용하여 HTTP Error 를 제어한다. restTemplate.setErrorHandler 를 통해 커스텀 핸들러를 등록할 수 있다.

비동기 처리

RestTemplate 는 동기처리에 사용된다. 비동기 처리는 org.springframework.web.client.AsyncRestTemplate 를 사용해야 한다. 언젠가 쓸 일이 오겠지..

5. 예제

import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

public class RestTemplateEx {
    public static void main(String[] args) {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setReadTimeout(5000);  // 읽기시간초과, ms
        factory.setConnectTimeout(3000); // 연결시간초과, ms
        HttpClient httpClient = HttpClientBuilder.create()
            .setMaxConnTotal(100) // connection pool 적용
            .setMaxConnPerRoute(5) // connection pool 적용
            .build();
        factory.setHttpClient(httpClient); // 동기실행에 사용될 HttpClient 세팅
        RestTemplate restTemplate = new RestTemplate(factory);

        String url = "http://testapi.com/search?text=1234"; // 예제니까 애초에 때려박음..

        Object obj = restTemplate.getForObject("요청할 URI 주소", "응답내용과 자동으로 매핑시킬 java object");
System.out.println(obj); } }



출처: https://sjh836.tistory.com/141 [빨간색코딩]

'JAVA > Spring' 카테고리의 다른 글

[Spring JPA] ORM과 JPA 그리고 Hibernate  (0) 2019.08.20
Spring boot yml 파일 사용  (1) 2019.03.21
Mapper XML 파일  (0) 2016.06.23
logback 설정  (0) 2016.06.17
iBatis MyBatis 차이  (0) 2016.06.17
반응형

Spring boot의 설정 파일 YAML을 사용하는 방법

대부분의 애플리케이션에서 설정과 관련된 변수들은 보통 파일에다가 쓰고 읽어오는 방식으로 프로그래밍한다.

외부에 설정파일을 넣을 수도 있고 내부적으로 프로젝트에 넣을 수도 있다.

해당 파일들은 포맷(.properties, .ini 등)도 다양하다.

스프링 부트에서도 설정에 대한 내용을 다양한 파일에 적고 읽어와 사용하는데 그 중에서 가장 적합하고 스프링 부트에서 권장하는 형식인 yaml에 대해서 간단히 설명하고 사용해본다.

왜 YAML 이어야 하는가?

-> 사람이 보기 편하다!

1
2
3
4
5
6
7
environments:
    dev:
        url: http://dev.example.com
        name: Developer Setup
    prod:
        url: http://another.example.com
        name: My Cool App


[YAML]

1
2
3
4
environments.dev.url=http://dev.example.com
environments.dev.name=Developer Setup
environments.prod.url=http://another.example.com
environments.prod.name=My Cool App


[Properties]

똑같은 설정이지만 yaml을 사용할 경우 위와 같이 들여쓰기(띄어쓰기)로 구분하여 사람이 보기가 편하다. 곧 관리가 편리하다.

또한 아래와 같이 리스트로 표현하고자 할 때는 "-" (대쉬) 하나로 쓸 수 있다.

1
2
3
4
my:
servers:
    - dev.example.com
    - another.example.com


뿐만 아니라 profile을 지정해서 환경에 따라 설정값을 다르게 가져갈 수 있는 장점이 있고, 하나의 파일에서 마치 여러개의 설정파일을 쓰는 듯하게 구분하여 사용할 수 있다. (아래처럼 "---"를 써서 구분)

1
2
3
4
5
6
7
8
9
10
11
12
server:
    address: 192.168.1.100
---
spring:
    profiles: development
server:
    address: 127.0.0.1
---
spring:
    profiles: production & eu-central
server:
    address: 192.168.1.120


여러모로 장점이 많아서 잘 쓰면 아주 유용하게 사용할 수 있다.

굳이 단점을 말하자면 문법(?)이 아주 약간 엄격한 것이 단점이다. 예를들면 하위 계층을 표현할 때 tab들여쓰기가 아닌 공백문자를 써야한다든가, :(콜론) 다음에 공백 한칸이 있어야한다든가하는 문제가 있다.

이런 문제는 몇 번 삽질해보면 금방 깨닫게 되고 사용법을 아래에서 간단히 익히면 해결될 문제다.


진짜로 yaml 사용(테스트) 해보기 

example 프로젝트를 만들어 보았다.

[프로젝트 구조]

프로젝트 구조는 위와 같이 구성하였다. 기존에 있던 application.properties는 지우고 classpath인 src/main/resources에 application.yml 파일을 생성했다.

참고로 .yml = .yaml 이다. 또한 스프링부트에서는 자동으로 classpath안에 있는 설정파일을 찾게 되어있다.

위 그림은 spring-boot-starter-parent의 pom.xml의 일부로, 보다시피 application이 접두사(prefix?)로 붙은 설정파일을 classpath이하의 어떤 디렉토리에 있든 .yml, .yaml, .properties 순서로 찾아서 읽는다.

1
2
3
4
env:
  servers:
    - dev.example.com
    - prod.example.com


[application.yml]

보다시피 env 밑에 servers가 있는데 그 안에 List형태로 두 개의 String값이 들어가 있다.

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
package com.example.demo.config;
 
import java.util.ArrayList;
import java.util.List;
 
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
 
@Component
@ConfigurationProperties(prefix="env")
public class TestConfig {
    
    //getter, setter
    private List<String> servers = new ArrayList<String>();
 
    public List<String> getServers() {
        return this.servers;
    }
 
    public void setServers(List<String> servers) {
        this.servers = servers;
    }
}
 


[TestConfig.java]

스프링 부트가 설정파일에서 읽어온 값을 관리할 객체를 나타낸 것이다.

@ConfigurationProperties로 설정 파일에서 읽어온 값을 해당 클래스에 바인딩할 것이고 prefix 부분을 두어서 필요한 부분만 가져올 수도 있다.

따라서 prefix인 "env" 아래에 있는 servers라는 List만 가져올 것이므로 클래스에 있는 변수의 자료형도 List<String>으로 설정해서 가져왔다.

또한 @Component로 설정해서 bean객체로 등록할 수 있게 했다.

bean객체로 등록하게되면 알다시피 스프링 부트 어디에든(?) bean객체를 주입받아 설정 값들을 사용할 수 있게 된다.

(* 참고로 스프링부트에서는 @SpringBootApplication가 쓰인 메인클래스가 있는 최상위 패키지 이하의 패키지에서 등록된 모든 Component를 스캔한다.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.example.demo;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import com.example.demo.config.TestConfig;
 
@RestController
public class TestController {
    
    @Autowired
    TestConfig testConfig;
    
    @RequestMapping("/")
    public String test() {
        return testConfig.getServers().get(0+ " / " + testConfig.getServers().get(1); 
    }
}


[TestController.java]

테스트를 위해 RestController를 만들었다.

또한 아까 등록한 bean객체를 주입받기 위해 @Autowired를 사용했고 루트경로(localhost:8080)로 들어왔을 때 해당 값을 리턴해서 테스트 해보았다.

[결과] -> 잘 나왔다.


변형된 사용법

무난하게 사용하는 방법은 위에서 설명한 것과 같고 이제는 조금 변형된 yaml파일의 경우에 설정 값을 가져오는 방법을 알아보려고한다.

1. application.yml이 아닌 다른 파일을 사용할 경우

특별한 경우가 아니면 application.yml 혹은 applicationXXX.yml 이런식으로 사용하면 자동으로 찾아진다.

그러나 abc.yml 같이 이름을 다르게 지정하고 싶고 경로도 다르게 하고 싶으면 아래와 같이 쓰면된다.

1
@ConfigurationProperties(locations="classpath:abc.yml", prefix="env")



2. 여러 자료구조를 사용할 경우

바인딩이 자연스럽게 된다.

1
2
3
4
5
env:
  servers: helloworld
  obj:
    number: 10
    sec: 10s


[applicatoin.yml]

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
package com.example.demo.config;
 
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
 
@Component
@ConfigurationProperties(prefix="env")
public class TestConfig {
    
    private String servers;
    private Sample obj;
    
    public String getServers() {
        return servers;
    }
    public void setServers(String servers) {
        this.servers = servers;
    }
    public Sample getObj() {
        return obj;
    }
    public void setObj(Sample obj) {
        this.obj = obj;
    }
    
}
cs

[TestConfig.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.example.demo.config;
 
import java.time.Duration;
 
public class Sample {
    private int number;
    private Duration sec;
    
    public int getNumber() {
        return number;
    }
    public void setNumber(int number) {
        this.number = number;
    }
    public Duration getSec() {
        return sec;
    }
    public void setSec(Duration sec) {
        this.sec = sec;
    }
    
}
 


[Sample.java]

[결과]

역시 잘 들어가 있다.


3. profile로 환경에 따른 설정파일 세팅하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
spring:
  profiles:
    active: local # 기본 환경 선택
 
# local 환경
---
spring:
  profiles: local
  datasource:
    data: classpath:data-h2.sql # 시작할때 실행시킬 script
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: create-drop
  h2:
    console:
      enabled: true
 
# 운영 환경
---
spring:
  profiles: set1
server:
  port: 8081


<출처 : http://jojoldu.tistory.com/269>

위와 같이 spring.profiles.active: local로 하면 profiles가 :local인 설정이 적용되고 set1으로하면 set1인 설정이 적용된다.

default 환경을 설정해둘 수 있고 active되는 것마다 바꾸면 되니 개발하고 배포할 때 아주 편리하게 할 수 있다.


4. 여러 파일을 하나의 yml파일로 관리하기

이것은 3번처럼 그냥 ---으로 나누면 다른 파일에서 불러온 것처럼 쓸 수 있다. (고로 생략..)


* 참고사항

- 꼭 String이 아니어도 binding이 유연하게 적용된다.

- yaml을 작성할 때는 kebab case(중간에 -를 쓰는것)를 사용하는 것을 권장한다. ex) jeong-pro.server.ip: ...



출처: https://jeong-pro.tistory.com/159 [기본기를 쌓는 정아마추어 코딩블로그]

반응형

Mapper XML 파일

마이바티스의 가장 큰 장점은 매핑구문이다. 이건 간혹 마법을 부리는 것처럼 보일 수 있다. SQL Map XML 파일은 상대적으로 간단하다. 더군다나 동일한 기능의 JDBC 코드와 비교하면 아마도 95% 이상 코드수가 감소하기도 한다. 마이바티스는 SQL을 작성하는데 집중하도록 만들어졌다.

SQL Map XML파일은 첫번째(first class)엘리먼트만을 가진다.

  • cache - 해당 네임스페이스을 위한 캐시 설정
  • cache-ref - 다른 네임스페이스의 캐시 설정에 대한 참조
  • resultMap - 데이터베이스 결과데이터를 객체에 로드하는 방법을 정의하는 엘리먼트
  • parameterMap - 비권장됨! 예전에 파라미터를 매핑하기 위해 사용되었으나 현재는 사용하지 않음
  • sql - 다른 구문에서 재사용하기 위한 SQL 조각
  • insert - 매핑된 INSERT 구문.
  • update - 매핑된 UPDATE 구문.
  • delete - 매핑된 DELEETE 구문.
  • select - 매핑된 SELECT 구문.

다음 섹션에서는 각각에 대해 세부적으로 살펴볼 것이다.

select

select구문은 마이바티스에서 가장 흔히 사용할 엘리먼트이다. 데이터베이스에서 데이터를 가져온다. 아마도 대부분의 애플리케이션은 데이터를 수정하기보다는 조회하는 기능이 많다. 그래서 마이바티스는 데이터를 조회하고 그 결과를 매핑하는데 집중하고 있다. 조회는 다음 예제처럼 단순한 경우에는 단순하게 설정된다.

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>

이 구문의 이름은 selectPerson이고 int타입의 파라미터를 가진다. 그리고 결과 데이터는 HashMap 에 저장된다.

파라미터 표기법을 보자.

#{id}

이 표기법은 마이바티스에게 PreparedStatement파라미터를 만들도록 지시한다. JDBC를 사용할 때 PreparedStatement에는 “?”형태로 파라미터가 전달된다. 즉 결과적으로 위 설정은 아래와 같이 작동하게 되는 셈이다.

// JDBC 코드와 유사함, 마이바티스 코드는 아님…
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);

물론 JDBC 를 사용하면 결과를 가져와서 객체의 인스턴스에 매핑하기 위한 많은 코드가 필요하겠지만 마이바티스는 그 코드들을 작성하지 않아도 되게 해준다.

select 엘리먼트는 각각의 구문이 처리하는 방식에 대해 세부적으로 설정하도록 많은 속성을 설정할 수 있다.

<select
  id="selectPerson"
  parameterType="int"
  parameterMap="deprecated"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10000"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">
Select &#xc5d8;&#xb9ac;&#xba3c;&#xd2b8; &#xc18d;&#xc131;
속성설명
id구문을 찾기 위해 사용될 수 있는 네임스페이스내 유일한 구분자
parameterType구문에 전달될 파라미터의 패키지 경로를 포함한 전체 클래스명이나 별칭
parameterMap외부 parameterMap을 찾기 위한 비권장된 접근방법. 인라인 파라미터 매핑과 parameterType을 대신 사용하라.
resultType이 구문에 의해 리턴되는 기대타입의 패키지 경로를 포함한 전체 클래스명이나 별칭. collection인 경우 collection 타입 자체가 아닌 collection 이 포함된 타입이 될 수 있다. resultType이나 resultMap을 사용하라.
resultMap외부 resultMap 의 참조명. 결과맵은 마이바티스의 가장 강력한 기능이다. resultType이나 resultMap을 사용하라.
flushCache이 값을 true 로 셋팅하면 구문이 호출될때마다 로컬, 2nd 레벨 캐시가 지워질것이다(flush). 디폴트는 false이다.
useCache이 값을 true 로 셋팅하면 구문의 결과가 2nd 레벨 캐시에 캐시 될 것이다. 디폴트는 true이다.
timeout예외가 던져지기 전에 데이터베이스의 요청 결과를 기다리는 최대시간을 설정한다. 디폴트는 셋팅하지 않는 것이고 드라이버에 따라 다소 지원되지 않을 수 있다.
fetchSize지정된 수만큼의 결과를 리턴하도록 하는 드라이버 힌트 형태의 값이다. 디폴트는 셋팅하지 않는 것이고 드라이버에 따라 다소 지원되지 않을 수 있다.
statementTypeSTATEMENT, PREPARED 또는 CALLABLE 중 하나를 선택할 수 있다. 마이바티스에게 Statement, PreparedStatement 또는 CallableStatement를 사용하게 한다. 디폴트는 PREPARED이다.
resultSetTypeFORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE중 하나를 선택할 수 있다. 디폴트는 셋팅하지 않는 것이고 드라이버에 다라 다소 지원되지 않을 수 있다.
databaseId설정된 databaseIdProvider가 있는 경우 마이바티스는 databaseId 속성이 없는 모든 구문을 로드하거나 일치하는 databaseId와 함께 로드될 것이다. 같은 구문에서 databaseId가 있거나 없는 경우 모두 있다면 뒤에 나온 것이 무시된다.
resultOrdered이 설정은 내포된 결과를 조회하는 구문에서만 적용이 가능하다. true로 설정하면 내포된 결과를 가져오거나 새로운 주요 결과 레코드를 리턴할때 함께 가져오도록 한다. 이전의 결과 레코드에 대한 참조는 더 이상 발생하지 않는다. 이 설정은 내포된 결과를 처리할때 조금 더 많은 메모리를 채운다. 디폴트값은 false 이다.

insert, update and delete

데이터를 변경하는 구문인 insert, update, delete는 매우 간단하다.

<insert
  id="insertAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  keyProperty=""
  keyColumn=""
  useGeneratedKeys=""
  timeout="20">

<update
  id="updateAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

<delete
  id="deleteAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">
Insert, Update &#xc640; Delete &#xc5d8;&#xb9ac;&#xba3c;&#xd2b8; &#xc18d;&#xc131;
속성설명
id구문을 찾기 위해 사용될 수 있는 네임스페이스내 유일한 구분자
parameterType구문에 전달될 파라미터의 패키지 경로를 포함한 전체 클래스명이나 별칭
parameterMap외부 parameterMap 을 찾기 위한 비권장된 접근방법. 인라인 파라미터 매핑과 parameterType을 대신 사용하라.
flushCache이 값을 true 로 셋팅하면 구문이 호출될때마다 캐시가 지원질것이다(flush). 디폴트는 false 이다.
timeout예외가 던져지기 전에 데이터베이스의 요청 결과를 기다리는 최대시간을 설정한다. 디폴트는 셋팅하지 않는 것이고 드라이버에 따라 다소 지원되지 않을 수 있다.
statementTypeSTATEMENT, PREPARED 또는 CALLABLE중 하나를 선택할 수 있다. 마이바티스에게 Statement, PreparedStatement 또는 CallableStatement를 사용하게 한다. 디폴트는 PREPARED 이다.
useGeneratedKeys(입력(insert, update)에만 적용) 데이터베이스에서 내부적으로 생성한 키 (예를들어 MySQL또는 SQL Server와 같은 RDBMS의 자동 증가 필드)를 받는 JDBC getGeneratedKeys메소드를 사용하도록 설정하다. 디폴트는 false 이다.
keyProperty(입력(insert, update)에만 적용) getGeneratedKeys 메소드나 insert 구문의 selectKey 하위 엘리먼트에 의해 리턴된 키를 셋팅할 프로퍼티를 지정. 디폴트는 셋팅하지 않는 것이다. 여러개의 칼럼을 사용한다면 프로퍼티명에 콤마를 구분자로 나열할수 있다.
keyColumn(입력(insert, update)에만 적용) 생성키를 가진 테이블의 칼럼명을 셋팅. 키 칼럼이 테이블이 첫번째 칼럼이 아닌 데이터베이스(PostgreSQL 처럼)에서만 필요하다. 여러개의 칼럼을 사용한다면 프로퍼티명에 콤마를 구분자로 나열할수 있다.
databaseId설정된 databaseIdProvider가 있는 경우 마이바티스는 databaseId 속성이 없는 모든 구문을 로드하거나 일치하는 databaseId와 함께 로드될 것이다. 같은 구문에서 databaseId가 있거나 없는 경우 모두 있다면 뒤에 나온 것이 무시된다.

Insert, update, delete 구문의 예제이다.

<insert id="insertAuthor">
  insert into Author (id,username,password,email,bio)
  values (#{id},#{username},#{password},#{email},#{bio})
</insert>

<update id="updateAuthor">
  update Author set
    username = #{username},
    password = #{password},
    email = #{email},
    bio = #{bio}
  where id = #{id}
</update>

<delete id="deleteAuthor">
  delete from Author where id = #{id}
</delete>

앞서 설명했지만 insert는 key생성과 같은 기능을 위해 몇가지 추가 속성과 하위 엘리먼트를 가진다.

먼저 사용하는 데이터베이스가 자동생성키(예를들면 MySQL과 SQL서버)를 지원한다면 useGeneratedKeys=”true” 로 설정하고 대상 프로퍼티에 keyProperty 를 셋팅할 수 있다. 예를들어 Author 테이블이 id 칼럼에 자동생성키를 적용했다고 하면 구문은 아래와 같은 형태일 것이다.

<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username,password,email,bio)
  values (#{username},#{password},#{email},#{bio})
</insert>

If your database also supports multi-row insert, you can pass a list or an array of Authors and retrieve the auto-generated keys.

<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username, password, email, bio) values
  <foreach item="item" collection="list" separator=",">
    (#{item.username}, #{item.password}, #{item.email}, #{item.bio})
  </foreach>
</insert>

마이바티스는 자동생성키 칼럼을 지원하지 않는 다른 데이터베이스를 위해 다른 방법 또한 제공한다.

이 예제는 랜덤 ID 를 생성하고 있다.

<insert id="insertAuthor">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
  </selectKey>
  insert into Author
    (id, username, password, email,bio, favourite_section)
  values
    (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>

위 예제에서 selectKey구문이 먼저 실행되고 Author id프로퍼티에 셋팅된다. 그리고 나서 insert 구문이 실행된다. 이건 복잡한 자바코드 없이도 데이터베이스에 자동생성키의 행위와 비슷한 효과를 가지도록 해준다.

selectKey 엘리먼트는 다음처럼 설정가능하다.

<selectKey
  keyProperty="id"
  resultType="int"
  order="BEFORE"
  statementType="PREPARED">
selectKey &#xc5d8;&#xb9ac;&#xba3c;&#xd2b8; &#xc18d;&#xc131;
속성설명
keyPropertyselectKey구문의 결과가 셋팅될 대상 프로퍼티.
keyColumn리턴되는 결과셋의 칼럼명은 프로퍼티에 일치한다. 여러개의 칼럼을 사용한다면 칼럼명의 목록은 콤마를 사용해서 구분한다.
resultType결과의 타입. 마이바티스는 이 기능을 제거할 수 있지만 추가하는게 문제가 되지는 않을것이다. 마이바티스는 String을 포함하여 키로 사용될 수 있는 간단한 타입을 허용한다.
orderBEFORE 또는 AFTER를 셋팅할 수 있다. BEFORE로 설정하면 키를 먼저 조회하고 그 값을 keyProperty 에 셋팅한 뒤 insert 구문을 실행한다. AFTER로 설정하면 insert 구문을 실행한 뒤 selectKey 구문을 실행한다. 오라클과 같은 데이터베이스에서는 insert구문 내부에서 일관된 호출형태로 처리한다.
statementType위 내용과 같다. 마이바티스는 Statement, PreparedStatement 그리고 CallableStatement을 매핑하기 위해 STATEMENT, PREPARED 그리고 CALLABLE 구문타입을 지원한다.

sql

이 엘리먼트는 다른 구문에서 재사용가능한 SQL구문을 정의할 때 사용된다. 로딩시점에 정적으로 파라미터처럼 사용할 수 있다. 다른 프로퍼티값은 포함된 인스턴스에서 달라질 수 있다.

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

SQL 조각은 다른 구문에 포함시킬수 있다.

<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1
    cross join some_table t2
</select>

프로퍼티값은 다음처럼 refid속성이나 include절 내부에서 프로퍼티값으로 사용할 수 있다.

<sql id="sometable">
  ${prefix}Table
</sql>

<sql id="someinclude">
  from
    <include refid="${include_target}"/>
</sql>

<select id="select" resultType="map">
  select
    field1, field2, field3
  <include refid="someinclude">
    <property name="prefix" value="Some"/>
    <property name="include_target" value="sometable"/>
  </include>
</select>

Parameters

앞서 본 구문들에서 간단한 파라미터들의 예를 보았을 것이다. Parameters는 마이바티스에서 매우 중요한 엘리먼트이다. 대략 90%정도 간단한 경우 이러한 형태로 설정할 것이다.

<select id="selectUsers" resultType="User">
  select id, username, password
  from users
  where id = #{id}
</select>

위 예제는 매우 간단한 명명된 파라미터 매핑을 보여준다. parameterType은 “int”로 설정되어 있다. Integer과 String과 같은 원시타입 이나 간단한 데이터 타입은 프로퍼티를 가지지 않는다. 그래서 파라미터 전체가 값을 대체하게 된다. 하지만 복잡한 객체를 전달하고자 한다면 다음의 예제처럼 상황은 조금 다르게 된다.

<insert id="insertUser" parameterType="User">
  insert into users (id, username, password)
  values (#{id}, #{username}, #{password})
</insert>

User타입의 파라미터 객체가 구문으로 전달되면 id, username, password 프로퍼티는 찾아서 PreparedStatement파라미터로 전달된다.

비록 파라미터들을 구문에 전달하는 괜찮은 예제이지만 파라미터 매핑을 위한 다른 기능 또한 더 있다.

먼저 파라미터에 데이터 타입을 명시할 수 있다.

#{property,javaType=int,jdbcType=NUMERIC}

javaType은 파라미터 객체의 타입을 판단하는 기준이 된다. javaType은 TypeHandler를 사용하여 정의할 수도 있다.

참고 만약 특정 칼럼에 null 이 전달되면 JDBC 타입은 null가능한 칼럼을 위해 필요하다. 처리 방법에 대해서는 PreparedStatement.setNull()메소드의 JavaDoc을 보라.

다양하게 타입 핸들링하기 위해서는 TypeHandler클래스를 명시할 수 있다.

#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}

다소 설정이 장황하게 보일수 있지만 실제로 이렇게 설정할일은 거의 없다.

숫자 타입을 위해서 크기를 판단하기 위한 numericScale속성도 있다.

#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}

마지막으로 mode속성은 IN, OUT 또는 INOUT 파라미터를 명시하기 위해 사용한다. 파라미터가 OUT 또는 INOUT 이라면 파라미터의 실제 값은 변경될 것이다. mode=OUT(또는 INOUT) 이고 jdbcType=CURSOR(예를들어 오라클 REFCURSOR)라면 파라미터의 타입에 ResultSet 를 매핑하기 위해 resultMap을 명시해야만 한다.

#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}

마이바티스는 structs와 같은 향상된 데이터 타입을 지원하지만 파라미터를 등록할 때 타입명을 구문에 전달해야 한다. 예를들면,

#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}

이런 강력한 옵션들에도 불구하고 대부분은 프로퍼티명만 명시하거나 null 가능한 칼럼을 위해 jdbcType 정도만 명시할 것이다.

#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}

문자열 대체(String Substitution)

#{} 문법은 마이바티스로 하여금 PreparedStatement프로퍼티를 만들어서 PreparedStatement파라미터(예를들면 ?)에 값을 셋팅하도록 할 것이다. 이 방법이 안전하기는 하지만 빠른 방법이 선호되기도 한다. 가끔은 SQL 구문에 변하지 않는 값으로 삽입하길 원하기도 한다. 예를들면 ORDER BY와 같은 구문들이다.

ORDER BY ${columnName}

여기서 마이바티스는 문자열을 변경하거나 이스케이프 처리하지 않는다.

참고 사용자로부터 받은 값을 이 방법으로 변경하지 않고 구문에 전달하는 건 안전하지 않다. 이건 잠재적으로 SQL 주입 공격에 노출된다. 그러므로 사용자 입력값에 대해서는 이 방법을 사용하면 안된다. 사용자 입력값에 대해서는 언제나 자체적으로 이스케이프 처리하고 체크해야 한다.

Result Maps

resultMap엘리먼트는 마이바티스에서 가장 중요하고 강력한 엘리먼트이다. ResultSet에서 데이터를 가져올때 작성되는 JDBC코드를 대부분 줄여주는 역할을 담당한다. 사실 join매핑과 같은 복잡한 코드는 굉장히 많은 코드가 필요하다. ResultMap은 간단한 구문에서는 매핑이 필요하지 않고 복잡한 구문에서 관계를 서술하기 위해 필요하다.

이미 앞에서 명시적인 resultMap을 가지지 않는 간단한 매핑 구문은 봤을 것이다.

<select id="selectUsers" resultType="map">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

모든 칼럼의 값이 결과가 되는 간단한 구문에서는 HashMap에서 키 형태로 자동으로 매핑된다. 하지만 대부분의 경우 HashMap은 매우 좋은 도메인 모델이 되지는 못한다. 그래서 대부분 도메인 모델로는 자바빈이나 POJO 를 사용할 것이다. 마이바티스는 둘다 지원한다. 자바빈의 경우를 보자.

package com.someapp.model;
public class User {
  private int id;
  private String username;
  private String hashedPassword;
  
  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public String getUsername() {
    return username;
  }
  public void setUsername(String username) {
    this.username = username;
  }
  public String getHashedPassword() {
    return hashedPassword;
  }
  public void setHashedPassword(String hashedPassword) {
    this.hashedPassword = hashedPassword;
  }
}

자바빈 스펙에 기반하여 위 클래스는 3개의 프로퍼티(id, username, hashedPassword)를 가진다. 이 프로퍼티는 select구문에서 칼럼명과 정확히 일치한다.

그래서 자바빈은 HashMap과 마찬가지로 매우 쉽게 ResultSet에 매핑될 수 있다.

<select id="selectUsers" resultType="com.someapp.model.User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

그리고 TypeAliases 가 편리한 기능임을 기억해두는게 좋다. TypeAliases를 사용하면 타이핑 수를 줄일 수 있다. 예를들면,

<!-- In Config XML file -->
<typeAlias type="com.someapp.model.User" alias="User"/>

<!-- In SQL Mapping XML file -->
<select id="selectUsers" resultType="User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

이 경우 마이바티스는 칼럼을 자바빈에 이름 기준으로 매핑하여 ResultMap을 자동으로 생성할 것이다. 만약 칼럼명이 프로퍼티명과 다르다면 SQL구문에 별칭을 지정할 수 있다. 예를들면.

<select id="selectUsers" resultType="User">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password     as "hashedPassword"
  from some_table
  where id = #{id}
</select>

ResultMap에 대한 중요한 내용은 다 보았다. 하지만 다 본건 아니다. 칼럼명과 프로퍼티명이 다른 경우에 대해 데이터베이스 별칭을 사용하는 것과 다른 방법으로 명시적인 resultMap 을 선언하는 방법이 있다.

<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id" />
  <result property="username" column="username"/>
  <result property="password" column="password"/>
</resultMap>

구문에서는 resultMap속성에 이를 지정하여 참조한다. 예를들면

<select id="selectUsers" resultMap="userResultMap">
  select user_id, user_name, hashed_password
  from some_table
  where id = #{id}
</select>

대부분 이런 유형이라면 지극히 간단할 것이다.

복잡한 결과매핑

마이바티스는 한가지 기준으로 만들어졌다. 데이터베이스는 당신이 원하거나 필요로 하는 것이 아니다. 정규화 개념 중 3NF나 BCNF가 완벽히 되도록 하는게 좋지만 실제로는 그렇지도 않다. 그래서 하나의 데이터베이스를 모든 애플리케이션에 완벽히 매핑하는 것이 가능하다면 그것이 가장 좋겠지만 그렇지도 않다. 마이바티스가 이 문제를 해결하기 위해 제공하는 답은 결과매핑이다.

이런 복잡한 구문은 어떻게 매핑할까?

<!-- 매우 복잡한 구문 -->
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
  select
       B.id as blog_id,
       B.title as blog_title,
       B.author_id as blog_author_id,
       A.id as author_id,
       A.username as author_username,
       A.password as author_password,
       A.email as author_email,
       A.bio as author_bio,
       A.favourite_section as author_favourite_section,
       P.id as post_id,
       P.blog_id as post_blog_id,
       P.author_id as post_author_id,
       P.created_on as post_created_on,
       P.section as post_section,
       P.subject as post_subject,
       P.draft as draft,
       P.body as post_body,
       C.id as comment_id,
       C.post_id as comment_post_id,
       C.name as comment_name,
       C.comment as comment_text,
       T.id as tag_id,
       T.name as tag_name
  from Blog B
       left outer join Author A on B.author_id = A.id
       left outer join Post P on B.id = P.blog_id
       left outer join Comment C on P.id = C.post_id
       left outer join Post_Tag PT on PT.post_id = P.id
       left outer join Tag T on PT.tag_id = T.id
  where B.id = #{id}
</select>

아마 Author에 의해 작성되고 Comments 이나 태그를 가지는 많은 포스트를 가진 Blog 를 구성하는 괜찮은 객체 모델에 매핑하고 싶을 것이다. 이건 복잡한 ResultMap 으로 충분한 예제이다. 복잡해보이지만 단계별로 살펴보면 지극히 간단하다.

<!-- Very Complex Result Map -->
<resultMap id="detailedBlogResultMap" type="Blog">
  <constructor>
    <idArg column="blog_id" javaType="int"/>
  </constructor>
  <result property="title" column="blog_title"/>
  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
    <result property="favouriteSection" column="author_favourite_section"/>
  </association>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <association property="author" javaType="Author"/>
    <collection property="comments" ofType="Comment">
      <id property="id" column="comment_id"/>
    </collection>
    <collection property="tags" ofType="Tag" >
      <id property="id" column="tag_id"/>
    </collection>
    <discriminator javaType="int" column="draft">
      <case value="1" resultType="DraftPost"/>
    </discriminator>
  </collection>
</resultMap>

resultMap엘리먼트는 많은 하위 엘리먼트를 가진다. 다음은 resultMap 엘리먼트의 개념적인 뷰(conceptual view)이다.

resultMap

  • constructor - 인스턴스화되는 클래스의 생성자에 결과를 삽입하기 위해 사용됨
    • idArg - ID 인자. ID 와 같은 결과는 전반적으로 성능을 향상시킨다.
    • arg - 생성자에 삽입되는 일반적인 결과
  • id – ID 결과. ID 와 같은 결과는 전반적으로 성능을 향상시킨다.
  • result – 필드나 자바빈 프로퍼티에 삽입되는 일반적인 결과
  • association – 복잡한 타입의 연관관계. 많은 결과는 타입으로 나타난다.
    • 중첩된 결과 매핑 – resultMap 스스로의 연관관계
  • collection – 복잡한 타입의 컬렉션
    • 중첩된 결과 매핑 – resultMap 스스로의 연관관계
  • discriminator – 사용할 resultMap 을 판단하기 위한 결과값을 사용
    • case – 몇가지 값에 기초한 결과 매핑
      • 중첩된 결과 매핑 – 이 경우 또한 결과매핑 자체이고 이러한 동일한 엘리먼트를 많이 포함하거나 외부 resultMap을 참조할 수 있다.
resultMap &#xc5d8;&#xb9ac;&#xba3c;&#xd2b8; &#xc18d;&#xc131;
속성설명
id결과매핑을 참조하기 위해 사용할 수 있는 값으로 네임스페이스에서 유일한 식별자
type패키지를 포함한 자바 클래스명이나 타입별칭(내장된 타입별칭이 목록은 위 표를 보자).
autoMapping이 설정을 사용하면 마이바티스는 결과매핑을 자동매핑으로 처리할지 말지를 처리한다. 이 속성은 autoMappingBehavior 라는 전역설정을 덮는다. 디폴트는 unset이다.

가장 좋은 형태: 매번 ResultMap 을 추가해서 빌드한다. 이 경우 단위 테스트가 도움이 될 수 있다. 한번에 모든 resultMap 을 빌드하면 작업하기 어려울 것이다. 간단히 시작해서 단계별로 처리하는 것이 좋다. 프레임워크를 사용하는 것은 종종 블랙박스와 같다. 가장 좋은 방법은 단위 테스트를 통해 기대하는 행위를 달성하는 것이다. 이건 버그가 발견되었을때 디버깅을 위해서도 좋은 방법이다.

다음 섹션은 각각의 엘리먼트에 대해 상세하게 살펴볼 것이다.

id, result

<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>

이건 결과 매핑의 가장 기본적인 형태이다. id와 result 모두 한개의 칼럼을 한개의 프로퍼티나 간단한 데이터 타입의 필드에 매핑한다.

둘 사이의 차이점은 id 값은 객체 인스턴스를 비교할 때 사용되는 구분자 프로퍼티로 처리되는 점이다. 이 점은 일반적으로 성능을 향상시키지만 특히 캐시와 내포된(nested) 결과 매핑(조인 매핑)의 경우에 더 그렇다.

둘다 다수의 속성을 가진다.

id &#xc640; result &#xc5d8;&#xb9ac;&#xba3c;&#xd2b8; &#xc18d;&#xc131;
속성설명
property결과 칼럼에 매핑하기 위한 필드나 프로퍼티. 자바빈 프로퍼티가 해당 이름과 일치한다면 그 프로퍼티가 사용될 것이다. 반면에 마이바티스는 해당 이름이 필드를 찾을 것이다. 점 표기를 사용하여 복잡한 프로퍼티 검색을 사용할 수 있다. 예를들어 “username”과 같이 간단하게 매핑될 수 있거나 “address.street.number” 처럼 복잡하게 매핑될수도 있다.
column데이터베이스의 칼럼명이나 별칭된 칼럼 라벨. resultSet.getString(columnName) 에 전달되는 같은 문자열이다.
javaType패키지 경로를 포함한 클래스 전체명이거나 타입 별칭. 자바빈을 사용한다면 마이바티스는 타입을 찾아낼 수 있다. 반면에 HashMap으로 매핑한다면 기대하는 처리를 명확히 하기 위해 javaType 을 명시해야 한다.
jdbcType지원되는 타입 목록에서 설명하는 JDBC 타입. JDBC타입은 insert, update 또는 delete 하는 null 입력이 가능한 칼럼에서만 필요하다. JDBC의 요구사항이지 마이바티스의 요구사항이 아니다. JDBC로 직접 코딩을 하다보면 null이 가능한 값에 이 타입을 지정할 필요가 있을 것이다.
typeHandler이 문서 앞에서 이미 타입 핸들러에 대해 설명했다. 이 프로퍼티를 사용하면 디폴트 타입 핸들러를 오버라이드 할 수 있다. 이 값은 TypeHandler구현체의 패키지를 포함한 전체 클래스명이나 타입별칭이다.

지원되는 JDBC 타입

상세한 설명전에 마이바티스는 jdbcType열거를 통해 다음의 JDBC 타입들을 지원한다.

BITFLOATCHARTIMESTAMPOTHERUNDEFINED
TINYINTREALVARCHARBINARYBLOGNVARCHAR
SMALLINTDOUBLELONGVARCHARVARBINARYCLOBNCHAR
INTEGERNUMERICDATELONGVARBINARYBOOLEANNCLOB
BIGINTDECIMALTIMENULLCURSORARRAY

constructor

<constructor>
   <idArg column="id" javaType="int"/>
   <arg column="username" javaType="String"/>
</constructor>

프로퍼티가 데이터 전송 객체(DTO) 타입 클래스로 작동한다. 변하지 않는 클래스를 사용하고자 하는 경우가 있다. 거의 변하지 않는 데이터를 가진 테이블은 종종 이 변하지 않는 클래스에 적합하다. 생성자 주입은 public 메소드가 없어도 인스턴스화할 때 값을 셋팅하도록 해준다. 마이바티스는 private 프로퍼티와 private 자바빈 프로퍼티를 지원하지만 많은 사람들은 생성자 주입을 선호한다. constructor엘리먼트는 이러한 처리를 가능하게 한다.

다음의 생성자를 보자.

public class User {
   //...
   public User(int id, String username) {
     //...
  }
//...
}

결과를 생성자에 주입하기 위해 마이바티스는 파라미터 타입에 일치하는 생성자를 찾을 필요가 있다. 자바는 파라미터 이름에서 타입을 체크할 방법이 없다. 그래서 constructor엘리먼트를 생성할 때 인자의 순서에 주의하고 데이터 타입을 명시해야 한다.

<constructor>
   <idArg column="id" javaType="int"/>
   <arg column="username" javaType="String"/>
</constructor>

나머지 속성과 규칙은 id와 result엘리먼트와 동일하다.

속성설명
column데이터베이스의 칼럼명이나 별칭된 칼럼 라벨. resultSet.getString(columnName)에 전달되는 같은 문자열이다.
javaType패키지 경로를 포함한 클래스 전체명이거나 타입 별칭. 자바빈을 사용한다면 마이바티스는 타입을 찾아낼 수 있다. 반면에 HashMap 으로 매핑한다면 기대하는 처리를 명확히 하기 위해 javaType을 명시해야 한다.
jdbcType지원되는 타입 목록에서 설명하는 JDBC 타입. JDBC타입은 insert, update 또는 delete 하는 null 입력이 가능한 칼럼에서만 필요하다. JDBC의 요구사항이지 마이바티스의 요구사항이 아니다. JDBC로 직접 코딩을 하다보면 null 이 가능한 값에 이 타입을 지정할 필요가 있을 것이다.
typeHandler이 문서 앞에서 이미 타입 핸들러에 대해 설명했다. 이 프로퍼티를 사용하면 디폴트 타입 핸들러를 오버라이드 할 수 있다. 이 값은 TypeHandler구현체의 패키지를 포함한 전체 클래스명이나 타입별칭이다.
select다른 매핑된 구문의 ID 는 이 프로퍼티 매핑이 필요로 하는 복잡한 타입을 로드할 것이다. column 속성의 칼럼으로 부터 가져온 값은 대상 select 구문에 파라미터로 전달될 것이다. 세부적인 설명은 association엘리먼트를 보라.
resultMap이 인자의 내포된 결과를 적절한 객체로 매핑할 수 있는 ResultMap 의 ID이다. 다른 select구문을 호출하기 위한 대체방법이다. 여러개의 테이블을 조인하는 것을 하나의 ResultSet 으로 매핑하도록 해준다. ResultSet 은 사본을 포함할 수 있고 데이터를 반복할 수도 있다. 가능하게 하기 위해서 내포된 결과를 다루도록 결과맵을 “연결”하자. 자세히 알기 위해서는 association엘리먼트를 보라.

association

<association property="author" column="blog_author_id" javaType="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
</association>

association 엘리먼트는 “has-one”타입의 관계를 다룬다. 예를들어 Blog는 하나의 Author를 가진다. association 매핑은 다른 결과와 작동한다. 값을 가져오기 위해 대상 프로퍼티를 명시한다.

마이바티스는 관계를 정의하는 두가지 방법을 제공한다.

  • 내포된(Nested) Select: 복잡한 타입을 리턴하는 다른 매핑된 SQL 구문을 실행하는 방법.
  • 내포된(Nested) Results: 조인된 결과물을 반복적으로 사용하여 내포된 결과 매핑을 사용하는 방법.

먼저 엘리먼트내 프로퍼티들을 보자. 보이는 것처럼 select와 resultMap 속성만을 사용하는 간단한 결과 매핑과는 다르다.

속성설명
property결과 칼럼에 매핑하기 위한 필드나 프로퍼티. 자바빈 프로퍼티가 해당 이름과 일치한다면 그 프로퍼티가 사용될 것이다. 반면에 마이바티스는 해당 이름이 필드를 찾을 것이다. 점 표기를 사용하여 복잡한 프로퍼티 검색을 사용할 수 있다. 예를들어 “username”과 같이 간단하게 매핑될 수 있거나 “address.street.number” 처럼 복잡하게 매핑될수도 있다.
javaType패키지 경로를 포함한 클래스 전체명이거나 타입 별칭. 자바빈을 사용한다면 마이바티스는 타입을 찾아낼 수 있다. 반면에 HashMap 으로 매핑한다면 기대하는 처리를 명확히 하기 위해 javaType을 명시해야 한다.
jdbcType지원되는 타입 목록에서 설명하는 JDBC 타입. JDBC타입은 insert, update 또는 delete 하는 null 입력이 가능한 칼럼에서만 필요하다. JDBC의 요구사항이지 마이바티스의 요구사항이 아니다. JDBC로 직접 코딩을 하다보면 null이 가능한 값에 이 타입을 지정할 필요가 있을 것이다.
typeHandler이 문서 앞에서 이미 타입 핸들러에 대해 설명했다. 이 프로퍼티를 사용하면 디폴트 타입 핸들러를 오버라이드 할 수 있다. 이 값은 TypeHandler 구현체의 패키지를 포함한 전체 클래스명이나 타입별칭이다.

연관(Association)을 위한 중첩된 Select

속성설명
column데이터베이스의 칼럼명이나 별칭된 칼럼 라벨. resultSet.getString(columnName)에 전달되는 같은 문자열이다. Note: 복합키를 다루기 위해서 column=”{prop1=col1,prop2=col2}” 문법을 사용해서 여러개의 칼럼명을 내포된 select 구문에 명시할 수 있다. 이것은 대상의 내포된 select 구문의 파라미터 객체에 prop1, prop2 형태로 셋팅하게 될 것이다.
select다른 매핑된 구문의 ID는 이 프로퍼티 매핑이 필요로 하는 복잡한 타입을 로드할 것이다. column 속성의 칼럼으로 부터 가져온 값은 대상 select 구문에 파라미터로 전달될 것이다. 노트: 복합키를 다루기 위해서 column=”{prop1=col1,prop2=col2}” 문법을 사용해서 여러개의 칼럼명을 내포된 select 구문에 명시할 수 있다. 이것은 대상의 내포된 select 구문의 파라미터 객체에 prop1, prop2 형태로 셋팅하게 될 것이다.
fetchType선택가능한 속성으로 사용가능한 값은 lazy와 eager이다. 이 속성을 사용하면 전역 설정파라미터인 lazyLoadingEnabled를 대체한다.

예를들면.

<resultMap id="blogResult" type="Blog">
  <association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectAuthor" resultType="Author">
  SELECT * FROM AUTHOR WHERE ID = #{id}
</select>

여기엔 두개의 select 구문이 있다. 하나는 Blog를 로드하고 다른 하나는 Author를 로드한다. 그리고 Blog의 resultMap은 author프로퍼티를 로드하기 위해 “selectAuthor”구문을 사용한다.

다른 프로퍼티들은 칼럼과 프로퍼티명에 일치하는 것들로 자동으로 로드 될 것이다.

이 방법은 간단한 반면에 큰 데이터나 목록에는 제대로 작동하지 않을 것이다. 이 방법은 “N+1 Selects 문제” 으로 알려진 문제점을 가진다. N+1 조회 문제는 처리과정의 특이성으로 인해 야기된다.

  • 레코드의 목록을 가져오기 위해 하나의 SQL 구문을 실행한다. (“+1” 에 해당).
  • 리턴된 레코드별로 각각의 상세 데이터를 로드하기 위해 select 구문을 실행한다. (“N” 에 해당).

이 문제는 수백 또는 수천의 SQL 구문 실행이라는 결과를 야기할 수 있다. 아마도 언제나 바라는 형태의 처리가 아닐 것이다.

목록을 로드하고 내포된 데이터에 접근하기 위해 즉시 반복적으로 처리한다면 지연로딩으로 호출하고 게다가 성능은 많이 나빠질 것이다.

그래서 다른 방법이 있다.

관계를 위한 내포된 결과(Nested Results)

속성설명
resultMap이 인자의 내포된 결과를 적절한 객체로 매핑할 수 있는 ResultMap의 ID 이다. 다른 select구문을 호출하기 위한 대체방법이다. 여러개의 테이블을 조인하는 것을 하나의 ResultSet으로 매핑하도록 해준다. ResultSet은 사본을 포함할 수 있고 데이터를 반복할 수도 있다. 가능하게 하기 위해서 내포된 결과를 다루도록 결과맵을 “연결”하자. 자세히 알기 위해서는 association엘리먼트를 보라.
columnPrefix여러개의 테이블을 조인할때 ResultSet에서 칼럼명의 중복을 피하기 위해 칼럼별칭을 사용할 수 있다. 칼럼을 외부 결과매핑에 매핑하기 위해 columnPrefix를 명시하자. 이 절의 뒤에 나오는 에제를 보자.
notNullColumn기본적으로 자식객체는 칼럼중 적어도 하나를 null이 아닌 자식객체의 프로퍼티에 매핑할때 만들어진다. 이 속성을 사용해서 칼럼이 값을 가져야만 하는 것을 명시해서 행위를 변경할 수 있다. 그래서 마이바티스는 이러한 칼럼이 null이 아닐때만 자식 객체를 만들것이다. 여러개의 칼럼명은 구분자로 콤마를 사용해서 명시한다. 디폴트값은 unset이다.
autoMapping이 속성을 사용하면 마이바티스는 결과를 프로퍼티에 매핑할때 자동매핑을 사용할지 말지를 정한다. 이 속성은 전역설정인 autoMappingBehavior를 무시하게 한다. 외부 결과매핑에는 영향을 주지 않는다. 그래서 select 나 resultMap 속성을 함께 사용하는 것은 의미가 없다. 디폴트값은 unset이다.

위에서 내포된 관계의 매우 복잡한 예제를 보았을 것이다. 다음은 작동하는 것을 보기 위한 간단한 예제이다. 개별구문을 실행하는 것 대신에 Blog와 Author테이블을 함께 조인했다.

<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    B.author_id     as blog_author_id,
    A.id            as author_id,
    A.username      as author_username,
    A.password      as author_password,
    A.email         as author_email,
    A.bio           as author_bio
  from Blog B left outer join Author A on B.author_id = A.id
  where B.id = #{id}
</select>

조인을 사용할 때 결과의 값들이 유일하거나 명확한 이름이 되도록 별칭을 사용하는 것이 좋다. 이제 결과를 매핑할 수 있다.

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
</resultMap>

<resultMap id="authorResult" type="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
  <result property="password" column="author_password"/>
  <result property="email" column="author_email"/>
  <result property="bio" column="author_bio"/>
</resultMap>

위 예제에서 Author인스턴스를 로드하기 위한 “authorResult” 결과매핑으로 위임된 Blog의 “author”관계를 볼 수 있을 것이다.

매우 중요 : id 엘리먼트는 내포된 결과 매핑에서 매우 중요한 역할을 담당한다. 결과 중 유일한 것을 찾아내기 위한 한개 이상의 프로퍼티를 명시해야만 한다. 가능하면 결과 중 유일한 것을 찾아낼 수 있는 프로퍼티들을 선택하라. 기본키가 가장 좋은 선택이 될 수 있다.

이제 위 예제는 관계를 매핑하기 위해 외부의 resultMap 엘리먼트를 사용했다. 이 외부 resultMap은 Author resultMap을 재사용가능하도록 해준다. 어쨌든 재사용할 필요가 있거나 한개의 resultMap 에 결과 매핑을 함께 위치시키고자 한다면 association 결과 매핑을 내포시킬수 있다. 다음은 이 방법을 사용한 예제이다.

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
  </association>
</resultMap>

블로그에 공동저자가 있다면 어쩌지? select구문은 다음과 같을 것이다.

<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    A.id            as author_id,
    A.username      as author_username,
    A.password      as author_password,
    A.email         as author_email,
    A.bio           as author_bio,
    CA.id           as co_author_id,
    CA.username     as co_author_username,
    CA.password     as co_author_password,
    CA.email        as co_author_email,
    CA.bio          as co_author_bio
  from Blog B
  left outer join Author A on B.author_id = A.id
  left outer join Author CA on B.co_author_id = CA.id
  where B.id = #{id}
</select>

저자(Author)를 위한 결과매핑은 다음처럼 정의했다.

<resultMap id="authorResult" type="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
  <result property="password" column="author_password"/>
  <result property="email" column="author_email"/>
  <result property="bio" column="author_bio"/>
</resultMap>

결과의 칼럼명은 결과매핑에 정의한 칼럼과는 다르기 때문에 공동저자 결과를 위한 결과매핑을 재사용하기 위해 columnPrefix를 명시할 필요가 있다.

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author"
    resultMap="authorResult" />
  <association property="coAuthor"
    resultMap="authorResult"
    columnPrefix="co_" />
</resultMap>

지금까지 “has one” 관계를 다루는 방법을 보았다. 하지만 “has many” 는 어떻게 처리할까? 그건 다음 섹션에서 다루어보자.

collection

<collection property="posts" ofType="domain.blog.Post">
  <id property="id" column="post_id"/>
  <result property="subject" column="post_subject"/>
  <result property="body" column="post_body"/>
</collection>

collection 엘리먼트는 관계를 파악하기 위해 작동한다. 사실 이 내용이 중복되는 내용으로 차이점에 대해서만 주로 살펴보자.

위 예제를 계속 진행하기 위해 Blog는 오직 하나의 Author를 가진다. 하지만 Blog는 많은 Post 를 가진다. Blog 클래스에 다음처럼 처리될 것이다.

private List<Post> posts;

List에 내포된 결과를 매핑하기 위해 collection엘리먼트를 사용한다. association 엘리먼트와는 달리 조인에서 내포된 select나 내포된 결과를 사용할 수 있다.

Collection 을 위한 내포된(Nested) Select

먼저 Blog의 Post를 로드하기 위한 내포된 select 를 사용해보자.

<resultMap id="blogResult" type="Blog">
  <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectPostsForBlog" resultType="Blog">
  SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>

바로 눈치챌 수 있는 몇가지가 있지만 대부분 앞서 배운 association 엘리먼트와 매우 유사하다. 먼저 collection 엘리먼트를 사용한 것이 보일 것이다. 그리고 나서 새로운 “ofType” 속성을 사용한 것을 알아차렸을 것이다. 이 속성은 자바빈 프로퍼티 타입과 collection 의 타입을 구분하기 위해 필요하다.

<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>

독자 왈: “Post의 ArrayList타입의 글 목록”

javaType 속성은 그다지 필요하지 않다. 마이바티스는 대부분의 경우 이 속성을 사용하지 않을 것이다. 그래서 간단하게 설정할 수 있다.

<collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/>

Collection을 위한 내포된(Nested) Results

이 시점에 collection 을 위한 내포된 결과가 어떻게 작동하는지 짐작할 수 있을 것이다. 왜냐하면 association와 정확히 일치하기 때문이다. 하지만 “ofType” 속성이 추가로 적용되었다.

먼저 SQL을 보자.

<select id="selectBlog" resultMap="blogResult">
  select
  B.id as blog_id,
  B.title as blog_title,
  B.author_id as blog_author_id,
  P.id as post_id,
  P.subject as post_subject,
  P.body as post_body,
  from Blog B
  left outer join Post P on B.id = P.blog_id
  where B.id = #{id}
</select>

다시보면 Blog와 Post테이블을 조인했고 간단한 매핑을 위해 칼럼명에 적절한 별칭을 주었다. 이제 Post의 Collection을 가진 Blog의 매핑은 다음처럼 간단해졌다.

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <result property="body" column="post_body"/>
  </collection>
</resultMap>

다시 여기서 id엘리먼트의 중요성을 기억해두거나 기억이 나지 않으면 association섹션에서 다시 읽어둬라.

혹시 결과 매핑의 재사용성을 위해 긴 형태를 선호한다면 다음과 같은 형태로도 가능하다.

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
</resultMap>

<resultMap id="blogPostResult" type="Post">
  <id property="id" column="id"/>
  <result property="subject" column="subject"/>
  <result property="body" column="body"/>
</resultMap>

참고 associations과 collections에서 내포의 단계 혹은 조합에는 제한이 없다. 매핑할때는 성능을 생각해야 한다. 단위테스트와 성능테스트는 애플리케이션에서 가장 좋은 방법을 찾도록 지속해야 한다. 마이바티스는 이에 수정비용을 최대한 줄이도록 해줄 것이다.

복잡한 association 과 collection 매핑은 어려운 주제다. 문서에서는 여기까지만 설명을 할수 있다. 연습을 더 하면 명확하게 이해할 수 있을것이다.

discriminator

<discriminator javaType="int" column="draft">
  <case value="1" resultType="DraftPost"/>
</discriminator>

종종 하나의 데이터베이스 쿼리는 많고 다양한 데이터 타입의 결과를 리턴한다. discriminator엘리먼트는 클래스 상속 관계를 포함하여 이러한 상황을 위해 고안되었다. discriminator는 자바의 switch와 같이 작동하기 때문에 이해하기 쉽다.

discriminator정의는 colume과 javaType속성을 명시한다. colume은 마이바티스로 하여금 비교할 값을 찾을 것이다. javaType은 동일성 테스트와 같은 것을 실행하기 위해 필요하다. 예를들어

<resultMap id="vehicleResult" type="Vehicle">
  <id property="id" column="id" />
  <result property="vin" column="vin"/>
  <result property="year" column="year"/>
  <result property="make" column="make"/>
  <result property="model" column="model"/>
  <result property="color" column="color"/>
  <discriminator javaType="int" column="vehicle_type">
    <case value="1" resultMap="carResult"/>
    <case value="2" resultMap="truckResult"/>
    <case value="3" resultMap="vanResult"/>
    <case value="4" resultMap="suvResult"/>
  </discriminator>
</resultMap>

이 예제에서 마이바티스는 결과데이터에서 각각의 레코드를 가져와서 vehicle_type값과 비교한다. 만약 discriminator비교값과 같은 경우가 생기면 이 경우에 명시된 resultMap을 사용할 것이다. 해당되는 경우가 없다면 무시된다. 만약 일치하는 경우가 하나도 없다면 마이바티스는 discriminator블럭 밖에 정의된 resultMap을 사용한다. carResult가 다음처럼 정의된다면

<resultMap id="carResult" type="Car">
  <result property="doorCount" column="door_count" />
</resultMap>

doorCount프로퍼티만이 로드될 것이다. discriminator경우들의 독립적인 결과를 만들어준다. 이 경우 우리는 물론 car와 vehicle간의 관계를 알 수 있다. 그러므로 나머지 프로퍼티들도 로드하길 원하게 된다. 그러기 위해서는 간단하게 하나만 변경하면 된다.

<resultMap id="carResult" type="Car" extends="vehicleResult">
  <result property="doorCount" column="door_count" />
</resultMap>

vehicleResult와 carResult의 모든 프로퍼티들이 로드 될 것이다.

한가지 더 도처에 설정된 외부 정의를 찾게 될지도 모른다. 그러므로 간결한 매핑 스타일의 문법이 있다. 예를들면

<resultMap id="vehicleResult" type="Vehicle">
  <id property="id" column="id" />
  <result property="vin" column="vin"/>
  <result property="year" column="year"/>
  <result property="make" column="make"/>
  <result property="model" column="model"/>
  <result property="color" column="color"/>
  <discriminator javaType="int" column="vehicle_type">
    <case value="1" resultType="carResult">
      <result property="doorCount" column="door_count" />
    </case>
    <case value="2" resultType="truckResult">
      <result property="boxSize" column="box_size" />
      <result property="extendedCab" column="extended_cab" />
    </case>
    <case value="3" resultType="vanResult">
      <result property="powerSlidingDoor" column="power_sliding_door" />
    </case>
    <case value="4" resultType="suvResult">
      <result property="allWheelDrive" column="all_wheel_drive" />
    </case>
  </discriminator>
</resultMap>

참고 모든 결과 매핑이 있고 모두 명시하고 싶지 않다면 마이바티스는 칼럼과 프로퍼티 명으로 자동으로 매핑할 것이다. 이 예제는 실제로 필요한 내용보다 많이 서술되어 있다.

Auto-mapping

이전의 절에서 이미 본것처럼 간단한 경우 마이바티스는 결과를 자동으로 매핑할 수 있고 간단하지 않은 경우에는 직접 결과매핑을 만들필요가 있다. 하지만 이 절에서 보는것처럼 두가지 방법을 적절히 혼용할수도 있다. 자동매핑을 처리하는 방법을 조금 더 보자.

결과를 자동매핑할때 마이바티스는 칼럼명을 가져와서 대소문자를 무시한 같은 이름의 프로퍼티를 찾을 것이다. 칼럼명이 ID라면 id 이름의 프로퍼티를 찾는다는 것을 뜻한다. 마이바티스는 ID 칼럼값을 사용해서 id 프로퍼티를 설정할것이다.

대개 데이터베이스 칼럼은 대문자를 사용해서 명명하고 단어 사이사이에는 밑줄을 넣는다. 자바 프로퍼티는 대개 낙나표기법을 사용해서 명명한다. 이런 둘사이의 자동매핑을 가능하게 하기 위해서는 mapUnderscoreToCamelCase 를 true로 설정하자.

자동매핑은 결과매핑을 선언한 경우에도 동작한다. 이런 경우 각각의 결과매핑에서 ResultSet의 모든 칼럼은 자동매핑이 아니라 수동매핑으로 처리한다. 다음 샘플에서 id 와 userName 칼럼은 자동매핑되고 hashed_password 칼럼은 수동으로 매핑한다.

<select id="selectUsers" resultMap="userResultMap">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password
  from some_table
  where id = #{id}
</select>
<resultMap id="userResultMap" type="User">
  <result property="password" column="hashed_password"/>
</resultMap>

자동매핑에는 3가지가 있다.

  • NONE - 자동매핑을 사용하지 않는다. 오직 수동으로 매핑한 프로퍼티만을 설정할것이다.
  • PARTIAL - 조인 내부에 정의한 내포된 결과매핑을 제외하고 자동매핑한다.
  • FULL - 모든것을 자동매핑한다.

디폴트값은 PARTIAL이다. FULL 을 사용할때 자동매핑은 조인결과나 같은 레코드의 여러가지 다른 엔터티를 가져올때 예상치 못한 문제를 야기할 수 있다. 이런 위험을 이해하기 위해 다음의 샘플을 보자.

<select id="selectBlog" resultMap="blogResult">
  select
    B.id,
    B.title,
    A.username,
  from Blog B left outer join Author A on B.author_id = A.id
  where B.id = #{id}
</select>
<resultMap id="blogResult" type="Blog">
  <association property="author" resultMap="authorResult"/>
</resultMap>

<resultMap id="authorResult" type="Author">
  <result property="username" column="author_username"/>
</resultMap>

Blog 와 Author 는 자동매핑으로 처리하지만 Author는 id 프로퍼티를 가지고 ResultSet은 id 칼럼을 가진다. 그래서 기대한 것과는 달리 저자(Author)의 id는 블로그(Blog)의 id로 채워질것이다. FULL 는 이런 위험을 가진다.

자동매핑 설정에 상관없이 구문별로 autoMapping속성을 추가해서 자동매핑을 사용하거나 사용하지 않을수도 있다.

<resultMap id="userResultMap" type="User" autoMapping="false">
  <result property="password" column="hashed_password"/>
</resultMap>

cache

마이바티스는 쉽게 설정가능하고 변경가능한 쿼리 캐싱 기능을 가지고 있다. 마이바티스 3 캐시 구현체는 강력하고 쉽게 설정할 수 있도록 많은 부분이 수정되었다.

성능을 개선하고 순환하는 의존성을 해결하기 위해 필요한 로컬 세션 캐싱을 제외하고 기본적으로 캐시가 작동하지 않는다. 캐싱을 활성화하기 위해서 SQL 매핑 파일에 한줄을 추가하면 된다.

<cache/>

하나의 간단한 구문에 다음과 같은 순서로 영향을 준다.

  • 매핑 구문 파일내 select 구문의 모든 결과가 캐시 될 것이다.
  • 매핑 구문 파일내 insert, update 그리고 delete 구문은 캐시를 지울(flush) 것이다.
  • 캐시는 Least Recently Used (LRU) 알고리즘을 사용할 것이다.
  • 캐시는 스케줄링 기반으로 시간순서대로 지워지지는 않는다. (예를들면. no Flush Interval)
  • 캐시는 리스트나 객체에 대해 1024 개의 참조를 저장할 것이다. (쿼리 메소드가 실행될때마다)
  • 캐시는 읽기/쓰기 캐시처럼 처리될 것이다. 이것은 가져올 객체는 공유되지 않고 호출자에 의해 안전하게 변경된다는 것을 의미한다.

모든 프로퍼티는 cache 엘리먼트의 속성을 통해 변경가능하다. 예를들면

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

많은 프로퍼티가 셋팅된 이 설정은 60 초마다 캐시를 지우는 FIFO 캐시를 생성한다. 이 캐시는 결과 객체 또는 결과 리스트를 512개까지 저장하고 각 객체는 읽기 전용이다. 캐시 데이터를 변경하는 것은 개별 쓰레드에서 호출자간의 충돌을 야기할 수 있다.

사용가능한 캐시 전략은 4가지이다.

  • LRU – Least Recently Used: 가장 오랜시간 사용하지 않는 객체를 제거
  • FIFO – First In First Out: 캐시에 들어온 순서대로 객체를 제거
  • SOFT – Soft Reference: 가비지 컬렉터의 상태와 강하지 않은 참조(Soft References )의 규칙에 기초하여 객체를 제거
  • WEAK – Weak Reference: 가비지 컬렉터의 상태와 약한 참조(Weak References)의 규칙에 기초하여 점진적으로 객체 제거

디폴트 값은 LRU 이다.

flushInterval 은 양수로 셋팅할 수 있고 밀리세컨드로 명시되어야 한다. 디폴트는 셋팅되지 않으나 플러시(flush) 주기를 사용하지 않으면 캐시는 오직 구문이 호출될때마다 캐시를 지운다.

size는 양수로 셋팅할 수 있고 캐시에 객체의 크기를 유지하지만 메모리 자원이 충분해야 한다. 디폴트 값은 1024 이다.

readOnly 속성은 true 또는 false 로 설정 할 수 있다. 읽기 전용 캐시는 모든 호출자에게 캐시된 객체의 같은 인스턴스를 리턴 할 것이다. 게다가 그 객체는 변경할 수 없다. 이건 종종 성능에 잇점을 준다. 읽고 쓰는 캐시는 캐시된 객체의 복사본을 리턴 할 것이다. 이건 조금 더 늦긴 하지만 안전하다. 디폴트는 false 이다.

사용자 지정 캐시 사용하기

앞서 본 다양한 설정방법에 더해 자체적으로 개발하거나 써드파티 캐시 솔루션을 사용하여 캐시 처리를 할 수 있다.

<cache type="com.domain.something.MyCustomCache"/>

이 예제는 사용자 지정 캐시 구현체를 사용하는 방법을 보여준다. type속성에 명시된 클래스는 org.mybatis.cache.Cache인터페이스를 반드시 구현해야 한다. 이 인터페이스는 마이바티스 프레임워크의 가장 복잡한 구성엘리먼트 중 하나이다. 하지만 하는 일은 간단하다.

public interface Cache {
  String getId();
  int getSize();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  boolean hasKey(Object key);
  Object removeObject(Object key);
  void clear();
}

캐시를 설정하기 위해 캐시 구현체에 public 자바빈 프로퍼티를 추가하고 cache 엘리먼트를 통해 프로퍼티를 전달한다. 예를들어 다음 예제는 캐시 구현체에서 “setCacheFile(String file)” 를 호출하여 메소드를 호출할 것이다.

<cache type="com.domain.something.MyCustomCache">
  <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>

모든 간단한 타입의 자바빈 프로퍼티를 사용할 수 있다. 마이바티스는 변환할 것이다.

캐시 설정과 캐시 인스턴스가 SQL Map 파일의 네임스페이스에 묶여지는(bound) 것을 기억하는게 중요하다. 게다가 같은 네임스페이스내 모든 구문은 묶여진다. 구문별로 캐시와 상호작동하는 방법을 변경할 수 있거나 두개의 간단한 속성을 통해 완전히 배제될 수도 있다. 디폴트로 구문은 아래와 같이 설정된다.

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

이러한 방법으로 구문을 설정하는 하는 방법은 굉장히 좋지 않다. 대신 디폴트 행위를 변경해야 할 경우에만 flushCache와 useCache 속성을 변경하는 것이 좋다. 예를들어 캐시에서 특정 select 구문의 결과를 제외하고 싶거나 캐시를 지우기 위한 select 구문을 원할 것이다. 유사하게 실행할때마다 캐시를 지울 필요가 없는 update구문도 있을 것이다.

cache-ref

이전 섹션 내용을 돌이켜보면서 특정 네임스페이스을 위한 캐시는 오직 하나만 사용되거나 같은 네임스페이스내에서는 구문마다 캐시를 지울수 있다. 네임스페이스간의 캐시 설정과 인스턴스를 공유하고자 할때가 있을 것이다. 이 경우 cache-ref 엘리먼트를 사용해서 다른 캐시를 참조할 수 있다.


출처: http://www.mybatis.org/mybatis-3/ko/sqlmap-xml.html

반응형

얼마전에 Spring의 기능을 참조할 일이 있어 Spring의 레퍼런스 프로젝트인 spring-petclinic 를 내려받게 되었다.프로젝트 구조를 살펴보는 중 생소한 파일이 발견되었다.
스크린샷 2015-01-18 20.28.36

바로 ‘logback.xml’ 파일이었는데, 직감적으로 “아! 어느새 log4j를 대체하는 기술이 나온 모양이구나”하는 생각이 들었다. 한동안 기술 트랜드에서 너무 벗어나 있었다는 생각이 다시금 들면서 이놈이 뭔지 좀 살펴보기로 했다.

logback 홈페이지에 들어가 보면 logback을 이렇게 소개한다.


“log4j 프로젝트의 후계자로 만들어졌다”

log4j개발자가 거의 지난 10년동안 log4j가 사용되면서 불편(?)했던 내용들을 모두 개선해서 logback이라는 제품을 만들어 낸 듯한 느낌이다.

개발자가 소개하는 log4j 에서 logback으로 옮겨야 하는 여러 이유들 중 특히 내가 관심이 가는 몇가지 항목들은 다음과 같다.

  • Automatic Reloading Configuration file
    • 필자에게는 logback을 도입해야 하는 제일 중요한 이유라고 생가되는 기능이다.
    • log4j를 사용하던 시절(?)에는 로깅 레벨을 WARN -> INFO 등으로 변경하게 되면 Application(WAS)를 다시 시작했어야 했지만 logback 에서는 그럴 필요가 없다. 설정 파일을 변경하면 지정된 시간이 지나게 되면 파일의 변경을 감지하고 다시 읽어들인다.
  • Graceful Recovery from I/O Failures
    • 기존 log4j에서 FileAppender 를 사용하여 로그를 파일서버에 저장하는 경우, 파일서버에 문제가 있어 I/O fail이 나면 Application(was)를 재시작 했어야 했지만 logback에서는 파일 서버가 정상으로 돌아오면 에러 상황에서 빠르게 자동으로 복구가 된다.
  • Automatic Compress
    • 아마도 기존에 log4j를 사용해서 파일로 로그를 떨어뜨려 놓는 경우 별도의 배치 프로그램을 이용해서 로그파일을 압축하고 다른 곳으로 옮겨놓는 작업을 많이 해 보았을 것이라 생각된다. logback에서는 로그 파일의 자동 압축을 지원하고, 시간(파일의 갯수)이 지난 파일을 자동으로 삭제하는 기능도 제공을 한다.
    • 로그 파일을 거대해서 압축하는데 시간이 오래 걸린다 해도 비동기 방식으로 동작하므로 application의 성능에는 영향을 주지 않는다.
  • Prudnect Mode
    • 다수의 JVM 인스턴스에서 같은 로그 파일을 사용하여 로그를 “안전하게” 기록할 수 있는 기능을 제공한다고 한다. 어떤 제한이 있는것 같다고 하는데 내부적으로 비동기 큐를 사용하고 있는 것이 아닌가 추측이 된다.
    • 정확하게 확인해 보지는 않았지만 이런 모델들이 보통 문제를 많이 일으키는 관계로 꼭 필요하지 않다면 되도록 사용하지 않는 것이 좋을 것 같다는 생각이다.
  • Conditional Processing Configurations
    • 기존에는 개발환경과 운영환경에 별도의 설정파일을 적용-개발환경에서는 로깅레벨을 DEBUG, 운영환경에서는 WARN-시키기 위해 로깅 설정파일을 분리해 놓고 사용하던 경험들이 있을 것이라 생각된다.
    • logback에서는 설정 파일에 if-then-else 의 조건문을 사용할 수 있어 설정 파일 하나로만 개발-운영 환경을 모두 커버할 수 있도록 만드는 것이 가능한다.
  • Stack Traces with Packaging Data
    • Logback을 사용해서 Stack Trace를 남기게 되면 각 패키지의 아티팩트 정보와 버전정보를 출력해 준다. 배포가 문제가 있다면 바로 확인이 가능하게 해주는 유용한 기능이라 생각된다.
    • 스크린샷 2015-01-18 21.02.29

프로젝트에 적용해 보기

1. 디펜던시 설정

logbook 응 사용하기 위해서는 logback-core, logback-classic, slf4j-api jar들이 필요한다. 디펜던시는 아래와 같이 설정한다.

메이븐을 사용하고 있다면 – [ pom.xml ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.1.2</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.1.2</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.10</version>
</Dependency>
<!— 조건부 설정을 사용하려면 아래의 디펜던시를 추가해 주어야 한다. —>
<dependency>
    <groupId>org.codehaus.janino</groupId>
    <artifactId>janino</artifactId>
    <version>2.7.7</version>
</dependency>

그래들을 사용하고 있다면 – [build.gradle]

1
2
3
4
5
compile 'ch.qos.logback:logback-classic:1.1.2'
compile 'ch.qos.logback:logback-core:1.1.2'
compile 'org.slf4j:slf4j-api:1.7.10'
// 조건부 설정을 사용하려면 아래의 디팬던시를 추가해 주어야 한다
compile 'org.codehaus.janino:janino:2.7.7'

2. logback.xml 파일의 설정은 아래와 같이 한다. 주석으로 처리한 부분을 잘 읽어보자.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?xml version="1.0" encoding="UTF-8"?>
<!-- 30초마다 설정 파일의 변경을 확인한다. 파일이 변경되면 다시 로딩한다 -->
<configuration scan="true" scanPeriod="30 seconds">
 
    <!-- 외부 설정파일을 사용할 수 있다. -->
    <property resource="resource.properties"/>
 
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern> %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${APP_HOME}/sujemall-webapp.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 파일이 하루에 한개씩 생성된다 -->
            <fileNamePattern>sujemall-webapp.%d{yyyy-MM-dd}.log</fileNamePattern>
 
            <!-- maxHIstory 설정은 위 부분에 롤링 정책에 따라 적용되 된다고 보면된다.
             위 설정데로 라면 30일이 지난 파일은 삭제가 된다.-->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
 
        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>
 
    <logger name="org.springframework" level="info"/>
    <logger name="org.hibernate" level="debug"/>
    <logger name="com.sujemall.webapp" level="debug"/>
    <if condition='property("RUN_MODE").equals("SERVICE")'>
        <then>
            <!-- 설정파일에 RUN_MODE 가 SERVICE로 정의되어 있으면 로깅 레벨을 INFO로 지정 -->
            <root level="info">
                <appender-ref ref="console"/>
                <appender-ref ref="FILE"/>
            </root>
        </then>
        <!-- 설정파일에 RUN_MODE 가 SERVICE로 정의되어 있지 않으면  로깅 레벨을 DEBUG 지정 -->
        <else>
            <root level="debug">
                <appender-ref ref="console"/>
                <appender-ref ref="FILE"/>
            </root>
        </else>
    </if>
</configuration>

3. 로그 찍기는 slf4j api를 사용하는 기존의 방식과 크게 다르지 않다. 마커를 이용해서 로그를 찍어봤다.

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
32
33
package com.sujemall.webapp.service;
 
import com.sujemall.webapp.model.User;
import com.sujemall.webapp.repository.UserRepository;
import com.sujemall.webapp.utils.CommonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
/**
 * Created by yhlee on 15. 1. 12..
 */
 
@Service
public class UserServiceImpl implements UserService {
 
    static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);
 
    @Autowired
    private UserRepository userRepository;
 
    @Override
    @Transactional
    public User enrollUser(User user) {
 
        user.setPassword(CommonUtils.hashString(user.getPassword()));
        user = userRepository.save(user);
        LOGGER.info("#### Success Save User : UserName is  {}, Email is {} " ,user.getUserName(), user.getMainEmail());
        return user;
    }
}

위와같이 설정하고 application을 돌려보면 아래와 같이 로그가 잘 찍히는 것을 확인할 수 있다.

스크린샷 2015-01-18 22.08.02



출처 : http://knot.tistory.com/92

'JAVA > Spring' 카테고리의 다른 글

Spring boot yml 파일 사용  (1) 2019.03.21
Mapper XML 파일  (0) 2016.06.23
iBatis MyBatis 차이  (0) 2016.06.17
RequestMapping 핸들러 매핑 정의  (0) 2016.06.08
Spring xml 태그 설명  (0) 2016.06.07
반응형


iBatis -> MyBatis


 

 



1       Ibatis에서 MyBatis로 변경된 이유

Ø  Apache project팀에서 google code팀으로 이동하면서 명칭이 변경됨.

 

2       차이점

2.1      Java 요구 버전

Ø    iBatis에서는 JDK 1.4 이상에서 사용 가능 MyBatis에서는 JDK 1.5 이상 사용 가능.

2.2      패키지 내부 구조가 변경되었음.

Ø    ibatis : com.ibatis.*

Ø    MyBatis : org.apache.ibatis.*

2.3      sqlMap.xml 내부 구조가 변경되었음.

Ø    parameterMap 사용 못함. -> parameterType으로 대체.

Ø    dtd가 변경 (“http://mybatis.org/dtd/mybatis-3-mapper.dtd”>

Ø    사용 용어의 변경

SqlMapConfig

Configration

sqlMap

Mapper

resultClass

resultType

 

2.4      MyBatis lib 별도 제공

Ø    Maven Dependency Information 예시

<dependency>

    <groupId>org.mybatis</groupId>

   <artifactId>mybatis</artifactId>

   <version>3.2.2</version>

</dependency>

 

<dependency>

   <groupId>org.mybatis</groupId>

   <artifactId>mybatis-spring</artifactId>

    <version>1.2.0</version>

</dependency>

 

2.5      Annotation 도입

Ø    sqlMapClient DI 설정 불필요

Ø    간편해짐

Ø    Bean id sqlSesstionFactory, sqlSesstionTemplate만 지정하면 됨.

2.6      rowHandler 대체

Ø    xml및 대량 데이터 처리를 위해 사용되었던 rowHandler가 삭제

Ø    rowHandler -> resulthandler로 변경됨

Ø    자바 annotation을 사용하여 xml을 사용하지 않고 자바로만 할 수 있게 됨.

Ø    자바 선언 보다 xml 선언이 우선순위를 가짐.

2.7      네임스페이스 방식 변경

Ø    ibatis : <sqlMap namespace=”User”>

Ø    MyBatis : <mapper namespace=”myBatis.mapper.UserMapper”>

Ø    네임스페이스 사용은 필수, userStatementNameSpace설정 제거

2.8      동적 SQL – XML 엘리먼트

Ø    If, choose(when, otherwise), trim (whre,set), foreach

2.9      동적 SQL – Provider annotation

Ø    @SelectProvider(type=CommentSqlProvider.class, method=”selectCommentByCondition”)

2.10    캐시 지원.

2.11    (스프링 연동모듈) mapper 자동 검색


 

3       변경되거나 추가된 속성들 (종합)

4       iBatis

MyBatis

비고

com.ibatis.*

org.apache.ibatis.*

패키지 구조 변경

SqlMapConfig

Configration

용어변경

sqlMap

mapper

용어변경

sqlMapClient

sqlSession

구문대체

rowHandler

resultHandler

구문대체

resultHandler

SqlSessionFactory

구문대체

parameterMap, parameterClass

parameterType

속성 통합

resultClass

resultType

용어변경

#var#

#{var}

구문대체

$var$

${var}

구문대체

<isEqual> , <isNull>

<if>

구문대체




일 작업하면서 정리하였던거 공유합니다.

출처 : http://uwostudy.tistory.com/19


'JAVA > Spring' 카테고리의 다른 글

Mapper XML 파일  (0) 2016.06.23
logback 설정  (0) 2016.06.17
RequestMapping 핸들러 매핑 정의  (0) 2016.06.08
Spring xml 태그 설명  (0) 2016.06.07
java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener  (0) 2016.06.02
반응형

@MVC의 핸들러 매핑을 위해서는 DefaultAnnotationHandlerMapping이 필요하다. 

디폴트 핸들러 매핑이므로 다른 핸들러 매핑을 등록하지 않았다면 기본으로 사용 가능.

(다른 핸들러 매핑을 등록했다면 디폴트가 자동 적용되지 않으므로 빈 설정이 필요함)

 

@RequestMapping 어노테이션의 엘리먼트

 · String[] value():URL 패턴

 - 디폴트 엘리먼트

 

 - 요청의 URL 패턴을 지정해서 매핑

  1. @RequestMapping("/hello")
  2. @RequestMapping({"/hello""/hello/""/hello.*"})
  3. // path variable 사용 가능{}
  4. @RequestMapping("main*")
  5. @RequestMapping("/view.*")
  6. @RequestMapping("/admin/**/user")

 

 · RequestMethod[] method:HTTP 메소드

 - HTTP 메소드를 정의한 enum

 

 - GET, POST, HEAD, PUT, DELETE, OPTIONS, TRACE 7개의 HTTP 메소드가 정의되어 있다.

 

 - 같은 URL이라도 요청 메소드에 따라 다른 메소드에 매핑 가능

  1. @RequestMapping(value="/user/add", method=RequestMethod.GET)
  2. @RequestMapping(value="/user/add", method=RequestMethod.POST)

  요청 방식이 GET 방식일 때는 첫번째에, POST 방식일 때는 두번째에 매핑된다.

  위의 두가지 매핑 설정만 있는 경우라면, GET이나 POST 방식 이외의 요청이 들어올 경우

  HTTP 405 - Method Not Allowed 응답을 받게 됨.

 

 - 타입 레벨에는 URL만 주고 메소드 레벨에는 HTTP 요청 메소드만 지정하는 방법

  1. @RequestMapping(method="RequestMethod.GET)

 

 - 단일 URL에 대해 다양한 HTTP 메소드(GET, POST, PUT, DELETE)를 적용하는 RESTful 스타일의 접근 방법

  HTML 폼에서는 GET과 POST만 지원

  자바스크립트나 스프링이 지원하는 <form:form>를 이용해서 히든 필드를 통해

  HTTP 메소드(PUT, DELETE 등)를 전달할 수 있다.

 

 · String[] params():요청 파라미터

 - 요청의 파라미터와 그 값을 비교해서 매핑

 

 - 같은 URL을 사용하더라도 파라미터에 따라 별도의 작업

  1. @RequestMapping(value="/user/edit", params="type=admin") // ①
  2. @RequestMapping(value="/user/edit", params="type=admin") // ②
  3. @RequestMapping("/user/edit")                            // ③

  요청이 /user/edit?type="admin"으로 들어왔을 때,

  ①에 매핑된다.

  왜? ③보다 더 상세한 조건을 만족한 ①이 선택된다.

 

 - 꼭 URL에 포함된 파라미터만 비교하는건 아니다.

  POST 방식으로 전송한 파라미터도 비교 대상이다.

  단, method=RequestMethod.GET/POST를 지정하지 않았을 경우

 

 - 특정 파라미터가 존재하지 않아야 한다는 조건도 지정 가능

  1. @RequestMapping(value="/user/edit", params="!type")

 

 · String[] headers():HTTP 헤더

 - HTTP의 헤더 정보를 가져와 비교해서 매핑

 

 - params와 비슷하게 '헤더이름 = 값'의 형식 사용

  1. @RequestMapping(value="/view", headers="context-type=text/*")

  context-type이 text/html이나 text/plan 등으로 되어 있는 경우에 매핑

 

 

 

 

타입 레벨 매핑과 메소드 레벨 (타입 레벨 = class 또는 interface, 메소드 레벨 = method)

 · 타입 레벨 + 메소드 레벨 매핑

 - 타입 레벨에 붙는 @RequestMapping은

    타입 내의 모든 매핑용 메소드의 공통 조건을 지정할 때 사용

    메소드 레벨에서 조건을 세분화

 

 - 메소드 레벨의 매핑은 클래드 레벨의 매핑을 상속 받는다.

 

  ex) user/add, user/edit, user/delete라는 URL에 매핑

  1. @RequestMapping("/user")
  2. public class UserController {
  3.     @RequestMapping("/add") public String add(){ ... }
  4.     @RequestMapping("/edit") public String edit(){ ... }
  5.     @RequestMapping("/delete") public String delete(){ ... }
  6. }

 

 - URL 패턴에 *이나 **을 사용가능

  1. @RequestMapping("/user/*")
  2. public class UserController {
  3.     @RequestMapping("/add") public String add(){ ... }
  4. }
  5. // => /user/add로 결합
  6.  
  7. @RequestMapping("/user/**")
  8. public class UserController {
  9.     @RequestMapping("/add") public String add(){ ... }
  10. }
  11. // => /user/**/add로 결합
  12.  
  13. @RequestMapping("/user" 또는 "/user/" 또는 "/user/*")
  14. public class UserController {
  15.     @RequestMapping("add") public String add(){ ... }
  16. }
  17. // => /user/add로 결합 (직관적이지 않아서 비추)

 

 - 메소드 레벨에서 다른 매핑 정보를 추가해서 사용 가능(methods라든지 params 등)

  1. @RequestMapping("/user/add")
  2. public class UserController {
  3.   @RequestMapping(methods=RequestMethod.GET) public String form(){ ...}
  4.   @RequestMapping(methods=RequestMethod.POST) public String submit(){... }
  5. }

 

 · 메소드 레벨 단독 매핑

 - 타입 레벨에서 공통점이 없다면 굳이 타입, 메소드 레벨을 결합하지 않아도 된다.

 

 - 타입 레벨에 조건 없는 @RequestMapping을 붙여두고,

    메소드 레벨마다 다른 @ReqeustMapping 조건을 선언하면 된다.

    주의!

    타입 레벨에 @RequestMapping을 생각하면 클래드 자체가 매핑 되지 않으니 반드시 선언한다.

  1. @RequestMapping
  2. public class UserController {
  3.     @RequestMapping("/hello") public String hello(){ ... }
  4.     @RequestMapping("/main") public String main(){ ... }
  5. }

 

 - 컨트롤러 클래스에 @Controller 어노테이션을 붙여서 빈 자동스캔 방식으로 등록되게 했다면

    클래스 레벨의 @RequestMapping을 생략할 수 있다.

  1. @Controller
  2. public class UserController {
  3.     @RequestMapping("/hello") public String hello(){ ... }
  4.     @RequestMapping("/main") public String main(){ ... }
  5. }

 

 · 타입 레벨 단독 매핑

 - 핸들러 매핑은 원래 핸들러 오브젝트를 결정하는 전략.

    다른 타입 컨트롤러와의 일관성을 위해 오브젝트까지만 매핑하고

    최종 실행할 메소드는 핸들러 어댑터가 선정하게 하기 위해서 사용

  1. @RequestMapping("/hello")
  2. public class helloController implements Controller { ... }

 

 - 원칙적으로 핸들러 매핑과 핸들러 어댑터는 독립적으로 조합될 수 있기 때문에 적용 가능한 방법

 

 - 클래스 레벨의 URL 패턴이 /*로 끝나는 경우

   메소드 이름이 메소드 레벨의 URL 패턴으로 사용되게 할 수 있다.

  1. @RequestMapping("/user/*")
  2. public class UserController {
  3.     @RequestMapping public string add(){ ... }  
  4.     @RequestMapping public string edit(){ ... }
  5. }
  6. // 각각 /user/add와 /user/edit로 매핑된다.

 

 

 

 

타입 상속과 매핑

@RequestMapping이 적용된 클래스를 상속해서

컨트롤러로 사용하는 경우

수퍼클래스의 매핑 정보는 어떻게 됨?

 

타입 상속에 관한 대원칙 2가지.

1. @RequestMapping 정보는 상속된다.

2. 단, 서브 클래스에서 @RequestMapping을 재정의 하면 수퍼클래스의 정보는 무시된다.

 

인터페이스 구현의 의한 @ReqeustMapping 정보 상속은

클래스 상속과 거의 비슷하지만

몇가지 차이점이 있다.

 

 · 상위 타입과 메소드의 @RequestMapping 상속

 - 수퍼 클래스의 매핑 정보 상속

  1. // Super
  2. @RequestMapping("/user") public class Super {
  3.     @RequestMapping("/list") public string list(){ ... }  
  4. }
  5. //Sub
  6. public class Sub extends Super {}

결과

  모두 상속

  Sub 클래스를 컨트롤러로 등록하면, /user/list URL은 list() 메소드로 매핑

 

 - 메소드 오버라이드

  1. //Sub
  2. public class Sub extends Super {
  3.     public String list(){ ... }
  4. }

결과

  메소드를 오버라이드했더라도

  메소드에 @RequestMapping을 붙이지 않는다면

  수퍼 클래스 메소드의 매핑 정보는 그대로 상속된다.

  인터페이스의 경우도 마찬가지

 

 · 상위 타입의 @RequestMapping과 하위 타입 메소드의 @RequestMapping 결합

 - 상위 타입과 하위 메소드 정보의 결합

  1. //Super
  2. @RequestMapping("/user") public class Super {}
  3. //Sub
  4. public class Sub extends Super {
  5.     @RequestMapping("/list") public String list(){ ... }
  6. }

결과

  마찬가지로 /user/list URL은 list() 메소드로 매핑

  매핑정보 결합은 URL 뿐 아니라, HTTP 메소드, 파라미터에도 적용된다.

  인터페이스의 경우도 마찬가지

 

 · 상위 타입 메소드의 @RequestMapping과 하위 타입의 @RequestMapping 결합

 - 상위 메소드와 하위 타입의 결합

  1. //Super
  2. public class Super {
  3.     @RequestMapping("/list") public String list(){ ... }
  4. }
  5. //Sub
  6. RequestMapping("/user") public class Sub extends Super {}

결과

  마찬가지로 /user/list는 Sub가 상속받은 list() 메소드에 매핑된다.

주의

  인터페이스의 경우도 동일한 방식으로 적용된다.

  단, 인터페이스를 구현하는 메소드에

  URL이 없는 빈 @RequestMapping을 붙이면

  인터페이스 메소드의 매핑정보가 무시된다.

 

 · 하위 타입과 메소드의 @RequestMapping 재정의

 - 타입 레벨의 매핑정보 재정의

  1. // Super
  2. @RequestMaping("/user") public class Super {
  3.     @RequestMapping(value="/catalog", methods=RequestMethod.POST)
  4.     public String list() { ... }
  5. }
  6. // Sub
  7. @RequestMapping("/user") public class Sub extends Super {
  8.     @RequestMapping("/list") public String list() { ... }
  9. }

결과

  @RequestMapping을 재정의하는 경우에는 상위 타입에서 정의한 정보와 결합되지 않는다.

  Sub를 컨트롤러로 등록하면 /user/list 요청에 대한 메소드 매핑은 Sub의 list() 메소드에 매핑된다.

 

 · 서브클래스 메소드의 URL 패턴 없은 @RequestMapping 재정의

 - 하위 타입의 @RequestMapping은 항상 상위 타입의 @RequestMapping 정보를 대신한다.

 

 - 하지만 클래스 상속에서 오버라이드한 하위 메소드에 한해서는

  URL 조건이 없는 @RequestMapping을 붙였을 경우

  상위 메소드의 @RequestMapping의 URL 조건이 그대로 상속된다.

 


출처: http://blog.naver.com/comkwang17?Redirect=Log&logNo=140182037265 


반응형

목차


beans

dispatcher-servlet.xml 혹은 applicationContext.xml 의 루트태그. 태그 속성으로 스프링 bean이 사용할 라이브러리를 정의한다. 

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
            http://www.springframework.org/schema/aop 
            http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
            http://www.springframework.org/schema/context 
            http://www.springframework.org/schema/context/spring-context-3.1.xsd
            http://www.springframework.org/schema/util 
            http://www.springframework.org/schema/util/spring-util-3.1.xsd
            http://www.springframework.org/schema/task 
            http://www.springframework.org/schema/task/spring-task-3.1.xsd">  
</beans>
cs

<beans>의 속성값은 실제 참조하는 스프링 라이브러리의 버전에 따라 달라진다. 위에선 spring 3.1을 사용하는 경우다.


bean

객체 생성 태그

<bean id="아이디" class="클래스"/>

  • id: 스프링 bean이 인식하는 식별자
  • class: 객체를 생성할 클래스
package com.bean;
 
public class User {
    String str;
    public User() {
        this.str = "ㅎㅇ";
    }
    
    public String result() {
        return str;
    }
}
cs

위와 같은 클래스가 있다고 할 때 applicationContext.xml 에 다음을 추가 한 뒤

<bean id="myBean" class="com.temp.User"/>
cs

실행한다:

AbstractApplicationContext context 
          = new ClassPathXmlApplicationContext("com/bean/applicationContext.xml");
User us = (User)context.getBean("myBean"); //bean이 생성한 객체는 Object 타입
System.out.println(us.result()); // "ㅎㅇ"
cs


constructor-arg

생성자의 가변인자에 값을 할당한다.

<bean id="아이디" class="클래스">

<constructor-arg value="값"/>

<constructor-arg ref="참조값"/>

</bean>

value: 원시형 데이터

ref: 참조형 데이터(bean으로 생성한 객체의 아이디)

package com.bean;
 
public class User1 {
    String str, str2;
    
    public User1(String str, String str2) {
        this.str = str;
        this.str2 = str2;
    }
    
    public String result() {
        return str + " " + str2;
    }
}
cs

위와 같은 클래스가 있을 때 다음처럼 태그 작성 후

<bean id="user1" class="com.bean.User1">
    <constructor-arg value="으앙"/<!--value : 기본자료형 데이터-->
    <constructor-arg value="쥬금"/>  <!--순서에 주의할 것-->
</bean>
cs

아래처럼 실행한다:

AbstractApplicationContext context
          = new ClassPathXmlApplicationContext("com/bean/applicationContext.xml");
User1 us1 = (User1)context.getBean("user1");
System.out.println(us1.result());
cs

생성자에 참조타입을 할당하는 것도 가능하다.

<bean id="user1" class="com.sp1.UserImpl"/>
<bean id="testService1" class="com.sp1.TestService">
    <constructor-arg ref="user1"/<!-- ref : 참조형 변수를 뜻함 -->
</bean>
cs


property

필드, 클래스 변수나 인스턴스 변수를 의미하며 setter 메서드를 이용해 값을 할당한다. 따라서 setter 메서드가 없으면 사용할 수 없다.

Syntex 1: body가 있는 경우

<bean id="아이디" class="클래스">

    <property name="변수명">

        <value type="클래스_타입">값</value>

        <ref bean="참조값"/>

    </property>

</bean>


Syntex 2: body가 없는 경우

<bean id="아이디" class="클래스">

    <property name="변수명" value="값"/>

    <property name="변수명" ref="참조값"/>

</bean>

<!--Syntex 1-->
<bean id="user1" class="com.sp2.UserImpl">
    <property name="name">
        <value type="java.lang.String">스프링</value
    </property>
 
    <property name="tel">
        <value type="java.lang.Integer">20</value
    </property>
</bean>
 
<!--Syntex 2-->
<bean id="user1" class="com.sp2.UserImpl">
    <property name="name" value="어렵네"/>
    <property name="age" value="30"/>
</bean>
cs

위는 자바에서 다음처럼 작성한 것과 같음: 

UserImpl user1 = new UserImpl();
user.setName("스프링");
user.setAge("20");
cs

참조형 데이터의 경우엔: 

<!--Syntex 1-->
<bean id="us" class="com.sp2.UserService">
    <property name="user">
        <ref bean="ob"/>
    </property>    
</bean>
 
<!--Syntex 2-->
<bean id="userService" class="com.sp2.UserService">
    <property name="user" ref="user1"/>
</bean>
cs
UserService us = new UserService();
user.setUser(ob);
cs


p 네임스페이스

기존의 구문을 축약하여 작성할 수 있게 해준다.

<bean id="아이디" class="클래스" p:변수명="값" p:변수명-ref="참조값"/>

<!-- p 네임스페이스를 이용한 프로퍼티 설정 -->
<bean id="user2" class="com.sp2.UserImpl" p:name="자바" p:tel="010-1111-1111"/>
 
<!-- p 네임스페이스를 이용한 의존관계설정 -->
<bean id="userService2" class="com.sp2.UserService" p:user-ref="user2"/>
cs


list

ArrayList에 대응된다.

<bean id="아이디" class="클래스">

    <property name="변수명">

        <list>

            <value>첫번째 값</value>

            <value>두번째 값</value>

            ...

        </list>

    </property>

</bean>

<bean id="sample" class="com.test.SampleClass">
    <property name="hobby">
        <list>
            <value>소개팅</value>
            <value>운동</value>
            <value>잠자기</value>
        </list>
    </property>
</bean>
cs


map

HashMap에 대응

Syntex 1:

<bean id="아이디" class="클래스">

    <property name="address">

        <map>

            <entry>

                <key><value>키</value></key>

                <value>벨류</value>

            </entry>

        </map>

    </property>

</bean>


Syntex 2:

<bean id="아이디" class="클래스">

    <property name="address">

        <map>

            <entry key="값_키" value="값_벨류"/>

            <entry key-ref="참조값_키" value-ref="참조값_벨류"/>

        </map>

    </property>

</bean>

<!--Syntex 1-->
<bean id="user" class="com.sp3.UserImpl">
    <property name="address">
        <map>
            <entry>
                <key><value>이상해</value></key>
                <value>서울</value<!-- key="이상해", value="서울" -->
            </entry>
        </map>
    </property>
</bean>
 
<!--Syntex 2-->
<bean id="user" class="com.sp3.UserImpl">
    <property name="address">
        <map>                
            <entry key="이이이" value="부산"/<!-- key="이이이", value="부산" -->
        </map>
    </property>
</bean>
 
<!--Syntex 3-->
<bean id="user" class="com.sp3.UserImpl">
    <property name="address">
        <map>
            <entry key-ref="객체" value-ref="객체2"/<!-- key=객체 value=객체2 -->
        </map>
    </property>
</bean>
cs


props

properties에 대응되며 문법은 map과 같다.

<bean id="handlerMapping" class="servlet.handler.SimpleUrlHandlerMapping">
     <property name="mappings">
         <props>
             <prop key="/bbs/*.action">bbs.boardController</prop>
         </props>
     </property>
</bean>
cs

출처 : http://noritersand.tistory.com/152

반응형

잘돌아가던 이클립스에서 톰캣 실행 시 갑자기 class를 찾지 못한다는 오류가 발생하면서,

서버가 정상적으로 실행되지 않는 문제가 한번씩 생기는데...

<Case 1>
2012. 4. 3 오전 11:18:51 org.apache.catalina.core.StandardContext listenerStart
심각: Error configuring application listener of class org.springframework.web.context.ContextLoaderListener
java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1688)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1533)
at org.apache.catalina.core.DefaultInstanceManager.loadClass(DefaultInstanceManager.java:525)
at org.apache.catalina.core.DefaultInstanceManager.loadClassMaybePrivileged(DefaultInstanceManager.java:507)
at org.apache.catalina.core.DefaultInstanceManager.newInstance(DefaultInstanceManager.java:124)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4701)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5260)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1525)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1515)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)

<Case 2>
6월 10, 2013 1:54:15 오후 org.apache.catalina.core.StandardContext listenerStart
SEVERE: Error configuring application listener of class org.springframework.web.util.Log4jConfigListener
java.lang.ClassNotFoundException: org.springframework.web.util.Log4jConfigListener
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1713)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1558)
at org.apache.catalina.core.DefaultInstanceManager.loadClass(DefaultInstanceManager.java:527)
at org.apache.catalina.core.DefaultInstanceManager.loadClassMaybePrivileged(DefaultInstanceManager.java:509)
at org.apache.catalina.core.DefaultInstanceManager.newInstance(DefaultInstanceManager.java:137)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4823)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5381)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:722)

두가지 경우 모두 maven dependency를 등록해주면 된다.

프로젝트 >> properties >> Deployment Assembly를 선택한 후,
"Add" >> Java Build Path Entries >> Maven Dependencies 선택한 후, "Apply"를 해준다.

톰캣 재시작하면 서버가 정상적으로 실행된다.

메이븐 메뉴에서 "Update Project Configuration"를 실행하는 경우,
해당 정보가 삭제되므로 다시 등록해주면 된다.

출처 : http://egloos.zum.com/dochi575/v/4691074


'JAVA > Spring' 카테고리의 다른 글

RequestMapping 핸들러 매핑 정의  (0) 2016.06.08
Spring xml 태그 설명  (0) 2016.06.07
Spring log4j SQL 직관적으로 보기  (0) 2016.06.02
커넥션 풀 이용시 DB 커넥션 에러  (0) 2015.10.05
[Spring] MVC 예제  (0) 2014.11.18
반응형

Spring log4j SQL 직관적으로 보기


    SELECT

        *   

    FROM

        term_info   

    WHERE

        rst_seq = 0   

    order by

        term_cd asc  

 

이렇게 디버깅 하는 방법. 


log4sql.jar

 

1. 첨부 파일을 WEB-INF/lib 에 추가해서 라이브러리에 추가. 

 

2. db.properties

   기존 : driver=com.mysql.jdbc.Driver

   변경 : driver=core.log.jdbc.driver.MysqlDriver

 

3. log4j.properties

아래 내용 추가. 

 

log4j.logger.com.ibatis=DEBUG

log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=DEBUG

log4j.logger.com.ibatis.common.jdbc.ScriptRunner=DEBUG

log4j.logger.com.ibatis.common.jdbc.impl.SqlMapClientDelegate=DEBUG

 

log4j.logger.java.sql.Connection=DEBUG

log4j.logger.java.sql.Statement=DEBUG

log4j.logger.java.sql.PreparedStatement=DEBUG

log4j.logger.java.sql.ResultSet=DEBUG

+ Recent posts