Mapper 설정

Violet_Evgadn·2023년 4월 24일
0

DB연동

목록 보기
9/22

settings

런타임 시 마이바티스의 행위를 조정하기 위한 값들이다.

settings 설정 방법 및 적용

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

    <settings>
        <setting name="cacheEnabled" value="false" />		
        <setting name="lazyLoadingEnabled" value="true" />		
        <setting name="multipleResultSetsEnabled" value="true" />
        <setting name="useColumnLabel" value="true" />
        <setting name="useGeneratedKeys" value="false" />
        <setting name="enhancementEnabled" value="false" />
        <setting name="defaultExecutorType" value="SIMPLE" />
        <setting name="defaultStatementTimeout" value="25000" />
        <setting name="safeRowBoundsEnabled" value="false" />
        <setting name="mapUnderscoreToCamelCase" value="false" />
        <setting name="localCacheScope" value="SESSION" />
        <setting name="jdbcTypeForNull" value="OTHER" />
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString" />
	</settings>
    
 </configuration>

먼저 위 파일과 같이 XML Config 파일을 만들어줘야 한다.

방법은 간단한데 < configuration> ~ < /configuration>으로 SQL이 아닌 Config에 대한 파일임을 알려주고, < settigns>를 통해 우리가 원하는 설정값들을 지정해주면 된다.

SqlSessionFactory 설정을 설명할 때 setConfigLocation()에 대해 설명한 적이 있었는데 바로 이 메서드를 활용할 차례이다. 하드 코딩을 하거나 application.properties에 Config XML 파일의 경로를 입력해준 뒤 경로를 SqlSessionFactoryBean에 먹여주면 된다. 이후 SqlSessionFactory는 해당 설정을 가지는 SqlSession을 생성하여 Query문을 처리할 것이다.

방법은 아래와 같다

@Configuration
public class MyBatisConfiguration {
    @Value("${mybatis.mapper-locations}")
    String mPath;

    @Value("${mybatis.type-aliases-package}")
    String package_name;

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource(){
        return DataSourceBuilder.create().build();
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource, ApplicationContext applicationContext) throws Exception{
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        
        ////////////// 추가된 부분 ///////////////////
        Resource configLocations = new ClassPathResource("/egovframework/sqlmap/"+ this.platform + "/config/sql-map-config.xml");
        sqlSession.setConfigLocation(configLocations);
        /////////////////////////////////////////////
        
        sqlSessionFactoryBean.setTypeAliasesPackage(package_name);
        sqlSessionFactoryBean.setMapperLocations(applicationContext.getResources(mPath));
        return sqlSessionFactoryBean.getObject();
    }

}

configLocations가 String이 아닌 Resource 객체임을 유의하자.

여러가지 Setting & Default

이렇게 setting을 통해 설정할 수 있는 설정값들은 많이 존재한다. 이 값들에 대해 알아보자

설명할 형식은 아래와 같다.

{Setting Name} : {Value로 들어갈 수 있는 값들}

  • 설명
  • Default Value

cacheEnabled : {true, false}

  • 각 Mapper에 설정된 캐시를 전역적으로 활용할지 여부
  • Default : true

lazyLoadingEnabled : {true, false}

  • 지연 로딩을 사용할지에 대한 여부로써 false일 경우 모두 즉시 로딩
  • Default : true

aggressiveLazyLoading : {true, false}

  • 활성화될 시 지연 로딩 Property를 가진 객체는 호출에 따라 로드되고 개별 Property는 요청할 때 로드됨
  • Default : true

multipleResultSetsEnabled : {true, false}

  • 1개 구문에서 여러 개의 ResultSet을 허용할지 여부인데, Drvier가 해당 기능을 지원해야 활용할 수 있음
  • Default : true

useColumnLabel : {true, false}

  • Column Name 대신 Column Label을 활용할지 여부인데 Driver마다 조금씩 다르게 작동함
  • Default : true

useGeneratedKeys : {true, false}

  • 생성 Key에 대한 JDBC 지원을 허용할지 여부인데 true로 설정했을 경우 생성 키를 강제로 생성한다. 지원하는 드라이버가 필요하다
  • Default : false

