View Javadoc
1   /*
2    *    Copyright 2009-2023 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.apache.ibatis.submitted.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.DeleteProvider;
34  import org.apache.ibatis.annotations.InsertProvider;
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       private SqlProvider() {
727       }
728 
729     }
730 
731   }
732 
733   public interface ErrorMapper {
734     @SelectProvider(type = ErrorSqlBuilder.class, method = "methodNotFound")
735     void methodNotFound();
736 
737     @SelectProvider(type = ErrorSqlBuilder.class, method = "overload")
738     void methodOverload(String value);
739 
740     @SelectProvider(type = ErrorSqlBuilder.class, method = "invokeError")
741     void invokeError();
742 
743     @SelectProvider(type = ErrorSqlBuilder.class, method = "invokeNestedError")
744     void invokeNestedError();
745 
746     @SelectProvider(type = ErrorSqlBuilder.class, method = "multipleProviderContext")
747     void multipleProviderContext();
748 
749     @SelectProvider
750     void omitType();
751 
752     @DeleteProvider(value = String.class, type = Integer.class)
753     void differentTypeAndValue();
754 
755     @DeleteProvider(type = ErrorSqlBuilder.class, method = "invalidArgumentsCombination")
756     void invalidArgumentsCombination(String value);
757 
758   }
759 
760   @SuppressWarnings("unused")
761   public static class ErrorSqlBuilder {
762     public void methodNotFound() {
763       throw new UnsupportedOperationException("methodNotFound");
764     }
765 
766     public String overload() {
767       throw new UnsupportedOperationException("overload");
768     }
769 
770     public String overload(String value) {
771       throw new UnsupportedOperationException("overload");
772     }
773 
774     public String invokeError() {
775       throw new UnsupportedOperationException("invokeError");
776     }
777 
778     public String invokeNestedError() {
779       throw new IllegalStateException(new UnsupportedOperationException("invokeNestedError"));
780     }
781 
782     public String multipleProviderContext(ProviderContext providerContext1, ProviderContext providerContext2) {
783       throw new UnsupportedOperationException("multipleProviderContext");
784     }
785 
786     public String invalidArgumentsCombination(ProviderContext providerContext, String value,
787         String unnecessaryArgument) {
788       return "";
789     }
790   }
791 
792   public interface StaticMethodSqlProviderMapper {
793     @SelectProvider(type = SqlProvider.class, method = "noArgument")
794     int noArgument();
795 
796     @SelectProvider(type = SqlProvider.class, method = "oneArgument")
797     int oneArgument(Integer value);
798 
799     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveByteArgument")
800     byte onePrimitiveByteArgument(byte value);
801 
802     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveShortArgument")
803     short onePrimitiveShortArgument(short value);
804 
805     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveIntArgument")
806     int onePrimitiveIntArgument(int value);
807 
808     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveLongArgument")
809     long onePrimitiveLongArgument(long value);
810 
811     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveFloatArgument")
812     float onePrimitiveFloatArgument(float value);
813 
814     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveDoubleArgument")
815     double onePrimitiveDoubleArgument(double value);
816 
817     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveBooleanArgument")
818     boolean onePrimitiveBooleanArgument(boolean value);
819 
820     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveCharArgument")
821     char onePrimitiveCharArgument(char value);
822 
823     @SelectProvider(type = SqlProvider.class, method = "boxing")
824     int boxing(int value);
825 
826     @SelectProvider(type = SqlProvider.class, method = "unboxing")
827     int unboxing(Integer value);
828 
829     @SelectProvider(type = SqlProvider.class, method = "multipleArgument")
830     int multipleArgument(@Param("value1") Integer value1, @Param("value2") Integer value2);
831 
832     @SelectProvider(type = SqlProvider.class, method = "onlyProviderContext")
833     String onlyProviderContext();
834 
835     @SelectProvider(type = SqlProvider.class, method = "oneArgumentAndProviderContext")
836     String oneArgumentAndProviderContext(Integer value);
837 
838     @SelectProvider(type = SqlProvider.class, method = "mapAndProviderContext")
839     String mapAndProviderContext(@Param("value") String value);
840 
841     @SelectProvider(type = SqlProvider.class, method = "providerContextAndParamMap")
842     String providerContextAndParamMap(@Param("value") String value);
843 
844     @SelectProvider(type = SqlProvider.class, method = "multipleMap")
845     String multipleMap(@Param("map1") Map<String, Object> map1, @Param("map2") Map<String, Object> map2);
846 
847     @SuppressWarnings("unused")
848     class SqlProvider {
849 
850       public static String noArgument() {
851         return "SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
852       }
853 
854       public static StringBuilder oneArgument(Integer value) {
855         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
856       }
857 
858       public static StringBuilder onePrimitiveByteArgument(byte value) {
859         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
860       }
861 
862       public static StringBuilder onePrimitiveShortArgument(short value) {
863         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
864       }
865 
866       public static StringBuilder onePrimitiveIntArgument(int value) {
867         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
868       }
869 
870       public static StringBuilder onePrimitiveLongArgument(long value) {
871         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
872       }
873 
874       public static StringBuilder onePrimitiveFloatArgument(float value) {
875         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
876       }
877 
878       public static StringBuilder onePrimitiveDoubleArgument(double value) {
879         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
880       }
881 
882       public static StringBuilder onePrimitiveBooleanArgument(boolean value) {
883         return new StringBuilder().append("SELECT ").append(value ? 1 : 0)
884             .append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
885       }
886 
887       public static StringBuilder onePrimitiveCharArgument(char value) {
888         return new StringBuilder().append("SELECT '").append(value).append("' FROM INFORMATION_SCHEMA.SYSTEM_USERS");
889       }
890 
891       public static StringBuilder boxing(Integer value) {
892         return new StringBuilder().append("SELECT '").append(value).append("' FROM INFORMATION_SCHEMA.SYSTEM_USERS");
893       }
894 
895       public static StringBuilder unboxing(int value) {
896         return new StringBuilder().append("SELECT '").append(value).append("' FROM INFORMATION_SCHEMA.SYSTEM_USERS");
897       }
898 
899       public static CharSequence multipleArgument(@Param("value1") Integer value1, @Param("value2") Integer value2) {
900         return "SELECT " + (value1 + value2) + " FROM INFORMATION_SCHEMA.SYSTEM_USERS";
901       }
902 
903       public static CharSequence onlyProviderContext(ProviderContext context) {
904         return new StringBuilder().append("SELECT '").append(context.getMapperMethod().getName())
905             .append("' FROM INFORMATION_SCHEMA.SYSTEM_USERS");
906       }
907 
908       public static String oneArgumentAndProviderContext(Integer value, ProviderContext context) {
909         return "SELECT '" + context.getMapperMethod().getName() + " " + value
910             + "' FROM INFORMATION_SCHEMA.SYSTEM_USERS";
911       }
912 
913       public static String mapAndProviderContext(Map<String, Object> map, ProviderContext context) {
914         return "SELECT '" + map.get("value") + "' FROM INFORMATION_SCHEMA.SYSTEM_USERS";
915       }
916 
917       public static String providerContextAndParamMap(ProviderContext context, MapperMethod.ParamMap<Object> map) {
918         return "SELECT '" + map.get("value") + "' FROM INFORMATION_SCHEMA.SYSTEM_USERS";
919       }
920 
921       public static String multipleMap(@Param("map1") Map<String, Object> map1,
922           @Param("map2") Map<String, Object> map2) {
923         return "SELECT '" + map1.get("value") + map2.get("value") + "' FROM INFORMATION_SCHEMA.SYSTEM_USERS";
924       }
925 
926       private SqlProvider() {
927       }
928 
929     }
930 
931   }
932 
933   @Test
934   void shouldInsertUserSelective() {
935     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
936       Mapper mapper = sqlSession.getMapper(Mapper.class);
937       User user = new User();
938       user.setId(999);
939       mapper.insertSelective(user);
940 
941       User loadedUser = mapper.getUser(999);
942       assertNull(loadedUser.getName());
943     }
944   }
945 
946   @Test
947   void shouldUpdateUserSelective() {
948     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
949       Mapper mapper = sqlSession.getMapper(Mapper.class);
950       User user = new User();
951       user.setId(999);
952       user.setName("MyBatis");
953       mapper.insert(user);
954 
955       user.setName(null);
956       mapper.updateSelective(user);
957 
958       User loadedUser = mapper.getUser(999);
959       assertEquals("MyBatis", loadedUser.getName());
960     }
961   }
962 
963   @Test
964   void mapperGetByEntity() {
965     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
966       Mapper mapper = sqlSession.getMapper(Mapper.class);
967       User query = new User();
968       query.setName("User4");
969       assertEquals(1, mapper.getByEntity(query).size());
970       query = new User();
971       query.setId(1);
972       assertEquals(1, mapper.getByEntity(query).size());
973       query = new User();
974       query.setId(1);
975       query.setName("User4");
976       assertEquals(0, mapper.getByEntity(query).size());
977     }
978   }
979 
980   @Test
981   void shouldPassedDatabaseIdToProviderMethod() {
982     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
983       DatabaseIdMapper mapper = sqlSession.getMapper(DatabaseIdMapper.class);
984       assertEquals("hsql", mapper.selectDatabaseId());
985     }
986     try (SqlSession sqlSession = sqlSessionFactoryForDerby.openSession()) {
987       DatabaseIdMapper mapper = sqlSession.getMapper(DatabaseIdMapper.class);
988       assertEquals("derby", mapper.selectDatabaseId());
989     }
990   }
991 
992   interface DatabaseIdMapper {
993     @SelectProvider(type = SqlProvider.class)
994     String selectDatabaseId();
995 
996     @SuppressWarnings("unused")
997     class SqlProvider {
998       public static String provideSql(ProviderContext context) {
999         if ("hsql".equals(context.getDatabaseId())) {
1000           return "SELECT '" + context.getDatabaseId() + "' FROM INFORMATION_SCHEMA.SYSTEM_USERS";
1001         }
1002         return "SELECT '" + context.getDatabaseId() + "' FROM SYSIBM.SYSDUMMY1";
1003       }
1004 
1005       private SqlProvider() {
1006       }
1007     }
1008   }
1009 
1010 }