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