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  
20  import java.io.Reader;
21  import java.lang.reflect.Method;
22  import java.util.Arrays;
23  import java.util.List;
24  import java.util.stream.Collectors;
25  
26  import org.apache.ibatis.BaseDataTest;
27  import org.apache.ibatis.annotations.DeleteProvider;
28  import org.apache.ibatis.annotations.InsertProvider;
29  import org.apache.ibatis.annotations.SelectProvider;
30  import org.apache.ibatis.annotations.UpdateProvider;
31  import org.apache.ibatis.builder.BuilderException;
32  import org.apache.ibatis.builder.annotation.ProviderContext;
33  import org.apache.ibatis.builder.annotation.ProviderMethodResolver;
34  import org.apache.ibatis.io.Resources;
35  import org.apache.ibatis.session.SqlSession;
36  import org.apache.ibatis.session.SqlSessionFactory;
37  import org.apache.ibatis.session.SqlSessionFactoryBuilder;
38  import org.junit.jupiter.api.Assertions;
39  import org.junit.jupiter.api.BeforeAll;
40  import org.junit.jupiter.api.Test;
41  
42  /**
43   * Test for https://github.com/mybatis/mybatis-3/issues/1279
44   */
45  class ProviderMethodResolutionTest {
46  
47    private static SqlSessionFactory sqlSessionFactory;
48  
49    @BeforeAll
50    static void setUp() throws Exception {
51      try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/sqlprovider/mybatis-config.xml")) {
52        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
53        sqlSessionFactory.getConfiguration().addMapper(ProvideMethodResolverMapper.class);
54      }
55      BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
56          "org/apache/ibatis/submitted/sqlprovider/CreateDB.sql");
57    }
58  
59    @Test
60    void shouldResolveWhenDefaultResolverMatchedMethodIsOne() {
61      try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
62        ProvideMethodResolverMapper mapper = sqlSession.getMapper(ProvideMethodResolverMapper.class);
63        assertEquals(1, mapper.select());
64      }
65    }
66  
67    @Test
68    void shouldErrorWhenDefaultResolverMethodNameMatchedMethodIsNone() {
69      BuilderException e = Assertions.assertThrows(BuilderException.class, () -> sqlSessionFactory.getConfiguration()
70          .addMapper(DefaultProvideMethodResolverMethodNameMatchedMethodIsNoneMapper.class));
71      assertEquals(
72          "Cannot resolve the provider method because 'insert' not found in SqlProvider 'org.apache.ibatis.submitted.sqlprovider.ProviderMethodResolutionTest$DefaultProvideMethodResolverMethodNameMatchedMethodIsNoneMapper$MethodResolverBasedSqlProvider'.",
73          e.getMessage());
74    }
75  
76    @Test
77    void shouldErrorWhenDefaultResolverReturnTypeMatchedMethodIsNone() {
78      BuilderException e = Assertions.assertThrows(BuilderException.class, () -> sqlSessionFactory.getConfiguration()
79          .addMapper(DefaultProvideMethodResolverReturnTypeMatchedMethodIsNoneMapper.class));
80      assertEquals(
81          "Cannot resolve the provider method because 'insert' does not return the CharSequence or its subclass in SqlProvider 'org.apache.ibatis.submitted.sqlprovider.ProviderMethodResolutionTest$DefaultProvideMethodResolverReturnTypeMatchedMethodIsNoneMapper$MethodResolverBasedSqlProvider'.",
82          e.getMessage());
83    }
84  
85    @Test
86    void shouldErrorWhenDefaultResolverMatchedMethodIsMultiple() {
87      BuilderException e = Assertions.assertThrows(BuilderException.class, () -> sqlSessionFactory.getConfiguration()
88          .addMapper(DefaultProvideMethodResolverMatchedMethodIsMultipleMapper.class));
89      assertEquals(
90          "Cannot resolve the provider method because 'update' is found multiple in SqlProvider 'org.apache.ibatis.submitted.sqlprovider.ProviderMethodResolutionTest$DefaultProvideMethodResolverMatchedMethodIsMultipleMapper$MethodResolverBasedSqlProvider'.",
91          e.getMessage());
92    }
93  
94    @Test
95    void shouldResolveReservedMethod() {
96      try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
97        ProvideMethodResolverMapper mapper = sqlSession.getMapper(ProvideMethodResolverMapper.class);
98        assertEquals(1, mapper.delete());
99      }
100   }
101 
102   @Test
103   void shouldUseSpecifiedMethodOnSqlProviderAnnotation() {
104     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
105       ProvideMethodResolverMapper mapper = sqlSession.getMapper(ProvideMethodResolverMapper.class);
106       assertEquals(2, mapper.select2());
107     }
108   }
109 
110   @Test
111   void shouldResolveMethodUsingCustomResolver() {
112     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
113       ProvideMethodResolverMapper mapper = sqlSession.getMapper(ProvideMethodResolverMapper.class);
114       assertEquals(3, mapper.select3());
115     }
116   }
117 
118   @Test
119   void shouldResolveReservedNameMethodWhenCustomResolverReturnNull() {
120     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
121       ProvideMethodResolverMapper mapper = sqlSession.getMapper(ProvideMethodResolverMapper.class);
122       assertEquals(99, mapper.select4());
123     }
124   }
125 
126   @Test
127   void shouldErrorWhenCannotDetectsReservedNameMethod() {
128     BuilderException e = Assertions.assertThrows(BuilderException.class,
129         () -> sqlSessionFactory.getConfiguration().addMapper(ReservedNameMethodIsNoneMapper.class));
130     assertEquals(
131         "Error creating SqlSource for SqlProvider. Method 'provideSql' not found in SqlProvider 'org.apache.ibatis.submitted.sqlprovider.ProviderMethodResolutionTest$ReservedNameMethodIsNoneMapper$SqlProvider'.",
132         e.getMessage());
133   }
134 
135   interface ProvideMethodResolverMapper {
136 
137     @SelectProvider(MethodResolverBasedSqlProvider.class)
138     int select();
139 
140     @SelectProvider(type = MethodResolverBasedSqlProvider.class, method = "provideSelect2Sql")
141     int select2();
142 
143     @SelectProvider(type = CustomMethodResolverBasedSqlProvider.class)
144     int select3();
145 
146     @SelectProvider(type = CustomMethodResolverBasedSqlProvider.class)
147     int select4();
148 
149     @DeleteProvider(ReservedMethodNameBasedSqlProvider.class)
150     int delete();
151 
152     class MethodResolverBasedSqlProvider implements ProviderMethodResolver {
153       public static String select() {
154         return "SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
155       }
156 
157       public static String select2() {
158         throw new IllegalStateException(
159             "This method should not called when specify `method` attribute on @SelectProvider.");
160       }
161 
162       public static String provideSelect2Sql() {
163         return "SELECT 2 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
164       }
165     }
166 
167     class ReservedMethodNameBasedSqlProvider {
168       public static String provideSql() {
169         return "DELETE FROM memos WHERE id = 1";
170       }
171 
172       private ReservedMethodNameBasedSqlProvider() {
173       }
174     }
175 
176     class CustomMethodResolverBasedSqlProvider implements CustomProviderMethodResolver {
177       public static String select3Sql() {
178         return "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
179       }
180 
181       public static String provideSql() {
182         return "SELECT 99 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
183       }
184     }
185 
186   }
187 
188   interface CustomProviderMethodResolver extends ProviderMethodResolver {
189     @Override
190     default Method resolveMethod(ProviderContext context) {
191       List<Method> targetMethods = Arrays.stream(getClass().getMethods())
192           .filter(m -> m.getName().equals(context.getMapperMethod().getName() + "Sql"))
193           .filter(m -> CharSequence.class.isAssignableFrom(m.getReturnType())).collect(Collectors.toList());
194       if (targetMethods.size() == 1) {
195         return targetMethods.get(0);
196       }
197       return null;
198     }
199   }
200 
201   interface DefaultProvideMethodResolverMethodNameMatchedMethodIsNoneMapper {
202 
203     @InsertProvider(type = MethodResolverBasedSqlProvider.class)
204     int insert();
205 
206     class MethodResolverBasedSqlProvider implements ProviderMethodResolver {
207       public static String provideInsertSql() {
208         return "INSERT INTO foo (name) VALUES(#{name})";
209       }
210     }
211 
212   }
213 
214   interface DefaultProvideMethodResolverReturnTypeMatchedMethodIsNoneMapper {
215 
216     @InsertProvider(MethodResolverBasedSqlProvider.class)
217     int insert();
218 
219     class MethodResolverBasedSqlProvider implements ProviderMethodResolver {
220       public static int insert() {
221         return 1;
222       }
223     }
224 
225   }
226 
227   interface DefaultProvideMethodResolverMatchedMethodIsMultipleMapper {
228 
229     @UpdateProvider(MethodResolverBasedSqlProvider.class)
230     int update();
231 
232     class MethodResolverBasedSqlProvider implements ProviderMethodResolver {
233       public static String update() {
234         return "UPDATE foo SET name = #{name} WHERE id = #{id}";
235       }
236 
237       public static StringBuilder update(ProviderContext context) {
238         return new StringBuilder("UPDATE foo SET name = #{name} WHERE id = #{id}");
239       }
240     }
241 
242   }
243 
244   interface ReservedNameMethodIsNoneMapper {
245 
246     @UpdateProvider(type = SqlProvider.class)
247     int update();
248 
249     class SqlProvider {
250       public static String select() {
251         return "SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
252       }
253 
254       private SqlProvider() {
255       }
256     }
257 
258   }
259 
260 }