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.binding;
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  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertNotEquals;
24  import static org.junit.jupiter.api.Assertions.assertNotNull;
25  import static org.junit.jupiter.api.Assertions.assertNotSame;
26  import static org.junit.jupiter.api.Assertions.assertNull;
27  import static org.junit.jupiter.api.Assertions.assertSame;
28  import static org.junit.jupiter.api.Assertions.assertThrows;
29  import static org.junit.jupiter.api.Assertions.assertTrue;
30  
31  import java.io.IOException;
32  import java.lang.reflect.Method;
33  import java.util.ArrayList;
34  import java.util.Collection;
35  import java.util.Collections;
36  import java.util.HashMap;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Map;
40  
41  import javassist.util.proxy.Proxy;
42  
43  import javax.sql.DataSource;
44  
45  import net.sf.cglib.proxy.Factory;
46  
47  import org.apache.ibatis.BaseDataTest;
48  import org.apache.ibatis.binding.MapperProxy.MapperMethodInvoker;
49  import org.apache.ibatis.cursor.Cursor;
50  import org.apache.ibatis.domain.blog.Author;
51  import org.apache.ibatis.domain.blog.Blog;
52  import org.apache.ibatis.domain.blog.DraftPost;
53  import org.apache.ibatis.domain.blog.Post;
54  import org.apache.ibatis.domain.blog.Section;
55  import org.apache.ibatis.exceptions.PersistenceException;
56  import org.apache.ibatis.executor.result.DefaultResultHandler;
57  import org.apache.ibatis.mapping.Environment;
58  import org.apache.ibatis.session.Configuration;
59  import org.apache.ibatis.session.RowBounds;
60  import org.apache.ibatis.session.SqlSession;
61  import org.apache.ibatis.session.SqlSessionFactory;
62  import org.apache.ibatis.session.SqlSessionFactoryBuilder;
63  import org.apache.ibatis.transaction.TransactionFactory;
64  import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
65  import org.junit.jupiter.api.Assertions;
66  import org.junit.jupiter.api.BeforeAll;
67  import org.junit.jupiter.api.Disabled;
68  import org.junit.jupiter.api.Test;
69  
70  class BindingTest {
71    private static SqlSessionFactory sqlSessionFactory;
72  
73    @BeforeAll
74    static void setup() throws Exception {
75      DataSource dataSource = BaseDataTest.createBlogDataSource();
76      BaseDataTest.runScript(dataSource, BaseDataTest.BLOG_DDL);
77      BaseDataTest.runScript(dataSource, BaseDataTest.BLOG_DATA);
78      TransactionFactory transactionFactory = new JdbcTransactionFactory();
79      Environment environment = new Environment("Production", transactionFactory, dataSource);
80      Configuration configuration = new Configuration(environment);
81      configuration.setLazyLoadingEnabled(true);
82      configuration.setUseActualParamName(false); // to test legacy style reference (#{0} #{1})
83      configuration.getTypeAliasRegistry().registerAlias(Blog.class);
84      configuration.getTypeAliasRegistry().registerAlias(Post.class);
85      configuration.getTypeAliasRegistry().registerAlias(Author.class);
86      configuration.addMapper(BoundBlogMapper.class);
87      configuration.addMapper(BoundAuthorMapper.class);
88      sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
89    }
90  
91    @Test
92    void shouldSelectBlogWithPostsUsingSubSelect() {
93      try (SqlSession session = sqlSessionFactory.openSession()) {
94        BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
95        Blog b = mapper.selectBlogWithPostsUsingSubSelect(1);
96        assertEquals(1, b.getId());
97        assertNotNull(b.getAuthor());
98        assertEquals(101, b.getAuthor().getId());
99        assertEquals("jim", b.getAuthor().getUsername());
100       assertEquals("********", b.getAuthor().getPassword());
101       assertEquals(2, b.getPosts().size());
102     }
103   }
104 
105   @Test
106   void shouldFindPostsInList() {
107     try (SqlSession session = sqlSessionFactory.openSession()) {
108       BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
109       List<Post> posts = mapper.findPostsInList(new ArrayList<Integer>() {
110         private static final long serialVersionUID = 1L;
111         {
112           add(1);
113           add(3);
114           add(5);
115         }
116       });
117       assertEquals(3, posts.size());
118       session.rollback();
119     }
120   }
121 
122   @Test
123   void shouldFindPostsInArray() {
124     try (SqlSession session = sqlSessionFactory.openSession()) {
125       BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
126       Integer[] params = { 1, 3, 5 };
127       List<Post> posts = mapper.findPostsInArray(params);
128       assertEquals(3, posts.size());
129       session.rollback();
130     }
131   }
132 
133   @Test
134   void shouldFindThreeSpecificPosts() {
135     try (SqlSession session = sqlSessionFactory.openSession()) {
136       BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
137       List<Post> posts = mapper.findThreeSpecificPosts(1, new RowBounds(1, 1), 3, 5);
138       assertEquals(1, posts.size());
139       assertEquals(3, posts.get(0).getId());
140       session.rollback();
141     }
142   }
143 
144   @Test
145   void shouldInsertAuthorWithSelectKey() {
146     try (SqlSession session = sqlSessionFactory.openSession()) {
147       BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
148       Author author = new Author(-1, "cbegin", "******", "cbegin@nowhere.com", "N/A", Section.NEWS);
149       int rows = mapper.insertAuthor(author);
150       assertEquals(1, rows);
151       session.rollback();
152     }
153   }
154 
155   @Test
156   void verifyErrorMessageFromSelectKey() {
157     try (SqlSession session = sqlSessionFactory.openSession()) {
158       try {
159         BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
160         Author author = new Author(-1, "cbegin", "******", "cbegin@nowhere.com", "N/A", Section.NEWS);
161         when(() -> mapper.insertAuthorInvalidSelectKey(author));
162         then(caughtException()).isInstanceOf(PersistenceException.class)
163             .hasMessageContaining("### The error may exist in org/apache/ibatis/binding/BoundAuthorMapper.xml"
164                 + System.lineSeparator()
165                 + "### The error may involve org.apache.ibatis.binding.BoundAuthorMapper.insertAuthorInvalidSelectKey!selectKey"
166                 + System.lineSeparator() + "### The error occurred while executing a query");
167       } finally {
168         session.rollback();
169       }
170     }
171   }
172 
173   @Test
174   void verifyErrorMessageFromInsertAfterSelectKey() {
175     try (SqlSession session = sqlSessionFactory.openSession()) {
176       try {
177         BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
178         Author author = new Author(-1, "cbegin", "******", "cbegin@nowhere.com", "N/A", Section.NEWS);
179         when(() -> mapper.insertAuthorInvalidInsert(author));
180         then(caughtException()).isInstanceOf(PersistenceException.class).hasMessageContaining(
181             "### The error may exist in org/apache/ibatis/binding/BoundAuthorMapper.xml" + System.lineSeparator()
182                 + "### The error may involve org.apache.ibatis.binding.BoundAuthorMapper.insertAuthorInvalidInsert"
183                 + System.lineSeparator() + "### The error occurred while executing an update");
184       } finally {
185         session.rollback();
186       }
187     }
188   }
189 
190   @Test
191   void shouldInsertAuthorWithSelectKeyAndDynamicParams() {
192     try (SqlSession session = sqlSessionFactory.openSession()) {
193       BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
194       Author author = new Author(-1, "cbegin", "******", "cbegin@nowhere.com", "N/A", Section.NEWS);
195       int rows = mapper.insertAuthorDynamic(author);
196       assertEquals(1, rows);
197       assertNotEquals(-1, author.getId()); // id must be autogenerated
198       Author author2 = mapper.selectAuthor(author.getId());
199       assertNotNull(author2);
200       assertEquals(author.getEmail(), author2.getEmail());
201       session.rollback();
202     }
203   }
204 
205   @Test
206   void shouldSelectRandom() {
207     try (SqlSession session = sqlSessionFactory.openSession()) {
208       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
209       Integer x = mapper.selectRandom();
210       assertNotNull(x);
211     }
212   }
213 
214   @Test
215   void shouldExecuteBoundSelectListOfBlogsStatement() {
216     try (SqlSession session = sqlSessionFactory.openSession()) {
217       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
218       List<Blog> blogs = mapper.selectBlogs();
219       assertEquals(2, blogs.size());
220     }
221   }
222 
223   @Test
224   void shouldExecuteBoundSelectMapOfBlogsById() {
225     try (SqlSession session = sqlSessionFactory.openSession()) {
226       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
227       Map<Integer, Blog> blogs = mapper.selectBlogsAsMapById();
228       assertEquals(2, blogs.size());
229       for (Map.Entry<Integer, Blog> blogEntry : blogs.entrySet()) {
230         assertEquals(blogEntry.getKey(), (Integer) blogEntry.getValue().getId());
231       }
232     }
233   }
234 
235   @Test
236   void shouldExecuteMultipleBoundSelectOfBlogsByIdInWithProvidedResultHandlerBetweenSessions() {
237     final DefaultResultHandler handler = new DefaultResultHandler();
238     try (SqlSession session = sqlSessionFactory.openSession()) {
239       session.select("selectBlogsAsMapById", handler);
240     }
241 
242     final DefaultResultHandler moreHandler = new DefaultResultHandler();
243     try (SqlSession session = sqlSessionFactory.openSession()) {
244       session.select("selectBlogsAsMapById", moreHandler);
245     }
246     assertEquals(2, handler.getResultList().size());
247     assertEquals(2, moreHandler.getResultList().size());
248   }
249 
250   @Test
251   void shouldExecuteMultipleBoundSelectOfBlogsByIdInWithProvidedResultHandlerInSameSession() {
252     try (SqlSession session = sqlSessionFactory.openSession()) {
253       final DefaultResultHandler handler = new DefaultResultHandler();
254       session.select("selectBlogsAsMapById", handler);
255 
256       final DefaultResultHandler moreHandler = new DefaultResultHandler();
257       session.select("selectBlogsAsMapById", moreHandler);
258 
259       assertEquals(2, handler.getResultList().size());
260       assertEquals(2, moreHandler.getResultList().size());
261     }
262   }
263 
264   @Test
265   void shouldExecuteMultipleBoundSelectMapOfBlogsByIdInSameSessionWithoutClearingLocalCache() {
266     try (SqlSession session = sqlSessionFactory.openSession()) {
267       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
268       Map<Integer, Blog> blogs = mapper.selectBlogsAsMapById();
269       Map<Integer, Blog> moreBlogs = mapper.selectBlogsAsMapById();
270       assertEquals(2, blogs.size());
271       assertEquals(2, moreBlogs.size());
272       for (Map.Entry<Integer, Blog> blogEntry : blogs.entrySet()) {
273         assertEquals(blogEntry.getKey(), (Integer) blogEntry.getValue().getId());
274       }
275       for (Map.Entry<Integer, Blog> blogEntry : moreBlogs.entrySet()) {
276         assertEquals(blogEntry.getKey(), (Integer) blogEntry.getValue().getId());
277       }
278     }
279   }
280 
281   @Test
282   void shouldExecuteMultipleBoundSelectMapOfBlogsByIdBetweenTwoSessionsWithGlobalCacheEnabled() {
283     Map<Integer, Blog> blogs;
284     try (SqlSession session = sqlSessionFactory.openSession()) {
285       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
286       blogs = mapper.selectBlogsAsMapById();
287     }
288     try (SqlSession session = sqlSessionFactory.openSession()) {
289       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
290       Map<Integer, Blog> moreBlogs = mapper.selectBlogsAsMapById();
291       assertEquals(2, blogs.size());
292       assertEquals(2, moreBlogs.size());
293       for (Map.Entry<Integer, Blog> blogEntry : blogs.entrySet()) {
294         assertEquals(blogEntry.getKey(), (Integer) blogEntry.getValue().getId());
295       }
296       for (Map.Entry<Integer, Blog> blogEntry : moreBlogs.entrySet()) {
297         assertEquals(blogEntry.getKey(), (Integer) blogEntry.getValue().getId());
298       }
299     }
300   }
301 
302   @Test
303   void shouldSelectListOfBlogsUsingXMLConfig() {
304     try (SqlSession session = sqlSessionFactory.openSession()) {
305       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
306       List<Blog> blogs = mapper.selectBlogsFromXML();
307       assertEquals(2, blogs.size());
308     }
309   }
310 
311   @Test
312   void shouldExecuteBoundSelectListOfBlogsStatementUsingProvider() {
313     try (SqlSession session = sqlSessionFactory.openSession()) {
314       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
315       List<Blog> blogs = mapper.selectBlogsUsingProvider();
316       assertEquals(2, blogs.size());
317     }
318   }
319 
320   @Test
321   void shouldExecuteBoundSelectListOfBlogsAsMaps() {
322     try (SqlSession session = sqlSessionFactory.openSession()) {
323       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
324       List<Map<String, Object>> blogs = mapper.selectBlogsAsMaps();
325       assertEquals(2, blogs.size());
326     }
327   }
328 
329   @Test
330   void shouldSelectListOfPostsLike() {
331     try (SqlSession session = sqlSessionFactory.openSession()) {
332       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
333       List<Post> posts = mapper.selectPostsLike(new RowBounds(1, 1), "%a%");
334       assertEquals(1, posts.size());
335     }
336   }
337 
338   @Test
339   void shouldSelectListOfPostsLikeTwoParameters() {
340     try (SqlSession session = sqlSessionFactory.openSession()) {
341       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
342       List<Post> posts = mapper.selectPostsLikeSubjectAndBody(new RowBounds(1, 1), "%a%", "%a%");
343       assertEquals(1, posts.size());
344     }
345   }
346 
347   @Test
348   void shouldExecuteBoundSelectOneBlogStatement() {
349     try (SqlSession session = sqlSessionFactory.openSession()) {
350       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
351       Blog blog = mapper.selectBlog(1);
352       assertEquals(1, blog.getId());
353       assertEquals("Jim Business", blog.getTitle());
354     }
355   }
356 
357   @Test
358   void shouldExecuteBoundSelectOneBlogStatementWithConstructor() {
359     try (SqlSession session = sqlSessionFactory.openSession()) {
360       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
361       Blog blog = mapper.selectBlogUsingConstructor(1);
362       assertEquals(1, blog.getId());
363       assertEquals("Jim Business", blog.getTitle());
364       assertNotNull(blog.getAuthor(), "author should not be null");
365       List<Post> posts = blog.getPosts();
366       assertTrue(posts != null && !posts.isEmpty(), "posts should not be empty");
367     }
368   }
369 
370   @Test
371   void shouldExecuteBoundSelectBlogUsingConstructorWithResultMap() {
372     try (SqlSession session = sqlSessionFactory.openSession()) {
373       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
374       Blog blog = mapper.selectBlogUsingConstructorWithResultMap(1);
375       assertEquals(1, blog.getId());
376       assertEquals("Jim Business", blog.getTitle());
377       assertNotNull(blog.getAuthor(), "author should not be null");
378       List<Post> posts = blog.getPosts();
379       assertTrue(posts != null && !posts.isEmpty(), "posts should not be empty");
380     }
381   }
382 
383   @Test
384   void shouldExecuteBoundSelectBlogUsingConstructorWithResultMapAndProperties() {
385     try (SqlSession session = sqlSessionFactory.openSession()) {
386       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
387       Blog blog = mapper.selectBlogUsingConstructorWithResultMapAndProperties(1);
388       assertEquals(1, blog.getId());
389       assertEquals("Jim Business", blog.getTitle());
390       assertNotNull(blog.getAuthor(), "author should not be null");
391       Author author = blog.getAuthor();
392       assertEquals(101, author.getId());
393       assertEquals("jim@ibatis.apache.org", author.getEmail());
394       assertEquals("jim", author.getUsername());
395       assertEquals(Section.NEWS, author.getFavouriteSection());
396       List<Post> posts = blog.getPosts();
397       assertNotNull(posts, "posts should not be empty");
398       assertEquals(2, posts.size());
399     }
400   }
401 
402   @Disabled
403   @Test // issue #480 and #101
404   void shouldExecuteBoundSelectBlogUsingConstructorWithResultMapCollection() {
405     try (SqlSession session = sqlSessionFactory.openSession()) {
406       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
407       Blog blog = mapper.selectBlogUsingConstructorWithResultMapCollection(1);
408       assertEquals(1, blog.getId());
409       assertEquals("Jim Business", blog.getTitle());
410       assertNotNull(blog.getAuthor(), "author should not be null");
411       List<Post> posts = blog.getPosts();
412       assertTrue(posts != null && !posts.isEmpty(), "posts should not be empty");
413     }
414   }
415 
416   @Test
417   void shouldExecuteBoundSelectOneBlogStatementWithConstructorUsingXMLConfig() {
418     try (SqlSession session = sqlSessionFactory.openSession()) {
419       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
420       Blog blog = mapper.selectBlogByIdUsingConstructor(1);
421       assertEquals(1, blog.getId());
422       assertEquals("Jim Business", blog.getTitle());
423       assertNotNull(blog.getAuthor(), "author should not be null");
424       List<Post> posts = blog.getPosts();
425       assertTrue(posts != null && !posts.isEmpty(), "posts should not be empty");
426     }
427   }
428 
429   @Test
430   void shouldSelectOneBlogAsMap() {
431     try (SqlSession session = sqlSessionFactory.openSession()) {
432       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
433       Map<String, Object> blog = mapper.selectBlogAsMap(new HashMap<String, Object>() {
434         private static final long serialVersionUID = 1L;
435         {
436           put("id", 1);
437         }
438       });
439       assertEquals(1, blog.get("ID"));
440       assertEquals("Jim Business", blog.get("TITLE"));
441     }
442   }
443 
444   @Test
445   void shouldSelectOneAuthor() {
446     try (SqlSession session = sqlSessionFactory.openSession()) {
447       BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
448       Author author = mapper.selectAuthor(101);
449       assertEquals(101, author.getId());
450       assertEquals("jim", author.getUsername());
451       assertEquals("********", author.getPassword());
452       assertEquals("jim@ibatis.apache.org", author.getEmail());
453       assertEquals("", author.getBio());
454     }
455   }
456 
457   @Test
458   void shouldSelectOneAuthorFromCache() {
459     Author author1 = selectOneAuthor();
460     Author author2 = selectOneAuthor();
461     assertSame(author1, author2, "Same (cached) instance should be returned unless rollback is called.");
462   }
463 
464   private Author selectOneAuthor() {
465     try (SqlSession session = sqlSessionFactory.openSession()) {
466       BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
467       return mapper.selectAuthor(101);
468     }
469   }
470 
471   @Test
472   void shouldSelectOneAuthorByConstructor() {
473     try (SqlSession session = sqlSessionFactory.openSession()) {
474       BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
475       Author author = mapper.selectAuthorConstructor(101);
476       assertEquals(101, author.getId());
477       assertEquals("jim", author.getUsername());
478       assertEquals("********", author.getPassword());
479       assertEquals("jim@ibatis.apache.org", author.getEmail());
480       assertEquals("", author.getBio());
481     }
482   }
483 
484   @Test
485   void shouldSelectDraftTypedPosts() {
486     try (SqlSession session = sqlSessionFactory.openSession()) {
487       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
488       List<Post> posts = mapper.selectPosts();
489       assertEquals(5, posts.size());
490       assertTrue(posts.get(0) instanceof DraftPost);
491       assertFalse(posts.get(1) instanceof DraftPost);
492       assertTrue(posts.get(2) instanceof DraftPost);
493       assertFalse(posts.get(3) instanceof DraftPost);
494       assertFalse(posts.get(4) instanceof DraftPost);
495     }
496   }
497 
498   @Test
499   void shouldSelectDraftTypedPostsWithResultMap() {
500     try (SqlSession session = sqlSessionFactory.openSession()) {
501       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
502       List<Post> posts = mapper.selectPostsWithResultMap();
503       assertEquals(5, posts.size());
504       assertTrue(posts.get(0) instanceof DraftPost);
505       assertFalse(posts.get(1) instanceof DraftPost);
506       assertTrue(posts.get(2) instanceof DraftPost);
507       assertFalse(posts.get(3) instanceof DraftPost);
508       assertFalse(posts.get(4) instanceof DraftPost);
509     }
510   }
511 
512   @Test
513   void shouldReturnANotNullToString() {
514     try (SqlSession session = sqlSessionFactory.openSession()) {
515       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
516       assertNotNull(mapper.toString());
517     }
518   }
519 
520   @Test
521   void shouldReturnANotNullHashCode() {
522     try (SqlSession session = sqlSessionFactory.openSession()) {
523       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
524       assertNotNull(mapper.hashCode());
525     }
526   }
527 
528   @Test
529   void shouldCompareTwoMappers() {
530     try (SqlSession session = sqlSessionFactory.openSession()) {
531       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
532       BoundBlogMapper mapper2 = session.getMapper(BoundBlogMapper.class);
533       assertNotEquals(mapper, mapper2);
534     }
535   }
536 
537   @Test
538   void shouldFailWhenSelectingOneBlogWithNonExistentParam() {
539     try (SqlSession session = sqlSessionFactory.openSession()) {
540       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
541       assertThrows(Exception.class, () -> mapper.selectBlogByNonExistentParam(1));
542     }
543   }
544 
545   @Test
546   void shouldFailWhenSelectingOneBlogWithNullParam() {
547     try (SqlSession session = sqlSessionFactory.openSession()) {
548       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
549       assertThrows(Exception.class, () -> mapper.selectBlogByNullParam(null));
550     }
551   }
552 
553   @Test // Decided that maps are dynamic so no existent params do not fail
554   void shouldFailWhenSelectingOneBlogWithNonExistentNestedParam() {
555     try (SqlSession session = sqlSessionFactory.openSession()) {
556       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
557       mapper.selectBlogByNonExistentNestedParam(1, Collections.<String, Object>emptyMap());
558     }
559   }
560 
561   @Test
562   void shouldSelectBlogWithDefault30ParamNames() {
563     try (SqlSession session = sqlSessionFactory.openSession()) {
564       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
565       Blog blog = mapper.selectBlogByDefault30ParamNames(1, "Jim Business");
566       assertNotNull(blog);
567     }
568   }
569 
570   @Test
571   void shouldSelectBlogWithDefault31ParamNames() {
572     try (SqlSession session = sqlSessionFactory.openSession()) {
573       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
574       Blog blog = mapper.selectBlogByDefault31ParamNames(1, "Jim Business");
575       assertNotNull(blog);
576     }
577   }
578 
579   @Test
580   void shouldSelectBlogWithAParamNamedValue() {
581     try (SqlSession session = sqlSessionFactory.openSession()) {
582       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
583       Blog blog = mapper.selectBlogWithAParamNamedValue("id", 1, "Jim Business");
584       assertNotNull(blog);
585     }
586   }
587 
588   @Test
589   void shouldCacheMapperMethod() throws Exception {
590     try (SqlSession session = sqlSessionFactory.openSession()) {
591 
592       // Create another mapper instance with a method cache we can test against:
593       final MapperProxyFactory<BoundBlogMapper> mapperProxyFactory = new MapperProxyFactory<>(BoundBlogMapper.class);
594       assertEquals(BoundBlogMapper.class, mapperProxyFactory.getMapperInterface());
595       final BoundBlogMapper mapper = mapperProxyFactory.newInstance(session);
596       assertNotSame(mapper, mapperProxyFactory.newInstance(session));
597       assertTrue(mapperProxyFactory.getMethodCache().isEmpty());
598 
599       // Mapper methods we will call later:
600       final Method selectBlog = BoundBlogMapper.class.getMethod("selectBlog", Integer.TYPE);
601       final Method selectBlogByIdUsingConstructor = BoundBlogMapper.class.getMethod("selectBlogByIdUsingConstructor",
602           Integer.TYPE);
603 
604       // Call mapper method and verify it is cached:
605       mapper.selectBlog(1);
606       assertEquals(1, mapperProxyFactory.getMethodCache().size());
607       assertTrue(mapperProxyFactory.getMethodCache().containsKey(selectBlog));
608       final MapperMethodInvoker cachedSelectBlog = mapperProxyFactory.getMethodCache().get(selectBlog);
609 
610       // Call mapper method again and verify the cache is unchanged:
611       session.clearCache();
612       mapper.selectBlog(1);
613       assertEquals(1, mapperProxyFactory.getMethodCache().size());
614       assertSame(cachedSelectBlog, mapperProxyFactory.getMethodCache().get(selectBlog));
615 
616       // Call another mapper method and verify that it shows up in the cache as well:
617       session.clearCache();
618       mapper.selectBlogByIdUsingConstructor(1);
619       assertEquals(2, mapperProxyFactory.getMethodCache().size());
620       assertSame(cachedSelectBlog, mapperProxyFactory.getMethodCache().get(selectBlog));
621       assertTrue(mapperProxyFactory.getMethodCache().containsKey(selectBlogByIdUsingConstructor));
622     }
623   }
624 
625   @Test
626   void shouldGetBlogsWithAuthorsAndPosts() {
627     try (SqlSession session = sqlSessionFactory.openSession()) {
628       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
629       List<Blog> blogs = mapper.selectBlogsWithAutorAndPosts();
630       assertEquals(2, blogs.size());
631       assertTrue(blogs.get(0) instanceof Proxy);
632       assertEquals(101, blogs.get(0).getAuthor().getId());
633       assertEquals(1, blogs.get(0).getPosts().size());
634       assertEquals(1, blogs.get(0).getPosts().get(0).getId());
635       assertTrue(blogs.get(1) instanceof Proxy);
636       assertEquals(102, blogs.get(1).getAuthor().getId());
637       assertEquals(1, blogs.get(1).getPosts().size());
638       assertEquals(2, blogs.get(1).getPosts().get(0).getId());
639     }
640   }
641 
642   @Test
643   void shouldGetBlogsWithAuthorsAndPostsEagerly() {
644     try (SqlSession session = sqlSessionFactory.openSession()) {
645       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
646       List<Blog> blogs = mapper.selectBlogsWithAutorAndPostsEagerly();
647       assertEquals(2, blogs.size());
648       assertFalse(blogs.get(0) instanceof Factory);
649       assertEquals(101, blogs.get(0).getAuthor().getId());
650       assertEquals(1, blogs.get(0).getPosts().size());
651       assertEquals(1, blogs.get(0).getPosts().get(0).getId());
652       assertFalse(blogs.get(1) instanceof Factory);
653       assertEquals(102, blogs.get(1).getAuthor().getId());
654       assertEquals(1, blogs.get(1).getPosts().size());
655       assertEquals(2, blogs.get(1).getPosts().get(0).getId());
656     }
657   }
658 
659   @Test
660   void executeWithResultHandlerAndRowBounds() {
661     try (SqlSession session = sqlSessionFactory.openSession()) {
662       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
663       final DefaultResultHandler handler = new DefaultResultHandler();
664       mapper.collectRangeBlogs(handler, new RowBounds(1, 1));
665 
666       assertEquals(1, handler.getResultList().size());
667       Blog blog = (Blog) handler.getResultList().get(0);
668       assertEquals(2, blog.getId());
669     }
670   }
671 
672   @Test
673   void executeWithMapKeyAndRowBounds() {
674     try (SqlSession session = sqlSessionFactory.openSession()) {
675       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
676       Map<Integer, Blog> blogs = mapper.selectRangeBlogsAsMapById(new RowBounds(1, 1));
677 
678       assertEquals(1, blogs.size());
679       Blog blog = blogs.get(2);
680       assertEquals(2, blog.getId());
681     }
682   }
683 
684   @Test
685   void executeWithCursorAndRowBounds() {
686     try (SqlSession session = sqlSessionFactory.openSession()) {
687       BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);
688       try (Cursor<Blog> blogs = mapper.openRangeBlogs(new RowBounds(1, 1))) {
689         Iterator<Blog> blogIterator = blogs.iterator();
690         Blog blog = blogIterator.next();
691         assertEquals(2, blog.getId());
692         assertFalse(blogIterator.hasNext());
693       }
694     } catch (IOException e) {
695       Assertions.fail(e.getMessage());
696     }
697   }
698 
699   @Test
700   void registeredMappers() {
701     Collection<Class<?>> mapperClasses = sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers();
702     assertEquals(2, mapperClasses.size());
703     assertTrue(mapperClasses.contains(BoundBlogMapper.class));
704     assertTrue(mapperClasses.contains(BoundAuthorMapper.class));
705   }
706 
707   @Test
708   void shouldMapPropertiesUsingRepeatableAnnotation() {
709     try (SqlSession session = sqlSessionFactory.openSession()) {
710       BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
711       Author author = new Author(-1, "cbegin", "******", "cbegin@nowhere.com", "N/A", Section.NEWS);
712       mapper.insertAuthor(author);
713       Author author2 = mapper.selectAuthorMapToPropertiesUsingRepeatable(author.getId());
714       assertNotNull(author2);
715       assertEquals(author.getId(), author2.getId());
716       assertEquals(author.getUsername(), author2.getUsername());
717       assertEquals(author.getPassword(), author2.getPassword());
718       assertEquals(author.getBio(), author2.getBio());
719       assertEquals(author.getEmail(), author2.getEmail());
720       session.rollback();
721     }
722   }
723 
724   @Test
725   void shouldMapConstructorUsingRepeatableAnnotation() {
726     try (SqlSession session = sqlSessionFactory.openSession()) {
727       BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
728       Author author = new Author(-1, "cbegin", "******", "cbegin@nowhere.com", "N/A", Section.NEWS);
729       mapper.insertAuthor(author);
730       Author author2 = mapper.selectAuthorMapToConstructorUsingRepeatable(author.getId());
731       assertNotNull(author2);
732       assertEquals(author.getId(), author2.getId());
733       assertEquals(author.getUsername(), author2.getUsername());
734       assertEquals(author.getPassword(), author2.getPassword());
735       assertEquals(author.getBio(), author2.getBio());
736       assertEquals(author.getEmail(), author2.getEmail());
737       assertEquals(author.getFavouriteSection(), author2.getFavouriteSection());
738       session.rollback();
739     }
740   }
741 
742   @Test
743   void shouldMapUsingSingleRepeatableAnnotation() {
744     try (SqlSession session = sqlSessionFactory.openSession()) {
745       BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
746       Author author = new Author(-1, "cbegin", "******", "cbegin@nowhere.com", "N/A", Section.NEWS);
747       mapper.insertAuthor(author);
748       Author author2 = mapper.selectAuthorUsingSingleRepeatable(author.getId());
749       assertNotNull(author2);
750       assertEquals(author.getId(), author2.getId());
751       assertEquals(author.getUsername(), author2.getUsername());
752       assertNull(author2.getPassword());
753       assertNull(author2.getBio());
754       assertNull(author2.getEmail());
755       assertNull(author2.getFavouriteSection());
756       session.rollback();
757     }
758   }
759 
760   @Test
761   void shouldMapWhenSpecifyBothArgAndConstructorArgs() {
762     try (SqlSession session = sqlSessionFactory.openSession()) {
763       BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
764       Author author = new Author(-1, "cbegin", "******", "cbegin@nowhere.com", "N/A", Section.NEWS);
765       mapper.insertAuthor(author);
766       Author author2 = mapper.selectAuthorUsingBothArgAndConstructorArgs(author.getId());
767       assertNotNull(author2);
768       assertEquals(author.getId(), author2.getId());
769       assertEquals(author.getUsername(), author2.getUsername());
770       assertEquals(author.getPassword(), author2.getPassword());
771       assertEquals(author.getBio(), author2.getBio());
772       assertEquals(author.getEmail(), author2.getEmail());
773       assertEquals(author.getFavouriteSection(), author2.getFavouriteSection());
774       session.rollback();
775     }
776   }
777 
778   @Test
779   void shouldMapWhenSpecifyBothResultAndResults() {
780     try (SqlSession session = sqlSessionFactory.openSession()) {
781       BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
782       Author author = new Author(-1, "cbegin", "******", "cbegin@nowhere.com", "N/A", Section.NEWS);
783       mapper.insertAuthor(author);
784       Author author2 = mapper.selectAuthorUsingBothResultAndResults(author.getId());
785       assertNotNull(author2);
786       assertEquals(author.getId(), author2.getId());
787       assertEquals(author.getUsername(), author2.getUsername());
788       assertNull(author2.getPassword());
789       assertNull(author2.getBio());
790       assertNull(author2.getEmail());
791       assertNull(author2.getFavouriteSection());
792       session.rollback();
793     }
794   }
795 
796 }