Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Mybatis와 Mybatis-Spring를 입문하는 사람들을 위해 제작한 튜토리얼입니다.

NotificationsYou must be signed in to change notification settings

seongbin9786/mybatis-and-mybatis-spring-tutorial

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 

Repository files navigation

이 문서는 Mybatis를 Spring에서 사용하는 방법을 자세히 다룹니다. 실제로 사용하는 입장에서 즉각적으로 도움이 되기 위해서 설정, Mapper 정의, 생산성 향상 부분 등의 목차와 링크를 제공합니다. 이 문서의 모든 부분을 제대로 익힌다면 코딩의 즐거움과 편안함을 충분히 누릴 수 있을 것이라고 생각합니다 😄

의존성

MybatisMybatis-Spring 라이브러리를 사용합니다.

공식 문서 참고

한글 문서는 영문 독해가 어느 정도 되신다면 추천하지 않습니다. 최신 버전의 반영이 되지 않았기 때문입니다. 그러나 한글 문서더라도 참고하지 않는 것보다 100배 정도는 좋습니다. 이 문서는 설정 부분을 제외한 모든 부분을 영문 문서와 Mybatis github repository의 issue에 있는 QnA를 참고하여 만들었습니다.

Mybatis 3 한글문서

Mybatis-Spring 한글문서

사용 가능한 플러그인/라이브러리

Mybaptise - 이클립스 Mybatis 플러그인

Mybatis Generator - 라이브러리

목차

Mybatis란

Mybatis는 질의 후 수행해야 하는 객체 매핑을 대신 수행해줌으로써 프로그래머가 SQL 작성에만 신경쓸 수 있게 하는 Persistance Framework이다.Primitive(or Wrapper)Type,Map,POJO를 매핑할 수 있고, 일부 설정은 (Spring을 사용하지 않아도) 어노테이션을 사용할 수 있다.

Mybatis 설치

  • 최신버전: 3.4.6 (2018 3월 12일 배포)
<dependency>  <groupId>org.mybatis</groupId>  <artifactId>mybatis</artifactId>  <version>3.4.6</version></dependency>

Mybatis의 설정

Mybatis자체의 설정은 XML 설정만을 지원한다. 이번 챕터는 Mybatis의 설정만을 다룬다.

기본 XML 정의

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

