View Javadoc
1   /*
2    *    Copyright 2009-2024 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", mapper.multipleMap(Map.of("value", "123"), Map.of("value", "456")));
644     }
645   }
646 
647   @Test
648   void providerContextAndMap() {
649     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
650       StaticMethodSqlProviderMapper mapper = sqlSession.getMapper(StaticMethodSqlProviderMapper.class);
651       assertEquals("mybatis", mapper.providerContextAndParamMap("mybatis"));
652     }
653   }
654 
655   @Test
656   @SuppressWarnings("deprecation")
657   void keepBackwardCompatibilityOnDeprecatedConstructorWithAnnotation() throws NoSuchMethodException {
658     Class<?> mapperType = StaticMethodSqlProviderMapper.class;
659     Method mapperMethod = mapperType.getMethod("noArgument");
660     ProviderSqlSource sqlSource = new ProviderSqlSource(new Configuration(),
661         (Object) mapperMethod.getAnnotation(SelectProvider.class), mapperType, mapperMethod);
662     assertEquals("SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS", sqlSource.getBoundSql(null).getSql());
663   }
664 
665   @Test
666   void omitTypeWhenSpecifyDefaultType() throws NoSuchMethodException {
667     Class<?> mapperType = DefaultSqlProviderMapper.class;
668     Configuration configuration = new Configuration();
669     configuration.setDefaultSqlProviderType(DefaultSqlProviderMapper.SqlProvider.class);
670     {
671       Method mapperMethod = mapperType.getMethod("select", int.class);
672       String sql = new ProviderSqlSource(configuration, mapperMethod.getAnnotation(SelectProvider.class), mapperType,
673           mapperMethod).getBoundSql(1).getSql();
674       assertEquals("select name from foo where id = ?", sql);
675     }
676     {
677       Method mapperMethod = mapperType.getMethod("insert", String.class);
678       String sql = new ProviderSqlSource(configuration, mapperMethod.getAnnotation(InsertProvider.class), mapperType,
679           mapperMethod).getBoundSql("Taro").getSql();
680       assertEquals("insert into foo (name) values(?)", sql);
681     }
682     {
683       Method mapperMethod = mapperType.getMethod("update", int.class, String.class);
684       String sql = new ProviderSqlSource(configuration, mapperMethod.getAnnotation(UpdateProvider.class), mapperType,
685           mapperMethod).getBoundSql(Collections.emptyMap()).getSql();
686       assertEquals("update foo set name = ? where id = ?", sql);
687     }
688     {
689       Method mapperMethod = mapperType.getMethod("delete", int.class);
690       String sql = new ProviderSqlSource(configuration, mapperMethod.getAnnotation(DeleteProvider.class), mapperType,
691           mapperMethod).getBoundSql(Collections.emptyMap()).getSql();
692       assertEquals("delete from foo where id = ?", sql);
693     }
694   }
695 
696   public interface DefaultSqlProviderMapper {
697 
698     @SelectProvider
699     String select(int id);
700 
701     @InsertProvider
702     void insert(String name);
703 
704     @UpdateProvider
705     void update(int id, String name);
706 
707     @DeleteProvider
708     void delete(int id);
709 
710     final class SqlProvider {
711 
712       public static String provideSql(ProviderContext c) {
713         return switch (c.getMapperMethod().getName()) {
714           case "select" -> "select name from foo where id = #{id}";
715           case "insert" -> "insert into foo (name) values(#{name})";
716           case "update" -> "update foo set name = #{name} where id = #{id}";
717           default -> "delete from foo where id = #{id}";
718         };
719       }
720 
721       private SqlProvider() {
722       }
723 
724     }
725 
726   }
727 
728   public interface ErrorMapper {
729     @SelectProvider(type = ErrorSqlBuilder.class, method = "methodNotFound")
730     void methodNotFound();
731 
732     @SelectProvider(type = ErrorSqlBuilder.class, method = "overload")
733     void methodOverload(String value);
734 
735     @SelectProvider(type = ErrorSqlBuilder.class, method = "invokeError")
736     void invokeError();
737 
738     @SelectProvider(type = ErrorSqlBuilder.class, method = "invokeNestedError")
739     void invokeNestedError();
740 
741     @SelectProvider(type = ErrorSqlBuilder.class, method = "multipleProviderContext")
742     void multipleProviderContext();
743 
744     @SelectProvider
745     void omitType();
746 
747     @DeleteProvider(value = String.class, type = Integer.class)
748     void differentTypeAndValue();
749 
750     @DeleteProvider(type = ErrorSqlBuilder.class, method = "invalidArgumentsCombination")
751     void invalidArgumentsCombination(String value);
752 
753   }
754 
755   @SuppressWarnings("unused")
756   public static class ErrorSqlBuilder {
757     public void methodNotFound() {
758       throw new UnsupportedOperationException("methodNotFound");
759     }
760 
761     public String overload() {
762       throw new UnsupportedOperationException("overload");
763     }
764 
765     public String overload(String value) {
766       throw new UnsupportedOperationException("overload");
767     }
768 
769     public String invokeError() {
770       throw new UnsupportedOperationException("invokeError");
771     }
772 
773     public String invokeNestedError() {
774       throw new IllegalStateException(new UnsupportedOperationException("invokeNestedError"));
775     }
776 
777     public String multipleProviderContext(ProviderContext providerContext1, ProviderContext providerContext2) {
778       throw new UnsupportedOperationException("multipleProviderContext");
779     }
780 
781     public String invalidArgumentsCombination(ProviderContext providerContext, String value,
782         String unnecessaryArgument) {
783       return "";
784     }
785   }
786 
787   public interface StaticMethodSqlProviderMapper {
788     @SelectProvider(type = SqlProvider.class, method = "noArgument")
789     int noArgument();
790 
791     @SelectProvider(type = SqlProvider.class, method = "oneArgument")
792     int oneArgument(Integer value);
793 
794     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveByteArgument")
795     byte onePrimitiveByteArgument(byte value);
796 
797     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveShortArgument")
798     short onePrimitiveShortArgument(short value);
799 
800     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveIntArgument")
801     int onePrimitiveIntArgument(int value);
802 
803     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveLongArgument")
804     long onePrimitiveLongArgument(long value);
805 
806     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveFloatArgument")
807     float onePrimitiveFloatArgument(float value);
808 
809     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveDoubleArgument")
810     double onePrimitiveDoubleArgument(double value);
811 
812     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveBooleanArgument")
813     boolean onePrimitiveBooleanArgument(boolean value);
814 
815     @SelectProvider(type = SqlProvider.class, method = "onePrimitiveCharArgument")
816     char onePrimitiveCharArgument(char value);
817 
818     @SelectProvider(type = SqlProvider.class, method = "boxing")
819     int boxing(int value);
820 
821     @SelectProvider(type = SqlProvider.class, method = "unboxing")
822     int unboxing(Integer value);
823 
824     @SelectProvider(type = SqlProvider.class, method = "multipleArgument")
825     int multipleArgument(@Param("value1") Integer value1, @Param("value2") Integer value2);
826 
827     @SelectProvider(type = SqlProvider.class, method = "onlyProviderContext")
828     String onlyProviderContext();
829 
830     @SelectProvider(type = SqlProvider.class, method = "oneArgumentAndProviderContext")
831     String oneArgumentAndProviderContext(Integer value);
832 
833     @SelectProvider(type = SqlProvider.class, method = "mapAndProviderContext")
834     String mapAndProviderContext(@Param("value") String value);
835 
836     @SelectProvider(type = SqlProvider.class, method = "providerContextAndParamMap")
837     String providerContextAndParamMap(@Param("value") String value);
838 
839     @SelectProvider(type = SqlProvider.class, method = "multipleMap")
840     String multipleMap(@Param("map1") Map<String, Object> map1, @Param("map2") Map<String, Object> map2);
841 
842     @SuppressWarnings("unused")
843     final class SqlProvider {
844 
845       public static String noArgument() {
846         return "SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
847       }
848 
849       public static StringBuilder oneArgument(Integer value) {
850         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
851       }
852 
853       public static StringBuilder onePrimitiveByteArgument(byte value) {
854         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
855       }
856 
857       public static StringBuilder onePrimitiveShortArgument(short value) {
858         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
859       }
860 
861       public static StringBuilder onePrimitiveIntArgument(int value) {
862         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
863       }
864 
865       public static StringBuilder onePrimitiveLongArgument(long value) {
866         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
867       }
868 
869       public static StringBuilder onePrimitiveFloatArgument(float value) {
870         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
871       }
872 
873       public static StringBuilder onePrimitiveDoubleArgument(double value) {
874         return new StringBuilder().append("SELECT ").append(value).append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
875       }
876 
877       public static StringBuilder onePrimitiveBooleanArgument(boolean value) {
878         return new StringBuilder().append("SELECT ").append(value ? 1 : 0)
879             .append(" FROM INFORMATION_SCHEMA.SYSTEM_USERS");
880       }
881 
882       public static StringBuilder onePrimitiveCharArgument(char value) {
883         return new StringBuilder().append("SELECT '").append(value).append("' FROM INFORMATION_SCHEMA.SYSTEM_USERS");
884       }
885 
886       public static StringBuilder boxing(Integer value) {
887         return new StringBuilder().append("SELECT '").append(value).append("' FROM INFORMATION_SCHEMA.SYSTEM_USERS");
888       }
889 
890       public static StringBuilder unboxing(int value) {
891         return new StringBuilder().append("SELECT '").append(value).append("' FROM INFORMATION_SCHEMA.SYSTEM_USERS");
892       }
893 
894       public static CharSequence multipleArgument(@Param("value1") Integer value1, @Param("value2") Integer value2) {
895         return "SELECT " + (value1 + value2) + " FROM INFORMATION_SCHEMA.SYSTEM_USERS";
896       }
897 
898       public static CharSequence onlyProviderContext(ProviderContext context) {
899         return new StringBuilder().append("SELECT '").append(context.getMapperMethod().getName())
900             .append("' FROM INFORMATION_SCHEMA.SYSTEM_USERS");
901       }
902 
903       public static String oneArgumentAndProviderContext(Integer value, ProviderContext context) {
904         return "SELECT '" + context.getMapperMethod().getName() + " " + value
905             + "' FROM INFORMATION_SCHEMA.SYSTEM_USERS";
906       }
907 
908       public static String mapAndProviderContext(Map<String, Object> map, ProviderContext context) {
909         return "SELECT '" + map.get("value") + "' FROM INFORMATION_SCHEMA.SYSTEM_USERS";
910       }
911 
912       public static String providerContextAndParamMap(ProviderContext context, MapperMethod.ParamMap<Object> map) {
913         return "SELECT '" + map.get("value") + "' FROM INFORMATION_SCHEMA.SYSTEM_USERS";
914       }
915 
916       public static String multipleMap(@Param("map1") Map<String, Object> map1,
917           @Param("map2") Map<String, Object> map2) {
918         return "SELECT '" + map1.get("value") + map2.get("value") + "' FROM INFORMATION_SCHEMA.SYSTEM_USERS";
919       }
920 
921       private SqlProvider() {
922       }
923 
924     }
925 
926   }
927 
928   @Test
929   void shouldInsertUserSelective() {
930     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
931       Mapper mapper = sqlSession.getMapper(Mapper.class);
932       User user = new User();
933       user.setId(999);
934       mapper.insertSelective(user);
935 
936       User loadedUser = mapper.getUser(999);
937       assertNull(loadedUser.getName());
938     }
939   }
940 
941   @Test
942   void shouldUpdateUserSelective() {
943     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
944       Mapper mapper = sqlSession.getMapper(Mapper.class);
945       User user = new User();
946       user.setId(999);
947       user.setName("MyBatis");
948       mapper.insert(user);
949 
950       user.setName(null);
951       mapper.updateSelective(user);
952 
953       User loadedUser = mapper.getUser(999);
954       assertEquals("MyBatis", loadedUser.getName());
955     }
956   }
957 
958   @Test
959   void mapperGetByEntity() {
960     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
961       Mapper mapper = sqlSession.getMapper(Mapper.class);
962       User query = new User();
963       query.setName("User4");
964       assertEquals(1, mapper.getByEntity(query).size());
965       query = new User();
966       query.setId(1);
967       assertEquals(1, mapper.getByEntity(query).size());
968       query = new User();
969       query.setId(1);
970       query.setName("User4");
971       assertEquals(0, mapper.getByEntity(query).size());
972     }
973   }
974 
975   @Test
976   void shouldPassedDatabaseIdToProviderMethod() {
977     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
978       DatabaseIdMapper mapper = sqlSession.getMapper(DatabaseIdMapper.class);
979       assertEquals("hsql", mapper.selectDatabaseId());
980     }
981     try (SqlSession sqlSession = sqlSessionFactoryForDerby.openSession()) {
982       DatabaseIdMapper mapper = sqlSession.getMapper(DatabaseIdMapper.class);
983       assertEquals("derby", mapper.selectDatabaseId());
984     }
985   }
986 
987   interface DatabaseIdMapper {
988     @SelectProvider(type = SqlProvider.class)
989     String selectDatabaseId();
990 
991     @SuppressWarnings("unused")
992     final class SqlProvider {
993       public static String provideSql(ProviderContext context) {
994         if ("hsql".equals(context.getDatabaseId())) {
995           return "SELECT '" + context.getDatabaseId() + "' FROM INFORMATION_SCHEMA.SYSTEM_USERS";
996         }
997         return "SELECT '" + context.getDatabaseId() + "' FROM SYSIBM.SYSDUMMY1";
998       }
999 
1000       private SqlProvider() {
1001       }
1002     }
1003   }
1004 
1005 }