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.sqlprovider;
17  
18  import static org.junit.jupiter.api.Assertions.assertEquals;
19  import static org.junit.jupiter.api.Assertions.assertNotNull;
20  import static org.junit.jupiter.api.Assertions.assertNull;
21  import static org.junit.jupiter.api.Assertions.assertTrue;
22  import static org.junit.jupiter.api.Assertions.fail;
23  
24  import java.io.Reader;
25  import java.lang.reflect.Method;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  
32  import org.apache.ibatis.BaseDataTest;
33  import org.apache.ibatis.annotations.InsertProvider;
34  import org.apache.ibatis.annotations.DeleteProvider;
35  import org.apache.ibatis.annotations.Param;
36  import org.apache.ibatis.annotations.SelectProvider;
37  import org.apache.ibatis.annotations.UpdateProvider;
38  import org.apache.ibatis.binding.MapperMethod;
39  import org.apache.ibatis.builder.BuilderException;
40  import org.apache.ibatis.builder.annotation.ProviderContext;
41  import org.apache.ibatis.builder.annotation.ProviderSqlSource;
42  import org.apache.ibatis.io.Resources;
43  import org.apache.ibatis.session.Configuration;
44  import org.apache.ibatis.session.SqlSession;
45  import org.apache.ibatis.session.SqlSessionFactory;
46  import org.apache.ibatis.session.SqlSessionFactoryBuilder;
47  import org.junit.jupiter.api.BeforeAll;
48  import org.junit.jupiter.api.Test;
49  
50  class SqlProviderTest {
51  
52    private static SqlSessionFactory sqlSessionFactory;
53    private static SqlSessionFactory sqlSessionFactoryForDerby;
54  
55    @BeforeAll
56    static void setUp() throws Exception {
57      // create a SqlSessionFactory
58      try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/sqlprovider/mybatis-config.xml")) {
59        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
60        sqlSessionFactory.getConfiguration().addMapper(StaticMethodSqlProviderMapper.class);
61        sqlSessionFactory.getConfiguration().addMapper(DatabaseIdMapper.class);
62      }
63      // populate in-memory database
64      BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
65          "org/apache/ibatis/submitted/sqlprovider/CreateDB.sql");
66  
67      // create a SqlSessionFactory
68      try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/sqlprovider/mybatis-config.xml")) {
69        sqlSessionFactoryForDerby = new SqlSessionFactoryBuilder().build(reader, "development-derby");
70        sqlSessionFactoryForDerby.getConfiguration().addMapper(DatabaseIdMapper.class);
71      }
72    }
73  
74    // Test for list
75    @Test
76    void shouldGetTwoUsers() {
77      try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
78        Mapper mapper = sqlSession.getMapper(Mapper.class);
79        List<Integer> list = new ArrayList<>();
80        list.add(1);
81        list.add(3);
82        List<User> users = mapper.getUsers(list);
83        assertEquals(2, users.size());
84        assertEquals("User1", users.get(0).getName());
85        assertEquals("User3", users.get(1).getName());
86      }
87    }
88  
89    // Test for simple value without @Param
90    @Test
91    void shouldGetOneUser() {
92      try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
93        Mapper mapper = sqlSession.getMapper(Mapper.class);
94        {
95          User user = mapper.getUser(4);
96          assertNotNull(user);
97          assertEquals("User4", user.getName());
98        }
99        {
100         User user = mapper.getUser(null);
101         assertNull(user);
102       }
103     }
104   }
105 
106   // Test for empty
107   @Test
108   void shouldGetAllUsers() {
109     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
110       Mapper mapper = sqlSession.getMapper(Mapper.class);
111       List<User> users = mapper.getAllUsers();
112       assertEquals(4, users.size());
113       assertEquals("User1", users.get(0).getName());
114       assertEquals("User2", users.get(1).getName());
115       assertEquals("User3", users.get(2).getName());
116       assertEquals("User4", users.get(3).getName());
117     }
118   }
119 
120   // Test for single JavaBean
121   @Test
122   void shouldGetUsersByCriteria() {
123     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
124       Mapper mapper = sqlSession.getMapper(Mapper.class);
125       {
126         User criteria = new User();
127         criteria.setId(1);
128         List<User> users = mapper.getUsersByCriteria(criteria);
129         assertEquals(1, users.size());
130         assertEquals("User1", users.get(0).getName());
131       }
132       {
133         User criteria = new User();
134         criteria.setName("User");
135         List<User> users = mapper.getUsersByCriteria(criteria);
136         assertEquals(4, users.size());
137         assertEquals("User1", users.get(0).getName());
138         assertEquals("User2", users.get(1).getName());
139         assertEquals("User3", users.get(2).getName());
140         assertEquals("User4", users.get(3).getName());
141       }
142     }
143   }
144 
145   // Test for single map
146   @Test
147   void shouldGetUsersByCriteriaMap() {
148     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
149       Mapper mapper = sqlSession.getMapper(Mapper.class);
150       {
151         Map<String, Object> criteria = new HashMap<>();
152         criteria.put("id", 1);
153         List<User> users = mapper.getUsersByCriteriaMap(criteria);
154         assertEquals(1, users.size());
155         assertEquals("User1", users.get(0).getName());
156       }
157       {
158         Map<String, Object> criteria = new HashMap<>();
159         criteria.put("name", "User");
160         List<User> users = mapper.getUsersByCriteriaMap(criteria);
161         assertEquals(4, users.size());
162         assertEquals("User1", users.get(0).getName());
163         assertEquals("User2", users.get(1).getName());
164         assertEquals("User3", users.get(2).getName());
165         assertEquals("User4", users.get(3).getName());
166       }
167     }
168   }
169 
170   @Test
171   void shouldGetUsersByCriteriaMapWithParam() {
172     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
173       Mapper mapper = sqlSession.getMapper(Mapper.class);
174       {
175         Map<String, Object> criteria = new HashMap<>();
176         criteria.put("id", 1);
177         List<User> users = mapper.getUsersByCriteriaMapWithParam(criteria);
178         assertEquals(1, users.size());
179         assertEquals("User1", users.get(0).getName());
180       }
181       {
182         Map<String, Object> criteria = new HashMap<>();
183         criteria.put("name", "User");
184         List<User> users = mapper.getUsersByCriteriaMapWithParam(criteria);
185         assertEquals(4, users.size());
186         assertEquals("User1", users.get(0).getName());
187         assertEquals("User2", users.get(1).getName());
188         assertEquals("User3", users.get(2).getName());
189         assertEquals("User4", users.get(3).getName());
190       }
191     }
192   }
193 
194   // Test for multiple parameter without @Param
195   @Test
196   void shouldGetUsersByName() {
197     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
198       Mapper mapper = sqlSession.getMapper(Mapper.class);
199       List<User> users = mapper.getUsersByName("User", "id DESC");
200       assertEquals(4, users.size());
201       assertEquals("User4", users.get(0).getName());
202       assertEquals("User3", users.get(1).getName());
203       assertEquals("User2", users.get(2).getName());
204       assertEquals("User1", users.get(3).getName());
205     }
206   }
207 
208   // Test for map without @Param
209   @Test
210   void shouldGetUsersByNameUsingMap() {
211     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
212       Mapper mapper = sqlSession.getMapper(Mapper.class);
213       List<User> users = mapper.getUsersByNameUsingMap("User", "id DESC");
214       assertEquals(4, users.size());
215       assertEquals("User4", users.get(0).getName());
216       assertEquals("User3", users.get(1).getName());
217       assertEquals("User2", users.get(2).getName());
218       assertEquals("User1", users.get(3).getName());
219     }
220   }
221 
222   // Test for multiple parameter with @Param
223   @Test
224   void shouldGetUsersByNameWithParamNameAndOrderBy() {
225     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
226       Mapper mapper = sqlSession.getMapper(Mapper.class);
227       List<User> users = mapper.getUsersByNameWithParamNameAndOrderBy("User", "id DESC");
228       assertEquals(4, users.size());
229       assertEquals("User4", users.get(0).getName());
230       assertEquals("User3", users.get(1).getName());
231       assertEquals("User2", users.get(2).getName());
232       assertEquals("User1", users.get(3).getName());
233     }
234   }
235 
236   // Test for map with @Param
237   @Test
238   void shouldGetUsersByNameWithParamNameUsingMap() {
239     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
240       Mapper mapper = sqlSession.getMapper(Mapper.class);
241       List<User> users = mapper.getUsersByNameWithParamNameAndOrderBy("User", "id DESC");
242       assertEquals(4, users.size());
243       assertEquals("User4", users.get(0).getName());
244       assertEquals("User3", users.get(1).getName());
245       assertEquals("User2", users.get(2).getName());
246       assertEquals("User1", users.get(3).getName());
247     }
248   }
249 
250   // Test for simple value with @Param
251   @Test
252   void shouldGetUsersByNameWithParamName() {
253     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
254       Mapper mapper = sqlSession.getMapper(Mapper.class);
255       {
256         List<User> users = mapper.getUsersByNameWithParamName("User");
257         assertEquals(4, users.size());
258         assertEquals("User4", users.get(0).getName());
259         assertEquals("User3", users.get(1).getName());
260         assertEquals("User2", users.get(2).getName());
261         assertEquals("User1", users.get(3).getName());
262       }
263       {
264         List<User> users = mapper.getUsersByNameWithParamName(null);
265         assertEquals(4, users.size());
266         assertEquals("User4", users.get(0).getName());
267         assertEquals("User3", users.get(1).getName());
268         assertEquals("User2", users.get(2).getName());
269         assertEquals("User1", users.get(3).getName());
270       }
271     }
272   }
273 
274   @Test
275   void methodNotFound() throws NoSuchMethodException {
276     try {
277       Class<?> mapperType = ErrorMapper.class;
278       Method mapperMethod = mapperType.getMethod("methodNotFound");
279       new ProviderSqlSource(new Configuration(), mapperMethod.getAnnotation(SelectProvider.class), mapperType,
280           mapperMethod);
281       fail();
282     } catch (BuilderException e) {
283       assertTrue(e.getMessage().contains(
284           "Error creating SqlSource for SqlProvider. Method 'methodNotFound' not found in SqlProvider 'org.apache.ibatis.submitted.sqlprovider.SqlProviderTest$ErrorSqlBuilder'."));
285     }
286   }
287 
288   @Test
289   void methodOverload() throws NoSuchMethodException {
290     try {
291       Class<?> mapperType = ErrorMapper.class;
292       Method mapperMethod = mapperType.getMethod("methodOverload", String.class);
293       new ProviderSqlSource(new Configuration(), mapperMethod.getAnnotation(SelectProvider.class), mapperType,
294           mapperMethod);
295       fail();
296     } catch (BuilderException e) {
297       assertTrue(e.getMessage().contains(
298           "Error creating SqlSource for SqlProvider. Method 'overload' is found multiple in SqlProvider 'org.apache.ibatis.submitted.sqlprovider.SqlProviderTest$ErrorSqlBuilder'. Sql provider method can not overload."));
299     }
300   }
301 
302   @Test
303   @SuppressWarnings("deprecation")
304   void notSqlProvider() throws NoSuchMethodException {
305     Object testAnnotation = getClass().getDeclaredMethod("notSqlProvider").getAnnotation(Test.class);
306     try {
307       new ProviderSqlSource(new Configuration(), testAnnotation);
308       fail();
309     } catch (BuilderException e) {
310       assertTrue(e.getMessage().contains(
311           "Error creating SqlSource for SqlProvider.  Cause: java.lang.NoSuchMethodException: org.junit.jupiter.api.Test.type()"));
312     }
313   }
314 
315   @Test
316   void omitType() throws NoSuchMethodException {
317     try {
318       Class<?> mapperType = ErrorMapper.class;
319       Method mapperMethod = mapperType.getMethod("omitType");
320       new ProviderSqlSource(new Configuration(), mapperMethod.getAnnotation(SelectProvider.class), mapperType,
321           mapperMethod);
322       fail();
323     } catch (BuilderException e) {
324       assertTrue(e.getMessage().contains(
325           "Please specify either 'value' or 'type' attribute of @SelectProvider at the 'public abstract void org.apache.ibatis.submitted.sqlprovider.SqlProviderTest$ErrorMapper.omitType()'."));
326     }
327   }
328 
329   @Test
330   void differentTypeAndValue() throws NoSuchMethodException {
331     try {
332       Class<?> mapperType = ErrorMapper.class;
333       Method mapperMethod = mapperType.getMethod("differentTypeAndValue");
334       new ProviderSqlSource(new Configuration(), mapperMethod.getAnnotation(DeleteProvider.class), mapperType,
335           mapperMethod);
336       fail();
337     } catch (BuilderException e) {
338       assertTrue(e.getMessage().contains(
339           "Cannot specify different class on 'value' and 'type' attribute of @DeleteProvider at the 'public abstract void org.apache.ibatis.submitted.sqlprovider.SqlProviderTest$ErrorMapper.differentTypeAndValue()'."));
340     }
341   }
342 
343   @Test
344   void multipleProviderContext() throws NoSuchMethodException {
345     try {
346       Class<?> mapperType = ErrorMapper.class;
347       Method mapperMethod = mapperType.getMethod("multipleProviderContext");
348       new ProviderSqlSource(new Configuration(), mapperMethod.getAnnotation(SelectProvider.class), mapperType,
349           mapperMethod);
350       fail();
351     } catch (BuilderException e) {
352       assertTrue(e.getMessage().contains(
353           "Error creating SqlSource for SqlProvider. ProviderContext found multiple in SqlProvider method (org.apache.ibatis.submitted.sqlprovider.SqlProviderTest$ErrorSqlBuilder.multipleProviderContext). ProviderContext can not define multiple in SqlProvider method argument."));
354     }
355   }
356 
357   @Test
358   void notSupportParameterObjectOnMultipleArguments() throws NoSuchMethodException {
359     try {
360       Class<?> mapperType = Mapper.class;
361       Method mapperMethod = mapperType.getMethod("getUsersByName", String.class, String.class);
362       new ProviderSqlSource(new Configuration(), mapperMethod.getAnnotation(SelectProvider.class), mapperType,
363           mapperMethod).getBoundSql(new Object());
364       fail();
365     } catch (BuilderException e) {
366       assertTrue(e.getMessage().contains(
367           "Error invoking SqlProvider method 'public java.lang.String org.apache.ibatis.submitted.sqlprovider.OurSqlBuilder.buildGetUsersByNameQuery(java.lang.String,java.lang.String)' with specify parameter 'class java.lang.Object'.  Cause: java.lang.IllegalArgumentException: wrong number of arguments"));
368     }
369   }
370 
371   @Test
372   void notSupportParameterObjectOnNamedArgument() throws NoSuchMethodException {
373     try {
374       Class<?> mapperType = Mapper.class;
375       Method mapperMethod = mapperType.getMethod("getUsersByNameWithParamName", String.class);
376       new ProviderSqlSource(new Configuration(), mapperMethod.getAnnotation(SelectProvider.class), mapperType,
377           mapperMethod).getBoundSql(new Object());
378       fail();
379     } catch (BuilderException e) {
380       assertTrue(e.getMessage().contains(
381           "Error invoking SqlProvider method 'public java.lang.String org.apache.ibatis.submitted.sqlprovider.OurSqlBuilder.buildGetUsersByNameWithParamNameQuery(java.lang.String)' with specify parameter 'class java.lang.Object'.  Cause: java.lang.IllegalArgumentException: argument type mismatch"));
382     }
383   }
384 
385   @Test
386   void invokeError() throws NoSuchMethodException {
387     try {
388       Class<?> mapperType = ErrorMapper.class;
389       Method mapperMethod = mapperType.getMethod("invokeError");
390       new ProviderSqlSource(new Configuration(), mapperMethod.getAnnotation(SelectProvider.class), mapperType,
391           mapperMethod).getBoundSql(new Object());
392       fail();
393     } catch (BuilderException e) {
394       assertTrue(e.getMessage().contains(
395           "Error invoking SqlProvider method 'public java.lang.String org.apache.ibatis.submitted.sqlprovider.SqlProviderTest$ErrorSqlBuilder.invokeError()' with specify parameter 'class java.lang.Object'.  Cause: java.lang.UnsupportedOperationException: invokeError"));
396     }
397   }
398 
399   @Test
400   void invokeNestedError() throws NoSuchMethodException {
401     try {
402       Class<?> mapperType = ErrorMapper.class;
403       Method mapperMethod = mapperType.getMethod("invokeNestedError");
404       new ProviderSqlSource(new Configuration(), mapperMethod.getAnnotation(SelectProvider.class), mapperType,
405           mapperMethod).getBoundSql(new Object());
406       fail();
407     } catch (BuilderException e) {
408       assertTrue(e.getMessage().contains(
409           "Error invoking SqlProvider method 'public java.lang.String org.apache.ibatis.submitted.sqlprovider.SqlProviderTest$ErrorSqlBuilder.invokeNestedError()' with specify parameter 'class java.lang.Object'.  Cause: java.lang.UnsupportedOperationException: invokeNestedError"));
410     }
411   }
412 
413   @Test
414   void invalidArgumentsCombination() throws NoSuchMethodException {
415     try {
416       Class<?> mapperType = ErrorMapper.class;
417       Method mapperMethod = mapperType.getMethod("invalidArgumentsCombination", String.class);
418       new ProviderSqlSource(new Configuration(), mapperMethod.getAnnotation(DeleteProvider.class), mapperType,
419           mapperMethod).getBoundSql("foo");
420       fail();
421     } catch (BuilderException e) {
422       assertTrue(e.getMessage().contains(
423           "Cannot invoke SqlProvider method 'public java.lang.String org.apache.ibatis.submitted.sqlprovider.SqlProviderTest$ErrorSqlBuilder.invalidArgumentsCombination(org.apache.ibatis.builder.annotation.ProviderContext,java.lang.String,java.lang.String)' with specify parameter 'class java.lang.String' because SqlProvider method arguments for 'public abstract void org.apache.ibatis.submitted.sqlprovider.SqlProviderTest$ErrorMapper.invalidArgumentsCombination(java.lang.String)' is an invalid combination."));
424     }
425   }
426 
427   @Test
428   void shouldInsertUser() {
429     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
430       Mapper mapper = sqlSession.getMapper(Mapper.class);
431       User user = new User();
432       user.setId(999);
433       user.setName("MyBatis");
434       mapper.insert(user);
435 
436       User loadedUser = mapper.getUser(999);
437       assertEquals("MyBatis", loadedUser.getName());
438     }
439   }
440 
441   @Test
442   void shouldUpdateUser() {
443     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
444       Mapper mapper = sqlSession.getMapper(Mapper.class);
445       User user = new User();
446       user.setId(999);
447       user.setName("MyBatis");
448       mapper.insert(user);
449 
450       user.setName("MyBatis3");
451       mapper.update(user);
452 
453       User loadedUser = mapper.getUser(999);
454       assertEquals("MyBatis3", loadedUser.getName());
455     }
456   }
457 
458   @Test
459   void shouldDeleteUser() {
460     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
461       Mapper mapper = sqlSession.getMapper(Mapper.class);
462       User user = new User();
463       user.setId(999);
464       user.setName("MyBatis");
465       mapper.insert(user);
466 
467       user.setName("MyBatis3");
468       mapper.delete(999);
469 
470       User loadedUser = mapper.getUser(999);
471       assertNull(loadedUser);
472     }
473   }
474 
475   @Test
476   void mapperProviderContextOnly() {
477     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
478       Mapper mapper = sqlSession.getMapper(Mapper.class);
479       assertEquals("User4", mapper.selectById(4).getName());
480       assertNull(mapper.selectActiveById(4));
481     }
482   }
483 
484   @Test
485   void mapperOneParamAndProviderContext() {
486     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
487       Mapper mapper = sqlSession.getMapper(Mapper.class);
488       assertEquals(1, mapper.selectByName("User4").size());
489       assertEquals(0, mapper.selectActiveByName("User4").size());
490     }
491   }
492 
493   @Test
494   void mapperMultipleParamAndProviderContextWithAtParam() {
495     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
496       Mapper mapper = sqlSession.getMapper(Mapper.class);
497       assertEquals(1, mapper.selectByIdAndNameWithAtParam(4, "User4").size());
498       assertEquals(0, mapper.selectActiveByIdAndNameWithAtParam(4, "User4").size());
499     }
500   }
501 
502   @Test
503   void mapperMultipleParamAndProviderContext() {
504     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
505       Mapper mapper = sqlSession.getMapper(Mapper.class);
506       assertEquals(1, mapper.selectByIdAndName(4, "User4").size());
507       assertEquals(0, mapper.selectActiveByIdAndName(4, "User4").size());
508     }
509   }
510 
511   @Test
512   void staticMethodNoArgument() {
513     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
514       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
515       assertEquals(1, mapper.noArgument());
516     }
517   }
518 
519   @Test
520   void staticMethodOneArgument() {
521     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
522       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
523       assertEquals(10, mapper.oneArgument(10));
524     }
525   }
526 
527   @Test
528   void staticMethodOnePrimitiveByteArgument() {
529     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
530       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
531       assertEquals((byte) 10, mapper.onePrimitiveByteArgument((byte) 10));
532     }
533   }
534 
535   @Test
536   void staticMethodOnePrimitiveShortArgument() {
537     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
538       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
539       assertEquals((short) 10, mapper.onePrimitiveShortArgument((short) 10));
540     }
541   }
542 
543   @Test
544   void staticMethodOnePrimitiveIntArgument() {
545     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
546       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
547       assertEquals(10, mapper.onePrimitiveIntArgument(10));
548     }
549   }
550 
551   @Test
552   void staticMethodOnePrimitiveLongArgument() {
553     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
554       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
555       assertEquals(10L, mapper.onePrimitiveLongArgument(10L));
556     }
557   }
558 
559   @Test
560   void staticMethodOnePrimitiveFloatArgument() {
561     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
562       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
563       assertEquals(10.1F, mapper.onePrimitiveFloatArgument(10.1F));
564     }
565   }
566 
567   @Test
568   void staticMethodOnePrimitiveDoubleArgument() {
569     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
570       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
571       assertEquals(10.1D, mapper.onePrimitiveDoubleArgument(10.1D));
572     }
573   }
574 
575   @Test
576   void staticMethodOnePrimitiveBooleanArgument() {
577     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
578       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
579       assertTrue(mapper.onePrimitiveBooleanArgument(true));
580     }
581   }
582 
583   @Test
584   void staticMethodOnePrimitiveCharArgument() {
585     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
586       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
587       assertEquals('A', mapper.onePrimitiveCharArgument('A'));
588     }
589   }
590 
591   @Test
592   void boxing() {
593     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
594       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
595       assertEquals(10, mapper.boxing(10));
596     }
597   }
598 
599   @Test
600   void unboxing() {
601     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
602       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
603       assertEquals(100, mapper.unboxing(100));
604     }
605   }
606 
607   @Test
608   void staticMethodMultipleArgument() {
609     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
610       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
611       assertEquals(2, mapper.multipleArgument(1, 1));
612     }
613   }
614 
615   @Test
616   void staticMethodOnlyProviderContext() {
617     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
618       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
619       assertEquals("onlyProviderContext", mapper.onlyProviderContext());
620     }
621   }
622 
623   @Test
624   void staticMethodOneArgumentAndProviderContext() {
625     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
626       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
627       assertEquals("oneArgumentAndProviderContext 100", mapper.oneArgumentAndProviderContext(100));
628     }
629   }
630 
631   @Test
632   void mapAndProviderContext() {
633     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
634       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
635       assertEquals("mybatis", mapper.mapAndProviderContext("mybatis"));
636     }
637   }
638 
639   @Test
640   void multipleMap() {
641     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
642       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
643       assertEquals("123456",
644           mapper.multipleMap(Collections.singletonMap("value", "123"), Collections.singletonMap("value", "456")));
645     }
646   }
647 
648   @Test
649   void providerContextAndMap() {
650     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
651       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
652       assertEquals("mybatis", mapper.providerContextAndParamMap("mybatis"));
653     }
654   }
655 
656   @Test
657   @SuppressWarnings("deprecation")
658   void keepBackwardCompatibilityOnDeprecatedConstructorWithAnnotation() throws NoSuchMethodException {
659     Class<?> mapperType = StaticMethodSqlProviderMapper.class;
660     Method mapperMethod = mapperType.getMethod("noArgument");
661     ProviderSqlSource sqlSource = new ProviderSqlSource(new Configuration(),
662         (Object) mapperMethod.getAnnotation(SelectProvider.class), mapperType, mapperMethod);
663     assertEquals("SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS", sqlSource.getBoundSql(null).getSql());
664   }
665 
666   @Test
667   void omitTypeWhenSpecifyDefaultType() throws NoSuchMethodException {
668     Class<?> mapperType = DefaultSqlProviderMapper.class;
669     Configuration configuration = new Configuration();
670     configuration.setDefaultSqlProviderType(DefaultSqlProviderMapper.SqlProvider.class);
671     {
672       Method mapperMethod = mapperType.getMethod("select", int.class);
673       String sql = new ProviderSqlSource(configuration, mapperMethod.getAnnotation(SelectProvider.class), mapperType,
674         mapperMethod).getBoundSql(1).getSql();
675       assertEquals("select name from foo where id = ?", sql);
676     }
677     {
678       Method mapperMethod = mapperType.getMethod("insert", String.class);
679       String sql = new ProviderSqlSource(configuration, mapperMethod.getAnnotation(InsertProvider.class), mapperType,
680         mapperMethod).getBoundSql("Taro").getSql();
681       assertEquals("insert into foo (name) values(?)", sql);
682     }
683     {
684       Method mapperMethod = mapperType.getMethod("update", int.class, String.class);
685       String sql = new ProviderSqlSource(configuration, mapperMethod.getAnnotation(UpdateProvider.class), mapperType,
686         mapperMethod).getBoundSql(Collections.emptyMap() ).getSql();
687       assertEquals("update foo set name = ? where id = ?", sql);
688     }
689     {
690       Method mapperMethod = mapperType.getMethod("delete", int.class);
691       String sql = new ProviderSqlSource(configuration, mapperMethod.getAnnotation(DeleteProvider.class), mapperType,
692         mapperMethod).getBoundSql(Collections.emptyMap() ).getSql();
693       assertEquals("delete from foo where id = ?", sql);
694     }
695   }
696 
697   public interface DefaultSqlProviderMapper {
698 
699     @SelectProvider
700     String select(int id);
701 
702     @InsertProvider
703     void insert(String name);
704 
705     @UpdateProvider
706     void update(int id, String name);
707 
708     @DeleteProvider
709     void delete(int id);
710 
711     class SqlProvider {
712 
713       public static String provideSql(ProviderContext c) {
714         switch (c.getMapperMethod().getName()) {
715           case "select" :
716             return "select name from foo where id = #{id}";
717           case "insert" :
718             return "insert into foo (name) values(#{name})";
719           case "update" :
720             return "update foo set name = #{name} where id = #{id}";
721           default:
722             return "delete from foo where id = #{id}";
723         }
724       }
725 
726     }
727 
728   }
729 
730   public interface ErrorMapper {
731     @SelectProvider(type = ErrorSqlBuilder.class, method = "methodNotFound")
732     void methodNotFound();
733 
734     @SelectProvider(type = ErrorSqlBuilder.class, method = "overload")
735     void methodOverload(String value);
736 
737     @SelectProvider(type = ErrorSqlBuilder.class, method = "invokeError")
738     void invokeError();
739 
740     @SelectProvider(type = ErrorSqlBuilder.class, method = "invokeNestedError")
741     void invokeNestedError();
742 
743     @SelectProvider(type = ErrorSqlBuilder.class, method = "multipleProviderContext")
744     void multipleProviderContext();
745 
746     @SelectProvider
747     void omitType();
748 
749     @DeleteProvider(value = String.class, type = Integer.class)
750     void differentTypeAndValue();
751 
752     @DeleteProvider(type = ErrorSqlBuilder.class, method = "invalidArgumentsCombination")
753     void invalidArgumentsCombination(String value);
754 
755   }
756 
757   @SuppressWarnings("unused")
758   public static class ErrorSqlBuilder {
759     public void methodNotFound() {
760       throw new UnsupportedOperationException("methodNotFound");
761     }
762 
763     public String overload() {
764       throw new UnsupportedOperationException("overload");
765     }
766 
767     public String overload(String value) {
768       throw new UnsupportedOperationException("overload");
769     }
770 
771     public String invokeError() {
772       throw new UnsupportedOperationException("invokeError");
773     }
774 
775     public String invokeNestedError() {
776       throw new IllegalStateException(new UnsupportedOperationException("invokeNestedError"));
777     }
778 
779     public String multipleProviderContext(ProviderContext providerContext1, ProviderContext providerContext2) {
780       throw new UnsupportedOperationException("multipleProviderContext");
781     }
782 
783     public String invalidArgumentsCombination(ProviderContext providerContext, String value,
784         String unnecessaryArgument) {
785       return "";
786     }
787   }
788 
789   public interface StaticMethodSqlProviderMapper {
790     @SelectProvider(type = SqlProvider.class, method = "noArgument")
791     int noArgument();
792 
793     @SelectProvider(type = SqlProvider.class, method = "oneArgument")
794     int oneArgument(Integer value);
795 
796     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveByteArgument")
797     byte onePrimitiveByteArgument(byte value);
798 
799     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveShortArgument")
800     short onePrimitiveShortArgument(short value);
801 
802     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveIntArgument")
803     int onePrimitiveIntArgument(int value);
804 
805     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveLongArgument")
806     long onePrimitiveLongArgument(long value);
807 
808     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveFloatArgument")
809     float onePrimitiveFloatArgument(float value);
810 
811     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveDoubleArgument")
812     double onePrimitiveDoubleArgument(double value);
813 
814     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveBooleanArgument")
815     boolean onePrimitiveBooleanArgument(boolean value);
816 
817     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveCharArgument")
818     char onePrimitiveCharArgument(char value);
819 
820     @SelectProvider(type = SqlProvider.class, method = "boxing")
821     int boxing(int value);
822 
823     @SelectProvider(type = SqlProvider.class, method = "unboxing")
824     int unboxing(Integer value);
825 
826     @SelectProvider(type = SqlProvider.class, method = "multipleArgument")
827     int multipleArgument(@Param("value1") Integer value1, @Param("value2") Integer value2);
828 
829     @SelectProvider(type = SqlProvider.class, method = "onlyProviderContext")
830     String onlyProviderContext();
831 
832     @SelectProvider(type = SqlProvider.class, method = "oneArgumentAndProviderContext")
833     String oneArgumentAndProviderContext(Integer value);
834 
835     @SelectProvider(type = SqlProvider.class, method = "mapAndProviderContext")
836     String mapAndProviderContext(@Param("value") String value);
837 
838     @SelectProvider(type = SqlProvider.class, method = "providerContextAndParamMap")
839     String providerContextAndParamMap(@Param("value") String value);
840 
841     @SelectProvider(type = SqlProvider.class, method = "multipleMap")
842     String multipleMap(@Param("map1") Map<String, Object> map1, @Param("map2") Map<String, Object> map2);
843 
844     @SuppressWarnings("unused")
845     class SqlProvider {
846 
847       public static String noArgument() {
848         return "SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
849       }
850 
851       public static StringBuilder oneArgument(Integer value) {
852         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
853       }
854 
855       public static StringBuilder onePrimitiveByteArgument(byte value) {
856         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
857       }
858 
859       public static StringBuilder onePrimitiveShortArgument(short value) {
860         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
861       }
862 
863       public static StringBuilder onePrimitiveIntArgument(int value) {
864         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
865       }
866 
867       public static StringBuilder onePrimitiveLongArgument(long value) {
868         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
869       }
870 
871       public static StringBuilder onePrimitiveFloatArgument(float value) {
872         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
873       }
874 
875       public static StringBuilder onePrimitiveDoubleArgument(double value) {
876         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
877       }
878 
879       public static StringBuilder onePrimitiveBooleanArgument(boolean value) {
880         return new StringBuilder().append("SELECT ").append(value ? 1 : 0)
881             .append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
882       }
883 
884       public static StringBuilder onePrimitiveCharArgument(char value) {
885         return new StringBuilder().append("SELECT '").append(value).append("' FROM INFORMATION_SCHEMA.SYSTEM_USERS");
886       }
887 
888       public static StringBuilder boxing(Integer value) {
889         return new StringBuilder().append("SELECT '").append(value).append("' FROM INFORMATION_SCHEMA.SYSTEM_USERS");
890       }
891 
892       public static StringBuilder unboxing(int value) {
893         return new StringBuilder().append("SELECT '").append(value).append("' FROM INFORMATION_SCHEMA.SYSTEM_USERS");
894       }
895 
896       public static CharSequence multipleArgument(@Param("value1") Integer value1, @Param("value2") Integer value2) {
897         return "SELECT " + (value1 + value2) + " FROM INFORMATION_SCHEMA.SYSTEM_USERS";
898       }
899 
900       public static CharSequence onlyProviderContext(ProviderContext context) {
901         return new StringBuilder().append("SELECT '").append(context.getMapperMethod().getName())
902             .append("' FROM INFORMATION_SCHEMA.SYSTEM_USERS");
903       }
904 
905       public static String oneArgumentAndProviderContext(Integer value, ProviderContext context) {
906         return "SELECT '" + context.getMapperMethod().getName() + " " + value
907             + "' FROM INFORMATION_SCHEMA.SYSTEM_USERS";
908       }
909 
910       public static String mapAndProviderContext(Map<String, Object> map, ProviderContext context) {
911         return "SELECT '" + map.get("value") + "' FROM INFORMATION_SCHEMA.SYSTEM_USERS";
912       }
913 
914       public static String providerContextAndParamMap(ProviderContext context, MapperMethod.ParamMap<Object> map) {
915         return "SELECT '" + map.get("value") + "' FROM INFORMATION_SCHEMA.SYSTEM_USERS";
916       }
917 
918       public static String multipleMap(@Param("map1") Map<String, Object> map1,
919           @Param("map2") Map<String, Object> map2) {
920         return "SELECT '" + map1.get("value") + map2.get("value") + "' FROM INFORMATION_SCHEMA.SYSTEM_USERS";
921       }
922 
923     }
924 
925   }
926 
927   @Test
928   void shouldInsertUserSelective() {
929     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
930       Mapper mapper = sqlSession.getMapper(Mapper.class);
931       User user = new User();
932       user.setId(999);
933       mapper.insertSelective(user);
934 
935       User loadedUser = mapper.getUser(999);
936       assertNull(loadedUser.getName());
937     }
938   }
939 
940   @Test
941   void shouldUpdateUserSelective() {
942     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
943       Mapper mapper = sqlSession.getMapper(Mapper.class);
944       User user = new User();
945       user.setId(999);
946       user.setName("MyBatis");
947       mapper.insert(user);
948 
949       user.setName(null);
950       mapper.updateSelective(user);
951 
952       User loadedUser = mapper.getUser(999);
953       assertEquals("MyBatis", loadedUser.getName());
954     }
955   }
956 
957   @Test
958   void mapperGetByEntity() {
959     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
960       Mapper mapper = sqlSession.getMapper(Mapper.class);
961       User query = new User();
962       query.setName("User4");
963       assertEquals(1, mapper.getByEntity(query).size());
964       query = new User();
965       query.setId(1);
966       assertEquals(1, mapper.getByEntity(query).size());
967       query = new User();
968       query.setId(1);
969       query.setName("User4");
970       assertEquals(0, mapper.getByEntity(query).size());
971     }
972   }
973 
974   @Test
975   void shouldPassedDatabaseIdToProviderMethod() {
976     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
977       DatabaseIdMapper mapper = sqlSession.getMapper(DatabaseIdMapper.class);
978       assertEquals("hsql", mapper.selectDatabaseId());
979     }
980     try (SqlSession sqlSession = sqlSessionFactoryForDerby.openSession()) {
981       DatabaseIdMapper mapper = sqlSession.getMapper(DatabaseIdMapper.class);
982       assertEquals("derby", mapper.selectDatabaseId());
983     }
984   }
985 
986   interface DatabaseIdMapper {
987     @SelectProvider(type = SqlProvider.class)
988     String selectDatabaseId();
989 
990     @SuppressWarnings("unused")
991     class SqlProvider {
992       public static String provideSql(ProviderContext context) {
993         if ("hsql".equals(context.getDatabaseId())) {
994           return "SELECT '" + context.getDatabaseId() + "' FROM INFORMATION_SCHEMA.SYSTEM_USERS";
995         } else {
996           return "SELECT '" + context.getDatabaseId() + "' FROM SYSIBM.SYSDUMMY1";
997         }
998       }
999     }
1000   }
1001 
1002 }