Etc

[MyBatis] @Mapper는 언제 사용하는걸까?

coding-knowjam(코딩노잼) 2020. 12. 30.
728x90

안녕하세요 coding-knowjam입니다.

오늘은 @Mapper 어노테이션에 대해서 얘기해보겠습니다.

우리는 보통 Interface를 매퍼로 등록하기 위해 @Mapper 어노테이션을 사용합니다.

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface CodingNojamMapper {
	
	public String getTime();
	
	@Select("SELECT NOW()")
	public String getTime2();
}

 

등록된 매퍼를 사용하기 위해서는 보통 아래와 같이 매퍼를 스캔할 수 있게 추가 설정을 하게 됩니다. 

(공식 문서 참조 : https://mybatis.org/spring/ko/mappers.html#register)

 

  • XML 기반 설정 방식
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
  xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">

  <mybatis:scan base-package="org.mybatis.spring.sample.mapper" />

  <!-- ... -->

</beans>

 

  • Java 기반 설정 방식
@Configuration
@MapperScan("org.mybatis.spring.sample.mapper")
public class AppConfig {
  // ...
}

 

설정을 마치고 나면 매퍼 xml파일이나 메서드에 @Select 같은 어노테이션을 사용해서 쿼리를 작성하고 실행하게 되는데요

여기서 한 가지 의문점이 들었는데요

제가 Spring서적을 읽던 도중 매퍼 작성 예제를 보는데 @Mapper 어노테이션을 Interface에 붙이지 않아도 매퍼로 등록되고 테스트 코드에서 정상적으로 실행이 되더라고요

내가 뭔가 잘못 알고 있는 건가 싶어서 다른 분들이 정리해놓은 기술 블로그들을 이것저것 찾아보았는데 조금 이상했습니다.

저처럼 @Mapper를 붙인 글도 있고, 아닌 글도 있고, 어떤 분은 annotationClass 속성 값에 직접 만든 annotation을 할당해서 @Mapper의 역할을 대신 처리하도록 정리한 글도 있었습니다.

꽤나 많은 글들을 읽었는데 정확히 @Mapper를 언제 붙이고 언제는 안 붙여도 되는지에 대해 정리한 글은 없더라고요.

그냥 대부분의 글이 @Mapper를 붙이면 매퍼로 등록이 된다는 정도의 설명만 있었습니다.

그래서 제가 직접 공식문서를 살펴가면서 알아보았고 그 내용을 정리해보겠습니다.

 

 

1. @Mapper API Document

우선 @Mapper의 API 문서부터 보겠습니다. 

(API 문서 : mybatis.org/mybatis-3/ko/apidocs/index.html)

Mapper Annotation

API문서에서 보면 Mapper의 코드는 단순히 meta annotation이 붙어있고 안에 내용은 딱히 작성된 게 없고, MyBatis의 mappers를 위한 marker interface로 사용한다고 설명이 되어있습니다.

설명을 보면 @Mapper는 매퍼 등록을 위한 어노테이션으로 사용하는 것은 맞는 것 같습니다.

 

 

2. @MapperScan & <mybatis:scan/>

다음으로 mybatis-spring공식문서를 살펴보았습니다.

(공식문서 : https://mybatis.org/spring/ko/mappers.html#register)

매퍼를 1개씩 등록하는 것이 아니라 여러 개의 매퍼를 사용하기 위해서는 매퍼 스캔을 해야 하며 방법은 다음과 같이 3가지가 있습니다.

  1. <mybatis:scan/> 엘리먼트 사용 (XML 기반 설정 방식)
  2. @MapperScan 어노테이션 사용 (Java 기반 설정 방식)
  3. 스프링 XML 파일을 사용해서 MapperSacnnerConfiguer를 bean으로 등록 (XML 기반 설정 방식)

공식문서에서 <mybatis : scan/> 관련된 설명을 보겠습니다.

base-package 속성은 매퍼 인터페이스 파일이 있는 가장 상위 패키지를 지정하면 된다. 세미콜론이나 콤마를 구분자로 사용해서 한 개 이상의 패키지를 세팅할 수 있다. 매퍼는 지정된 패키지에서 재귀적으로 하위 패키지를 모두 검색할 것이다.
<mybatis:scan/>이 자동으로 주입할 수 있는 MapperFactoryBean를 생성하기 때문에 SqlSessionFactory 나 SqlSessionTemplate를 명시할 필요가 없다. 하지만 한 개 이상의 DataSource를 사용한다면 자동 주입이 생각한 데로 동작하지 않을 수도 있다. 이 경우 사용할 빈 이름을 지정하기 위해 factory-ref 나 template-ref 속성을 사용할 수 있다.
<mybatis:scan/>은 마커(marker) 인터페이스나 애노테이션을 명시해서 생성되는 매퍼를 필터링할 수도 있다. annotation 프로퍼티는 검색할 애노테이션을 지정한다. marker-interface 프로퍼티는 검색할 상위 인터페이스를 지정한다. 이 두 개의 프로퍼티를 모두 지정하면, 매퍼는 두 조건을 모두 만족하는 인터페이스만을 추가한다. 디폴트로 이 두 가지 프로퍼티는 모두 null이다. 그래서 base-package프로퍼티에 설정된 패키지 아래 모든 인터페이스가 매퍼로 로드될 것이다.
발견된 매퍼는 자동 검색된 컴포넌트를 위한 스프링의 디폴트 명명규칙 전략(see the Spring reference document(Core Technologies -Naming autodetected components-)을 사용해서 빈 이름이 명명된다. 빈 이름을 정하는 애노테이션이 없다면 매퍼의 이름에서 첫 글자를 소문자로 변환한 형태로 빈 이름을 사용할 것이다. @Component 나 JSR-330의 @Named 애노테이션이 있다면 애노테이션에 정의한 이름을 그대로 사용할 것이다. annotation 프로퍼티를 org.springframework.stereotype.Component, javax.inject.Named(자바 SE 1.6을 사용한다면) 또는 개발자가 스스로 작성한 애노테이션으로 세팅할 수 있다. 그러면 애노테이션은 마커와 이름을 제공하는 역할로 동작할 것이다.
중요 <context:component-scan/>가 매퍼를 검색해서 등록을 하지 못할 수도 있다. 매퍼는 인터페이스고 스프링에 빈으로 등록하기 위해서는 각각의 인터페이스를 찾기 위해 스캐너가 MapperFactoryBean를 생성하는 방법을 알아야만 한다.

파란색으로 표시된 문장을 자세히 읽어보면 base-package에 지정된 패키지의 아래의 모든 인터페이스는 기본적으로 매퍼로 로드가 되며, 특정 인터페이스만을 매퍼로 등록하기 위해서는 annotation과 marker-interface속성을 사용하라고 안내해주고 있습니다.

 

공식문서의 내용을 읽어보면 의문점에 대해서 바로 해결이 가능합니다.

base-package속성에 패키지만 지정해주면 하위에 존재하는 인터페이스들은 @Mapper를 붙여주지 않아도 자동으로 매퍼로 추가가 되는 것입니다.

또한 공식문서는 annotation속성을 사용해서 매퍼로 사용할 인터페이스를 지정해 줄 수 있다고 합니다.

즉 @Mapper를 사용해서 특정 인터페이스만 매퍼로 등록이 가능한 것입니다.

@MapperSacn은 <mybats : scan/>과 동일하게 동작하며 annotationClass속성을 사용해서 특정 인터페이스만 매퍼로 등록을 해줄 수 있습니다.

의문점이 해결되었으니 어떻게 설정하는지 알아보겠습니다.

 

2.1 XML 기반 설정 방식

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
  xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">


	<!-- mybatis:scan 사용 -->
  <mybatis:scan base-package="com.nojam.coding.mapper" 
                annotation="org.apache.ibatis.annotations.Mapper" /> 
    
    <!-- MapperScannerConfigurer 사용 -->
  <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
     <property name="basePackage" value="com.nojam.coding.mapper" />
     <property name="annotationClass" value="org.apache.ibatis.annotations.Mapper" />
  </bean>
		
	
  <!-- ... -->

</beans>

XML 기반 설정 방식은 위와 같이 사용합니다.

<mybatis : scan />을 사용할 경우 annotation프로퍼티를 사용합니다.

MapperScannerConfigurer를 bean으로 등록하여 설정하는 경우 annotationClass프로퍼티를 사용합니다.

해당 프로퍼티에 @Mapper 어노테이션의 FQCN(Fully Qualified Class Name)을 지정해줍니다.

이제 base-package에 지정된 패키지에 속한 인터페이스 중 @Mapper 어노테이션이 붙은 인터페이스만 매퍼로 로드가 됩니다.

 

2.2 Java 기반 설정 방식

@Configuration
@MapperScan(basePackages = {"com.nojam.coding.mapper"}, 
            annotationClass = org.apache.ibatis.annotations.Mapper.class)
public class AppConfig {
  // ...
}

Java 기반 설정 방식은 annotationClass프로퍼티를 사용합니다.

위와 같이 해당 프로퍼티에 @Mapper 어노테이션의 FQCN(Fully Qualified Class Name)을 지정해줍니다.

이제 base-package에 지정된 패키지에 속한 인터페이스 중 @Mapper 어노테이션이 붙은 인터페이스만 매퍼로 로드가 됩니다.

 

2.3 참고사항

@Mapper 어노테이션이 추가된 것은 MyBatis 3.4.0 버전부터입니다. (2016년 4월쯤입니다.)

(깃허브 이슈 내용 : https://github.com/mybatis/mybatis-3/issues/629)

그 이전에는 아래와 같이 직접 어노테이션을 생성해서 프로퍼티에 값을 지정해서 사용했습니다.

package com.nojam.coding;

public @interface MyMapper {

}

 

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
  xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">


   <!-- mybatis:scan 사용 -->
  <mybatis:scan base-package="com.nojam.coding.mapper" 
                annotation="com.nojam.coding.MyMapper" /> 
    
   <!-- MapperScannerConfigurer 사용 -->
  <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
     <property name="basePackage" value="com.nojam.coding.mapper" />
     <property name="annotationClass" value="com.nojam.coding.MyMapper" />
  </bean>
		
	
  <!-- ... -->

</beans>

 

@Configuration
@MapperScan(basePackages = {"com.nojam.coding.mapper"}, 
            annotationClass = com.nojam.coding.MyMapper.class)
public class AppConfig {
  // ...
}

3.4.0 버전 이후에 @Mapper 어노테이션이 추가되긴 했지만 굳이 해당 어노테이션을 사용하지 않고 위와 같이 직접 어노테이션을 생성해서 사용하셔도 됩니다.

 

 

3. 정리

  • base-package / basePackages 프로퍼티만 사용할 때는 @Mapper 어노테이션을 사용하지 않아도 자동으로 패키지 하위의 인터페이스는 매퍼로 등록된다.
  • 특정 인터페이스만 매퍼로 사용하기 위해서는 annotation / annotationClass 프로퍼티를 사용한다.
  • @Mapper 어노테이션 말고 직접 생성한 어노테이션을 사용해도 된다.

728x90

댓글