1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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 }