View Javadoc
1   /*
2    *    Copyright 2009-2024 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.apache.ibatis.submitted.cache;
17  
18  import static com.googlecode.catchexception.apis.BDDCatchException.caughtException;
19  import static com.googlecode.catchexception.apis.BDDCatchException.when;
20  import static org.assertj.core.api.BDDAssertions.then;
21  
22  import java.io.Reader;
23  import java.lang.reflect.Field;
24  
25  import org.apache.ibatis.BaseDataTest;
26  import org.apache.ibatis.annotations.CacheNamespace;
27  import org.apache.ibatis.annotations.CacheNamespaceRef;
28  import org.apache.ibatis.annotations.Property;
29  import org.apache.ibatis.builder.BuilderException;
30  import org.apache.ibatis.cache.Cache;
31  import org.apache.ibatis.cache.CacheException;
32  import org.apache.ibatis.io.Resources;
33  import org.apache.ibatis.session.SqlSession;
34  import org.apache.ibatis.session.SqlSessionFactory;
35  import org.apache.ibatis.session.SqlSessionFactoryBuilder;
36  import org.junit.jupiter.api.Assertions;
37  import org.junit.jupiter.api.BeforeEach;
38  import org.junit.jupiter.api.Test;
39  
40  // issue #524
41  class CacheTest {
42  
43    private static SqlSessionFactory sqlSessionFactory;
44  
45    @BeforeEach
46    void setUp() throws Exception {
47      // create a SqlSessionFactory
48      try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/cache/mybatis-config.xml")) {
49        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
50      }
51  
52      // populate in-memory database
53      BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
54          "org/apache/ibatis/submitted/cache/CreateDB.sql");
55    }
56  
57    /**
58     * Test Plan:
59     * <ol>
60     * <li>SqlSession 1 executes "select * from A".</li>
61     * <li>SqlSession 1 closes.</li>
62     * <li>SqlSession 2 executes "delete from A where id = 1"</li>
63     * <li>SqlSession 2 executes "select * from A"</li>
64     * </ol>
65     * Assert:
66     * <ol>
67     * <li>Step 4 returns 1 row. (This case fails when caching is enabled.)</li
68     * <ol>
69     */
70    @Test
71    void testplan1() {
72      try (SqlSession sqlSession1 = sqlSessionFactory.openSession(false)) {
73        PersonMapper pm = sqlSession1.getMapper(PersonMapper.class);
74        Assertions.assertEquals(2, pm.findAll().size());
75      }
76  
77      try (SqlSession sqlSession2 = sqlSessionFactory.openSession(false)) {
78        try {
79          PersonMapper pm = sqlSession2.getMapper(PersonMapper.class);
80          pm.delete(1);
81          Assertions.assertEquals(1, pm.findAll().size());
82        } finally {
83          sqlSession2.commit();
84        }
85      }
86    }
87  
88    /**
89     * Test Plan:
90     * <ol>
91     * <li>SqlSession 1 executes "select * from A".</li>
92     * <li>SqlSession 1 closes.</li>
93     * <li>SqlSession 2 executes "delete from A where id = 1"</li>
94     * <li>SqlSession 2 executes "select * from A"</li>
95     * <li>SqlSession 2 rollback</li>
96     * <li>SqlSession 3 executes "select * from A"</li>
97     * </ol>
98     * Assert:
99     * <ol>
100    * <li>Step 6 returns 2 rows.</li>
101    * </ol>
102    */
103   @Test
104   void testplan2() {
105     try (SqlSession sqlSession1 = sqlSessionFactory.openSession(false)) {
106       PersonMapper pm = sqlSession1.getMapper(PersonMapper.class);
107       Assertions.assertEquals(2, pm.findAll().size());
108     }
109 
110     try (SqlSession sqlSession2 = sqlSessionFactory.openSession(false)) {
111       try {
112         PersonMapper pm = sqlSession2.getMapper(PersonMapper.class);
113         pm.delete(1);
114       } finally {
115         sqlSession2.rollback();
116       }
117     }
118 
119     try (SqlSession sqlSession3 = sqlSessionFactory.openSession(false)) {
120       PersonMapper pm = sqlSession3.getMapper(PersonMapper.class);
121       Assertions.assertEquals(2, pm.findAll().size());
122     }
123   }
124 
125   /**
126    * Test Plan with Autocommit on:
127    * <ol>
128    * <li>SqlSession 1 executes "select * from A".</li>
129    * <li>SqlSession 1 closes.</li>
130    * <li>SqlSession 2 executes "delete from A where id = 1"</li>
131    * <li>SqlSession 2 closes.</li>
132    * <li>SqlSession 2 executes "select * from A".</li>
133    * <li>SqlSession 3 closes.</li>
134    * </ol>
135    * Assert:
136    * <ol>
137    * <li>Step 6 returns 1 row.</li>
138    * </ol>
139    */
140   @Test
141   void testplan3() {
142     try (SqlSession sqlSession1 = sqlSessionFactory.openSession(true)) {
143       PersonMapper pm = sqlSession1.getMapper(PersonMapper.class);
144       Assertions.assertEquals(2, pm.findAll().size());
145     }
146 
147     try (SqlSession sqlSession2 = sqlSessionFactory.openSession(true)) {
148       PersonMapper pm = sqlSession2.getMapper(PersonMapper.class);
149       pm.delete(1);
150     }
151 
152     try (SqlSession sqlSession3 = sqlSessionFactory.openSession(true)) {
153       PersonMapper pm = sqlSession3.getMapper(PersonMapper.class);
154       Assertions.assertEquals(1, pm.findAll().size());
155     }
156   }
157 
158   /**
159    * Test case for #405 Test Plan with Autocommit on:
160    * <ol>
161    * <li>SqlSession 1 executes "select * from A".</li>
162    * <li>SqlSession 1 closes.</li>
163    * <li>SqlSession 2 executes "insert into person (id, firstname, lastname) values (3, hello, world)"</li>
164    * <li>SqlSession 2 closes.</li>
165    * <li>SqlSession 3 executes "select * from A".</li>
166    * <li>SqlSession 3 closes.</li>
167    * </ol>
168    * Assert:
169    * <ol>
170    * <li>Step 5 returns 3 row.</li>
171    * </ol>
172    */
173   @Test
174   void shouldInsertWithOptionsFlushesCache() {
175     try (SqlSession sqlSession1 = sqlSessionFactory.openSession(true)) {
176       PersonMapper pm = sqlSession1.getMapper(PersonMapper.class);
177       Assertions.assertEquals(2, pm.findAll().size());
178     }
179 
180     try (SqlSession sqlSession2 = sqlSessionFactory.openSession(true)) {
181       PersonMapper pm = sqlSession2.getMapper(PersonMapper.class);
182       Person p = new Person(3, "hello", "world");
183       pm.createWithOptions(p);
184     }
185 
186     try (SqlSession sqlSession3 = sqlSessionFactory.openSession(true)) {
187       PersonMapper pm = sqlSession3.getMapper(PersonMapper.class);
188       Assertions.assertEquals(3, pm.findAll().size());
189     }
190   }
191 
192   /**
193    * Test Plan with Autocommit on:
194    * <ol>
195    * <li>SqlSession 1 executes select to cache result</li>
196    * <li>SqlSession 1 closes.</li>
197    * <li>SqlSession 2 executes insert without flushing cache</li>
198    * <li>SqlSession 2 closes.</li>
199    * <li>SqlSession 3 executes select (flushCache = false)</li>
200    * <li>SqlSession 3 closes.</li>
201    * <li>SqlSession 4 executes select (flushCache = true)</li>
202    * <li>SqlSession 4 closes.</li>
203    * </ol>
204    * Assert:
205    * <ol>
206    * <li>Step 5 returns 2 row.</li>
207    * <li>Step 7 returns 3 row.</li>
208    * </ol>
209    */
210   @Test
211   void shouldApplyFlushCacheOptions() {
212     try (SqlSession sqlSession1 = sqlSessionFactory.openSession(true)) {
213       PersonMapper pm = sqlSession1.getMapper(PersonMapper.class);
214       Assertions.assertEquals(2, pm.findAll().size());
215     }
216 
217     try (SqlSession sqlSession2 = sqlSessionFactory.openSession(true)) {
218       PersonMapper pm = sqlSession2.getMapper(PersonMapper.class);
219       Person p = new Person(3, "hello", "world");
220       pm.createWithoutFlushCache(p);
221     }
222 
223     try (SqlSession sqlSession3 = sqlSessionFactory.openSession(true)) {
224       PersonMapper pm = sqlSession3.getMapper(PersonMapper.class);
225       Assertions.assertEquals(2, pm.findAll().size());
226     }
227 
228     try (SqlSession sqlSession4 = sqlSessionFactory.openSession(true)) {
229       PersonMapper pm = sqlSession4.getMapper(PersonMapper.class);
230       Assertions.assertEquals(3, pm.findWithFlushCache().size());
231     }
232   }
233 
234   @Test
235   void shouldApplyCacheNamespaceRef() {
236     try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
237       PersonMapper pm = sqlSession.getMapper(PersonMapper.class);
238       Assertions.assertEquals(2, pm.findAll().size());
239       Person p = new Person(3, "hello", "world");
240       pm.createWithoutFlushCache(p);
241     }
242     try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
243       PersonMapper pm = sqlSession.getMapper(PersonMapper.class);
244       Assertions.assertEquals(2, pm.findAll().size());
245     }
246     try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
247       ImportantPersonMapper pm = sqlSession.getMapper(ImportantPersonMapper.class);
248       Assertions.assertEquals(3, pm.findWithFlushCache().size());
249     }
250     try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
251       PersonMapper pm = sqlSession.getMapper(PersonMapper.class);
252       Assertions.assertEquals(3, pm.findAll().size());
253       Person p = new Person(4, "foo", "bar");
254       pm.createWithoutFlushCache(p);
255     }
256     try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
257       SpecialPersonMapper pm = sqlSession.getMapper(SpecialPersonMapper.class);
258       Assertions.assertEquals(4, pm.findWithFlushCache().size());
259     }
260     try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
261       PersonMapper pm = sqlSession.getMapper(PersonMapper.class);
262       Assertions.assertEquals(4, pm.findAll().size());
263     }
264   }
265 
266   @Test
267   void shouldResultBeCachedAfterInsert() {
268     try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
269       PersonMapper pm = sqlSession.getMapper(PersonMapper.class);
270       // create
271       Person p = new Person(3, "hello", "world");
272       pm.create(p);
273       // select (result should be cached)
274       Assertions.assertEquals(3, pm.findAll().size());
275       // create without flush (cache unchanged)
276       Person p2 = new Person(4, "bonjour", "world");
277       pm.createWithoutFlushCache(p2);
278     }
279     try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
280       PersonMapper pm = sqlSession.getMapper(PersonMapper.class);
281       Assertions.assertEquals(3, pm.findAll().size());
282     }
283   }
284 
285   @Test
286   void shouldApplyCustomCacheProperties() {
287     CustomCache customCache = unwrap(sqlSessionFactory.getConfiguration().getCache(CustomCacheMapper.class.getName()));
288     Assertions.assertEquals("bar", customCache.getStringValue());
289     Assertions.assertEquals(1, customCache.getIntegerValue().intValue());
290     Assertions.assertEquals(2, customCache.getIntValue());
291     Assertions.assertEquals(3, customCache.getLongWrapperValue().longValue());
292     Assertions.assertEquals(4, customCache.getLongValue());
293     Assertions.assertEquals(5, customCache.getShortWrapperValue().shortValue());
294     Assertions.assertEquals(6, customCache.getShortValue());
295     Assertions.assertEquals((float) 7.1, customCache.getFloatWrapperValue(), 1);
296     Assertions.assertEquals((float) 8.1, customCache.getFloatValue(), 1);
297     Assertions.assertEquals(9.01, customCache.getDoubleWrapperValue(), 1);
298     Assertions.assertEquals(10.01, customCache.getDoubleValue(), 1);
299     Assertions.assertEquals((byte) 11, customCache.getByteWrapperValue().byteValue());
300     Assertions.assertEquals((byte) 12, customCache.getByteValue());
301     Assertions.assertTrue(customCache.getBooleanWrapperValue());
302     Assertions.assertTrue(customCache.isBooleanValue());
303   }
304 
305   @Test
306   void shouldErrorUnsupportedProperties() {
307     when(() -> sqlSessionFactory.getConfiguration().addMapper(CustomCacheUnsupportedPropertyMapper.class));
308     then(caughtException()).isInstanceOf(CacheException.class)
309         .hasMessage("Unsupported property type for cache: 'date' of type class java.util.Date");
310   }
311 
312   @Test
313   void shouldErrorInvalidCacheNamespaceRefAttributesSpecifyBoth() {
314     when(() -> sqlSessionFactory.getConfiguration().getMapperRegistry()
315         .addMapper(InvalidCacheNamespaceRefBothMapper.class));
316     then(caughtException()).isInstanceOf(BuilderException.class)
317         .hasMessage("Cannot use both value() and name() attribute in the @CacheNamespaceRef");
318   }
319 
320   @Test
321   void shouldErrorInvalidCacheNamespaceRefAttributesIsEmpty() {
322     when(() -> sqlSessionFactory.getConfiguration().getMapperRegistry()
323         .addMapper(InvalidCacheNamespaceRefEmptyMapper.class));
324     then(caughtException()).isInstanceOf(BuilderException.class)
325         .hasMessage("Should be specified either value() or name() attribute in the @CacheNamespaceRef");
326   }
327 
328   private CustomCache unwrap(Cache cache) {
329     Field field;
330     try {
331       field = cache.getClass().getDeclaredField("delegate");
332     } catch (NoSuchFieldException e) {
333       throw new IllegalStateException(e);
334     }
335     try {
336       field.setAccessible(true);
337       return (CustomCache) field.get(cache);
338     } catch (IllegalAccessException e) {
339       throw new IllegalStateException(e);
340     } finally {
341       field.setAccessible(false);
342     }
343   }
344 
345   @CacheNamespace(implementation = CustomCache.class, properties = { @Property(name = "date", value = "2016/11/21") })
346   private interface CustomCacheUnsupportedPropertyMapper {
347   }
348 
349   @CacheNamespaceRef(value = PersonMapper.class, name = "org.apache.ibatis.submitted.cache.PersonMapper")
350   private interface InvalidCacheNamespaceRefBothMapper {
351   }
352 
353   @CacheNamespaceRef
354   private interface InvalidCacheNamespaceRefEmptyMapper {
355   }
356 
357 }