1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.mybatis.spring.mapper;
17
18 import static org.assertj.core.api.Assertions.assertThat;
19 import static org.junit.jupiter.api.Assertions.assertEquals;
20 import static org.junit.jupiter.api.Assertions.assertTrue;
21 import static org.junit.jupiter.api.Assertions.fail;
22
23 import com.mockrunner.mock.jdbc.MockDataSource;
24
25 import java.util.List;
26 import java.util.stream.Collectors;
27 import java.util.stream.Stream;
28
29 import org.apache.ibatis.annotations.Mapper;
30 import org.apache.ibatis.session.ExecutorType;
31 import org.apache.ibatis.session.SqlSessionFactory;
32 import org.junit.jupiter.api.AfterEach;
33 import org.junit.jupiter.api.BeforeEach;
34 import org.junit.jupiter.api.Test;
35 import org.mybatis.spring.SqlSessionFactoryBean;
36 import org.mybatis.spring.SqlSessionTemplate;
37 import org.mybatis.spring.mapper.child.MapperChildInterface;
38 import org.mybatis.spring.type.DummyMapperFactoryBean;
39 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
40 import org.springframework.beans.factory.config.BeanDefinition;
41 import org.springframework.beans.factory.config.ConstructorArgumentValues;
42 import org.springframework.beans.factory.config.RuntimeBeanReference;
43 import org.springframework.beans.factory.support.BeanDefinitionRegistry;
44 import org.springframework.beans.factory.support.GenericBeanDefinition;
45 import org.springframework.context.support.GenericApplicationContext;
46 import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
47 import org.springframework.context.support.SimpleThreadScope;
48 import org.springframework.mock.env.MockPropertySource;
49 import org.springframework.stereotype.Component;
50
51 class MapperScannerConfigurerTest {
52 private GenericApplicationContext applicationContext;
53
54 @BeforeEach
55 void setupContext() {
56 applicationContext = new GenericApplicationContext();
57
58
59
60
61 var definition = new GenericBeanDefinition();
62 definition.setBeanClass(MapperScannerConfigurer.class);
63 definition.getPropertyValues().add("basePackage", "org.mybatis.spring.mapper");
64 applicationContext.registerBeanDefinition("mapperScanner", definition);
65 applicationContext.getBeanFactory().registerScope("thread", new SimpleThreadScope());
66
67 setupSqlSessionFactory("sqlSessionFactory");
68
69
70
71 }
72
73 private void startContext() {
74 applicationContext.refresh();
75 applicationContext.start();
76
77
78 applicationContext.getBean("sqlSessionFactory");
79 }
80
81 @AfterEach
82 void assertNoMapperClass() {
83 try {
84
85 assertBeanNotLoaded("mapperClass");
86
87
88 assertBeanNotLoaded("package-info");
89
90 } finally {
91 applicationContext.close();
92 }
93 }
94
95 @Test
96 void testInterfaceScan() {
97 startContext();
98
99 var sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
100
101 assertEquals(5, sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers().size());
102
103
104 applicationContext.getBean("mapperInterface");
105 applicationContext.getBean("mapperSubinterface");
106 applicationContext.getBean("mapperChildInterface");
107 applicationContext.getBean("annotatedMapper");
108 applicationContext.getBean("scopedProxyMapper");
109 applicationContext.getBean("scopedTarget.scopedProxyMapper");
110
111 assertThat(Stream.of(applicationContext.getBeanDefinitionNames()).filter(x -> x.startsWith("scopedTarget")))
112 .hasSize(1);
113 assertThat(applicationContext.getBeanDefinition("mapperInterface").getPropertyValues().get("mapperInterface"))
114 .isEqualTo(MapperInterface.class);
115 assertThat(applicationContext.getBeanDefinition("mapperSubinterface").getPropertyValues().get("mapperInterface"))
116 .isEqualTo(MapperSubinterface.class);
117 assertThat(applicationContext.getBeanDefinition("mapperChildInterface").getPropertyValues().get("mapperInterface"))
118 .isEqualTo(MapperChildInterface.class);
119 assertThat(applicationContext.getBeanDefinition("annotatedMapper").getPropertyValues().get("mapperInterface"))
120 .isEqualTo(AnnotatedMapper.class);
121 assertThat(applicationContext.getBeanDefinition("scopedTarget.scopedProxyMapper").getPropertyValues()
122 .get("mapperInterface")).isEqualTo(ScopedProxyMapper.class);
123 }
124
125 @Test
126 void testNameGenerator() {
127 var definition = new GenericBeanDefinition();
128 definition.setBeanClass(BeanNameGenerator.class);
129 applicationContext.registerBeanDefinition("beanNameGenerator", definition);
130
131 applicationContext.getBeanDefinition("mapperScanner").getPropertyValues().add("nameGenerator",
132 new RuntimeBeanReference("beanNameGenerator"));
133
134 startContext();
135
136
137 applicationContext.getBean(MapperInterface.class.getName());
138 applicationContext.getBean(MapperSubinterface.class.getName());
139 applicationContext.getBean(MapperChildInterface.class.getName());
140 applicationContext.getBean(AnnotatedMapper.class.getName());
141 }
142
143 @Test
144 void testMarkerInterfaceScan() {
145 applicationContext.getBeanDefinition("mapperScanner").getPropertyValues().add("markerInterface",
146 MapperInterface.class);
147
148 startContext();
149
150
151 applicationContext.getBean("mapperSubinterface");
152 applicationContext.getBean("mapperChildInterface");
153
154 assertBeanNotLoaded("mapperInterface");
155 assertBeanNotLoaded("annotatedMapper");
156 }
157
158 @Test
159 void testAnnotationScan() {
160 applicationContext.getBeanDefinition("mapperScanner").getPropertyValues().add("annotationClass", Component.class);
161
162 startContext();
163
164
165 applicationContext.getBean("annotatedMapper");
166 applicationContext.getBean("mapperChildInterface");
167
168 assertBeanNotLoaded("mapperInterface");
169 assertBeanNotLoaded("mapperSubinterface");
170 }
171
172 @Test
173 void testMarkerInterfaceAndAnnotationScan() {
174 applicationContext.getBeanDefinition("mapperScanner").getPropertyValues().add("markerInterface",
175 MapperInterface.class);
176 applicationContext.getBeanDefinition("mapperScanner").getPropertyValues().add("annotationClass", Component.class);
177
178 startContext();
179
180
181 applicationContext.getBean("annotatedMapper");
182 applicationContext.getBean("mapperSubinterface");
183 applicationContext.getBean("mapperChildInterface");
184
185 assertBeanNotLoaded("mapperInterface");
186 }
187
188 @Test
189 void testScopedProxyMapperScan() {
190 applicationContext.getBeanDefinition("mapperScanner").getPropertyValues().add("annotationClass", Mapper.class);
191
192 startContext();
193 {
194 var definition = applicationContext.getBeanDefinition("scopedProxyMapper");
195 assertThat(definition.getBeanClassName()).isEqualTo("org.springframework.aop.scope.ScopedProxyFactoryBean");
196 assertThat(definition.getScope()).isEqualTo("");
197 }
198 {
199 var definition = applicationContext.getBeanDefinition("scopedTarget.scopedProxyMapper");
200 assertThat(definition.getBeanClassName()).isEqualTo("org.mybatis.spring.mapper.MapperFactoryBean");
201 assertThat(definition.getScope()).isEqualTo("thread");
202 }
203 {
204 var mapper = applicationContext.getBean(ScopedProxyMapper.class);
205 assertThat(mapper.test()).isEqualTo("test");
206 }
207 {
208 var mapper = applicationContext.getBean("scopedTarget.scopedProxyMapper", ScopedProxyMapper.class);
209 assertThat(mapper.test()).isEqualTo("test");
210 }
211 {
212 var mapper = applicationContext.getBean("scopedProxyMapper", ScopedProxyMapper.class);
213 assertThat(mapper.test()).isEqualTo("test");
214 }
215
216 var sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
217 assertEquals(1, sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers().size());
218 }
219
220 @Test
221 void testScopedProxyMapperScanByDefault() {
222 applicationContext.getBeanDefinition("mapperScanner").getPropertyValues().add("defaultScope", "thread");
223
224 startContext();
225
226 List<String> scopedProxyTargetBeans = Stream.of(applicationContext.getBeanDefinitionNames())
227 .filter(x -> x.startsWith("scopedTarget")).collect(Collectors.toList());
228 assertThat(scopedProxyTargetBeans).hasSize(6).contains("scopedTarget.scopedProxyMapper",
229 "scopedTarget.annotatedMapper", "scopedTarget.annotatedMapperZeroMethods", "scopedTarget.mapperInterface",
230 "scopedTarget.mapperSubinterface", "scopedTarget.mapperChildInterface");
231
232 for (String scopedProxyTargetBean : scopedProxyTargetBeans) {
233 {
234 var definition = applicationContext.getBeanDefinition(scopedProxyTargetBean);
235 assertThat(definition.getBeanClassName()).isEqualTo("org.mybatis.spring.mapper.MapperFactoryBean");
236 assertThat(definition.getScope()).isEqualTo("thread");
237 }
238 {
239 var definition = applicationContext.getBeanDefinition(scopedProxyTargetBean.substring(13));
240 assertThat(definition.getBeanClassName()).isEqualTo("org.springframework.aop.scope.ScopedProxyFactoryBean");
241 assertThat(definition.getScope()).isEqualTo("");
242 }
243 }
244 {
245 var mapper = applicationContext.getBean(ScopedProxyMapper.class);
246 assertThat(mapper.test()).isEqualTo("test");
247 }
248 {
249 var mapper = applicationContext.getBean(AnnotatedMapper.class);
250 assertThat(mapper.test()).isEqualTo("main");
251 }
252
253 var sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
254 assertEquals(2, sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers().size());
255 }
256
257 @Test
258 void testScanWithExplicitSqlSessionFactory() {
259 setupSqlSessionFactory("sqlSessionFactory2");
260
261 applicationContext.getBeanDefinition("mapperScanner").getPropertyValues().add("sqlSessionFactoryBeanName",
262 "sqlSessionFactory2");
263
264 startContext();
265
266
267 applicationContext.getBean("mapperInterface");
268 applicationContext.getBean("mapperSubinterface");
269 applicationContext.getBean("mapperChildInterface");
270 applicationContext.getBean("annotatedMapper");
271 }
272
273 @Test
274 void testScanWithExplicitSqlSessionTemplate() {
275 var definition = new GenericBeanDefinition();
276 definition.setBeanClass(SqlSessionTemplate.class);
277 var constructorArgs = new ConstructorArgumentValues();
278 constructorArgs.addGenericArgumentValue(new RuntimeBeanReference("sqlSessionFactory"));
279 definition.setConstructorArgumentValues(constructorArgs);
280 applicationContext.registerBeanDefinition("sqlSessionTemplate", definition);
281
282 applicationContext.getBeanDefinition("mapperScanner").getPropertyValues().add("sqlSessionTemplateBeanName",
283 "sqlSessionTemplate");
284
285 startContext();
286
287
288 applicationContext.getBean("mapperInterface");
289 applicationContext.getBean("mapperSubinterface");
290 applicationContext.getBean("mapperChildInterface");
291 applicationContext.getBean("annotatedMapper");
292 }
293
294 @Test
295 void testScanWithExplicitSqlSessionFactoryViaPlaceholder() {
296 setupSqlSessionFactory("sqlSessionFactory2");
297
298
299 applicationContext.getBeanDefinition("mapperScanner").getPropertyValues().add("sqlSessionFactoryBeanName",
300 "${sqlSessionFactoryBeanNameProperty}");
301
302 var props = new java.util.Properties();
303 props.put("sqlSessionFactoryBeanNameProperty", "sqlSessionFactory2");
304
305 var propertyDefinition = new GenericBeanDefinition();
306 propertyDefinition.setBeanClass(PropertySourcesPlaceholderConfigurer.class);
307 propertyDefinition.getPropertyValues().add("properties", props);
308
309 applicationContext.registerBeanDefinition("propertiesPlaceholder", propertyDefinition);
310
311 startContext();
312
313
314 applicationContext.getBean("mapperInterface");
315 applicationContext.getBean("mapperSubinterface");
316 applicationContext.getBean("mapperChildInterface");
317 applicationContext.getBean("annotatedMapper");
318 }
319
320 @Test
321 void testScanWithNameConflict() {
322 var definition = new GenericBeanDefinition();
323 definition.setBeanClass(Object.class);
324 applicationContext.registerBeanDefinition("mapperInterface", definition);
325
326 startContext();
327
328 assertThat(applicationContext.getBean("mapperInterface").getClass())
329 .as("scanner should not overwrite existing bean definition").isSameAs(Object.class);
330 }
331
332 @Test
333 void testScanWithPropertyPlaceholders() {
334 var definition = (GenericBeanDefinition) applicationContext.getBeanDefinition("mapperScanner");
335
336
337 definition.getPropertyValues().removePropertyValue("basePackage");
338 definition.getPropertyValues().add("basePackage", "${basePackageProperty}");
339 definition.getPropertyValues().add("processPropertyPlaceHolders", true);
340
341 definition.getPropertyValues().add("lazyInitialization", "${mybatis.lazy-initialization:false}");
342
343
344
345
346 definition = (GenericBeanDefinition) applicationContext.getBeanDefinition("sqlSessionFactory");
347 definition.getPropertyValues().removePropertyValue("configLocation");
348 definition.getPropertyValues().add("configLocation", "${configLocationProperty}");
349
350 var props = new java.util.Properties();
351 props.put("basePackageProperty", "org.mybatis.spring.mapper");
352 props.put("configLocationProperty", "classpath:org/mybatis/spring/mybatis-config.xml");
353 props.put("mybatis.lazy-initialization", "true");
354
355 var propertyDefinition = new GenericBeanDefinition();
356 propertyDefinition.setBeanClass(PropertySourcesPlaceholderConfigurer.class);
357 propertyDefinition.getPropertyValues().add("properties", props);
358
359 applicationContext.registerBeanDefinition("propertiesPlaceholder", propertyDefinition);
360
361 startContext();
362
363 var sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
364 System.out.println(sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers());
365 assertEquals(1, sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers().size());
366
367
368 applicationContext.getBean("mapperInterface");
369 applicationContext.getBean("mapperSubinterface");
370 applicationContext.getBean("mapperChildInterface");
371 applicationContext.getBean("annotatedMapper");
372
373 assertEquals(5, sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers().size());
374
375
376
377 var sessionFactory = (SqlSessionFactory) applicationContext.getBean("sqlSessionFactory");
378 assertThat(sessionFactory.getConfiguration().getDefaultExecutorType()).isSameAs(ExecutorType.REUSE);
379 }
380
381 @Test
382 void testScanWithMapperFactoryBeanClass() {
383 DummyMapperFactoryBean.clear();
384 applicationContext.getBeanDefinition("mapperScanner").getPropertyValues().add("mapperFactoryBeanClass",
385 DummyMapperFactoryBean.class);
386
387 startContext();
388
389 applicationContext.getBean("mapperInterface");
390 applicationContext.getBean("mapperSubinterface");
391 applicationContext.getBean("mapperChildInterface");
392 applicationContext.getBean("annotatedMapper");
393
394 assertTrue(DummyMapperFactoryBean.getMapperCount() > 0);
395 }
396
397 @Test
398 void testMapperBeanAttribute() {
399 startContext();
400
401 assertThat(applicationContext.getBeanDefinition("annotatedMapper")
402 .getAttribute(ClassPathMapperScanner.FACTORY_BEAN_OBJECT_TYPE)).isEqualTo(AnnotatedMapper.class);
403 }
404
405 @Test
406 void testMapperBeanOnConditionalProperties() {
407 var propertySources = applicationContext.getEnvironment().getPropertySources();
408 propertySources.addLast(new MockPropertySource().withProperty("mapper.condition", "true"));
409
410 startContext();
411
412 assertThat(applicationContext.getBeanDefinition("annotatedMapperOnPropertyCondition")
413 .getAttribute(ClassPathMapperScanner.FACTORY_BEAN_OBJECT_TYPE))
414 .isEqualTo(AnnotatedMapperOnPropertyCondition.class);
415 }
416
417 private void setupSqlSessionFactory(String name) {
418 var definition = new GenericBeanDefinition();
419 definition.setBeanClass(SqlSessionFactoryBean.class);
420 definition.getPropertyValues().add("dataSource", new MockDataSource());
421 applicationContext.registerBeanDefinition(name, definition);
422 }
423
424 private void assertBeanNotLoaded(String name) {
425 try {
426 applicationContext.getBean(name);
427 fail("Spring bean should not be defined for class " + name);
428 } catch (NoSuchBeanDefinitionException nsbde) {
429
430 }
431 }
432
433 public static class BeanNameGenerator implements org.springframework.beans.factory.support.BeanNameGenerator {
434
435 @Override
436 public String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry definitionRegistry) {
437 return beanDefinition.getBeanClassName();
438 }
439
440 }
441
442 }