MyBatis 내장 cache 에 대해서
MyBatis 에는 2가지 내장 Cache 가 있다.
1) local session cache
2) second level cache
위의 2가지 Cache 중에서 local session cache 는 임의로 켜거나 끌 수 없고 무조건 활성화 된다. 반면 second level cache 는 mapper namespace 단위로 동작 하며 개발자가 켜거나 끌 수 있다. 이 글에서는 설명의 편의를 위해서 Spring 을 사용하지 않고 순수 MyBatis 만을 사용해서 설명하도록 하겠다.
1) second level cache
먼저 설명하기 쉬운 second level cache 부터 설명하도록 하겠다. 앞에서 언급 했듯이 이 Cache는 mapper namespace 단위로 동작 한다. 이 Cache를 켜기 위해서는 mapper xml 파일에 <cache/> 를 입력하면 된다. 예를 들면 다음과 같다.
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE mapper 3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 5 <mapper namespace="mapper.TestMapper"> 6 <cache/> 7 <select id="xxx" 8 parameterType="String" 9 resultType="int"> 10 select ~ 11 </select> 12 <insert id="xxx" 13 parameterType="~" 14 statementType="PREPARED" 15 keyProperty="" keyColumn="" 16 useGeneratedKeys="false" 17 > 18 insert into ~ 19 </insert> 20 </mapper> |
위의 mapper 의 namespace 는 "mapper.TestMapper" 이며, 여기서 선언한 모든 SQL 에 대해서는 Cache가 적용 된다.
각 mapper에 설정한 cache 기능을 일괄적으로 disable 하고 싶으면 Global configuration에 다음과 같이 선언하면 된다.
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE configuration 3 PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-config.dtd"> 5 <configuration> 6 <settings> 7 <setting name="cacheEnabled" value="false"/> 8 </settings> 9 <environments default="testdb"> 10 <environment id="testdb"> 11 <transactionManager type="JDBC"/> 12 <dataSource type="POOLED"> 13 <property name="driver" value="com.mysql.jdbc.Driver"/> 14 <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf8"/> 15 <property name="username" value="test"/> 16 <property name="password" value="test"/> 17 </dataSource> 18 </environment> 19 </environments> 20 <mappers> 21 <mapper resource="mybatis/test_mapper.xml"/> 22 </mappers> 23 </configuration> |
위의 설정에서 "cacheEnabled" 값을 false 로 설정 하였다. default 값은 true 이다. 위와 같이 false 설정을 하면 모든 mapper xml의 cache 선언이 무효화 된다.
2) local session cache
local session cache 는 MyBatis 공식 문서에서 구체적으로 설명하고 있지 않아서 아무 생각 없이 프로그램을 개발 하면 엉뚱한 결과가 나올 수 있다. local sesion cache 는 간단히 설명 하자면 SqlSession 객체마다 갖고 있는 Cache 이다. 이 기능은 개발자가 임의로 끌 수 없다.
기본적으로는 다음 조건을 만족할 때 Cache가 flush 된다.
- 명시적인 SqlSession 객체에 대한 commit(), rollback(), close() 호출 (auto commit 에 의한 commit 은 해당 안됨)
- SqlSession.clearCache() 호출
이 flush 조건 만으로는 충분하지 않은 경우가 있다. 설명의 편의를 위해서 다음 Table 을 갖고 설명하도록 하겠다.
create table session_test( col1 varchar(10) primary key, col2 int ); |
이 테이블에서 col1 은 primary key 이고, col2 는 count 값을 갖고 있는 컬럼이다.
이 테이블 관리를 위해서 다음과 같은 순서로 SQL을 처리 한다고 가정해 보자.
1) 레코드 생성
2) col2 의 값을 1만큼 증가
3) col2 값을 조회
이 SQL 을 처리하기 위해 다음과 같이 mapper xml 선언을 한다.
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE mapper 3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 5 <mapper namespace="mapper.TestMapper"> 6 <select id="selectTestTable" 7 parameterType="String" 8 resultType="int"> 9 select col2 10 from session_test 11 where col1 = #{col1} 12 </select> 13 <insert id="insertTestTable" 14 parameterType="domain.TestTable" 15 statementType="PREPARED" 16 keyProperty="" keyColumn="" 17 useGeneratedKeys="false" 18 > 19 insert into session_test(col1, col2) 20 values(#{col1}, #{col2}) 21 </insert> 22 <update id="updateTestTable" 23 parameterType="String" 24 statementType="PREPARED" 25 > 26 update session_test 27 set col2 = col2 + 1 28 where col1 = #{col1} 29 </update> 30 </mapper> |
다음은 이 SQL을 실행 하는 Java application 이다. (설명의 편의를 위해서 Mapper interface 선언은 생략 한다.)
<Insert 및 Select 를 하는 main 프로그램 - TestRun.java>
1 package test; 2 3 import java.io.Reader; 4 5 import org.apache.ibatis.io.Resources; 6 import org.apache.ibatis.session.SqlSession; 7 import org.apache.ibatis.session.SqlSessionFactory; 8 import org.apache.ibatis.session.SqlSessionFactoryBuilder; 9 10 import mapper.TestMapper; 11 import domain.TestTable; 12 13 public class TestRun { 14 private SqlSessionFactory sqlSessionFactory; 15 /** 16 * @param args 17 */ 18 public static void main(String[] args) throws Exception{ 19 TestRun main_class = new TestRun(); 20 main_class.initMybatis(); 21 main_class.runQuery(); 22 } // End of main() 23 24 private void initMybatis() throws Exception{ 25 String resource = "mybatis/configuration.xml"; 26 Reader reader = null; 27 reader = Resources.getResourceAsReader(resource); 28 this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); 29 } // End of initMybatis() 30 31 private void runQuery() throws Exception{ 32 SqlSession session = null; 33 session = this.sqlSessionFactory.openSession(true); 34 // Insert 35 TestMapper mapper = session.getMapper(TestMapper.class); 36 TestTable data = new TestTable(); 37 data.col1 = "key1"; 38 data.col2 = 1; 39 mapper.insertTestTable(data); 40 // Select 41 int col2 = mapper.selectTestTable("key1"); 42 System.out.println("col2 value : " + col2); 43 // Update 44 UpdateApp updateApp = new UpdateApp(this.sqlSessionFactory); 45 updateApp.updateData("key1"); 46 // Select again 47 col2 = mapper.selectTestTable("key1"); 48 System.out.println("col2 value : " + col2); 49 // 50 session.close(); 51 } // End of runQuery() 52 53 } // End of class TestRun 54 |
<Update 를 하는 Sub 프로그램 - UpdateApp.java>
1 package test; 2 3 import mapper.TestMapper; 4 5 import org.apache.ibatis.session.SqlSession; 6 import org.apache.ibatis.session.SqlSessionFactory; 7 8 public class UpdateApp { 9 private SqlSessionFactory sqlSessionFactory; 10 11 public UpdateApp(SqlSessionFactory factory){ 12 this.sqlSessionFactory = factory; 13 } 14 15 public void updateData(String col1){ 16 SqlSession session = null; 17 session = this.sqlSessionFactory.openSession(true); 18 // 19 TestMapper mapper = session.getMapper(TestMapper.class); 20 mapper.updateTestTable(col1); 21 session.close(); 22 } 23 } 24 |
위와 같이 개발한 후 TestRun.java 를 실행한다. 기대하는 결과값은 다음이다.
col2 value : 1
col2 value : 2 (두번째 Query 이전에 Update를 실행 했음)
그러나 실행 결과는 다음과 같다.
col2 value : 1
col2 value : 1
분명히 DB에 조회해 보면 2로 정상적으로 업데이트 되었음을 알 수 있다. 그러나 위와 같이 이상한 값이 나오는 이유는 두번째 Query 에서 SqlSession 객체 내부 Cache 를 사용하기 때문이다. 따라서 이 경우 2번째 Query 에서 DB에 조회하기 위해서는 다음 2가지 방법이 있다.
1) SqlSession.clearCache() 를 호출
2) Sql mapper 선언에 useCache="false" flushCache="true" 설정 (<select> 에 대한 default는 useCache="true", flushCache="false" 임)
1)의 방법을 사용하여 다음과 같이 프로그램을 수정해 보자.
1 // Insert 2 TestMapper mapper = session.getMapper(TestMapper.class); 3 TestTable data = new TestTable(); 4 data.col1 = "key1"; 5 data.col2 = 1; 6 mapper.insertTestTable(data); 7 // Select 8 int col2 = mapper.selectTestTable("key1"); 9 System.out.println("col2 value : " + col2); 10 // Update 11 UpdateApp updateApp = new UpdateApp(this.sqlSessionFactory); 12 updateApp.updateData("key1"); 13 // Cache clear 14 session.clearCache(); 15 // Select again 16 col2 = mapper.selectTestTable("key1"); 17 System.out.println("col2 value : " + col2); 18 // 19 session.close(); |
위의 14 라인에 clearCache() 호출을 추가 하였다.
또는 SQL Mapper 선언을 수정해도 Cache 동작을 피할 수 있다.
1 <select id="selectTestTable" 2 parameterType="String" 3 resultType="int" 4 useCache="false" flushCache="true"> 5 select col2 6 from session_test 7 where col1 = #{col1} 8 </select> |
위의 2가지 방법 중 하나를 선택 하면 원하는 결과값을 얻을 수 있다.
위의 샘플 프로그램 예제는 상태 count 를 관리하는 프로그램에서 구현하는 전형적인 유형이다. 이 경우 MyBatis local session cache 의 특징을 정확히 이해해야 버그 없는 프로그램 개발을 할 수 있다.
[출처] MyBatis 내장 cache 에 대해서|작성자 예영아빠