autoMappingBehavior : {NONE, PARTIAL, FULL}

  • MyBatis가 Column을 Field/Property에 자동으로 매핑할지와 방법에 대해 명시
  • Default : NONE
    • None : 아무것도 하지 않음
    • PARTIAL : 간단한 자동 Mapping만 수행하고 내포된 결과에 대해서는 처리하지 않음
    • FULL : 처리 가능한 모든 자동 Mapping을 처리함

autoMappingUnknownColumnBehavior : {NONE, WARNING, FAILING}

  • 자동 매핑 대상 중 알 수 없는 컬럼이나 Property를 발견했을 때 행위
  • Default : NONE
    • NONE : 아무것도 하지 않음
    • WARNING : 경고 로그를 출력함. Log Level은 WARN이여야 함
    • FAILING : 매핑이 실패하여 SqlSessionException을 던짐

defaultExecutorType : {SIMPLE, REUSE, BATCH}

  • Default 실행자(Executor)를 설정
  • Default : SIMPLE
    • SIMPLE : 특별히 하는 것 없음
    • REUSE : PreparedStatement를 재사용
    • BATCH : 구문을 재사용하고 수정을 배치처리함

defaultStatementTimeout : {양수}

  • DB에서의 응답을 얼마나 기다릴지에 대한 Timeout 시간 설정
  • Default : null(설정되지 않음)

safeRowBoundsEnabled : {true, false}

  • 중첩 구문 내 Row Bound 허용 여부를 나타내는 것으로 중첩 구문에서 Paging처럼 "특정 범위의 Row값"만 가져올 수 있는지 여부를 반환함
  • Default : false

mapUnderscoreToCamelCase : {true, false}

  • 전통적으로 DB Column은 Underscore를 활용한 A_COLUMN 형태로 되어있는데 이를 aColumn처럼 자바에서 많이 활용하는 Camel Case로 변경하여 매핑하도록 함
  • Defualt : false

localCacheScope

  • MyBatis는 순환 참조를 막거나 반복된 쿼리의 속도를 높이기 위해 Local Cache를 활용하는데, 어떤 방식으로 캐시를 사용할지에 대한 설정값
  • Default : SESSION
    • SESSION : 동일 세션의 모든 Query를 캐시함
    • STATEMENT : Local Session은 구문 실행할 때마다 사용하고 같은 SqlSession에서 2개의 다른 호출 사이에는 데이터를 공유하지 않음

jdbcTypeForNull : {JdbcType Enum. 대부분 NULL, VARCHAR, OTHER를 활용}

  • Jdbc타입을 Parameter에서 제공하지 않을 때 null값을 처리한 JDBC 타입을 명시함
  • Default : OTHER
    • JDBC Type : BIGINT, BINARY 등 JDBC에서 Data를 다룰 때 활용하는 형식을 말함

lazyLoadTriggerMethods : {메서드 이름을 나열하고, 여러 개일 경우 콤마(,)로 구분}

  • 지연 로딩을 발생시키는 객체 메소드 명시
  • Default : equals, clone, hashoCode, toString

callSettersOnNulls : {true, false}

  • Null 값을 가져왔을 때 Setter나 Map의 Put 메서드를 호출할지 명시하는 것으로, null값을 초기화할 때 유용
  • Defualt : false

logPrefix : {문자열}

  • MyBatis가 Logeer 이름에 추가할 접두사 문자열 명시
  • Default : null(설정하지 않음)

logImpl : {SLF4J, LOG4J2, JDK_LOGGIN, COMMONS_LOGGING, STDOUT_LOGGING, NO_LOGGING}

  • MyBatis가 사용할 로깅 구현체를 명시한 것으로 설정하지 않으며 MyBatis에서 알아서 로깅 구현체를 찾음. Query문을 로깅하고 싶을 때 어떤 로깅 구현체로 로깅할지를 명시함
  • Default : null(설정하지 않음)

proxyFactory : {CGLIB | JAVASSIST}

  • MyBatis가 지연 로딩을 처리할 객체를 생성할 때 사용할 Proxy Tool을 명시
  • Default : JAVASSIST
    • CGLIB은 MyBatis 3.5.10 버전부터 Deprecated 되었으므로 사실상 JAVASSIST 1개밖에 없는 셈이다

