View Javadoc
1   /*
2    *    Copyright 2009-2023 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    // @formatter:off
58    /**
59     * Test Plan:
60     *  1) SqlSession 1 executes "select * from A".
61     *  2) SqlSession 1 closes.
62     *  3) SqlSession 2 executes "delete from A where id = 1"
63     *  4) SqlSession 2 executes "select * from A"
64     *
65     * Assert:
66     *   Step 4 returns 1 row. (This case fails when caching is enabled.)
67     */
68    // @formatter:on
69    @Test
70    void testplan1() {
71      try (SqlSession sqlSession1 = sqlSessionFactory.openSession(false)) {
72        PersonMapper pm = sqlSession1.getMapper(PersonMapper.class);
73        Assertions.assertEquals(2, pm.findAll().size());
74      }
75  
76      try (SqlSession sqlSession2 = sqlSessionFactory.openSession(false)) {
77        try {
78          PersonMapper pm = sqlSession2.getMapper(PersonMapper.class);
79          pm.delete(1);
80          Assertions.assertEquals(1, pm.findAll().size());
81        } finally {
82          sqlSession2.commit();
83        }
84      }
85    }
86  
87    // @formatter:off
88    /**
89     * Test Plan:
90     *  1) SqlSession 1 executes "select * from A".
91     *  2) SqlSession 1 closes.
92     *  3) SqlSession 2 executes "delete from A where id = 1"
93     *  4) SqlSession 2 executes "select * from A"
94     *  5) SqlSession 2 rollback
95     *  6) SqlSession 3 executes "select * from A"
96     *
97     * Assert:
98     *   Step 6 returns 2 rows.
99     */
100   // @formatter:on
101   @Test
102   void testplan2() {
103     try (SqlSession sqlSession1 = sqlSessionFactory.openSession(false)) {
104       PersonMapper pm = sqlSession1.getMapper(PersonMapper.class);
105       Assertions.assertEquals(2, pm.findAll().size());
106     }
107 
108     try (SqlSession sqlSession2 = sqlSessionFactory.openSession(false)) {
109       try {
110         PersonMapper pm = sqlSession2.getMapper(PersonMapper.class);
111         pm.delete(1);
112       } finally {
113         sqlSession2.rollback();
114       }
115     }
116 
117     try (SqlSession sqlSession3 = sqlSessionFactory.openSession(false)) {
118       PersonMapper pm = sqlSession3.getMapper(PersonMapper.class);
119       Assertions.assertEquals(2, pm.findAll().size());
120     }
121   }
122 
123   // @formatter:off
124   /**
125    * Test Plan with Autocommit on:
126    *  1) SqlSession 1 executes "select * from A".
127    *  2) SqlSession 1 closes.
128    *  3) SqlSession 2 executes "delete from A where id = 1"
129    *  4) SqlSession 2 closes.
130    *  5) SqlSession 2 executes "select * from A".
131    *  6) SqlSession 3 closes.
132    *
133    * Assert:
134    *   Step 6 returns 1 row.
135    */
136   // @formatter:on
137   @Test
138   void testplan3() {
139     try (SqlSession sqlSession1 = sqlSessionFactory.openSession(true)) {
140       PersonMapper pm = sqlSession1.getMapper(PersonMapper.class);
141       Assertions.assertEquals(2, pm.findAll().size());
142     }
143 
144     try (SqlSession sqlSession2 = sqlSessionFactory.openSession(true)) {
145       PersonMapper pm = sqlSession2.getMapper(PersonMapper.class);
146       pm.delete(1);
147     }
148 
149     try (SqlSession sqlSession3 = sqlSessionFactory.openSession(true)) {
150       PersonMapper pm = sqlSession3.getMapper(PersonMapper.class);
151       Assertions.assertEquals(1, pm.findAll().size());
152     }
153   }
154 
155   // @formatter:off
156   /**
157    * Test case for #405
158    *
159    * Test Plan with Autocommit on:
160    *  1) SqlSession 1 executes "select * from A".
161    *  2) SqlSession 1 closes.
162    *  3) SqlSession 2 executes "insert into person (id, firstname, lastname) values (3, hello, world)"
163    *  4) SqlSession 2 closes.
164    *  5) SqlSession 3 executes "select * from A".
165    *  6) SqlSession 3 closes.
166    *
167    * Assert:
168    *   Step 5 returns 3 row.
169    */
170   // @formatter:on
171   @Test
172   void shouldInsertWithOptionsFlushesCache() {
173     try (SqlSession sqlSession1 = sqlSessionFactory.openSession(true)) {
174       PersonMapper pm = sqlSession1.getMapper(PersonMapper.class);
175       Assertions.assertEquals(2, pm.findAll().size());
176     }
177 
178     try (SqlSession sqlSession2 = sqlSessionFactory.openSession(true)) {
179       PersonMapper pm = sqlSession2.getMapper(PersonMapper.class);
180       Person p = new Person(3, "hello", "world");
181       pm.createWithOptions(p);
182     }
183 
184     try (SqlSession sqlSession3 = sqlSessionFactory.openSession(true)) {
185       PersonMapper pm = sqlSession3.getMapper(PersonMapper.class);
186       Assertions.assertEquals(3, pm.findAll().size());
187     }
188   }
189 
190   // @formatter:off
191   /**
192    * Test Plan with Autocommit on:
193    *  1) SqlSession 1 executes select to cache result
194    *  2) SqlSession 1 closes.
195    *  3) SqlSession 2 executes insert without flushing cache
196    *  4) SqlSession 2 closes.
197    *  5) SqlSession 3 executes select (flushCache = false)
198    *  6) SqlSession 3 closes.
199    *  7) SqlSession 4 executes select (flushCache = true)
200    *  8) SqlSession 4 closes.
201    *
202    * Assert:
203    *   Step 5 returns 2 row.
204    *   Step 7 returns 3 row.
205    */
206   // @formatter:on
207   @Test
208   void shouldApplyFlushCacheOptions() {
209     try (SqlSession sqlSession1 = sqlSessionFactory.openSession(true)) {
210       PersonMapper pm = sqlSession1.getMapper(PersonMapper.class);
211       Assertions.assertEquals(2, pm.findAll().size());
212     }
213 
214     try (SqlSession sqlSession2 = sqlSessionFactory.openSession(true)) {
215       PersonMapper pm = sqlSession2.getMapper(PersonMapper.class);
216       Person p = new Person(3, "hello", "world");
217       pm.createWithoutFlushCache(p);
218     }
219 
220     try (SqlSession sqlSession3 = sqlSessionFactory.openSession(true)) {
221       PersonMapper pm = sqlSession3.getMapper(PersonMapper.class);
222       Assertions.assertEquals(2, pm.findAll().size());
223     }
224 
225     try (SqlSession sqlSession4 = sqlSessionFactory.openSession(true)) {
226       PersonMapper pm = sqlSession4.getMapper(PersonMapper.class);
227       Assertions.assertEquals(3, pm.findWithFlushCache().size());
228     }
229   }
230 
231   @Test
232   void shouldApplyCacheNamespaceRef() {
233     try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
234       PersonMapper pm = sqlSession.getMapper(PersonMapper.class);
235       Assertions.assertEquals(2, pm.findAll().size());
236       Person p = new Person(3, "hello", "world");
237       pm.createWithoutFlushCache(p);
238     }
239     try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
240       PersonMapper pm = sqlSession.getMapper(PersonMapper.class);
241       Assertions.assertEquals(2, pm.findAll().size());
242     }
243     try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
244       ImportantPersonMapper pm = sqlSession.getMapper(ImportantPersonMapper.class);
245       Assertions.assertEquals(3, pm.findWithFlushCache().size());
246     }
247     try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
248       PersonMapper pm = sqlSession.getMapper(PersonMapper.class);
249       Assertions.assertEquals(3, pm.findAll().size());
250       Person p = new Person(4, "foo", "bar");
251       pm.createWithoutFlushCache(p);
252     }
253     try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
254       SpecialPersonMapper pm = sqlSession.getMapper(SpecialPersonMapper.class);
255       Assertions.assertEquals(4, pm.findWithFlushCache().size());
256     }
257     try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
258       PersonMapper pm = sqlSession.getMapper(PersonMapper.class);
259       Assertions.assertEquals(4, pm.findAll().size());
260     }
261   }
262 
263   @Test
264   void shouldResultBeCachedAfterInsert() {
265     try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
266       PersonMapper pm = sqlSession.getMapper(PersonMapper.class);
267       // create
268       Person p = new Person(3, "hello", "world");
269       pm.create(p);
270       // select (result should be cached)
271       Assertions.assertEquals(3, pm.findAll().size());
272       // create without flush (cache unchanged)
273       Person p2 = new Person(4, "bonjour", "world");
274       pm.createWithoutFlushCache(p2);
275     }
276     try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
277       PersonMapper pm = sqlSession.getMapper(PersonMapper.class);
278       Assertions.assertEquals(3, pm.findAll().size());
279     }
280   }
281 
282   @Test
283   void shouldApplyCustomCacheProperties() {
284     CustomCache customCache = unwrap(sqlSessionFactory.getConfiguration().getCache(CustomCacheMapper.class.getName()));
285     Assertions.assertEquals("bar", customCache.getStringValue());
286     Assertions.assertEquals(1, customCache.getIntegerValue().intValue());
287     Assertions.assertEquals(2, customCache.getIntValue());
288     Assertions.assertEquals(3, customCache.getLongWrapperValue().longValue());
289     Assertions.assertEquals(4, customCache.getLongValue());
290     Assertions.assertEquals(5, customCache.getShortWrapperValue().shortValue());
291     Assertions.assertEquals(6, customCache.getShortValue());
292     Assertions.assertEquals((float) 7.1, customCache.getFloatWrapperValue(), 1);
293     Assertions.assertEquals((float) 8.1, customCache.getFloatValue(), 1);
294     Assertions.assertEquals(9.01, customCache.getDoubleWrapperValue(), 1);
295     Assertions.assertEquals(10.01, customCache.getDoubleValue(), 1);
296     Assertions.assertEquals((byte) 11, customCache.getByteWrapperValue().byteValue());
297     Assertions.assertEquals((byte) 12, customCache.getByteValue());
298     Assertions.assertTrue(customCache.getBooleanWrapperValue());
299     Assertions.assertTrue(customCache.isBooleanValue());
300   }
301 
302   @Test
303   void shouldErrorUnsupportedProperties() {
304     when(() -> sqlSessionFactory.getConfiguration().addMapper(CustomCacheUnsupportedPropertyMapper.class));
305     then(caughtException()).isInstanceOf(CacheException.class)
306         .hasMessage("Unsupported property type for cache: 'date' of type class java.util.Date");
307   }
308 
309   @Test
310   void shouldErrorInvalidCacheNamespaceRefAttributesSpecifyBoth() {
311     when(() -> sqlSessionFactory.getConfiguration().getMapperRegistry()
312         .addMapper(InvalidCacheNamespaceRefBothMapper.class));
313     then(caughtException()).isInstanceOf(BuilderException.class)
314         .hasMessage("Cannot use both value() and name() attribute in the @CacheNamespaceRef");
315   }
316 
317   @Test
318   void shouldErrorInvalidCacheNamespaceRefAttributesIsEmpty() {
319     when(() -> sqlSessionFactory.getConfiguration().getMapperRegistry()
320         .addMapper(InvalidCacheNamespaceRefEmptyMapper.class));
321     then(caughtException()).isInstanceOf(BuilderException.class)
322         .hasMessage("Should be specified either value() or name() attribute in the @CacheNamespaceRef");
323   }
324 
325   private CustomCache unwrap(Cache cache) {
326     Field field;
327     try {
328       field = cache.getClass().getDeclaredField("delegate");
329     } catch (NoSuchFieldException e) {
330       throw new IllegalStateException(e);
331     }
332     try {
333       field.setAccessible(true);
334       return (CustomCache) field.get(cache);
335     } catch (IllegalAccessException e) {
336       throw new IllegalStateException(e);
337     } finally {
338       field.setAccessible(false);
339     }
340   }
341 
342   // @formatter:off
343   @CacheNamespace(implementation = CustomCache.class, properties = {
344       @Property(name = "date", value = "2016/11/21")
345     })
346   // @formatter:on
347   private interface CustomCacheUnsupportedPropertyMapper {
348   }
349 
350   @CacheNamespaceRef(value = PersonMapper.class, name = "org.apache.ibatis.submitted.cache.PersonMapper")
351   private interface InvalidCacheNamespaceRefBothMapper {
352   }
353 
354   @CacheNamespaceRef
355   private interface InvalidCacheNamespaceRefEmptyMapper {
356   }
357 
358 }