Mybatis(1) - Mybatis
이번 포스팅은 Mybatis에 관해서 적을 예정입니다.
대부분 구글링을 하니 정확하게 Mybatis에 대해 설명하는 블로그가 없는 것 같았습니다.
그래서 이번 프로젝트 중 JPA를 사용할 필요가 없어서 Mybatis를 사용 할 기회가 생겨 정리해보려고 합니다.
물론 @Mapper와 어노테이션 혹은 .XML파일로 맵핑해서 사용하면 된다라고 하는 글들은 많습니다.
그러나 이것이 어떻게 동작하고 JPA와 무엇이 다른지 자세하게 알아보는 시간을 가져보도록 하겠습니다.
일단 요즘 대부분의 프로젝트에서 JPA를 사용하고 있습니다.
그리고 세계적 통계로 봐도 JPA가 훨씬 우세하다고 생각합니다.
그러나 언젠가는 JPA가 아닌 다른 프레임워크가 유행 할 수 있습니다.
그 때 중요한 것은 기본적인 것을 아는 것 이라고 생각합니다. 그래서 JPA보다도 Mybatis가 근본적인
JDBC를 활용하는 느낌이고 SQL문도 직접 작성할 수 있기 때문에 선택했습니다.
MyBatis 란?
개발자가 지정한 SQL, 저장프로시저 그리고 고급 맵핑을 지원해주는 퍼시스턴스 프레임워크입니다.
여기서 퍼시스턴스 프레임워크란 복잡한 JDBC프로그래밍을 설정 파일과 클래스로 설정할 수 있게 만든 프레임워크입니다.
SqlSession
Mybatis의 핵심은 "SqlSession"입니다. JPA는 비슷한 것으로 'Entity"가 있죠.
SqlSession도 SqlSessionManager에 의해서 운영됩니다.
SqlSession은 SqlSessionFactory에 의해 생성되고 SqlSessionFactory는 SqlSessionFactoryBuilder에 의해 생성됩니다.
아래는 생성자 오버로딩입니다.
SqlSessionFactory build(InputStream inputStream)
SqlSessionFactory build(InputStream inputStream, String environment)
SqlSessionFactory build(InputStream inputStream, Properties properties)
SqlSessionFactory build(InputStream inputStream, String env, Properties props)
SqlSessionFactory build(Configuration config)
그리고 이러한 SqlSession상위 요소들은 사용하고 지워줘야합니다.
상위 요소들에 대한 자세한 이야기는 이 부분에서 하지 않겠습니다.
왜냐하면 SpringBoot를 이용할 경우 DI컨테이너가 SqlSession을 주입해주기 때문에 상위 요소를 생성하지 않아도 됩니다.
그래도 오리지널 간단한 예시만 한 번 참고자료로 놔두겠습니다.
그리고 우리는 프로젝트에 DBMS를 2개 이용해야하기 때문에 SqlSessionFactory를 2개 생성해야합니다.
String resource = "org/mybatis/builder/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
SqlSession의 역할로써는 트랜잭션 커밋, 롤백, 구문 실행, mapper인스턴스 습득 등이 있습니다.대표적인 메소드는 아래와 같습니다. 아래의 메소드를 구현하는것은 SqlSessionTemplate입니다.
<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<T> Cursor<T> selectCursor(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
JPA에서의 Entity 같은 역할을 합니다. 기본적인 CRUD메소드를 내장하고 있습니다.
Select메소드 같은 경우 NPE가 발생할 수 있으므로 List<E>을 쓰는 것을 Mybatis에서는 권장합니다.
대표적으로 구현되어진 selectList함수 구현입니다. 이것은 순수 JDBC의 Prestatement를 이용해 쿼리를 실행하는 것과
같은 것입니다.
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
List var6;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
} catch (Exception var10) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var10, var10);
} finally {
ErrorContext.instance().reset();
}
return var6;
}
MyBatis설정
설정 방법은 크게 어노테이션 활용과 XML파일 방식으로 나눌 수 있습니다.
어노테이션 방식은 Spring과 결합했을 때 사용하는 것이므로 생략하고 순수 XML파일로 설정해보겠습니다.
첫 번째로 도메인과 XML이 인식할 수 있게 연동하는 방법입니다.
도메인이름과 경로를 이용하여 설정해줍니다.
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>
두 번째로 사용할 dataSource를 지정해줘야합니다. .yml파일로 손쉽게 조정할 수 있습니다.
설정 시 3가지의 dataSource타입을 선택해야합니다.
1. UNPOOLED
매번 요청에 대해서 커넥션을 열고 닫는 간단한 dataSource입니다.
driver - JDBC드라이버의 패키지 경로를 포함한 결제 자바 클래스명
url - 데이터베이스 인스턴스에 대한 JDBC URL
username - 데이터베이스에 로그인 할 때 사용할 사용자명
password - 데이터베이스에 로그인 할 때 사용할 패스워드
2. POOL
새로운 커넥션 인스턴스를 생성하는 것을 피하게 만들어줍니다.
스프링 부트의 Hikari ConnectionPool을 이용하는 것입니다.
poolMaximumActiveConnections - 주어진 시간에 존재할 수 있는 활성화된(사용중인) 커넥션의 수. 디폴트는 10이다.
poolMaximumIdleConnections - 주어진 시간에 존재할 수 있는 유휴 커넥션의 수강제로 리턴되기 전에 풀에서 “체크아웃” 될 수 있는 커넥션의 시간. 디폴트는 20000ms(20 초)
poolTimeToWait - 풀이 로그 상태를 출력하고 비정상적으로 긴 경우 커넥션을 다시 얻을려고 시도하는 로우 레벨 설정. 디폴트는 20000ms(20 초)
poolMaximumLocalBadConnectionTolerance – 이것은 모든 쓰레드에 대해 bad Connection이 허용되는 정도에 대한 낮은 수준의 설정입니다. 만약 쓰레드가 bad connection 을 얻게 되어도 유효한 또 다른 connection 을 다시 받을 수 있습니다. 하지만 재시도 횟수는 poolMaximumIdleConnections 과 poolMaximumLocalBadConnectionTolerance 의 합보다 많아야 합니다. 디폴트는 3이다. (3.4.5 부터)
poolPingQuery - 커넥션이 작업하기 좋은 상태이고 요청을 받아서 처리할 준비가 되었는지 체크하기 위해 데이터베이스에 던지는 핑쿼리(Ping Query). 디폴트는 “핑 쿼리가 없음” 이다. 이 설정은 대부분의 데이터베이스로 하여금 에러메시지를 보게 할수도 있다.
poolPingEnabled - 핑쿼리를 사용할지 말지를 결정. 사용한다면 오류가 없는(그리고 빠른) SQL 을 사용하여 poolPingQuery 프로퍼티를 설정해야 한다. 디폴트는 false 이다.
poolPingConnectionsNotUsedFor - poolPingQuery가 얼마나 자주 사용될지 설정한다. 필요이상의 핑을 피하기 위해 데이터베이스의 타임아웃 값과 같을 수 있다. 디폴트는 0이다. 디폴트 값은 poolPingEnabled가 true일 경우에만 모든 커넥션이 매번 핑을 던지는 값이다.
3. JNDI
컨테이너에 따라 설정이 변경되며 JNDI컨텍스트를 참조합니다.
initial_context - 이 프로퍼티는 InitialContext 에서 컨텍스트를 찾기(예를 들어 initialContext.lookup(initial_context))위해 사용된다. 이 프로퍼티는 선택적인 값이다. 이 설정을 생략하면 data_source프로퍼티가 InitialContext에서 직접 찾을 것이다.
data_source - DataSource인스턴스의 참조를 찾을 수 있는 컨텍스트 경로이다. initial_context 룩업을 통해 리턴된 컨텍스트에서 찾을 것이다. initial_context 가 지원되지 않는다면 InitialContext 에서 직접 찾을 것이다.
MyBatis Mapper기능
1. CRUD
Insert, select, update, delete기능을 Mapper XML파일에서 제공해줍니다.
예시로 Select구문을 JDBC로 구현한 것과 Mybatis로 구현한 것을 비교해보겠습니다.
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
JDBC의 단점은 하나하나 set을 이용해 파라미터를 전달받아야 했습니다.
그러나 Mybatis의 경우 자동으로 id라는 변수를 맵핑시켜줍니다.
이것 말고도 다양한 설정 기능이 있습니다. 아래를 참고해주세요.
select
id="selectPerson"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="personResultMap"
flushCache="false"
useCache="true"
timeout="10"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY">
2. ResultMap
흔히 JDBC에서는 ResultSet이라는 객체로 결과값을 받아옵니다.
Mybatis에서는 ResultMap함수로 결과값을 받아옵니다.
XML에 한 개의 객체를 만들어준다고 생각하시면됩니다.
결과적으로 가장 큰 장점은 Dto를 따로 생성해주지 않아도 된다는 점이죠.
주의 할 점은 쿼리문에 나열되어있는 컬럼명이 resultSet 프로퍼티에 모두 있어야한다는 점입니다.
<resultMap id="privacy" type="eventJoinUser">
<result property="seq" column="SEQ" />
<result property="privacySeq" column="PRIVACY_SEQ" />
<result property="eventMasterSeq" column="EVENT_MASTER_SEQ" />
<result property="eventMasterSeq" column="EVENT_MASTER_SEQ" />
<result property="regDate" column="REG_DATE" />
<result property="modDate" column="MOD_DATE" />
<association property="privacyVo" javaType="privacy">
<id property="seq" column="SEQ" />
<result property="userName" column="USER_NAME" />
<result property="phone1" column="PHONE1" />
<result property="phone2" column="PHONE2" />
<result property="phone3" column="PHONE3" />
</association>
</resultMap>
3. Cache
이 부분도 아주 유용한 부분입니다.
JPA는 영속성 컨텍스트에서 DB에 접근을 최소화하면서 Entity를 관리해줍니다.
MyBatis는 Cache를 가지고 있어서 DB에 접근하지 않더라도 Cache의 내용을 사용할 수 있습니다.
사용 시 몇 가지 원칙이 있습니다.
매핑 파일 내의 select구문의 모든 결과가 캐싱됩니다.
그리고 inser, update, delete는 무조건 캐시에서 삭제됩니다.
디폴트로 LRU알고리즘을 이용하고 있습니다.
아래는 속성을 설정하는 예시입니다.
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
위의 내용보다 조금 깊숙한 내용이 많이 있습니다.
그러나 위의 내용을 숙지하시면 Mybatis를 잘 다룰 수 있을겁니다.
분명 틀린 내용도 존재할 수 있습니다. 꼭 댓글 남겨주세요.