이외에도 여러 가지 설정이 있지만, 외울 생각은 하지 말고 이런 게 있구나 정도로 넘어가면 된다.


typeAliases

타입 별칭은 자바 타입(클래스)에 대한 짧은 이름이다. 오직 XML 설정에서만 활용되며 타이핑을 줄이기 위해 존재한다.

예를 들어 <select id="selectBannerList" parameterType="Author">이라고 가정하자.

그런데 XML 파일에서는 Author라는 객체가 어디에 존재하는지 알 수가 없기 때문에 모든 경로(ex. domain.blog.Author)를 입력해줘야 한다. 만약 Author 객체를 활용할 때마다 풀 경로를 매번 입력하려면 엄청난 타이핑양이 필요할 것이며 유지보수도 힘들어질 것이다.

따라서 우리는 이를 typeAliases로 설정해줌으로써 클래스 이름, 즉 Author만 입력해도 자동으로 domain.blog.Author라는 것을 인식할 수 있도록 별칭을 설정해주는 것이다.

이전 설정 Section 중 application.properties에서 mybatis.type-aliases-package, 혹은 setTypeAliasesPackage()를 통해 설정해주었지만, Config 파일을 하나 파서 < typeAlias>를 모두 입력해주는 것도 가능하다.

(위에서 설정했던 < configuration> ~ </ configuration> 사이에 추가해줄 수 있음)

< typeAlias> 활용

<typeAliases>
    <typeAlias type="com.example.Capability" alias="Capability" />
    <typeAlias type="com.example.Role" alias="Role" />
</typeAliases>

먼저 type은 별칭과 연결될 실제 클래스의 경로이며, alias는 별칭이다. 이때 type는 "src/main/java"를 시작 Path로 잡고 Search를 시작하기 때문에, java 아래 경로만 입력해주면 된다.

위처럼 설정하면 com.example.Capability는 앞으로 Capability라는 이름으로, com.example.Role은 앞으로 Role이라는 짧은 이름으로 대체하여 활용할 수 있게 되는 것이다.

< package> 활용

<typeAliases>
    <package name="com.example">
</typeAliases>

< typeAlias>를 활용하는 것은 직관적일 수는 있지만 개발 시 클래스 1개를 추가할 때마다 typeAlias를 추가해줘야 함을 알 수 있다. 예를 들어, com.example.Member를 넣고 싶다면 <typeAlias type="com.example.Member" alias="Member"/> 구문을 추가해줘야 하는 것이다.

이런 귀찮음을 줄이기 위해 패키지 내에 존재하는 모든 클래스를 자동으로 typeAlias 설정할 수 없을까?라는 생각으로 나온 것이 < package>이다.

< package name="com.example">로 지정하면 com.example 아래 존재하는 모든 클래스를 < typeAlias>로 자동 등록해주는 것이다. 클래스에 @Alias 어노테이션이 없다면 클래스를 모두 소문자로 변환한 형태로 Alias가 지정되고(Author 같은 경우는 author이 될 것이다), 클래스에 @Alias("Author")처럼 어노테이션을 붙여준다면 어노테이션에 지정한 값으로 Alias가 지정될 것이다.

솔직히 이 방법을 추천하지는 않는데, 일단 package 아래 있는 모든 클래스를 typeAlias로 지정하기 때문에 쓰지 않는 클래스들도 다 Alias로 등록된다는 단점이 존재한다. 또한 @Alias()를 붙여줘서 내가 원하는 이름으로 별칭을 지정하는 방법도 있고 기본값인 소문자로 변환된 값을 별칭으로 설정하는 방법도 있는데, 여러 명이 개발을 해서 반은 @Alias, 반은 Default인 소문자로 지정하게 된다면 유지보수가 어마어마하게 힘들어질 것 같다.

그래서 차라리 조금 귀찮지만 < typeAlias>를 추천한다.

위 방식들을 활용하면 < typeAliases>를 기입한 XML 파일에만 Alias를 활용할 수 있게 된다. 따라서, 위에서 말했듯 그냥 < configuration> ~ < /confiugration>에 < typeAliases>에 대한 모든 설정을 넣고 이렇게 만들어진 Config 파일을 SqlSessionFactoryBean에 먹여줌으로써 모든 XML 파일에 대하여 동일한 typeAliases를 적용할 수도 있다.

