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.cursor_simple;
17  
18  import java.io.IOException;
19  import java.io.Reader;
20  import java.util.ArrayList;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.NoSuchElementException;
24  
25  import org.apache.ibatis.BaseDataTest;
26  import org.apache.ibatis.cursor.Cursor;
27  import org.apache.ibatis.io.Resources;
28  import org.apache.ibatis.session.RowBounds;
29  import org.apache.ibatis.session.SqlSession;
30  import org.apache.ibatis.session.SqlSessionFactory;
31  import org.apache.ibatis.session.SqlSessionFactoryBuilder;
32  import org.junit.jupiter.api.Assertions;
33  import org.junit.jupiter.api.BeforeAll;
34  import org.junit.jupiter.api.Test;
35  
36  class CursorSimpleTest {
37  
38    private static SqlSessionFactory sqlSessionFactory;
39  
40    @BeforeAll
41    static void setUp() throws Exception {
42      // create a SqlSessionFactory
43      try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/cursor_simple/mybatis-config.xml")) {
44        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
45      }
46  
47      // populate in-memory database
48      BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
49          "org/apache/ibatis/submitted/cursor_simple/CreateDB.sql");
50    }
51  
52    @Test
53    void shouldGetAllUser() {
54      try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
55        Mapper mapper = sqlSession.getMapper(Mapper.class);
56        Cursor<User> usersCursor = mapper.getAllUsers();
57  
58        Assertions.assertFalse(usersCursor.isOpen());
59  
60        // Cursor is just created, current index is -1
61        Assertions.assertEquals(-1, usersCursor.getCurrentIndex());
62  
63        Iterator<User> iterator = usersCursor.iterator();
64  
65        // Check if hasNext, fetching is started
66        Assertions.assertTrue(iterator.hasNext());
67        Assertions.assertTrue(usersCursor.isOpen());
68        Assertions.assertFalse(usersCursor.isConsumed());
69  
70        // next() has not been called, index is still -1
71        Assertions.assertEquals(-1, usersCursor.getCurrentIndex());
72  
73        User user = iterator.next();
74        Assertions.assertEquals("User1", user.getName());
75        Assertions.assertEquals(0, usersCursor.getCurrentIndex());
76  
77        user = iterator.next();
78        Assertions.assertEquals("User2", user.getName());
79        Assertions.assertEquals(1, usersCursor.getCurrentIndex());
80  
81        user = iterator.next();
82        Assertions.assertEquals("User3", user.getName());
83        Assertions.assertEquals(2, usersCursor.getCurrentIndex());
84  
85        user = iterator.next();
86        Assertions.assertEquals("User4", user.getName());
87        Assertions.assertEquals(3, usersCursor.getCurrentIndex());
88  
89        user = iterator.next();
90        Assertions.assertEquals("User5", user.getName());
91        Assertions.assertEquals(4, usersCursor.getCurrentIndex());
92  
93        // Check no more elements
94        Assertions.assertFalse(iterator.hasNext());
95        Assertions.assertFalse(usersCursor.isOpen());
96        Assertions.assertTrue(usersCursor.isConsumed());
97      }
98    }
99  
100   @Test
101   void testCursorClosedOnSessionClose() {
102     Cursor<User> usersCursor;
103     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
104       Mapper mapper = sqlSession.getMapper(Mapper.class);
105       usersCursor = mapper.getAllUsers();
106 
107       Assertions.assertFalse(usersCursor.isOpen());
108 
109       Iterator<User> iterator = usersCursor.iterator();
110 
111       // Check if hasNext, fetching is started
112       Assertions.assertTrue(iterator.hasNext());
113       Assertions.assertTrue(usersCursor.isOpen());
114       Assertions.assertFalse(usersCursor.isConsumed());
115 
116       // Consume only the first result
117       User user = iterator.next();
118       Assertions.assertEquals("User1", user.getName());
119 
120       // Check there is still remaining elements
121       Assertions.assertTrue(iterator.hasNext());
122       Assertions.assertTrue(usersCursor.isOpen());
123       Assertions.assertFalse(usersCursor.isConsumed());
124     }
125 
126     // The cursor was not fully consumed, but it should be close since we closed the session
127     Assertions.assertFalse(usersCursor.isOpen());
128     Assertions.assertFalse(usersCursor.isConsumed());
129   }
130 
131   @Test
132   void testCursorWithRowBound() {
133     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
134       // RowBound starting at offset 1 and limiting to 2 items
135       Cursor<User> usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(1, 3));
136 
137       Iterator<User> iterator = usersCursor.iterator();
138 
139       User user = iterator.next();
140       Assertions.assertEquals("User2", user.getName());
141       Assertions.assertEquals(1, usersCursor.getCurrentIndex());
142 
143       // Calling hasNext() before next()
144       Assertions.assertTrue(iterator.hasNext());
145       user = iterator.next();
146       Assertions.assertEquals("User3", user.getName());
147       Assertions.assertEquals(2, usersCursor.getCurrentIndex());
148 
149       // Calling next() without a previous hasNext() call
150       user = iterator.next();
151       Assertions.assertEquals("User4", user.getName());
152       Assertions.assertEquals(3, usersCursor.getCurrentIndex());
153 
154       Assertions.assertFalse(iterator.hasNext());
155       Assertions.assertFalse(usersCursor.isOpen());
156       Assertions.assertTrue(usersCursor.isConsumed());
157     }
158   }
159 
160   @Test
161   void testCursorIteratorNoSuchElementExceptionWithHasNext() throws IOException {
162 
163     try (SqlSession sqlSession = sqlSessionFactory.openSession();
164         Cursor<User> usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(1, 1))) {
165       try {
166         Iterator<User> iterator = usersCursor.iterator();
167 
168         User user = iterator.next();
169         Assertions.assertEquals("User2", user.getName());
170         Assertions.assertEquals(1, usersCursor.getCurrentIndex());
171 
172         Assertions.assertFalse(iterator.hasNext());
173         iterator.next();
174         Assertions.fail("We should have failed since we call next() when hasNext() returned false");
175       } catch (NoSuchElementException e) {
176         Assertions.assertFalse(usersCursor.isOpen());
177         Assertions.assertTrue(usersCursor.isConsumed());
178       }
179     }
180   }
181 
182   @Test
183   void testCursorIteratorNoSuchElementExceptionNoHasNext() throws IOException {
184     try (SqlSession sqlSession = sqlSessionFactory.openSession();
185         Cursor<User> usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(1, 1))) {
186       try {
187         Iterator<User> iterator = usersCursor.iterator();
188         User user = iterator.next();
189         Assertions.assertEquals("User2", user.getName());
190         Assertions.assertEquals(1, usersCursor.getCurrentIndex());
191 
192         // Trying next() without hasNext()
193         iterator.next();
194         Assertions.fail("We should have failed since we call next() when is no more items");
195       } catch (NoSuchElementException e) {
196         Assertions.assertFalse(usersCursor.isOpen());
197         Assertions.assertTrue(usersCursor.isConsumed());
198       }
199     }
200   }
201 
202   @Test
203   void testCursorWithBadRowBound() {
204     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
205       // Trying to start at offset 10 (which does not exist, since there is only 4 items)
206       Cursor<User> usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(10, 2));
207       Iterator<User> iterator = usersCursor.iterator();
208 
209       Assertions.assertFalse(iterator.hasNext());
210       Assertions.assertFalse(usersCursor.isOpen());
211       Assertions.assertTrue(usersCursor.isConsumed());
212     }
213   }
214 
215   @Test
216   void testCursorMultipleHasNextCall() {
217     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
218       Mapper mapper = sqlSession.getMapper(Mapper.class);
219       Cursor<User> usersCursor = mapper.getAllUsers();
220 
221       Iterator<User> iterator = usersCursor.iterator();
222 
223       Assertions.assertEquals(-1, usersCursor.getCurrentIndex());
224 
225       User user = iterator.next();
226       Assertions.assertEquals("User1", user.getName());
227       Assertions.assertEquals(0, usersCursor.getCurrentIndex());
228 
229       Assertions.assertTrue(iterator.hasNext());
230       Assertions.assertTrue(iterator.hasNext());
231       Assertions.assertTrue(iterator.hasNext());
232       // assert that index has not changed after hasNext() call
233       Assertions.assertEquals(0, usersCursor.getCurrentIndex());
234     }
235   }
236 
237   @Test
238   void testCursorMultipleIteratorCall() {
239     Iterator<User> iterator2 = null;
240     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
241       Mapper mapper = sqlSession.getMapper(Mapper.class);
242       Cursor<User> usersCursor = mapper.getAllUsers();
243 
244       Iterator<User> iterator = usersCursor.iterator();
245       User user = iterator.next();
246       Assertions.assertEquals("User1", user.getName());
247       Assertions.assertEquals(0, usersCursor.getCurrentIndex());
248 
249       iterator2 = usersCursor.iterator();
250       iterator2.hasNext();
251       Assertions.fail("We should have failed since calling iterator several times is not allowed");
252     } catch (IllegalStateException e) {
253       Assertions.assertNull(iterator2, "iterator2 should be null");
254       return;
255     }
256     Assertions.fail("Should have returned earlier");
257   }
258 
259   @Test
260   void testCursorMultipleCloseCall() throws IOException {
261     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
262       Mapper mapper = sqlSession.getMapper(Mapper.class);
263       Cursor<User> usersCursor = mapper.getAllUsers();
264 
265       Assertions.assertFalse(usersCursor.isOpen());
266 
267       Iterator<User> iterator = usersCursor.iterator();
268 
269       // Check if hasNext, fetching is started
270       Assertions.assertTrue(iterator.hasNext());
271       Assertions.assertTrue(usersCursor.isOpen());
272       Assertions.assertFalse(usersCursor.isConsumed());
273 
274       // Consume only the first result
275       User user = iterator.next();
276       Assertions.assertEquals("User1", user.getName());
277 
278       usersCursor.close();
279       // Check multiple close are no-op
280       usersCursor.close();
281 
282       // hasNext now return false, since the cursor is closed
283       Assertions.assertFalse(iterator.hasNext());
284       Assertions.assertFalse(usersCursor.isOpen());
285       Assertions.assertFalse(usersCursor.isConsumed());
286     }
287   }
288 
289   @Test
290   void testCursorUsageAfterClose() throws IOException {
291 
292     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
293       Mapper mapper = sqlSession.getMapper(Mapper.class);
294 
295       Cursor<User> usersCursor = mapper.getAllUsers();
296       try {
297         Iterator<User> iterator = usersCursor.iterator();
298         User user = iterator.next();
299         Assertions.assertEquals("User1", user.getName());
300         Assertions.assertEquals(0, usersCursor.getCurrentIndex());
301 
302         user = iterator.next();
303         Assertions.assertEquals("User2", user.getName());
304         Assertions.assertEquals(1, usersCursor.getCurrentIndex());
305 
306         usersCursor.close();
307 
308         // hasNext now return false, since the cursor is closed
309         Assertions.assertFalse(iterator.hasNext());
310         Assertions.assertFalse(usersCursor.isOpen());
311         Assertions.assertFalse(usersCursor.isConsumed());
312 
313         // trying next() will fail
314         iterator.next();
315 
316         Assertions.fail("We should have failed with NoSuchElementException since Cursor is closed");
317       } catch (NoSuchElementException e) {
318         // We had an exception and current index has not changed
319         Assertions.assertEquals(1, usersCursor.getCurrentIndex());
320         usersCursor.close();
321         return;
322       }
323     }
324 
325     Assertions.fail("Should have returned earlier");
326   }
327 
328   @Test
329   void shouldGetAllUserUsingAnnotationBasedMapper() {
330     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
331       sqlSession.getConfiguration().getMapperRegistry().addMapper(AnnotationMapper.class);
332       AnnotationMapper mapper = sqlSession.getMapper(AnnotationMapper.class);
333       Cursor<User> usersCursor = mapper.getAllUsers();
334 
335       Assertions.assertFalse(usersCursor.isOpen());
336       Assertions.assertFalse(usersCursor.isConsumed());
337       Assertions.assertEquals(-1, usersCursor.getCurrentIndex());
338 
339       List<User> userList = new ArrayList<>();
340       for (User user : usersCursor) {
341         userList.add(user);
342         Assertions.assertEquals(userList.size() - 1, usersCursor.getCurrentIndex());
343       }
344 
345       Assertions.assertFalse(usersCursor.isOpen());
346       Assertions.assertTrue(usersCursor.isConsumed());
347       Assertions.assertEquals(4, usersCursor.getCurrentIndex());
348 
349       Assertions.assertEquals(5, userList.size());
350       User user = userList.get(0);
351       Assertions.assertEquals("User1", user.getName());
352       user = userList.get(1);
353       Assertions.assertEquals("User2", user.getName());
354       user = userList.get(2);
355       Assertions.assertEquals("User3", user.getName());
356       user = userList.get(3);
357       Assertions.assertEquals("User4", user.getName());
358       user = userList.get(4);
359       Assertions.assertEquals("User5", user.getName());
360     }
361   }
362 
363   @Test
364   void shouldThrowIllegalStateExceptionUsingIteratorOnSessionClosed() {
365     Cursor<User> usersCursor;
366     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
367       usersCursor = sqlSession.getMapper(Mapper.class).getAllUsers();
368     }
369     try {
370       usersCursor.iterator();
371       Assertions.fail("Should throws the IllegalStateException when call the iterator method after session is closed.");
372     } catch (IllegalStateException e) {
373       Assertions.assertEquals("A Cursor is already closed.", e.getMessage());
374     }
375 
376     // verify for checking order
377     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
378       usersCursor = sqlSession.getMapper(Mapper.class).getAllUsers();
379       usersCursor.iterator();
380     }
381     try {
382       usersCursor.iterator();
383       Assertions.fail("Should throws the IllegalStateException when call the iterator already.");
384     } catch (IllegalStateException e) {
385       Assertions.assertEquals("Cannot open more than one iterator on a Cursor", e.getMessage());
386     }
387 
388   }
389 
390   @Test
391   void shouldNullItemNotStopIteration() {
392     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
393       Mapper mapper = sqlSession.getMapper(Mapper.class);
394       Cursor<User> cursor = mapper.getNullUsers(new RowBounds());
395       Iterator<User> iterator = cursor.iterator();
396 
397       Assertions.assertFalse(cursor.isOpen());
398 
399       // Cursor is just created, current index is -1
400       Assertions.assertEquals(-1, cursor.getCurrentIndex());
401 
402       // Check if hasNext, fetching is started
403       Assertions.assertTrue(iterator.hasNext());
404       // Re-invoking hasNext() should not fetch the next row
405       Assertions.assertTrue(iterator.hasNext());
406       Assertions.assertTrue(cursor.isOpen());
407       Assertions.assertFalse(cursor.isConsumed());
408 
409       // next() has not been called, index is still -1
410       Assertions.assertEquals(-1, cursor.getCurrentIndex());
411 
412       User user;
413       user = iterator.next();
414       Assertions.assertNull(user);
415       Assertions.assertEquals(0, cursor.getCurrentIndex());
416 
417       Assertions.assertTrue(iterator.hasNext());
418       user = iterator.next();
419       Assertions.assertEquals("Kate", user.getName());
420       Assertions.assertEquals(1, cursor.getCurrentIndex());
421 
422       Assertions.assertTrue(iterator.hasNext());
423       user = iterator.next();
424       Assertions.assertNull(user);
425       Assertions.assertEquals(2, cursor.getCurrentIndex());
426 
427       Assertions.assertTrue(iterator.hasNext());
428       user = iterator.next();
429       Assertions.assertNull(user);
430       Assertions.assertEquals(3, cursor.getCurrentIndex());
431 
432       // Check no more elements
433       Assertions.assertFalse(iterator.hasNext());
434       Assertions.assertFalse(cursor.isOpen());
435       Assertions.assertTrue(cursor.isConsumed());
436     }
437   }
438 
439   @Test
440   void shouldRowBoundsCountNullItem() {
441     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
442       Mapper mapper = sqlSession.getMapper(Mapper.class);
443       Cursor<User> cursor = mapper.getNullUsers(new RowBounds(1, 2));
444       Iterator<User> iterator = cursor.iterator();
445 
446       Assertions.assertFalse(cursor.isOpen());
447 
448       // Check if hasNext, fetching is started
449       Assertions.assertTrue(iterator.hasNext());
450       // Re-invoking hasNext() should not fetch the next row
451       Assertions.assertTrue(iterator.hasNext());
452       Assertions.assertTrue(cursor.isOpen());
453       Assertions.assertFalse(cursor.isConsumed());
454 
455       User user;
456       user = iterator.next();
457       Assertions.assertEquals("Kate", user.getName());
458       Assertions.assertEquals(1, cursor.getCurrentIndex());
459 
460       Assertions.assertTrue(iterator.hasNext());
461       user = iterator.next();
462       Assertions.assertNull(user);
463       Assertions.assertEquals(2, cursor.getCurrentIndex());
464 
465       // Check no more elements
466       Assertions.assertFalse(iterator.hasNext());
467       Assertions.assertFalse(cursor.isOpen());
468       Assertions.assertTrue(cursor.isConsumed());
469     }
470   }
471 }