프로그래밍/Java

[Java DB] 2-1. MyBatis - SQL 쿼리를 재사용하자 (개념편)

Churnobyl 2024. 6. 12. 19:42
728x90
반응형

 

Common Java Web Application

 


1. What is MyBatis

MyBatis 로고

 

 이전 글에서 자바와 DB가 상호작용할 수 있는 밑거름인 JDBC에 대해 알아보았다. JDBC는 DB와 가장 단순하게 연결하는 원시적인 방법이기 때문에 속도적인 측면에서 유리하나 (1) DB 접근이 필요한 코드마다 비슷한 SQL문을 반복해서 적어야 한다는 큰 단점이 존재한다. 또한 (2) SQL문이 자바 코드 안에 포함되기 때문에 유지 보수적인 측면에서도 불리하다. 쉽게 말해 휴먼 에러가 발생할 가능성이 높다. 추가적으로 (3) JDBC에서 SELECT문의 결과는 ResultSet으로 리턴되며 우리가 원하는 객체로 사용하기 위해서는 한번 더 처리가 필요하다는 불편함도 있다.

 

MyBatis를 사용하면 반복적인 SQL쿼리문을 상당량 줄일 수 있다.

 

 

 MyBatis는 이러한 단점들을 커버할 수 있는 강력한 SQL Mapper 라이브러리다. MyBatis는 반복적인 SQL 쿼리문을 xml파일로 분리하고 인터페이스와 매핑해 필요한 코드에 재사용할 수 있도록 한다. 또한 DB와 상호작용의 결과를 ResultSet이 아닌 지정한 객체나 컬렉션으로 리턴해주는 엄청난 기능도 있다.

 

 그럼 이제 MyBatis에 대해서 알아보자.


2. MyBatis 이해하기

 우선 MyBatis 라이브러리를 프로젝트에 추가하는 방법은 여러가지가 있다. IDE와 함께 Maven이나 Gradle과 같은 의존성 관리 도구를 사용하면 간편하게 프로젝트에 추가할 수 있다.

 

Maven

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>

 

Gradle

implementation 'org.mybatis:mybatis:x.x.x'

 

하지만 이번 글의 목표는 MyBatis 자체를 이해하는 것이므로 가장 원시적인 형태로 돌아가서 mybatis.jar파일을 프로젝트에 추가하는 방식을 사용하려고 한다. 추가적으로 데이터베이스는 H2를 사용할 것이므로 H2 데이터베이스의 JDBC Driver도 함께 추가해준다.

 

프로젝트 구조

 

 다음과 같이 lib폴더에 h2-2.2.224.jar JDBC Drivermybatis-3.5.16.jar를 추가해 주었다. 이번 글에서는 위 프로젝트를 통해서 MyBatis를 알아볼 것이며 2편인 활용편에서 실제로 사용해볼 것이다.

 

 MyBatis를 뜯어보기 전에 한가지 짚고 넘어가야 할 게 있다. MyBatis는 xml파일에 SQL문을 저장하고 이를 Mapper나 DAO라는 이름의 자바 인터페이스와 매핑해 자바 애플리케이션에서 사용할 수 있도록 하는 것이 핵심이다. 따라서 위 프로젝트에서 MemberMapper.xml 파일과 MemberMapper 인터페이스를 어떻게 구현했는지 확인하고 넘어가자.

 

MemberMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.churnobyl.mappers.MemberMapper">
    <select id="selectAll" resultType="Member">
        SELECT * from member
    </select>

    <insert id="insertMember" parameterType="Member">
        INSERT INTO member
        (name, age)
        VALUES
        (#{name}, #{age})
    </insert>
</mapper>

 

MyBatis Docs에서 제공하는 기본 양식

 

MemberMapper.java

package org.churnobyl.mappers;

import org.churnobyl.Member;
import java.util.List;

public interface MemberMapper {
    List<Member> selectAll();
    void insertMember(Member member);
}

 

 mapper.xml파일에서 mapper element의 attribute인 namespace는 해당 xml파일이 어느 자바 파일과 매핑될 것인지 지시해주고, 그 아래에는 select, insert같은 element안에 실제 SQL 쿼리를 넣어 준다. 그리고 각 element의 id를 매핑되는 자바 파일의 메서드 이름과 동일하게 설정해 MyBatis가 이해할 수 있도록 한다.

 

 이 과정을 통해 xml파일로 저장되어 있는 SQL 쿼리를 MyBatis를 통해 자바 메서드처럼 사용할 수 있다. 그리고 이 인터페이스의 메서드를 다음과 같이 호출해 자바 애플리케이션 안에서 사용할 수 있다.

 

HelloService.java

package org.churnobyl;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class HelloService {
    private final SqlSessionFactory sqlSessionFactory;

    public List<Member> getAll() {
        List<Member> members = null;

        try (SqlSession session = sqlSessionFactory.openSession()) {
            members = session.selectList("org.churnobyl.mappers.MemberMapper.selectAll");
            return members;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return members;
    }

    public boolean saveMember(String name, int age) {
        Member member = new Member(name, age);

        try (SqlSession session = sqlSessionFactory.openSession()) {
            session.insert(
                    "org.churnobyl.mappers.MemberMapper.insertMember",
                    member
            );

            session.commit();

            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }
}

 

 실제 매핑된 메서드가 어떻게 사용되는지가 중요하기 때문에 SqlSessionFactory 의존성 주입 코드는 생략했다. 아래에서 더 자세히 설명할 예정이다.

 

 Service단에서 SqlSessionFactory를 주입한 뒤 각 메서드의 try-with-resources 안에서 openSession() 메서드를 호출해 새로운 SqlSession 자원을 받아온다. 그리고 나서 SqlSession의 메서드와 매핑된 이름인 "org.churnobyl.mappers.MemberMapper.insertMember"등을 적절히 사용해 DB와의 상호작용을 수행할 수 있다.

 

 MyBatis가 작동하는 과정을 간단하게 알아보았고 이제 MyBatis를 뜯어보면서 내부 구조에 대해서 조금 더 깊게 알아보자. 우선 MyBatis가 수행하는 전체적인 과정을 그림으로 그려보았다.

 

전체적인 MyBatis의 프로세스

 

 위 그림만 이해하면 MyBatis를 전반적으로 이해할 수 있다. 잘 안보이면 클릭해서 보면 된다. 핵심적인 맥락은 왼쪽의 초록색 화살표들과 보라색 화살표들의 흐름이지만, 우리는 먼저 MyBatis에서 가장 중요한 SqlSession이 어떻게 만들어지는지부터 알아볼 것이다.

 


(1) SqlSessionFactoryBuilder

SqlSessionFactoryBuilder와 SqlSessionFactory, 그리고 SqlSession

 

 SqlSessionFactoryBuilderConfiguration 객체에 저장된 정보를 가지고 SqlSessionFactory를 만들기 위해 사용한다. SqlSessionFactory는 다시 SqlSession을 얻기 위해 사용하며, 이 SqlSession이 MyBatis에서 하나의 트랜잭션 단위가 된다. SqlSessionFactory을 중심으로 생각하면 이해하기 편하다. SqlSessionFactory는  자바와 데이터베이스가 상호작용하기 위한 모든 정보인 Configuration을 가지고 있으며 다시 Configuration을 주입한 SqlSession을 만들어 준다.

 

 SqlSessionFactoryBuilder의 코드 일부를 보자.

 

package org.apache.ibatis.session;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;

import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;

public class SqlSessionFactoryBuilder {

  (...생략...)
  
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
  
  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        if (inputStream != null) {
          inputStream.close();
        }
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

}

 

 

 코드를 보면 SqlSessionFactoryBuilder 클래스는 build()메서드를 통해 MyBatis 실행 정보를 담고 있는 mybatis-config.xml를 읽어 들여 Configuration로 만들고 이를 SqlSessionFactory 구현체인 DefaultSqlSessionFactory에 담아 최종적으로 리턴해준다.

 

 MyBatis가 데이터베이스에 접근하기 위해서는 mybatis-config.xml 파일을 읽어야 하기 때문에 build() 메서드에서 Reader나 InputStream 인터페이스를 파라미터로 받아야 한다. MyBatis는 이러한 정보를 쉽게 읽어들일 수 있도록 Resources 클래스를 자체적으로 지원한다. 이를 이용해 Builder를 세팅해보자.

 

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <typeAlias alias="Member" type="org.churnobyl.Member" />
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="org.h2.Driver"/>
                <property name="url" value="jdbc:h2:tcp://localhost/~/dbtest"/>
                <property name="username" value="sa"/>
                <property name="password" value=""/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="org/churnobyl/mappers/MemberMapper.xml"/>
    </mappers>
</configuration>

 

 

String resource = "properties/mybatis-config.xml";
InputStream inputStream = null;

try {
    inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
    throw new RuntimeException(e);
}

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

 

  MyBatis 설정 파일인 mybatis-config.xmlResources 클래스의 getResourceAsStream() 메서드를 이용해 다음과 같이 InputStream으로 읽어들여 SqlSessionFactoryBuilder클래스의 build() 메서드 인자로 넣으면 최종적으로 SqlSessionFactory를 얻을 수 있다. 한번 만든 SqlSessionFactory은 애플리케이션이 실행하는 동안 존재해야만 하며, 삭제하거나 재생성하지 않는 편이 좋다. 그러므로 SqlSessionFactory의 가장 좋은 스코프는 애플리케이션 스코프다. Spring에서도 SqlSessionFactory의 일반적인 라이프사이클을 싱글턴 패턴으로 관리한다. SqlSessionFactoryBuilder는 SqlSessionFactory를 만들고 나면 필요없으므로 메서드 스코프로 사용하는 것을 권장한다.

 

 다시 SqlSessionFactoryBuilder클래스의 구현 코드를 슬쩍 들여다보면 xml을 읽을 InputStream을 이용해 최종적으로 build(InputStream inputStream, String environment, Properties properties) 메서드에서 XmlConfigBuilder를 통해 xml의 정보를 모두 파싱하고 Configuration을 만든다. 이때 데이터베이스 정보나 특히 SQL 매핑 정보도 함께 파싱되어 Configuration 안에 저장된다.

 


(2) Configuration

Configuration이 생성되는 흐름

 

 Configuration 클래스는 해당 어플리케이션에서 MyBatis를 사용하기 위한 모든 정보를 담고 있는 아주 중요한 클래스다. 위 절에서 언급한 것처럼 XmlConfigBuilder가 파싱한 SQL 매핑 정보도 가지고 있다. 코드를 간단하게 살펴보자.

 

package org.apache.ibatis.session;

public class Configuration {

	(...생략...)

    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>(
      "Mapped Statements collection")
          .conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource() + " and "
              + targetValue.getResource());
              
    (...생략...)
              
    protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
    
    (...생략...)
    
}

 

 대부분의 코드는 생략하고 SQL문을 매핑하는 부분만 보자. Configuration 클래스는 최종적으로 매핑한 SQL문들을 mappedStatements 맵MappedStatement 객체로 저장한다. 앞서 SqlSession의 insert()나 selectList()메서드를 사용할 때 파라미터로 "org.churnobyl.mappers.MemberMapper.insertMember"와 같은 statement가 들어갔었는데, 바로 이 statement가 mappedStatements 맵의 key로 저장되고, insertMember()와 매핑된 MemberMapper.xml의 정보들은 MappedStatement 객체로 저장되는 것이다.

 

 주목해야 할 점은 incompleteStatements 맵인데, 이름 그대로 불완전한 SQL문이다. MyBatis는 애플리케이션이 시작되고 xml로부터 매핑 정보를 임시로 incompleteStatements에 먼저 저장한 뒤, 모든 참조와 종속성을 해결하고 나서 SQL문을 최종적으로 MappedStatement 객체에 저장하는 전처리 과정이 존재한다. 이를 통해 모든 참조와 종속성이 올바르게 해결되도록 보장한다.

 

 


(3) SqlSessionFactory

 다음으로 SqlSessionFactory에 의해 Configuration이 주입되어 탄생하는 SqlSessionFactory다. 앞서 언급한 것처럼 SqlSessionFactory는 애플리케이션 전반에 걸쳐 하나만 존재해야 하므로 일반적으로 싱글턴 패턴으로 구현된다. SqlSessionFactory에서 openSession()메서드를 사용하면 ConfigurationExecutor를 주입해 SqlSession 인터페이스의 구현체인 DefaultSqlSession 객체를 리턴한다. 이 DefaultSqlSession이 실질적으로 DB와 상호작용을 수행한다.

 

 코드를 보자.

 

package org.apache.ibatis.session.defaults;

public class DefaultSqlSessionFactory implements SqlSessionFactory {

  private final Configuration configuration;

  public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }

  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

  @Override
  public SqlSession openSession(boolean autoCommit) {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
  }
  
  @Override
  public SqlSession openSession(ExecutorType execType) {
    return openSessionFromDataSource(execType, null, false);
  }
  
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,
      boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}

 

 openSession() 메서드는 여러가지 파라미터를 받을 수 있도록 오버로딩되어 있다. 수행 결과를 자동으로 커밋할 것인지 여부에 따른 autoCommit을 불리언 타입으로 줄 수도 있고, Executor의 타입을 어떻게 할 것인지를 ExecutorType enum으로 줄 수도 있다. 어떤 것을 사용하든 최종적으로 openSessionFromDataSource() 메서드를 거쳐 결과를 리턴한다.

 

 openSessionFromDataSource() 메서드는 SqlSession 인스턴스를 리턴하기 위해 필요한 Transaction이나 Executor등을 생성해 주입한다. 그중에서 Executor를 만드는 configuration.newExecutor(tx, execType)에 주목하자. 이 메서드는 새로운 Executor 인스턴스를 리턴할 때 파라미터로 넘긴 execType에 따라 다양한 형태의 Executor 구현체를 리턴해준다.

 

 ExecutorType enum을 보자.

 

ExecutorType

package org.apache.ibatis.session;

public enum ExecutorType {

  SIMPLE,

  REUSE,

  BATCH

}

 

 

 Configuration.java

package org.apache.ibatis.session;

public class Configuration {

  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    return (Executor) interceptorChain.pluginAll(executor);
  }
  
}

 

 코드를 보면 알 수 있듯이 ExecutorType 파라미터가 없다면 기본적으로 SimpleExecutor를 생성하고, REUSE라면, ReuseExecutor, BATCH라면 BatchExecutor를 생성한다. 세가지 Executor의 차이점을 간략하게 알아보자.

 

  • SimpleExecutor는 기본적인 작업만 진행하며, 한 세션 안에서 같은 작업이 일어나더라도 재사용하지 않고 새로운 Statement를 생성해 실행한다.
  • ReuseExecutor는 ReuseExecutor 내에 statementMap 맵이 존재하며, 한 세션 안에서 일어나는 작업을 맵에 저장했다가 만약 다음 실행할 작업이 맵 안에 존재한다면 해당 Statement를 재사용한다.
  • BatchExecutor는 BatchExecutor 내에 statementList가 존재하며, 가장 마지막에 실행한 Statement와 SQL문이 다음 실행할 작업과 같다면 해당 Statement를 재사용한다.

 

 이를 적절히 이용하면 각 작업마다 상황에 맞는 Executor를 이용해 최적화할 여지가 있다.

 

 