물론 우리는 이전에도 활용했듯 setTypeAliasesPackage()를 통해 쉽고 유지보수도 원활하게 설정할 수 있었기 때문에 이 방법을 활용하도록 하자.

(setTypeAliasesPackage 또한 < package>처럼 쓸모없는 클래스들도 다 Alias로 등록된다는 단점은 존재하지만 유지보수가 정말 매우 쉬워지기 때문에 조금의 손해를 감수하더라도 활용할 가치가 있다고 생각한다)


typeHandler

MyBatis가 ResultSet에서 값을 가져올 때마다 Value가 적절한지 확인하기 위해 활용되는 것이다. 적절하다는 것은 "내가 원하는 Java Type으로 변환할 수 있다는 것이다.

예를 들어, 나는 Boolean형 데이터를 원하는데 "WOW"라는 값이 오면 "WOW"는 true나 false로 변환 불가능하기 때문에 에러를 발생시켜야 하며, 이를 위해 활용하는 것이 TypeHandler이다.

Default TypeHandler가 존재하며 TypeHandler를 Override 하여 직접 TypeHandler를 만들어줄 수도 있다.

<result typeHandler="">로 지정해줌으로써 객체의 멤버 변수(Table의 Column값)마다 typeHandler를 적용해주거나 Instance(Table Row Data)마다 다른 TypeHandler를 적용하여 객체의 특성에 맞도록 Value 적절성 여부를 판단하게 활용할 수 있을 것이다.

Custom TypeHandler

출처 : https://blog.naver.com/pure137/220440699402

public abstract class AbstractCipherTypeHandler implements TypeHandler<String> {
 
    @Override
    public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        
        if(isCipher()){
        // isCipher() = true일 경우 Data를 암호화 함
            parameter = encode(parameter);
        }
        
        // 만약 if문에서 암호화를 수행했다면 암호화된 데이터가 저장될 것이다.
        ps.setString(i, parameter);
    }
 
    @Override
    public String getResult(ResultSet rs, String columnName) throws SQLException {
        String value = rs.getString(columnName);
        
        if(isCipher()){
        // 암호화되어 있는 데이터라면 복호화 함
            value = decode(value);
        }
       
        return value;
    }

    @Override
    public String getResult(ResultSet rs, int columnIndex) throws SQLException {
        String value = rs.getString(columnIndex);
        
        if(isCipher()){
        // 암호화되어 있는 데이터라면 복호화 함
            value = decode(value);
        }
        return value;
    }
 
    @Override
    public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String value = cs.getString(columnIndex);

        if(isCipher()){
        // 암호화되어 있는 데이터라면 복호화 함
            value = decode(value);
        }
        return value;
    }
    
    // isCipher()이 True를 반환하면 암호화하고 싶은 Data라는 것을 의미함
    protected abstract boolean isCipher();
    
    // 암호화 과정
    protected String encode(String value){
        try{
            value = Base64.encode(value);
        }catch(Exception e){}
        
        return value;
    }
    
	// 복호화 과정
    protected String decode(String value){
        try{
            value = Base64.decode(value);
        }catch(Exception e){}
        
        return value;
    }
}
public class EmailCipherTypeHandler extends AbstractCipherTypeHandler {
    
