View Javadoc
1   /**
2    *    Copyright 2009-2020 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.apache.ibatis.submitted.sqlprovider;
17  
18  import static org.junit.jupiter.api.Assertions.assertEquals;
19  
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
52          .getResourceAsReader("org/apache/ibatis/submitted/sqlprovider/mybatis-config.xml")) {
53        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
54        sqlSessionFactory.getConfiguration().addMapper(ProvideMethodResolverMapper.class);
55      }
56      BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
57          "org/apache/ibatis/submitted/sqlprovider/CreateDB.sql");
58    }
59  
60    @Test
61    void shouldResolveWhenDefaultResolverMatchedMethodIsOne() {
62      try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
63        ProvideMethodResolverMapper mapper = sqlSession.getMapper(ProvideMethodResolverMapper.class);
64        assertEquals(1, mapper.select());
65      }
66    }
67  
68    @Test
69    void shouldErrorWhenDefaultResolverMethodNameMatchedMethodIsNone() {
70      BuilderException e = Assertions.assertThrows(BuilderException.class,
71          () -> sqlSessionFactory.getConfiguration().addMapper(DefaultProvideMethodResolverMethodNameMatchedMethodIsNoneMapper.class));
72      assertEquals(
73          "Cannot resolve the provider method because 'insert' not found in SqlProvider 'org.apache.ibatis.submitted.sqlprovider.ProviderMethodResolutionTest$DefaultProvideMethodResolverMethodNameMatchedMethodIsNoneMapper$MethodResolverBasedSqlProvider'.",
74          e.getCause().getMessage());
75    }
76  
77    @Test
78    void shouldErrorWhenDefaultResolverReturnTypeMatchedMethodIsNone() {
79      BuilderException e = Assertions.assertThrows(BuilderException.class,
80          () -> sqlSessionFactory.getConfiguration().addMapper(DefaultProvideMethodResolverReturnTypeMatchedMethodIsNoneMapper.class));
81      assertEquals(
82          "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'.",
83          e.getCause().getMessage());
84    }
85  
86    @Test
87    void shouldErrorWhenDefaultResolverMatchedMethodIsMultiple() {
88      BuilderException e = Assertions.assertThrows(BuilderException.class,
89          () -> sqlSessionFactory.getConfiguration().addMapper(DefaultProvideMethodResolverMatchedMethodIsMultipleMapper.class));
90      assertEquals(
91          "Cannot resolve the provider method because 'update' is found multiple in SqlProvider 'org.apache.ibatis.submitted.sqlprovider.ProviderMethodResolutionTest$DefaultProvideMethodResolverMatchedMethodIsMultipleMapper$MethodResolverBasedSqlProvider'.",
92          e.getCause().getMessage());
93    }
94  
95    @Test
96    void shouldResolveReservedMethod() {
97      try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
98        ProvideMethodResolverMapper mapper = sqlSession.getMapper(ProvideMethodResolverMapper.class);
99        assertEquals(1, mapper.delete());
100     }
101   }
102 
103   @Test
104   void shouldUseSpecifiedMethodOnSqlProviderAnnotation() {
105     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
106       ProvideMethodResolverMapper mapper = sqlSession.getMapper(ProvideMethodResolverMapper.class);
107       assertEquals(2, mapper.select2());
108     }
109   }
110 
111   @Test
112   void shouldResolveMethodUsingCustomResolver() {
113     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
114       ProvideMethodResolverMapper mapper = sqlSession.getMapper(ProvideMethodResolverMapper.class);
115       assertEquals(3, mapper.select3());
116     }
117   }
118 
119   @Test
120   void shouldResolveReservedNameMethodWhenCustomResolverReturnNull() {
121     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
122       ProvideMethodResolverMapper mapper = sqlSession.getMapper(ProvideMethodResolverMapper.class);
123       assertEquals(99, mapper.select4());
124     }
125   }
126 
127   @Test
128   void shouldErrorWhenCannotDetectsReservedNameMethod() {
129     BuilderException e = Assertions.assertThrows(BuilderException.class,
130         () -> sqlSessionFactory.getConfiguration().addMapper(ReservedNameMethodIsNoneMapper.class));
131     assertEquals(
132         "Error creating SqlSource for SqlProvider. Method 'provideSql' not found in SqlProvider 'org.apache.ibatis.submitted.sqlprovider.ProviderMethodResolutionTest$ReservedNameMethodIsNoneMapper$SqlProvider'.",
133         e.getCause().getMessage());
134   }
135 
136   interface ProvideMethodResolverMapper {
137 
138     @SelectProvider(MethodResolverBasedSqlProvider.class)
139     int select();
140 
141     @SelectProvider(type = MethodResolverBasedSqlProvider.class, method = "provideSelect2Sql")
142     int select2();
143 
144     @SelectProvider(type = CustomMethodResolverBasedSqlProvider.class)
145     int select3();
146 
147     @SelectProvider(type = CustomMethodResolverBasedSqlProvider.class)
148     int select4();
149 
150     @DeleteProvider(ReservedMethodNameBasedSqlProvider.class)
151     int delete();
152 
153     class MethodResolverBasedSqlProvider implements ProviderMethodResolver {
154       public static String select() {
155         return "SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
156       }
157 
158       public static String select2() {
159         throw new IllegalStateException("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 
173     class CustomMethodResolverBasedSqlProvider implements CustomProviderMethodResolver {
174       public static String select3Sql() {
175         return "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
176       }
177 
178       public static String provideSql() {
179         return "SELECT 99 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
180       }
181     }
182 
183   }
184 
185   interface CustomProviderMethodResolver extends ProviderMethodResolver {
186     @Override
187     default Method resolveMethod(ProviderContext context) {
188       List<Method> targetMethods = Arrays.stream(getClass().getMethods())
189           .filter(m -> m.getName().equals(context.getMapperMethod().getName() + "Sql"))
190           .filter(m -> CharSequence.class.isAssignableFrom(m.getReturnType()))
191           .collect(Collectors.toList());
192       if (targetMethods.size() == 1) {
193         return targetMethods.get(0);
194       }
195       return null;
196     }
197   }
198 
199   interface DefaultProvideMethodResolverMethodNameMatchedMethodIsNoneMapper {
200 
201     @InsertProvider(type = MethodResolverBasedSqlProvider.class)
202     int insert();
203 
204     class MethodResolverBasedSqlProvider implements ProviderMethodResolver {
205       public static String provideInsertSql() {
206         return "INSERT INTO foo (name) VALUES(#{name})";
207       }
208     }
209 
210   }
211 
212   interface DefaultProvideMethodResolverReturnTypeMatchedMethodIsNoneMapper {
213 
214     @InsertProvider(MethodResolverBasedSqlProvider.class)
215     int insert();
216 
217     class MethodResolverBasedSqlProvider implements ProviderMethodResolver {
218       public static int insert() {
219         return 1;
220       }
221     }
222 
223   }
224 
225   interface DefaultProvideMethodResolverMatchedMethodIsMultipleMapper {
226 
227     @UpdateProvider(MethodResolverBasedSqlProvider.class)
228     int update();
229 
230     class MethodResolverBasedSqlProvider implements ProviderMethodResolver {
231       public static String update() {
232         return "UPDATE foo SET name = #{name} WHERE id = #{id}";
233       }
234 
235       public static StringBuilder update(ProviderContext context) {
236         return new StringBuilder("UPDATE foo SET name = #{name} WHERE id = #{id}");
237       }
238     }
239 
240   }
241 
242   interface ReservedNameMethodIsNoneMapper {
243 
244     @UpdateProvider(type = SqlProvider.class)
245     int update();
246 
247     class SqlProvider {
248       public static String select() {
249         return "SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
250       }
251     }
252 
253   }
254 
255 }