(4) SqlSession

 SqlSession은 MyBatis에서 DB와 CRUD를 수행하는 실질적인 인터페이스다. 기본적인 구현체로는 DefaultSqlSession이 있으나 Thread-safe하지 않다. 단일쓰레드 환경에서는 상관없지만, 웹 애플리케이션 같은 멀티쓰레딩 환경에서는 여러 쓰레드가 하나의 DefaultSqlSession을 동시에 사용하려고 시도할 때 충돌이 발생할 수 있다.

 

 그래서 MyBatis는 SqlSession의 또 다른 구현체인 SqlSessionManager를 제공한다. SqlSessionManager는 클래스내에 ThreadLocal 클래스를 사용해 각 쓰레드가 독립적인 SqlSession 인스턴스를 보유하는 것을 보장한다. 추가적으로 Spring에서 사용하는 MyBatis-Spring에서도 Thread-safe한 SqlSessionTemplate 클래스를 제공한다.

 

 지금은 가장 일반적인 Mybatis를 공부하고 있으므로 DefaultSqlSession을 보자. 대부분을 덜어내고 SELECT문을 수행해 List형태로 반환하는 selectList()메서드와 commit(), rollback() 메서드만 남겨두었다.

 

package org.apache.ibatis.session.defaults;

public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;

  private final boolean autoCommit;
  private boolean dirty;

  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

  @Override
  public <E> List<E> selectList(String statement) {
    return this.selectList(statement, null);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
  }

  private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      dirty |= ms.isDirtySelect();
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public void commit() {
    commit(false);
  }

  @Override
  public void commit(boolean force) {
    try {
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public void rollback() {
    rollback(false);
  }

  @Override
  public void rollback(boolean force) {
    try {
      executor.rollback(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}

 

 DefaultSqlSession는 CRUD와 관련한 다양한 메서드를 구현해 작업을 수행할 수 있도록 되어 있다. 여기는 selectList()메서드를 포함해 일부만 적어놓았지만 실제 구현체를 찾아보면 delete(), update(), selectOne() 등 다양한 메서드를 구현해놓은 것을 알 수 있다. 모든 메서드들은 실행할 statement를 파라미터로 받아 Configuration으로부터 해당 statement와 매핑된 MappedStatement객체를 찾고 Executor를 이용해 결과를 받아온다.

 

 다시 HelloService 서비스을 보면 조금 더 이해하기 쉬울 수 있다.

HelloService.java

package org.churnobyl;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class HelloService {

    private final SqlSessionFactory sqlSessionFactory;
    
    private HelloService() {
        String resource = "properties/mybatis-config.xml";
        InputStream inputStream = null;

        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    public List<Member> getAll() {
        List<Member> members = null;

        try (SqlSession session = sqlSessionFactory.openSession()) {
            members = session.selectList("org.churnobyl.mappers.MemberMapper.selectAll");
            return members;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return members;
    }
    
}

 

 맨 위에 잠깐 나온 HelloService 코드와 다른 점은 생성자에서 sqlSessionFactory에 인스턴스를 주입하도록 했다.

 

 getAll() 메서드에 주목하자. 만약 Controller가 모든 멤버 정보를 알고 싶어 HelloService의 getAll() 메서드를 호출한다고 해보자.

 

 이제 HelloService의 getAll() 메서드에서는 DB로부터 정보를 가져오기 위해 sqlSessionFactory의 openSession()메서드를 호출해 새로운 SqlSession을 요청한다. 새로운 session이 생성되면 우리가 만든 매퍼인 MemberMapper 인터페이스에서 모든 멤버 정보를 가져오는 selectAll()을 호출하기 위해 파라미터로 "org.churnobyl.mappers.MemberMapper.selectAll" 을 넣을 수 있다. 이 파라미터를 이용해 SqlSession은 Configuration의 mappedStatements 맵을 검색한 뒤 MappedStatement객체를 찾고 다시 이 정보를 Executor에게 넘겨 결과를 받아올 수 있는 것이다.

 

 

반응형