    @Override
    protected final boolean isCipher() {
        return true;
    }
}
/*
*	위에서 지정했던 AbstractCipherTypeHandler를 상속받고 isCipher()가 true를 반환하게 했다.
*	즉, EmailCipherTypeHandler는 DB에 "암호화된 데이터를 저장"하는 로직을 가지게 된다.
*
*	만약 <result typeHandler="EmailCipherTypeHandler">로 지정한다면 해당 Column에 저장될
*	데이터는 암호화된 이후 DB에 저장되게 될 것이며 Column에 저장된 데이터를 가지고 올 때는
*	복호화를 수행하게 될 것이다.
/

자 여기에서 TypeHandler의 진수가 나온다.

먼저 TypeHandler는 "Query의 결과가 내가 지정한 Java Type과 일치하는가" 여부를 확인하여 적절한 Value를 가지고 오기 위해 명시해주는 것임을 알고 있다. 그렇다면 Query의 결과가 Java Type과 연동될지를 확인하기 위해서는 Query의 결과를 원하는 객체 멤버 변수에 값을 저장하기 전에 미리 뽑아볼 필요가 있다는 것이다.

예를 들어 Query의 결과가 {"ABC", "Male", "111-111"}이라고 하고 MyBatis는 Member라는 객체에 이 정보를 담아서 반환한다고 가정하자. 그런데 TypeHandler가 설정되어 있다면 {"ABC", "Male", "111-111"}라는 값을 Member 객체의 멤버 변수에 저장하기 이전에 "ABC", "Male", "111-111"이라는 값이 "각각의 Data가 내가 사전에 지정했던 Java Type과 일치하는가" 여부를 먼저 확인하고, 사전에 지정했던 Java Type과 일치함을 인지한 이후에 Member 객체의 멤버 변수에 값을 저장해야 할 것이다.

즉, TypeHandler는 Query문의 순수한 결과물을 확인할 수 있는 유일한 중간 과정이며, 동시에 객체를 DB에 저장할 때 멤버 변수에 저장되어 있던 값을 순수한 데이터로써 확인할 수 있는 유일한 중간 다리라는 것이다.

이를 활용하면 데이터를 DB에 넣기 이전 암호화를 수행할 수도 있고, 반대로 DB에서 데이터를 빼와서 암호화된 데이터를 복호화시킨 이후 암호화 이전의 Raw Data를 user에게 보여주는 작업을 수행할 수도 있게 되는 것이다.

암호화를 예로 들었지만, DB에 저장할 데이터에 대한 전처리나 DB에서 가지고 온 데이터에 대한 후처리도 가능할 것이다.

위에서 명시한 코드는 데이터를 DB에 저장할 때 암호화를 하며, 반대로 DB에서 데이터를 뽑을 때 암호화된 데이터를 복호화시키는 과정을 추가시키기 위해 Override 하여 구현한 Custom TypeHandler이다. 다른 블로그에서 퍼온 코드이지만 TypeHandler 활용법에 대해 잘 표현하고 있다고 생각해서 가지고 왔다.

여러 가지 TypeHandler

TypeHandler는 결국 사용할 Query의 Output이 적절한지를 검증하는 방법이다. Default TypeHandler들은 Default Java Type과 연동되어 Query의 Value가 내가 원하는 Java Type으로 변환 가능한 Value인지를 확인하는 Case가 많다.

아래 기입할 TypeHandler들은 {타입 핸들러} : {해당 핸들러가 매칭 될 Java Type} 쌍으로 기입되어 있다

  • BooleanTypeHandler : boolean
  • ByteTypeHandler : byte
  • ShortTypehandler : short
  • IntegerTypeHandler : int
  • LongTypeHandler : long
  • DoubleTypeHandler : double
  • BigDecimalTypeHandler : java.math.BigDecimal
  • BigDecimal : Java 언어에서 소수점을 "정확"하게 나타내기 위한 정밀한 숫자를 위한 클래스
  • StringTypeHandler : String
  • ByteArrayTypeHandler : byte[]
  • DateTypeHandler : java.util.Date(TIMESTAMP)
  • DateOnlyTypeHandler : java.util.Date(DATE)
  • TimeOnlyTypeHandler : java.util.Date(TIME)
    • DateTypeHandler, DateOnlyTypeHandler, TimeOnlyTypeHandler가 모두 java.util.Date과 연동되는지 여부를 확인하는데 굳이 나눠놓은 이유는 바로 "JDBC Type" 때문이다. () 안에 써 놨듯이 JDBC Type이 다르기 때문이다.
    • TIME : 시간을 표시하는 데이터 타입으로 HH:MM:SS 형으로 저장됨
    • DATE : 날짜를 표시하는 데이터 타입으로 YYYY-MM-DD 형으로 저장됨
    • TIMESTAMP : 날짜와 시간을 포함하는 데이터 타입으로 YYYY-M-DD HH:MM:SS(.FFFFFF) 형으로 저장됨
    • DATETIME : 날짜와 시간을 포함하는 데이터 타입이지만 TIMESTAMP는 초단위까지 세지만 DATETIME은 초단위는 세지 않는다. YYYY-MM-DD HH:MM:SS로만 저장됨
  • LocalDateTimeTypeHandler : java.time.LocalDateTime(TIMESTAMP)
  • LocalDateTypeHandler : java.time.LocalDate(DATE)
  • LocalTimeTypeHandler : java.time.LocaTime(TIME)
  • YearTypeHandler : java.time.Year
  • MonthTypeHandler : java.time.Month
    • 결국 Year와 Month는 "연도"와 "달"을 의미하고, 이는 1 ~ 31 사이의 값을 가지기 때문에 Integer JDBC Type과 연동될 수 있다
  • YearMonthTypeHandler : java.time.YearMonth

위에서 보듯이, 결국 DB에서 뽑아온 값이 내가 원하는 Java Type과 연동되는가(변환되는가)를 따지기 위한 방법이라고 이해하면 될 것 같다.

다음 Section에서 배우겠지만 Default Java Type 같은 경우 TypeHandler도 활용하지만 javaType 파라미터를 통해 쉽고 직관적인 설정이 가능하므로 javaType 방법도 자주 활용하는 방식이다


objectFactory

MyBatis는 Query의 결과를 자바 타입, 즉 객체(Instance)로 반환한다. 그렇다면 어떻게 MyBatis는 Query의 결과문을 객체에 담아 반환할 수 있는 걸까?

MyBatis는 결과를 Instance에 담기 위해 ObjectFactory라는 것을 활용한다. ObjectFactory는 Override 해서도 활용할 수 있지만, 이미 구현된 DefaultObjectFactory가 매우 잘 구현되어 있으므로 큰 의미는 없다.

MyBatis에서 Query가 결과문을 내놓으면 ObjectFactory는 Query의 결괏값을 객체 안에 존재하는 멤버 변수에 값을 담는다. MyBatis는 이렇게 만들어진 객체를 반환하고 우리는 ObjectFactory가 만든 객체를 보는 것이므로 MyBatis가 결과를 Instance로 반환하는 것이라고 인지하게 되는 것이다.

DefaultObjectFactory를 Override 하는 것에 큰 의미는 없지만 DefaultObjectFactory의 내부 메서드를 보는 것은 ObjectFactory의 동작 방식에 대한 이해를 돕기 때문에 활용하지는 않더라도 소스 코드를 보는 것을 추천한다

public class ExampleObjectFactory extends DefaultObjectFactory {
  @Override
  public <T> T create(Class<T> type) {
    // Parameter가 없는 기본 생성자
    return super.create(type);
  }

  @Override
  public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    // Parameter가 있는 생성자를 다룸
    return super.create(type, constructorArgTypes, constructorArgs);
  }

  @Override
  public void setProperties(Properties properties) {
    // Object Factory에 대한 Property 적용
    super.setProperties(properties);
  }

  @Override
  public <T> boolean isCollection(Class<T> type) {
    // 결과값이 Collection인지 아닌지를 반환함
    // 아마 isCollection = true일 경우 객체를 List에 담아 반환하게 될 것이다.
    return Collection.class.isAssignableFrom(type);
  }}

이번 Section을 끝마치기 전...

사실 설정 방법은 그렇게까지 중요하다고는 볼 수 없다.

settings 같은 경우 Default로만 설정해도 충분히 작동하며 typeAliases도 우리는 메서드를 통해 SqlSessionfactory에 먹여주므로 큰 걱정할 필요가 없고 objectFactory는 Override 해서 활용할 일이 거의 없기 때문에 활용할 일이 없다.

typeHandler 같은 경우만 컬럼이나 resultMap에 대한 검증 및 DB Query 결과문에 대한 처리나 사전 작업을 위해서 활용할 수는 있을 것이다.

즉, Setting name과 Value를 외우는 것에 집중하기보다는 어떤 기능이 존재하며, 어떤 방식으로 설정하는지 방법에 대해서만 이해할 수 있다면 완벽하게 Setting 방법을 익혔다고 할 수 있을 것 같다.

이제는 MyBatis의 핵심인 SQL Mapping File(XML 파일)에 대해 알아보자

profile
혹시 틀린 내용이 있다면 언제든 말씀해주세요!

0개의 댓글