기본이 되는 XML 설정 예시

  • Mybatis의 XML 설정 옵션은 정말 많지만 이번에는 기본적이고 필수적인 설정만 다룬다. 아래 설정만 익혀도 Mybatis의 사용에는 문제가 없다.

  • Mybatis-Spring은 Bean으로 요소들을 등록하기 때문에 아래 방식으로 environment, dataSource,transactionManager를 사용할 수 없다. (무시된다. 공식 문서 참고) Spring과의 연동을 원하는 경우 참고만 하고 넘어가면 된다.

    <?xml version="1.0" encoding="UTF-8" ?><!DOCTYPEconfigurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><!-- 어떤 environment를 사용할지 기본값 설정-->    <environmentsdefault="development"><!-- environment 의 정의 - 'development'-->        <environmentid="development"><!-- transactionManager 정의-->        <transactionManagertype="JDBC"/><!-- dataSource 정의-->        <dataSourcetype="POOLED">            <propertyname="driver"value="${driver}"/>            <propertyname="url"value="${url}"/>            <propertyname="username"value="${username}"/>            <propertyname="password"value="${password}"/>        </dataSource>        </environment>    </environments><!-- Mapper들을 등록할 태그-->    <mappers><!-- Mapper XML 등록--><!-- classpath 상대주소 방식-->        <mapperresource="org/mybatis/example/BlogMapper.xml"/>        <mapperresource="org/mybatis/builder/PostMapper.xml"/><!-- URL 접근 방식--><!-- /var 폴더 하에 있는 XML 파일에 접근-->        <mapperurl="file:///var/sqlmaps/AuthorMapper.xml"/><!-- Mapper 인터페이스 등록-->        <mapperclass="org.mybatis.builder.AuthorMapper" /><!-- 해당 Package 이하의 모든 인터페이스가 등록됨-->        <packagename="org.mybatis.builder" />    </mappers></configuration>
    • environtments는 환경을 구분하는 데 사용되는 Tag이다.environments 태그에선 반드시default 값이 필요하다.

    • environment는 각 환경을 정의하며,id를 필수적으로 정의해야 한다.

    • transcationManager는 Mybatis가 제공하는 TransactionManager를 설정하는 Tag로type을 필수적으로 지정해야 한다. Mybatis는type으로JDBCMANAGED를 지원한다.JDBC는 JDBC의 트랜잭션을 Mybatis가 처리하게 되고,MANAGED는 Container에게 맡기고 (CMT(Container-Managed Transaction) connection을 close하는 것 이외에 아무것도 하지 않는다. -MANAGED 사용 시 공식 문서 참고

    • dataSource는 JDBC Connection 객체를 생성하는 방법을 정의하는type을 필수적으로 정의해야 한다.POOLEDUNPOOLED,JNDI가 올 수 있다.UNPOOLED,JNDI 사용 시 공식문서를 참고하기 바란다.POOLED의 경우 pooling을 수행하는 것으로, 옵션이 굉장히 많다.

      • poolMaximumActiveConnections = 10 – 주어진 시간에 존재할 수 있는 활성화된(사용중인) 커넥션의 수.

      • poolMaximumIdleConnections – 주어진 시간에 존재할 수 있는 유휴 커넥션의 수

      • poolMaximumCheckoutTime = 20000(ms, 20초) – 강제로 리턴되기 전에 풀에서 “체크아웃” 될 수 있는 커넥션의 시간.

      • poolTimeToWait = 20000(ms, 20초) – 풀이 로그 상태를 출력하고 비정상적으로 긴 경우 커넥션을 다시 얻을려고 시도하는 로우 레벨 설정.

      • poolPingQuery – 커넥션이 작업하기 좋은 상태이고 요청을 받아서 처리할 준비가 되었는지 체크하기 위해 데이터베이스에 던지는 핑쿼리(Ping Query). 디폴트는 “핑 쿼리가 없음” 이다. 이 설정은 대부분의 데이터베이스로 하여금 에러메시지를 보게 할수도 있다.

      • poolPingEnabled = false – 핑쿼리를 사용할지 말지를 결정. 사용한다면, 오류가 없는(그리고 빠른) SQL을 사용하여 -poolPingQuery 프로퍼티를 설정해야 한다.

      • poolPingConnectionsNotUsedFor = 0poolPingQuery가 얼마나 자주 사용될지 설정한다. 필요이상의 핑을 피하기 위해 데이터베이스의 타임아웃 값과 같을 수 있다. 디폴트 값은poolPingEnabledtrue일 경우에만, 모든 커넥션이 매번 핑을 던지는 값이다.

    • mappers는 Mapper를 감싸는 Tag 이다.

    • mapperresource값을 필수적으로 지정해야 한다. 해당 값은 XML의 위치를 나타낸다. classpath 상대주소로 XML에 접근하거나, URL에 접근하는 방식, Mapper 인터페이스를 명시하는 방식, 패키지 명시 방식이 있다.

Mybatis-Spring이란

Mybatis-Spring은 Mybatis를 Spring에 매끈하게 통합하는 라이브러리입니다. 이 라이브러리는 Mybatis가 Spring Transanction에 참여할 수 있게하고, Mybatis Mapper과 SqlSession 객체의 생성과 이들을 다른 객체에 주입하는 일, Mybatis의 Exception을 Spring의 Exception으로 변환하는 등의 일을 수행합니다.

Mybatis-spring 설치

<dependency>    <groupId>org.mybatis</groupId>    <artifactId>mybatis-spring</artifactId>    <version>1.3.2</version></dependency>

Mybatis-Spring의 설정 방법

Spring 프레임워크 상에서 Mybatis를 사용하려면, Spring Application Context에SqlSessionFactory 빈과 최소 하나의Mapper 인터페이스가 존재해야합니다. (Mapper 인터페이스는 Annotation, XML 방식 어느 것을 사용하더라도 존재해야 함.)

  1. SqlSessionFactory 빈 등록

    Mybatis에선sqlSessionFactory 객체를SqlSessionFactoryBuilder 객체로 생성하지만, Mybatis-Spring에서는SqlSessionFactory 객체를 Spring의 FactoryBean인SqlSessionFactoryBean 객체를 Bean으로 등록하게 되므로 Factory의 빈은 아래와 같이 XML 설정을 해야 합니다. (참고: FactoryBean은getObject() 메소드로 반환한 객체가 Spring에서 빈으로 최종 생성됨)

    <!-- class를 SqlSessionFactoryBean을 사용함--><beanid="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean"><!-- sqlSessionFactory에 dataSource 주입-->    <propertyname="dataSource"ref="dataSource" /><!-- Mybatis XML config 파일이 전달될 수 있음--><!-- 이 때 config 파일 내용은 완전한 Mybatis 설정일 필요가 없음--><!-- 오히려, environtment, dataSource, transactionManager는 무시됨--><!-- 해당 config에선 settings, typeAliases, mappers 등을 정의할 수 있음-->    <propertyname="configLocation"value="..." /><!-- Mybatis XML mapper 파일들의 경로를 지정함 (Ant 패턴 사용)--><!-- 이 방식을 사용하면, SqlSessionTemplate을 사용해서 실행하게 된다.--><!-- 이 방식을 사용하는 경우, Mapper를 직접 @Autowired 받을 수 없다.-->    <propertyname="mapperLocations"value="classpath*:sample/cfg/mappers/**/*.xml" /></bean><!-- 1.3.0 버전부터 configuration 프로퍼티가 추가되었음.--><!-- Configuration 객체를 지정하는 것으로, Mybatis Config 파일을 대체할 수도 있음.--><beanid="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean">    <propertyname="dataSource"ref="dataSource" />    <propertyname="configuration"><!-- Configuration 구현체 등록-->        <beanclass="org.apache.ibatis.session.Configuration">            <propertyname="mapUnderscoreToCamelCase"value="true"/>        </bean>    </property></bean>
  2. Mapper 빈 등록

    • Mybatis-spring 라이브러리 사용 시에는 직접 SqlSession을 사용하지 않고도, Mapper 객체를 Spring에서@Autowired로 의존성 주입을 받을 수 있다. (이 객체는org.apache.ibatis.binding.MapperProxy 프록시 객체이다.)

    • 위에서 빈으로 등록한sqlSessionFactorymapperLocations으로 mapper XML 설정을 반드시 완료해야 한다.MapperProxy 객체에서sqlSession 객체에 대한 참조를 갖고,sqlSession 객체는configuration 객체에서 mapper 설정을 보유하기 때문이다.

    • 당연하게도 Mapper 객체를 주입받으려면, Spring Context에 빈으로 등록해야 한다. Mybatis-Spring에서는 한꺼번에 등록하는 방식을 지원한다.MapperFactoryBean을 통해서 1개씩 등록할 수도 있는데, 권장하진 않는다.

    • 한꺼번에 등록하기 위해, Scan 대상의 basePackage를 지정하게 된다. package 값에는 Ant 패턴을 사용할 수 있다.

    • 사용할 수 있는 옵션이 3개가 있다.

      • basePackage는 패키지 아래에 있는 모든 클래스를 등록하며, comma, semicolon으로 구분한다.

      • annotation은 해당 어노테이션이 붙어있는 클래스만 등록한다.

      • markerInterface는 해당 마커 인터페이스를 상속한 인터페이스만 등록한다. annotation 옵션과 동시에 사용가능하며 OR 조건으로 기능한다.

    1. XML방식:<mybatis:scan base-package="" /> 사용

      <beansxmlns="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-3.0.xsd    http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd"><!-- 실제 인터페이스가 존재하는 패키지이다. XML 설정 파일의 경로가 아니다.-->    <mybatis:scanbase-package="org.mybatis.spring.sample.mapper" /></beans>
    2. 어노테이션 방식:@MapperScan("") 사용

      @Configuration 어노테이션이 붙은 Config 목적의 클래스에 붙여야 한다.

      @Configuration@MapperScan("org.mybatis.spring.sample.mapper")publicclassAppConfig {// 이 Config 빈에선 주로 JavaConfig로 dataSource와 transactionManager를 정의한다.}
    3. MapperScannerConfiguerer 빈을 Spring Application Context에 등록해 사용

      <beanclass="org.mybatis.spring.mapper.MapperScannerConfigurer">    <propertyname="basePackage"value="org.mybatis.spring.sample.mapper" />    <propertyname="annotationClass"value="ex.annotation.Mapper"/>    <propertyname="markerInterface"value="ex.marker.Mapper"/></bean>
    4. Mapper를 하나씩 등록하는MapperFactoryBean 정의하는 방법

      <!-- 상속용으로 사용하기 위해 abstract=true로 설정했다.--><beanid="baseMapper"class="....MapperFactoryBean"abstract="true"lazy-init="true">    <propertyname="sqlSessionFactory"ref="sqlSessionFactory" /></bean><!-- Spring Bean 정의 시 parent 속성을 이용하여 속성을 상속받을 수 있다.--><!-- parent 속성 사용 시 property 모두를 상속받고 따라서 sqlSesionFactory를 주입받는다.--><!-- parent 속성 사용 시 class를 정의하지 않은 경우 parent의 class를 사용하게 된다.--><!-- class 속성 말고도, constructor, init-method, destroy-method도 마찬가지이다.--><!-- lazy-init, depends-on, autowire-mode, dependency-check, scope는 상속하지 않는다.--><beanid="oneMapper"parent="baseMapper">    <propertyname="mapperInterface"value="my.package.MyMapperInterface" /></bean><beanid="anotherMapper"parent="baseMapper">    <propertyname="mapperInterface"value="my.package.MyAnotherMapperInterface" /></bean>

Mybatis-Spring을 썼을 때의 장단점

공통으로 적용되는 내용

Mybatis-spring를 사용하더라도 동일한 작업을 해야 하는 것들에 대한 내용입니다. Spring에 비의존적인 부분을 다룹니다.

Mapper 정의하기

  • Mybatis는 SQL 작성과 리턴타입(직접 Mapping을 정의할 수도 있음. 명시하지 않을 시 자동 매핑), 캐시 여부만 지정한다면 질의 이후 결과 객체를 받는 것까지의 기능이 구현된다. Mapper를 정의하는 것은 XML과 어노테이션(그냥 Mybatis에서도 가능) 모두 사용할 수 있다.

  • XML로 Mapper를 정의하는 방법

    한 개의 매퍼 XML 파일에는 많은 수의 매핑 구문을 정의할 수 있다. 아래는org.mybatis.example.BlogMapper 네임스페이스에서 selectBlog라는 매핑 구문을 정의한 것이다. 아래 Select는org.mybatis.example.BlogMapper.selectBlog 형태로 실행할 수 있게 된다.

    <mappernamespace="org.mybatis.example.BlogMapper">    <selectid="selectBlog"resultType="Blog">        SELECT * FROM blog WHERE id = #{id}    </select></mapper>
  • 어노테이션으로 Mapper를 정의하는 방법

    인터페이스로 메소드를 정의하고, 각 메소드 위에 어노테이션으로 SQL을 정의하는 방법이다. (추가 설정 없음)

    packageorg.mybatis.example;publicinterfaceBlogMapper {@Select("SELECT * FROM blog WHERE id = #{id}")BlogselectBlog(intid);}
  • Mapper에 정의된 질의 실행 방법

    첫 번째 방법

    // select 예시이다.Blogblog = (Blog)session.selectOne("org.mybatis.example.BlogMapper.selectBlog",101);

    두 번째 방법

    BlogMappermapper =session.getMapper(BlogMapper.class);Blogblog =mapper.selectBlog(101);

    두 번째 방법은 Magic Constant(namespace 문자열)에 의존하지 않아 더 클린 코드이며리턴타입에 대해 타입 캐스팅을 하지 않는 장점을 가진다.

  • XML vs Annotation

    둘 중 특정 방식이 낫다고 할 순 없다. 매핑된 구문을 일관된 방식으로 정의하는 것이 중요하다. 어노테이션은 XML로 쉽게 변환할 수 있고 그 반대의 형태 역시 쉽게 처리할 수 있다.

Mapper에서 SQL을 정의하는 방법

간단한 POJO 객체를 CRUD 하는 방법

SELECT, INSERT, UPDATE, DELETE 태그
  • <select> 태그

    아래와 같은 XML 설정을int(or Integer)를 파라미터로 받아HashMap을 반환하는selectPerson 문이라고 한다. 이 때 key는 column명이고, value는 각 column에 대응되는 값이 된다. 당연하게도, hashmap이므로 해당 SELECT의 결과는 1 row 여야 한다. 객체 내에 Primitive(or Wrapper) Type만 있는 경우 아래 정도의 SELECT문을 사용하기만 해도, 클래스의 필드명과 결과 테이블의 컬럼명만 일치한다면 자동 매핑된다. (camelCase의 경우 이 곳 참고)

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

    위의 SELECT 쿼리문에서 사용된#{id}PreparedStatement를 사용하라고 지정하는 것이다. 즉, 아래와 같은 구현 코드가 Mybatis 내부적으로 사용되게 된다.

    // JDBC로 따지면 아래와 비슷한 코드일 것이다. (실제 구현 코드는 아님.)StringselectPerson ="SELECT * FROM person WHERE id=?";PreparedStatementps =conn.prepareStatement(selectPerson);ps.setInt(1,id);// ...
    • SELECT문은 이후sqlSession에서 호출하게 된다. 이 때 파라미터는 하나만 전달할 수 있는데, 그 때문에#{propName} 사용 시 전달한 타입의 클래스 여부에 따라 값이 바인딩되는 전략이 다르다.

      1. 클래스가 아닌 경우 - 값 그대로 사용 - 이 때propName은 아무 값이나 가능하다.
      2. 클래스의 경우 - 값를 객체 내에서 찾아 사용 -propName은 필드명과 동일해야 한다.
    • Parameter 설정 시 사용할 수 있는 기호는 2가지가 있다.$#이다.

      1. $는 SQL문에서 값을replace하는 방식으로 질의한다. 이 때 escape은 자동으로 처리되지 않아 SQL Injection에 대한 방어가 수행되지 않기 때문에 권장되는 옵션이 아니다.
      2. #은 PreparedStatement의 값으로 전달한다.

    <select>태그에 사용될 수 있는 속성은 아래와 같이 많다.

    <selectid="selectPerson"parameterType="int"parameterMap="deprecated"resultType="hashmap"resultMap="personResultMap"flushCache="false"useCache="true"timeout="10000"fetchSize="256"statementType="PREPARED"resultSetType="FORWARD_ONLY">

    id - 같은 namespace 내(ex:org.mybatis.example.BlogMapper)에서 해당 구문(<select> 등의 구문을 정의한 것)을 식별할 때 사용되는 문자열.

    parameterType,parameterMap - 사용하지 않는다.

    resultType - 패키지명까지 명시된 클래스명, 혹은 클래스에 대한 별칭(alias)을 값으로 사용한다. 해당 클래스로 SELECT문의 결과가자동으로 매핑되게 된다. 만약 1 row가 아닌 여러 row의 결과가 나오는 경우,<select>가 아닌<collection>을 사용해야 한다.

    resultMap -resultMap의 ID값을 지정하여 결괏값이 해당 resultMap에서 정의한대로 생성되게 한다. 복잡한 객체를 SELECT를 통해 만드는 경우, (특히 JOIN 사용 시) resultMap을 사용하는 것이 권장된다.

    flushCache,useCache - 캐시에 관련된 내용은 공식 문서 참고 바람.

    timeout,fetchSize,statementType,resultSetType,databaseId,resultSets,resultOrdered의 경우 자주 사용하지 않으므로 공식 문서 참고 바람.

  • <insert>,<update>,<delete> 태그

    INSERT,UPDATE는 서로 동일한 속성을,DELETESELECT보다도 다소 적은 속성을 갖는다. 아래는 XML로 각 구문을 정의하는 예제이다.

    <insertid="insertAuthor">    INSERT INTO author (id,username,password,email,bio)    VALUES (#{id},#{username},#{password},#{email},#{bio})</insert><updateid="updateAuthor">    UPDATE author SET        username = #{username},        password = #{password},        email = #{email},        bio = #{bio}    WHERE id = #{id}</update><deleteid="deleteAuthor">    DELETE FROM author WHERE id = #{id}</delete>

    이 태그들이 가질 수 있는 속성은 아래와 같다.id,parameterType,flushCache,statementType,timeout,databaseId 등의<select>와 동일하다. INSERT와 UPDATE에는useGeneratedKeys,keyProperty,keyColumn 등의 추가적인 속성이 있다. 전체적인 속성의 수는 SELECT보다 꽤 적다.

    <insertid="insertAuthor"parameterType="domain.blog.Author"flushCache="true"statementType="PREPARED"keyProperty=""keyColumn=""useGeneratedKeys=""timeout="20"><updateid="updateAuthor"parameterType="domain.blog.Author"flushCache="true"statementType="PREPARED"timeout="20"><deleteid="deleteAuthor"parameterType="domain.blog.Author"flushCache="true"statementType="PREPARED"timeout="20">

    useGeneratedKeys -INSERT,UPDATE에서만 사용할 수 있는 속성으로, JDBC의getGeneratedKeys 메소드를 사용하여 Database(My or MSSQL)에서 생성된 key를 가져와 사용한다.

    keyProperty -INSERT,UPDATE에서만 사용할 수 있는 속성으로,getGeneratedKeys 옵션 사용시 반환되는 Key 값을 value로 사용할 column명을 지정한다.

    keyColumn -INSERT,UPDATE에서만 사용할 수 있는 속성으로, id로 사용되는 column명을 지정하는 데 사용된다. PostgreSQL과 같은 일부 DBMS에서만 필요하다. 복합키인 경우 ,로 구분한다.

    INSERT를 여러번 해야 할 때, 아래와 같이<foreach> 태그를 사용할 수 있다.

    <insertid="insertAuthor"useGeneratedKeys="true"keyProperty="id">    INSERT INTO author (username, password, email, bio) VALUES    <foreachitem="item"collection="list"separator=",">        (#{item.username}, #{item.password}, #{item.email}, #{item.bio})    </foreach></insert>

    <selectKey> -<insert>내에서 사용하는 태그로, Key를 데이터베이스에서 조회하여 가져와야 할 경우 사용한다. 공식 문서를 참고바란다.

  • 3.2.4 버전부터의parameterType의 생략에 대하여

    Mybatis 3.2.4 버전부터 parameter를 자동으로 추론하기 때문에 해당 속성이 무시된다. (한글 사용자 가이드에서는 소개돼있지 않음. 영문 가이드 참고.)

SELECT 시 각 필드를 매핑하는 데 사용되는 태그
  • <id>,<result> 태그

    이 태그들은 하나의단순 타입 (Date, String, int, ... 등) 속성을 객체의 속성으로 매핑한다.idresult의 차이는 두 객체를 비교할 때id로 선언된 속성이 사용된다는 점이다.id 속성을 명시함으로써 캐시와 JOIN 성능이 향상된다고 한다.

    property는 자바 객체의 필드명을 의미한다.

    column은 결과 테이블의 속성명을 의미한다.

    javaType은 생략해도 되지만, HashMap일 경우 명시해야 한다.

  • <constructor> 태그

    어떤 생성자를 선택할지 표현하는 태그이다. 아래의 XML 설정 예제를 참고하라.

    <constructor>    <idArgcolumn="id"javaType="int"/>    <argcolumn="username"javaType="String"/>    <argcolumn="age"javaType="_int"/></constructor>

    위 XML 설정에 의해 아래의 생성자가 선택된다.

    publicclassUser {// 해당 생성자가 선택됨publicUser(Integerid,Stringusername,intage) {//...    }//...}

    생성자에 많은 매개변수가 전달되는 경우, 생성자 선택 측면에서 오류 발생 가능성이 높기 때문에, 3.4.3 버전부터는 각 매개변수 변수의 이름을 명시하면 매개변수 순서를 무시할 수 있다. 단 이 때는 각 매개변수에@Param 어노테이션을 붙이거나 compile 옵션에-parameters를 추가해야 한다.

좀 더 생산성을 높이는 방법

  • 재사용 가능한 SQL 코드 조각을 정의 -<sql> 태그 사용

    <!-- User 객체를 매핑하는 SQL--><!-- alias라는 매개변수를 <property> 태그로 전달받는다.--><sqlid="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql><!-- User 객체를 실제로 사용하는 SELECT문--><!-- 2명의 유저 객체를 조회한다--><selectid="selectUsers"resultType="map">    SELECT<!-- <sql>을 사용할 때는 <include refid="id">와 같이 사용한다.--><!-- <property>로 "alias"의 value로 t1, t2를 각각 전달한다.-->        <includerefid="userColumns"><propertyname="alias"value="t1"/></include>,        <includerefid="userColumns"><propertyname="alias"value="t2"/></include>    FROM some_table t1    CROSS JOIN some_table t2</select>
  • 타이핑을 줄이는 방법 -typeAlias 정의

    <!-- Mybatis XML 설정파일에서--><typeAliases>    <typeAliastype="com.someapp.model.User"alias="User"/></typeAliases><!-- Mapper XML파일에서--><selectid="selectUsers"resultType="User">    SELECT id, username, hashedPassword    FROM some_table    WHERE id = #{id}</select>

    적용 전/후

    <!-- 적용 전: Fully Qualified Name를 사용한다.--><selectid="selectUsers"resultType="com.someapp.model.User" ... /><!-- 적용 후: 별칭을 사용한다.--><selectid="selectUsers"resultType="User" ... />
  • 자동 매핑 설정

    Mybatis에서 resultMap을 명시하지 않아도 resultType을 명시하면 해당 객체의 속성을 참고하여 필드와 속성명이 같은 필드에 대해 자동으로 값을 전달하게 된다.이 때lowercase with underscore -> camelCase를 감지하여 매핑되기하려면,mapUnderscoreToCamelCase 속성을true로 지정하면 된다. (기본으로 적용되지 않으므로 명시적으로 지정해야 함!)

    참고로 자동 매핑은 resultMap을 정의했을 때에도 작동하는데,<result> 등의 태그로 매핑되지 않은 속성 중 필드와 일치하는 컬럼의 경우 매핑된다. 이는auto-mapping=PARTIAL(이게 기본값이다.)의 특징인데,NONE의 경우 아예 자동 매핑을 사용하지 못하니 문제이고,FULL의 경우, JOIN 등의 경우에 필드 이름이 겹치는 경우 자동 매핑 때문에 잘못된 값을 매핑할 수 있어 주의하여 사용해야 하는 옵션이다. 더 나은 방법으로,<resultMap>autoMapping="false" 속성을 주는 것이 낫다.

매핑 객체가1-depth POJO가 아닌 경우

  • <associaton> 태그

    1:1 관계의 객체를 불러올 때 사용한다. 동시에 (이후 나오는)resultMap이다.

    property 속성으로 대응하는 필드명을 받고,javaType 속성으로 타입을 받는다(생략가능).

    columnPrefix 속성으로 prefix 값을 명시할 수 있다. 결과 테이블에서 필드로 매핑 시 해당 값이 붙어있는 속성을 사용하게 된다. 주로 JOIN 시 속성명이 곂치고, 따로 resultMap을 정의해서 재사용할 때 사용하게 된다. 권장하는 옵션이다.

    아래는columnPrefix 사용 시의 SQL 예시이다.

    <select id="selectBlog" resultMap="blogResult">SELECTB.idAS blog_id,B.titleAS blog_title,B.author_idAS blog_author_id,P.idAS post_id,P.subjectAS post_subject,P.bodyAS post_body,FROM Blog BLEFT OUTER JOIN Post PONB.id=P.blog_idWHEREB.id=#{id}</select>

    아래는 위의 SQL에 대응하는 resultMap 정의이다.

    <resultMapid="blogResult"type="Blog">    <idproperty="id"column="blog_id" />    <resultproperty="title"column="blog_title"/>    <collectionproperty="posts"ofType="Post"resultMap="blogPostResult"columnPrefix="post_"/></resultMap><resultMapid="blogPostResult"type="Post">    <idproperty="id"column="id"/>    <resultproperty="subject"column="subject"/>    <resultproperty="body"column="body"/></resultMap>

    resultMap 속성으로 이미 정의된 resultMap을 참조할 수도 있다.

    fetchType이라는 속성을 명시하여lazy,eager 로딩 방식을 선택할 수 있다. 해당 속성에 넘겨진 값은 전역 속성인lazyLoadingEnabled보다 우선한다.lazy 방식 선택시에 ORM에서 겪게 되는OSIV 이슈가 없다 😄

    select 속성으로 정의된<select>를 재활용할 수 있다. 만약 key가 다중 컬럼인 경우column="{prop1=col1, prop2=col2}"와 같이 명시할 수 있다. 해당 속성 사용 시 객체 매핑 시에 Select가 각각 실행된다.

    <!-- association의 select 속성 사용 예시--><resultMapid="blogResult"type="Blog">    <associationproperty="author"column="author_id"javaType="Author"select="selectAuthor"/></resultMap><!-- association 대상 객체를 갖는 Blog 객체의 SELECT 문--><selectid="selectBlog"resultMap="blogResult">    SELECT * FROM BLOG WHERE ID = #{id}</select><!-- association 대상 객체의 SELECT 문--><selectid="selectAuthor"resultType="Author">    SELECT * FROM AUTHOR WHERE ID = #{id}</select>

    notNullColumn 속성은 값으로 전달된 속성이(여러 개라면 ,를 사용)null인 경우 대상 객체를 생성하지 않는다.

  • <collection> 태그

    collection 태그는 association 태그와 거의 동일한 동작과 속성을 가진다.

    collection 사용 시ofType으로 타입을 명시해야한다.

    아래는select 쿼리를 명시한 collection 예시이다. (미사용 시는 association과 너무 동일하여 기재하지 않음.)

    <!-- javaType 속성은 추론이 가능하므로 필요 없다. 명시 하지말 것!--><collectionproperty="posts"javaType="ArrayList"column="id"ofType="Post"select="selectPostsForBlog"/>

    <collection> 사용 시select 속성을 사용하면 SQL이 굉장히 간단해지는 대신, 1:N인 관계를 표현하는 이상, N개의 대상이 존재하게 되며, (예:List<Author> authors) 조회가 각각의 N번 더 실행되는1+N(혹은 N+1) 문제를 겪게 된다. 따라서collection의 경우는 JOIN을 사용하는 것을 추천한다. (association의 경우는select 옵션을 사용하더라도 최대 1회만 더 호출되므로 덜 영향을 받는다.)

  • <discriminator> 태그

    설명은 생략한다.

    아래는 예시이다.

    <!-- 예시 1--><discriminatorjavaType="int"column="draft">    <casevalue="1"resultType="DraftPost"/></discriminator><!-- 예시 2--><!-- Vehicle 객체를 조회하는 SELECT--><!-- discriminator의 case를 모두 만족하지 않는 경우 Vehicle 객체가 반환된다.--><!-- vehicle_type 값에 따라 discriminator가 다른 resultMap을 선택하게 된다.--><!-- 다른 resultMap이 선택된 경우 Vehicle 필드의 매핑은 모두 취소된다.--><resultMapid="vehicleResult"type="Vehicle">    <idproperty="id"column="id" />    <resultproperty="vin"column="vin"/>    <resultproperty="year"column="year"/>    <resultproperty="make"column="make"/>    <resultproperty="model"column="model"/>    <resultproperty="color"column="color"/>    <discriminatorjavaType="int"column="vehicle_type">        <casevalue="1"resultMap="carResult"/>        <casevalue="2"resultMap="truckResult"/>        <casevalue="3"resultMap="vanResult"/>        <casevalue="4"resultMap="suvResult"/>    </discriminator></resultMap><!-- discriminator에 의해 결정된 타입의 resultMap 정의--><!-- Car를 반환하는 경우, doorCount 필드 외에 다른 필드는 매핑되지 않는다.--><resultMapid="carResult"type="Car">    <resultproperty="doorCount"column="door_count" /></resultMap><!-- 만약 Car가 선택되더라도 Vehicle의 필드까지 매핑되게 하려면 아래와 같이 extends 옵션으로 resultMap을 상속해야 한다.--><resultMapid="carResult"type="Car"extends="vehicleResult">    <resultproperty="doorCount"column="door_count" /></resultMap><!-- 위와 같이 resultMap을 선언하기에 정말 적은 수의 추가 속성만 갖는 경우에는 resultType을 사용하면 되는데, 이게 가능한 이유는 resultType을 명시하는 경우 resultMap이 자동 생성되어 모든 필드에 대응되기 때문이다. (참고: 기본 값이 자동 생성이다.)-->```xml<resultMapid="vehicleResult"type="Vehicle">    <idproperty="id"column="id" />    <resultproperty="vin"column="vin"/>    <resultproperty="year"column="year"/>    <resultproperty="make"column="make"/>    <resultproperty="model"column="model"/>    <resultproperty="color"column="color"/>    <discriminatorjavaType="int"column="vehicle_type">        <casevalue="1"resultType="carResult">            <resultproperty="doorCount"column="door_count" />        </case>        <casevalue="2"resultType="truckResult">            <resultproperty="boxSize"column="box_size" />            <resultproperty="extendedCab"column="extended_cab" />        </case>            <casevalue="3"resultType="vanResult">        <resultproperty="powerSlidingDoor"column="power_sliding_door" />        </case>        <casevalue="4"resultType="suvResult">            <resultproperty="allWheelDrive"column="all_wheel_drive" />        </case>    </discriminator></resultMap>
  • <resultMap> 정의 및 사용

    ResultMapResultSet을 Mapping하는 방법을 정의한 것이다.
    놀랍게도(?)ResultMapresultType으로 등록된 객체를 기준으로 자동 생성된다.
    그러나 이 좋은 기능을 걷어차버리고 프로그래머가 직접 정의할 수도 있다.
    직접 정의하는 경우resultMap 속성을 사용하게 된다.

    <resultMapid="userResultMap"type="User">    <idproperty="id"column="user_id" />    <resultproperty="username"column="username"/>    <resultproperty="password"column="password"/></resultMap>

    그러나 이 정도로 간단한 객체는 자동 생성에 당연히 맡길 것이다. 따라서 실제로는 JOIN 등의 사용 시에나 resultMap 정의하게 된다. 아래는 그 예시이다.

    <!-- resultMap 정의: Blog 객체에 매핑--><resultMapid="blogResultMap"type="Blog"><!-- 호출할 constructor 및 매개변수 명시-->    <constructor><!-- 아래 2가지 방법으로 정의할 수 있다.--><!-- id 속성의 경우 'idArg' 태그를 사용하면 전체 성능을 향상시킬 수 있다고 한다-->        <argcolumn="blog_id"javaType="int" />        <idArgcolumn="blog_id"javaType="int"/>    </constructor><!-- result 태그는 필드를 의미한다.--><!-- property = POJO의 속성명, column = SELECT된 결과의 속성명-->    <resultproperty="title"column="blog_title" /><!-- association 태그는 1:1 관계를 표현한다.--><!-- association 태그는 중첩된 ResultMap이다.--><!-- 이미 정의된 ResultMap의 ID를 참조시킬 수도 있다.--><!-- ResultMap이므로 동일한 태그를 사용하게 된다.--><!-- 중첩된 객체를 처리하는 데 사용된다.-->    <associationproperty="author"javaType="Author"><!-- id 태그는 result와 동일하나 ID임을 명시한다. 캐시와 JOIN 시 성능 향상이 있다고 한다.-->        <idproperty="id"column="author_id" />        <resultproperty="username"column="author_username" />        <resultproperty="password"column="author_password" />        <resultproperty="email"column="author_email" />        <resultproperty="bio"column="author_bio" />        <resultproperty="favouriteSection"column="author_favourite_section" />    </association><!-- collection 태그는 1:N 관계를 표현한다.--><!-- collection 태그는 Collection을 의미한다.--><!-- collection 태그 또한 중첩된 ResultMap이다.--><!-- 마찬가지로 이미 정의된 ResultMap의 ID를 참조시킬 수 있다.-->    <collectionproperty="posts"ofType="Post">        <idproperty="id"column="post_id" />        <resultproperty="subject"column="post_subject" /><!-- 두 번째 사용 시엔 그냥 막 써도 되는건가?-->        <associationproperty="author"javaType="Author" />        <collectionproperty="comments"ofType="Comment">            <idproperty="id"column="comment_id" />        </collection>        <collectionproperty="tags"ofType="Tag" >            <idproperty="id"column="tag_id" />        </collection><!-- column의 값을 읽어서 값에 따라 사용할 resultMap(Type)을 달리한다.--><!-- 값에 따라 다른 객체를 선택할 수 있도록 case 문을 사용할 수 있다.--><!-- 아래는 draft=1일 때 resultType을 DraftPost로 사용한다는 얘기이다.-->        <discriminatorjavaType="int"column="draft">            <casevalue="1"resultType="DraftPost" />        </discriminator>    </collection></resultMap>

    아래는 Association Tag에서 resultMap의 ID를 사용하는 방법이다.

    /* 예제로 사용되는 SELECT문*/SELECTB.idAS blog_id,B.titleAS blog_title,B.author_idAS blog_author_id,A.idAS author_id,A.usernameAS author_username,A.passwordAS author_password,A.emailAS author_email,A.bioAS author_bioFROM Blog BLEFT OUTER JOIN Author AONB.author_id=A.idWHEREB.id=#{id}
    <!-- author 속성에 대응하는 resultMap 사용--><resultMapid="blogResult"type="Blog">    <idproperty="id"column="blog_id" />    <resultproperty="title"column="blog_title"/>    <associationproperty="author"resultMap="authorResultMap" /></resultMap><!-- Author 객체에 대응되는 resultMap 정의--><resultMapid="authorResultMap"type="Author">    <idproperty="id"column="author_id"/>    <resultproperty="username"column="author_username"/>    <resultproperty="password"column="author_password"/>    <resultproperty="email"column="author_email"/>    <resultproperty="bio"column="author_bio"/></resultMap>

Mybatis 수준에서 캐시하는 방법

Mybatis의 동적 SQL: 조건문

Mybatis에서 사용되는 객체에 대하여

Mybatis 처음부터 끝까지 직접 설정하기

About

Mybatis와 Mybatis-Spring를 입문하는 사람들을 위해 제작한 튜토리얼입니다.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp