728x90
반응형

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 의 특징을 정확히 이해해야 버그 없는 프로그램 개발을 할 수 있다.

 

 

728x90
반응형
블로그 이미지

nineDeveloper

안녕하세요 현직 개발자 입니다 ~ 빠르게 변화하는 세상에 뒤쳐지지 않도록 우리모두 열심히 공부합시다 ~! 개발공부는 넘나 재미있는 것~!

,