View Javadoc
1   /*
2    *    Copyright 2015-2025 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.mybatis.spring.boot.autoconfigure;
17  
18  import static org.assertj.core.api.Assertions.assertThat;
19  
20  import com.example.mapper.DateTimeMapper;
21  
22  import java.math.BigInteger;
23  import java.sql.CallableStatement;
24  import java.sql.PreparedStatement;
25  import java.sql.ResultSet;
26  import java.util.Map;
27  import java.util.Properties;
28  import java.util.UUID;
29  import java.util.concurrent.atomic.AtomicInteger;
30  import java.util.concurrent.atomic.AtomicLong;
31  
32  import javax.sql.DataSource;
33  
34  import org.apache.ibatis.cache.impl.PerpetualCache;
35  import org.apache.ibatis.mapping.Environment;
36  import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
37  import org.apache.ibatis.plugin.Interceptor;
38  import org.apache.ibatis.plugin.Intercepts;
39  import org.apache.ibatis.plugin.Invocation;
40  import org.apache.ibatis.plugin.Plugin;
41  import org.apache.ibatis.plugin.Signature;
42  import org.apache.ibatis.scripting.LanguageDriver;
43  import org.apache.ibatis.scripting.LanguageDriverRegistry;
44  import org.apache.ibatis.scripting.defaults.RawLanguageDriver;
45  import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
46  import org.apache.ibatis.session.ExecutorType;
47  import org.apache.ibatis.session.SqlSessionFactory;
48  import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
49  import org.apache.ibatis.type.BaseTypeHandler;
50  import org.apache.ibatis.type.JdbcType;
51  import org.apache.ibatis.type.TypeHandlerRegistry;
52  import org.flywaydb.core.Flyway;
53  import org.junit.jupiter.api.Test;
54  import org.mockito.Mockito;
55  import org.mybatis.scripting.freemarker.FreeMarkerLanguageDriver;
56  import org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriver;
57  import org.mybatis.scripting.velocity.VelocityLanguageDriver;
58  import org.mybatis.spring.SqlSessionFactoryBean;
59  import org.mybatis.spring.SqlSessionTemplate;
60  import org.mybatis.spring.annotation.MapperScan;
61  import org.mybatis.spring.boot.autoconfigure.handler.AtomicNumberTypeHandler;
62  import org.mybatis.spring.boot.autoconfigure.handler.DummyTypeHandler;
63  import org.mybatis.spring.boot.autoconfigure.mapper.CityMapper;
64  import org.mybatis.spring.boot.autoconfigure.repository.CityMapperImpl;
65  import org.mybatis.spring.mapper.MapperFactoryBean;
66  import org.mybatis.spring.mapper.MapperScannerConfigurer;
67  import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
68  import org.springframework.aop.scope.ScopedProxyFactoryBean;
69  import org.springframework.beans.BeansException;
70  import org.springframework.beans.factory.BeanCreationException;
71  import org.springframework.beans.factory.annotation.Qualifier;
72  import org.springframework.beans.factory.config.BeanDefinition;
73  import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
74  import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
75  import org.springframework.beans.factory.config.PropertiesFactoryBean;
76  import org.springframework.beans.factory.config.RuntimeBeanReference;
77  import org.springframework.boot.autoconfigure.AutoConfigurations;
78  import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
79  import org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration;
80  import org.springframework.boot.flyway.autoconfigure.FlywayMigrationInitializer;
81  import org.springframework.boot.jdbc.autoconfigure.EmbeddedDataSourceConfiguration;
82  import org.springframework.boot.liquibase.autoconfigure.LiquibaseAutoConfiguration;
83  import org.springframework.boot.test.context.runner.ApplicationContextRunner;
84  import org.springframework.context.annotation.Bean;
85  import org.springframework.context.annotation.Configuration;
86  import org.springframework.context.annotation.Primary;
87  import org.springframework.context.support.SimpleThreadScope;
88  import org.springframework.core.Ordered;
89  import org.springframework.core.annotation.Order;
90  
91  import liquibase.integration.spring.SpringLiquibase;
92  
93  /**
94   * Tests for {@link MybatisAutoConfiguration}
95   *
96   * @author Eddú Meléndez
97   * @author Josh Long
98   * @author Kazuki Shimizu
99   */
100 class MybatisAutoConfigurationTest {
101 
102   private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
103       .withConfiguration(AutoConfigurations.of(MybatisAutoConfiguration.class));
104 
105   @Test
106   void testNoDataSource() {
107     this.contextRunner.withUserConfiguration(PropertyPlaceholderAutoConfiguration.class).run(context -> {
108       assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).isEmpty();
109       assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).isEmpty();
110       assertThat(context.getBeanNamesForType(MybatisProperties.class)).isEmpty();
111     });
112   }
113 
114   @Test
115   void testMultipleDataSource() {
116     this.contextRunner
117         .withUserConfiguration(MultipleDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
118         .run(context -> {
119           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).isEmpty();
120           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).isEmpty();
121           assertThat(context.getBeanNamesForType(MybatisProperties.class)).isEmpty();
122         });
123   }
124 
125   @Test
126   void testSingleCandidateDataSource() {
127     this.contextRunner
128         .withUserConfiguration(SingleCandidateDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
129         .run(context -> {
130           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
131           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
132           assertThat(context.getBeanNamesForType(MybatisProperties.class)).hasSize(1);
133         });
134   }
135 
136   @Test
137   void testDefaultConfiguration() {
138     this.contextRunner
139         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisLanguageDriverAutoConfiguration.class,
140             MybatisScanMapperConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
141         .run(context -> {
142           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
143           assertThat(sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers()).hasSize(1);
144           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
145           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
146           assertThat(context.getBeanNamesForType(DateTimeMapper.class)).hasSize(1);
147           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.SIMPLE);
148           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().isMapUnderscoreToCamelCase())
149               .isFalse();
150           Map<String, LanguageDriver> languageDriverBeans = context.getBeansOfType(LanguageDriver.class);
151           assertThat(languageDriverBeans).hasSize(3).containsKeys("freeMarkerLanguageDriver", "velocityLanguageDriver",
152               "thymeleafLanguageDriver");
153           assertThat(languageDriverBeans.get("freeMarkerLanguageDriver")).isInstanceOf(FreeMarkerLanguageDriver.class);
154           assertThat(languageDriverBeans.get("velocityLanguageDriver")).isInstanceOf(VelocityLanguageDriver.class);
155           assertThat(languageDriverBeans.get("thymeleafLanguageDriver")).isInstanceOf(ThymeleafLanguageDriver.class);
156           LanguageDriverRegistry languageDriverRegistry = sqlSessionFactory.getConfiguration().getLanguageRegistry();
157           assertThat(languageDriverRegistry.getDefaultDriverClass()).isEqualTo(XMLLanguageDriver.class);
158           assertThat(languageDriverRegistry.getDefaultDriver()).isInstanceOf(XMLLanguageDriver.class);
159           assertThat(languageDriverRegistry.getDriver(XMLLanguageDriver.class)).isNotNull();
160           assertThat(languageDriverRegistry.getDriver(RawLanguageDriver.class)).isNotNull();
161           assertThat(languageDriverRegistry.getDriver(FreeMarkerLanguageDriver.class)).isNotNull();
162           assertThat(languageDriverRegistry.getDriver(VelocityLanguageDriver.class)).isNotNull();
163           assertThat(languageDriverRegistry.getDriver(ThymeleafLanguageDriver.class)).isNotNull();
164         });
165   }
166 
167   @Test
168   void testScanWithLazy() {
169     this.contextRunner
170         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisScanMapperConfiguration.class,
171             PropertyPlaceholderAutoConfiguration.class)
172         .withPropertyValues("mybatis.lazy-initialization:true").run(context -> {
173           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
174           assertThat(sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers()).hasSize(0);
175           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
176           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
177           assertThat(context.getBeanNamesForType(DateTimeMapper.class)).hasSize(1);
178           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.SIMPLE);
179           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().isMapUnderscoreToCamelCase())
180               .isFalse();
181           context.getBean(DateTimeMapper.class);
182           assertThat(sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers()).hasSize(1);
183         });
184   }
185 
186   @Test
187   void testAutoScanWithDefault() {
188     this.contextRunner
189         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
190             PropertyPlaceholderAutoConfiguration.class, CityMapperRepositoryConfiguration.class)
191         .run(context -> {
192           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
193           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
194           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
195           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
196           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.SIMPLE);
197           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().isMapUnderscoreToCamelCase())
198               .isFalse();
199           context.getBean(CityMapper.class);
200           assertThat(sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers()).hasSize(1);
201           assertThat(((RuntimeBeanReference) context.getBeanFactory().getBeanDefinition("cityMapper")
202               .getPropertyValues().getPropertyValue("sqlSessionTemplate").getValue()).getBeanName())
203                   .isEqualTo("sqlSessionTemplate");
204           assertThat(context.getBeanFactory()
205               .getBeanDefinition(context.getBeanNamesForType(MapperScannerConfigurer.class)[0]).getRole())
206                   .isEqualTo(BeanDefinition.ROLE_INFRASTRUCTURE);
207         });
208   }
209 
210   @Test
211   void testAutoScanWithInjectSqlSessionOnMapperScanIsFalse() {
212     this.contextRunner
213         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
214             PropertyPlaceholderAutoConfiguration.class, CityMapperRepositoryConfiguration.class)
215         .withPropertyValues("mybatis.inject-sql-session-on-mapper-scan:false").run(context -> {
216           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
217           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
218           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
219           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
220           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.SIMPLE);
221           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().isMapUnderscoreToCamelCase())
222               .isFalse();
223           context.getBean(CityMapper.class);
224           assertThat(sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers()).hasSize(1);
225           assertThat(context.getBeanFactory().getBeanDefinition("cityMapper").getPropertyValues()
226               .getPropertyValue("sqlSessionTemplate")).isNull();
227           assertThat(context.getBeanFactory().getBeanDefinition("cityMapper").getPropertyValues()
228               .getPropertyValue("sqlSessionFactory")).isNull();
229         });
230   }
231 
232   @Test
233   void testAutoScanWithLazy() {
234     this.contextRunner
235         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
236             PropertyPlaceholderAutoConfiguration.class, CityMapperRepositoryConfiguration.class)
237         .withPropertyValues("mybatis.lazy-initialization:true").run(context -> {
238           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
239           assertThat(sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers()).hasSize(0);
240           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
241           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
242           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
243           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.SIMPLE);
244           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().isMapUnderscoreToCamelCase())
245               .isFalse();
246           context.getBean(CityMapper.class);
247           assertThat(sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers()).hasSize(1);
248         });
249   }
250 
251   @Test
252   void testAutoScanWithDefaultScope() {
253     this.contextRunner
254         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
255             PropertyPlaceholderAutoConfiguration.class, CityMapperRepositoryConfiguration.class)
256         .withPropertyValues("mybatis.mapper-default-scope:thread").run(context -> {
257           context.getBean(CityMapper.class);
258           BeanDefinition bd = context.getBeanFactory().getBeanDefinition("cityMapper");
259           assertThat(bd.getBeanClassName()).isEqualTo(ScopedProxyFactoryBean.class.getName());
260           BeanDefinition spbd = context.getBeanFactory().getBeanDefinition("scopedTarget.cityMapper");
261           assertThat(spbd.getBeanClassName()).isEqualTo(MapperFactoryBean.class.getName());
262           assertThat(spbd.getScope()).isEqualTo("thread");
263         });
264   }
265 
266   @Test
267   void testAutoScanWithoutDefaultScope() {
268     this.contextRunner
269         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
270             PropertyPlaceholderAutoConfiguration.class, CityMapperRepositoryConfiguration.class)
271         .run(context -> {
272           context.getBean(CityMapper.class);
273           BeanDefinition df = context.getBeanFactory().getBeanDefinition("cityMapper");
274           assertThat(df.getBeanClassName()).isEqualTo(MapperFactoryBean.class.getName());
275           assertThat(df.getScope()).isEqualTo("singleton");
276         });
277   }
278 
279   @Test
280   void testWithConfigLocation() {
281     this.contextRunner
282         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisMapperConfiguration.class,
283             PropertyPlaceholderAutoConfiguration.class)
284         .withPropertyValues("mybatis.config-location:mybatis-config.xml").run(context -> {
285           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
286           assertThat(context.getBeanNamesForType(CityMapperImpl.class)).hasSize(1);
287           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.BATCH);
288           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().isMapUnderscoreToCamelCase()).isTrue();
289         });
290   }
291 
292   @Test
293   void testWithCheckConfigLocationFileExists() {
294     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisAutoConfiguration.class)
295         .withPropertyValues("mybatis.config-location:mybatis-config.xml", "mybatis.check-config-location=true")
296         .run(context -> assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1));
297   }
298 
299   @Test
300   void testWithCheckConfigLocationFileNotSpecify() {
301     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
302         .withPropertyValues("mybatis.check-config-location=true")
303         .run(context -> assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1));
304   }
305 
306   @Test
307   void testWithCheckConfigLocationFileDoesNotExists() {
308     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
309         .withPropertyValues("mybatis.config-location:foo.xml", "mybatis.check-config-location=true")
310         .run(context -> assertThat(context).getFailure().isInstanceOf(BeanCreationException.class).hasMessageContaining(
311             "Cannot find config location: class path resource [foo.xml] (please add config file or check your Mybatis configuration)"));
312   }
313 
314   @Test
315   void testWithTypeHandlersPackage() {
316     this.contextRunner
317         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
318         .withPropertyValues("mybatis.type-handlers-package:org.mybatis.spring.boot.autoconfigure.handler")
319         .run(context -> {
320           TypeHandlerRegistry typeHandlerRegistry = context.getBean(SqlSessionFactory.class).getConfiguration()
321               .getTypeHandlerRegistry();
322           assertThat(typeHandlerRegistry.hasTypeHandler(BigInteger.class)).isTrue();
323           assertThat(typeHandlerRegistry.hasTypeHandler(AtomicInteger.class)).isTrue();
324           assertThat(typeHandlerRegistry.hasTypeHandler(AtomicLong.class)).isTrue();
325         });
326   }
327 
328   @Test
329   void testWithMapperLocation() {
330     this.contextRunner
331         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
332         .withPropertyValues("mybatis.type-aliases-package:org.mybatis.spring.boot.autoconfigure.domain",
333             "mybatis.mapper-locations:classpath:org/mybatis/spring/boot/autoconfigure/repository/CityMapper.xml")
334         .run(
335             context -> assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getMappedStatementNames())
336                 .hasSize(2));
337   }
338 
339   @Test
340   void testWithExecutorType() {
341     this.contextRunner
342         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisMapperConfiguration.class,
343             PropertyPlaceholderAutoConfiguration.class)
344         .withPropertyValues("mybatis.config-location:mybatis-config.xml", "mybatis.executor-type:REUSE")
345         .run(context -> assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType())
346             .isEqualTo(ExecutorType.REUSE));
347   }
348 
349   @Test
350   void testDefaultBootConfiguration() {
351     this.contextRunner
352         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
353             PropertyPlaceholderAutoConfiguration.class, CityMapperRepositoryConfiguration.class)
354         .run(context -> {
355           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
356           assertThat(sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers()).hasSize(1);
357           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
358           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
359           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
360         });
361   }
362 
363   @Test
364   void testWithInterceptorsOrder1() {
365     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class,
366         MybatisInterceptorConfiguration.class, PropertyPlaceholderAutoConfiguration.class).run(context -> {
367           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
368           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
369           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getInterceptors()).hasSize(2);
370           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getInterceptors().get(0))
371               .isInstanceOf(MyInterceptor2.class);
372           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getInterceptors().get(1))
373               .isInstanceOf(MyInterceptor.class);
374         });
375   }
376 
377   @Test
378   void testWithInterceptorsOrder2() {
379     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class,
380         MybatisInterceptorConfiguration2.class, PropertyPlaceholderAutoConfiguration.class).run(context -> {
381           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
382           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
383           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getInterceptors()).hasSize(2);
384           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getInterceptors().get(0))
385               .isInstanceOf(MyInterceptor.class);
386           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getInterceptors().get(1))
387               .isInstanceOf(MyInterceptor2.class);
388         });
389   }
390 
391   @Test
392   void testWithTypeHandlers() {
393     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class,
394         MybatisTypeHandlerConfiguration.class, PropertyPlaceholderAutoConfiguration.class).run(context -> {
395           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
396           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
397           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getTypeHandlerRegistry()
398               .getTypeHandler(UUID.class)).isInstanceOf(MyTypeHandler.class);
399         });
400   }
401 
402   @Test
403   void testWithDatabaseIdProvider() {
404     this.contextRunner
405         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, DatabaseProvidersConfiguration.class,
406             PropertyPlaceholderAutoConfiguration.class)
407         .run(context -> assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getDatabaseId())
408             .isEqualTo("h2"));
409   }
410 
411   @Test
412   void testMixedWithConfigurationFileAndInterceptor() {
413     this.contextRunner
414         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisInterceptorConfiguration.class,
415             CityMapperRepositoryConfiguration.class)
416         .withPropertyValues("mybatis.config-location:mybatis-config-settings-only.xml").run(context -> {
417           org.apache.ibatis.session.Configuration configuration = context.getBean(SqlSessionFactory.class)
418               .getConfiguration();
419           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
420           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
421           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
422           assertThat(configuration.getDefaultFetchSize()).isEqualTo(1000);
423           assertThat(configuration.getInterceptors()).hasSize(2);
424           assertThat(configuration.getInterceptors().get(0)).isInstanceOf(MyInterceptor2.class);
425           assertThat(configuration.getInterceptors().get(1)).isInstanceOf(MyInterceptor.class);
426         });
427   }
428 
429   @Test
430   void testMixedWithConfigurationFileAndDatabaseIdProvider() {
431     this.contextRunner
432         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
433             DatabaseProvidersConfiguration.class, CityMapperRepositoryConfiguration.class)
434         .withPropertyValues("mybatis.config-location:mybatis-config-settings-only.xml").run(context -> {
435           org.apache.ibatis.session.Configuration configuration = context.getBean(SqlSessionFactory.class)
436               .getConfiguration();
437           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
438           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
439           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
440           assertThat(configuration.getDefaultFetchSize()).isEqualTo(1000);
441           assertThat(configuration.getDatabaseId()).isEqualTo("h2");
442         });
443   }
444 
445   @Test
446   void testMixedWithConfigurationFileAndTypeHandlersPackage() {
447     this.contextRunner
448         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
449             CityMapperRepositoryConfiguration.class)
450         .withPropertyValues("mybatis.config-location:mybatis-config-settings-only.xml",
451             "mybatis.type-handlers-package:org.mybatis.spring.boot.autoconfigure.handler.")
452         .run(context -> {
453           org.apache.ibatis.session.Configuration configuration = context.getBean(SqlSessionFactory.class)
454               .getConfiguration();
455           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
456           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
457           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
458           assertThat(configuration.getDefaultFetchSize()).isEqualTo(1000);
459           assertThat(configuration.getTypeHandlerRegistry().getTypeHandler(BigInteger.class))
460               .isInstanceOf(DummyTypeHandler.class);
461           assertThat(configuration.getTypeHandlerRegistry().getTypeHandler(AtomicInteger.class))
462               .isInstanceOf(AtomicNumberTypeHandler.class);
463           assertThat(configuration.getTypeHandlerRegistry().getTypeHandler(AtomicLong.class))
464               .isInstanceOf(AtomicNumberTypeHandler.class);
465           assertThat(configuration.getTypeHandlerRegistry().getTypeHandler(AtomicInteger.class))
466               .hasToString("type=" + AtomicInteger.class);
467         });
468   }
469 
470   @Test
471   void testMixedWithConfigurationFileAndTypeAliasesPackageAndMapperLocations() {
472     this.contextRunner
473         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
474             CityMapperRepositoryConfiguration.class)
475         .withPropertyValues("mybatis.config-location:mybatis-config-settings-only.xml",
476             "mybatis.type-aliases-package:org.mybatis.spring.boot.autoconfigure.domain",
477             "mybatis.mapper-locations:classpath:org/mybatis/spring/boot/autoconfigure/repository/CityMapper.xml")
478         .run(context -> {
479           org.apache.ibatis.session.Configuration configuration = context.getBean(SqlSessionFactory.class)
480               .getConfiguration();
481           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
482           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
483           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
484           assertThat(configuration.getDefaultFetchSize()).isEqualTo(1000);
485           assertThat(configuration.getMappedStatementNames()).contains("selectCityById");
486           assertThat(configuration.getMappedStatementNames())
487               .contains("org.mybatis.spring.boot.autoconfigure.repository.CityMapperImpl.selectCityById");
488           assertThat(configuration.getTypeAliasRegistry().getTypeAliases()).containsKey("city");
489           assertThat(configuration.getTypeAliasRegistry().getTypeAliases()).containsKey("name");
490         });
491   }
492 
493   @Test
494   void testMixedWithFullConfigurations() {
495     this.contextRunner
496         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
497             MybatisInterceptorConfiguration.class, DatabaseProvidersConfiguration.class,
498             CityMapperRepositoryConfiguration.class)
499         .withPropertyValues("mybatis.config-location:mybatis-config-settings-only.xml",
500             "mybatis.type-handlers-package:org.mybatis.spring.**.handler",
501             "mybatis.type-aliases-package:org.mybatis.spring.boot.autoconfigure.domain",
502             "mybatis.mapper-locations:classpath:org/mybatis/spring/boot/autoconfigure/repository/CityMapper.xml",
503             "mybatis.executor-type=REUSE")
504         .run(context -> {
505           org.apache.ibatis.session.Configuration configuration = context.getBean(SqlSessionFactory.class)
506               .getConfiguration();
507           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
508           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
509           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
510           assertThat(configuration.getDefaultFetchSize()).isEqualTo(1000);
511           assertThat(configuration.getTypeHandlerRegistry().getTypeHandler(BigInteger.class))
512               .isInstanceOf(DummyTypeHandler.class);
513           assertThat(configuration.getTypeHandlerRegistry().getTypeHandler(AtomicInteger.class))
514               .isInstanceOf(AtomicNumberTypeHandler.class);
515           assertThat(configuration.getTypeHandlerRegistry().getTypeHandler(AtomicLong.class))
516               .isInstanceOf(AtomicNumberTypeHandler.class);
517           assertThat(configuration.getMappedStatementNames()).hasSize(4);
518           assertThat(configuration.getMappedStatementNames()).contains("selectCityById");
519           assertThat(configuration.getMappedStatementNames())
520               .contains("org.mybatis.spring.boot.autoconfigure.repository.CityMapperImpl.selectCityById");
521           assertThat(configuration.getMappedStatementNames()).contains("findById");
522           assertThat(configuration.getMappedStatementNames())
523               .contains("org.mybatis.spring.boot.autoconfigure.mapper.CityMapper.findById");
524           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.REUSE);
525           assertThat(configuration.getInterceptors()).hasSize(2);
526           assertThat(configuration.getInterceptors().get(0)).isInstanceOf(MyInterceptor2.class);
527           assertThat(configuration.getInterceptors().get(1)).isInstanceOf(MyInterceptor.class);
528           assertThat(configuration.getDatabaseId()).isEqualTo("h2");
529         });
530   }
531 
532   @Test
533   void testWithMyBatisConfiguration() {
534     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
535         .withPropertyValues("mybatis.configuration.map-underscore-to-camel-case:true").run(context -> assertThat(
536             context.getBean(SqlSessionFactory.class).getConfiguration().isMapUnderscoreToCamelCase()).isTrue());
537   }
538 
539   @Test
540   void testWithMyBatisConfigurationCustomizer() {
541     this.contextRunner
542         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MyBatisConfigurationCustomizerConfiguration.class)
543         .run(context -> {
544           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
545           assertThat(sqlSessionFactory.getConfiguration().getTypeHandlerRegistry().getTypeHandler(BigInteger.class))
546               .isInstanceOf(DummyTypeHandler.class);
547           assertThat(sqlSessionFactory.getConfiguration().getCache("test")).isNotNull();
548         });
549   }
550 
551   @Test
552   void testWithSqlSessionFactoryBeanCustomizer() {
553     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class,
554         SqlSessionFactoryBeanCustomizerConfiguration.class).run(context -> {
555           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
556           assertThat(sqlSessionFactory.getConfiguration().getTypeHandlerRegistry().getTypeHandler(BigInteger.class))
557               .isInstanceOf(DummyTypeHandler.class);
558           assertThat(sqlSessionFactory.getConfiguration().getCache("test")).isNotNull();
559         });
560   }
561 
562   @Test
563   void testConfigFileAndConfigurationWithTogether() {
564     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
565         .withPropertyValues("mybatis.config-location:mybatis-config.xml",
566             "mybatis.configuration.default-statement-timeout:30")
567         .run(context -> {
568           assertThat(context).hasFailed();
569           assertThat(context).getFailure().isInstanceOf(BeanCreationException.class)
570               .hasMessageContaining("Property 'configuration' and 'configLocation' can not specified with together");
571         });
572   }
573 
574   @Test
575   void testWithoutConfigurationVariablesAndProperties() {
576     this.contextRunner
577         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
578         .run(context -> {
579           Properties variables = context.getBean(SqlSessionFactory.class).getConfiguration().getVariables();
580           assertThat(variables).isEmpty();
581         });
582   }
583 
584   @Test
585   void testWithConfigurationVariablesOnly() {
586     this.contextRunner
587         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
588         .withPropertyValues("mybatis.configuration.variables.key1:value1").run(context -> {
589           Properties variables = context.getBean(SqlSessionFactory.class).getConfiguration().getVariables();
590           assertThat(variables).hasSize(1);
591           assertThat(variables.getProperty("key1")).isEqualTo("value1");
592         });
593   }
594 
595   @Test
596   void testWithConfigurationPropertiesOnly() {
597     this.contextRunner
598         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
599         .withPropertyValues("mybatis.configuration-properties.key2:value2").run(context -> {
600           Properties variables = context.getBean(SqlSessionFactory.class).getConfiguration().getVariables();
601           assertThat(variables).hasSize(1);
602           assertThat(variables.getProperty("key2")).isEqualTo("value2");
603         });
604   }
605 
606   @Test
607   void testWithConfigurationVariablesAndPropertiesOtherKey() {
608     this.contextRunner
609         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
610         .withPropertyValues("mybatis.configuration.variables.key1:value1",
611             "mybatis.configuration-properties.key2:value2")
612         .run(context -> {
613           Properties variables = context.getBean(SqlSessionFactory.class).getConfiguration().getVariables();
614           assertThat(variables).hasSize(2);
615           assertThat(variables.getProperty("key1")).isEqualTo("value1");
616           assertThat(variables.getProperty("key2")).isEqualTo("value2");
617         });
618   }
619 
620   @Test
621   void testWithConfigurationVariablesAndPropertiesSameKey() {
622     this.contextRunner
623         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
624         .withPropertyValues("mybatis.configuration.variables.key:value1", "mybatis.configuration-properties.key:value2")
625         .run(context -> {
626           Properties variables = context.getBean(SqlSessionFactory.class).getConfiguration().getVariables();
627           assertThat(variables).hasSize(1);
628           assertThat(variables.getProperty("key")).isEqualTo("value2");
629         });
630   }
631 
632   @Test
633   void testCustomSqlSessionFactory() {
634     this.contextRunner
635         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
636             CustomSqlSessionFactoryConfiguration.class, CityMapperRepositoryConfiguration.class)
637         .run(context -> {
638           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
639           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getVariables().getProperty("key"))
640               .isEqualTo("value");
641           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
642           assertThat(((RuntimeBeanReference) context.getBeanFactory().getBeanDefinition("cityMapper")
643               .getPropertyValues().getPropertyValue("sqlSessionFactory").getValue()).getBeanName())
644                   .isEqualTo("customSqlSessionFactory");
645         });
646   }
647 
648   @Test
649   void testMySqlSessionFactory() {
650     this.contextRunner
651         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MySqlSessionFactoryConfiguration.class)
652         .run(context -> {
653           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
654           assertThat(context.getBean(SqlSessionFactory.class)).isInstanceOf(MySqlSessionFactory.class);
655         });
656   }
657 
658   @Test
659   void testCustomSqlSessionTemplate() {
660     this.contextRunner
661         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
662             CustomSqlSessionTemplateConfiguration.class, CityMapperRepositoryConfiguration.class)
663         .run(context -> {
664           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
665           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.BATCH);
666           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
667           assertThat(((RuntimeBeanReference) context.getBeanFactory().getBeanDefinition("cityMapper")
668               .getPropertyValues().getPropertyValue("sqlSessionTemplate").getValue()).getBeanName())
669                   .isEqualTo("customSqlSessionTemplate");
670         });
671   }
672 
673   @Test
674   void testMySqlSessionTemplate() {
675     this.contextRunner
676         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MySqlSessionTemplateConfiguration.class)
677         .run(context -> {
678           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
679           assertThat(context.getBean(SqlSessionTemplate.class)).isInstanceOf(MySqlSessionTemplate.class);
680         });
681   }
682 
683   @Test
684   void testCustomSqlSessionTemplateAndSqlSessionFactory() {
685     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class,
686         MybatisBootMapperScanAutoConfiguration.class, CustomSqlSessionFactoryConfiguration.class,
687         CustomSqlSessionTemplateConfiguration.class, CityMapperRepositoryConfiguration.class).run(context -> {
688           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
689           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.BATCH);
690           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
691           assertThat(((RuntimeBeanReference) context.getBeanFactory().getBeanDefinition("cityMapper")
692               .getPropertyValues().getPropertyValue("sqlSessionTemplate").getValue()).getBeanName())
693                   .isEqualTo("customSqlSessionTemplate");
694         });
695   }
696 
697   @Test
698   void testTypeAliasesSuperTypeIsSpecify() {
699     this.contextRunner
700         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class)
701         .withPropertyValues("mybatis.type-aliases-package:org.mybatis.spring.boot.autoconfigure.domain",
702             "mybatis.type-aliases-super-type:org.mybatis.spring.boot.autoconfigure.domain.Domain")
703         .run(context -> {
704           org.apache.ibatis.session.Configuration configuration = context.getBean(SqlSessionFactory.class)
705               .getConfiguration();
706           assertThat(configuration.getTypeAliasRegistry().getTypeAliases()).containsKey("city");
707           assertThat(configuration.getTypeAliasRegistry().getTypeAliases()).doesNotContainKey("name");
708         });
709   }
710 
711   @Test
712   void testMapperFactoryBean() {
713     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class,
714         MapperFactoryBeanConfiguration.class, PropertyPlaceholderAutoConfiguration.class).run(context -> {
715           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
716           assertThat(sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers()).hasSize(1);
717           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
718           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
719           assertThat(context.getBeanNamesForType(DateTimeMapper.class)).hasSize(1);
720           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.SIMPLE);
721           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().isMapUnderscoreToCamelCase())
722               .isFalse();
723         });
724   }
725 
726   @Test
727   void testMapperScannerConfigurer() {
728     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class,
729         MapperScannerConfigurerConfiguration.class, PropertyPlaceholderAutoConfiguration.class).run(context -> {
730           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
731           assertThat(sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers()).hasSize(1);
732           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
733           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
734           assertThat(context.getBeanNamesForType(DateTimeMapper.class)).hasSize(1);
735           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.SIMPLE);
736           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().isMapUnderscoreToCamelCase())
737               .isFalse();
738         });
739   }
740 
741   @Test
742   void testDefaultScriptingLanguageIsSpecify() {
743     this.contextRunner
744         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisScanMapperConfiguration.class,
745             PropertyPlaceholderAutoConfiguration.class)
746         .withPropertyValues(
747             "mybatis.default-scripting-language-driver:org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriver")
748         .run(context -> {
749           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
750           LanguageDriverRegistry languageDriverRegistry = sqlSessionFactory.getConfiguration().getLanguageRegistry();
751           assertThat(languageDriverRegistry.getDefaultDriverClass()).isEqualTo(ThymeleafLanguageDriver.class);
752           assertThat(languageDriverRegistry.getDefaultDriver()).isInstanceOf(ThymeleafLanguageDriver.class);
753           assertThat(languageDriverRegistry.getDriver(ThymeleafLanguageDriver.class)).isNotNull();
754         });
755   }
756 
757   @Test
758   void testExcludeMybatisLanguageDriverAutoConfiguration() {
759     this.contextRunner
760         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisScanMapperConfiguration.class,
761             PropertyPlaceholderAutoConfiguration.class)
762         .withPropertyValues(
763             "spring.autoconfigure.exclude:org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration")
764         .run(context -> {
765           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
766           assertThat(sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers()).hasSize(1);
767           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
768           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
769           assertThat(context.getBeanNamesForType(DateTimeMapper.class)).hasSize(1);
770           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.SIMPLE);
771           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().isMapUnderscoreToCamelCase())
772               .isFalse();
773           assertThat(context.getBeanNamesForType(LanguageDriver.class)).hasSize(0);
774         });
775   }
776 
777   @Test
778   void testMybatisLanguageDriverAutoConfigurationWithSingleCandidate() {
779     this.contextRunner
780         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisScanMapperConfiguration.class,
781             SingleLanguageDriverConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
782         .withPropertyValues(
783             "spring.autoconfigure.exclude:org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration")
784         .run(context -> {
785           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
786           LanguageDriverRegistry languageDriverRegistry = sqlSessionFactory.getConfiguration().getLanguageRegistry();
787           assertThat(context.getBeanNamesForType(LanguageDriver.class)).hasSize(1);
788           assertThat(languageDriverRegistry.getDefaultDriverClass()).isEqualTo(ThymeleafLanguageDriver.class);
789           assertThat(languageDriverRegistry.getDefaultDriver()).isInstanceOf(ThymeleafLanguageDriver.class);
790           assertThat(languageDriverRegistry.getDriver(ThymeleafLanguageDriver.class)).isNotNull();
791         });
792   }
793 
794   @Test
795   void testMybatisLanguageDriverAutoConfigurationWithSingleCandidateWhenDefaultLanguageDriverIsSpecify() {
796     this.contextRunner
797         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisScanMapperConfiguration.class,
798             SingleLanguageDriverConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
799         .withPropertyValues(
800             "mybatis.default-scripting-language-driver:org.apache.ibatis.scripting.xmltags.XMLLanguageDriver",
801             "spring.autoconfigure.exclude:org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration")
802         .run(context -> {
803           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
804           LanguageDriverRegistry languageDriverRegistry = sqlSessionFactory.getConfiguration().getLanguageRegistry();
805           assertThat(context.getBeanNamesForType(LanguageDriver.class)).hasSize(1);
806           assertThat(languageDriverRegistry.getDefaultDriverClass()).isEqualTo(XMLLanguageDriver.class);
807           assertThat(languageDriverRegistry.getDefaultDriver()).isInstanceOf(XMLLanguageDriver.class);
808           assertThat(languageDriverRegistry.getDriver(ThymeleafLanguageDriver.class)).isNotNull();
809         });
810   }
811 
812   @Test
813   void whenFlywayIsAutoConfiguredThenMybatisSqlSessionTemplateDependsOnFlywayBeans() {
814     ApplicationContextRunner contextRunner = new ApplicationContextRunner()
815         .withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class, MybatisAutoConfiguration.class));
816     contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> {
817       BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("sqlSessionTemplate");
818       assertThat(beanDefinition.getDependsOn()).containsExactlyInAnyOrder("flywayInitializer", "flyway");
819     });
820   }
821 
822   @Test
823   void whenCustomMigrationInitializerIsDefinedThenMybatisSqlSessionTemplateDependsOnIt() {
824     ApplicationContextRunner contextRunner = new ApplicationContextRunner()
825         .withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class, MybatisAutoConfiguration.class));
826     contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, CustomFlywayMigrationInitializer.class)
827         .run((context) -> {
828           BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("sqlSessionTemplate");
829           assertThat(beanDefinition.getDependsOn()).containsExactlyInAnyOrder("flywayMigrationInitializer", "flyway");
830         });
831   }
832 
833   @Test
834   void whenCustomFlywayIsDefinedThenMybatisSqlSessionTemplateDependsOnIt() {
835     ApplicationContextRunner contextRunner = new ApplicationContextRunner()
836         .withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class, MybatisAutoConfiguration.class));
837     contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, CustomFlyway.class).run((context) -> {
838       BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("sqlSessionTemplate");
839       assertThat(beanDefinition.getDependsOn()).containsExactlyInAnyOrder("customFlyway");
840     });
841   }
842 
843   @Test
844   void whenLiquibaseIsAutoConfiguredThenMybatisSqlSessionTemplateDependsOnSpringLiquibaseBeans() {
845     ApplicationContextRunner contextRunner = new ApplicationContextRunner()
846         .withConfiguration(AutoConfigurations.of(LiquibaseAutoConfiguration.class, MybatisAutoConfiguration.class));
847     contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> {
848       BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("sqlSessionTemplate");
849       assertThat(beanDefinition.getDependsOn()).containsExactly("liquibase");
850     });
851   }
852 
853   @Test
854   void whenCustomSpringLiquibaseIsDefinedThenMybatisSqlSessionTemplateDependsOnSpringLiquibaseBeans() {
855     ApplicationContextRunner contextRunner = new ApplicationContextRunner()
856         .withConfiguration(AutoConfigurations.of(LiquibaseAutoConfiguration.class, MybatisAutoConfiguration.class));
857     contextRunner.withUserConfiguration(LiquibaseUserConfiguration.class, EmbeddedDataSourceConfiguration.class)
858         .run((context) -> {
859           BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("sqlSessionTemplate");
860           assertThat(beanDefinition.getDependsOn()).containsExactly("springLiquibase");
861         });
862   }
863 
864   @Test
865   void testTypeAliasesWithMultiByteCharacterInPackageName() {
866     this.contextRunner
867         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class)
868         .withPropertyValues("mybatis.config-location:mybatis-config2.xml").run(context -> {
869           org.apache.ibatis.session.Configuration configuration = context.getBean(SqlSessionFactory.class)
870               .getConfiguration();
871           assertThat(configuration.getTypeAliasRegistry().getTypeAliases()).containsKey("シティー");
872         });
873   }
874 
875   @Configuration
876   static class MultipleDataSourceConfiguration {
877     @Bean
878     DataSource dataSourcePrimary() {
879       return Mockito.mock(DataSource.class);
880     }
881 
882     @Bean
883     DataSource dataSourceReplica() {
884       return Mockito.mock(DataSource.class);
885     }
886   }
887 
888   @Configuration
889   static class SingleCandidateDataSourceConfiguration {
890     @Bean
891     @Primary
892     DataSource dataSourcePrimary() {
893       return Mockito.mock(DataSource.class);
894     }
895 
896     @Bean
897     DataSource dataSourceReplica() {
898       return Mockito.mock(DataSource.class);
899     }
900   }
901 
902   @Configuration
903   @MapperScan(basePackages = "com.example.mapper", lazyInitialization = "${mybatis.lazy-initialization:false}")
904   static class MybatisScanMapperConfiguration {
905   }
906 
907   @Configuration
908   static class MapperFactoryBeanConfiguration {
909     @Bean
910     MapperFactoryBean<DateTimeMapper> dateTimeMapper(SqlSessionFactory sqlSessionFactory) {
911       MapperFactoryBean<DateTimeMapper> factoryBean = new MapperFactoryBean<>(DateTimeMapper.class);
912       factoryBean.setSqlSessionFactory(sqlSessionFactory);
913       return factoryBean;
914     }
915   }
916 
917   @Configuration
918   static class MapperScannerConfigurerConfiguration {
919     @Bean
920     static MapperScannerConfigurer mapperScannerConfigurer() {
921       MapperScannerConfigurer configurer = new MapperScannerConfigurer();
922       configurer.setBasePackage("com.example.mapper");
923       return configurer;
924     }
925   }
926 
927   @Configuration
928   static class MybatisBootMapperScanAutoConfiguration implements BeanFactoryPostProcessor {
929     @Override
930     public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
931       beanFactory.registerScope("thread", new SimpleThreadScope());
932     }
933   }
934 
935   @Configuration
936   static class MybatisMapperConfiguration {
937 
938     @Bean
939     public CityMapperImpl cityMapper() {
940       return new CityMapperImpl();
941     }
942 
943   }
944 
945   @Configuration
946   static class MybatisInterceptorConfiguration {
947 
948     @Bean
949     @Order(2)
950     public MyInterceptor myInterceptor() {
951       return new MyInterceptor();
952     }
953 
954     @Bean
955     @Order(1)
956     public MyInterceptor2 myInterceptor2() {
957       return new MyInterceptor2();
958     }
959 
960   }
961 
962   @Configuration
963   static class MybatisInterceptorConfiguration2 {
964 
965     @Bean
966     @Order(1)
967     public MyInterceptor myInterceptor() {
968       return new MyInterceptor();
969     }
970 
971     @Bean
972     @Order(2)
973     public MyInterceptor2 myInterceptor2() {
974       return new MyInterceptor2();
975     }
976 
977   }
978 
979   @Configuration
980   static class MybatisTypeHandlerConfiguration {
981 
982     @Bean
983     public MyTypeHandler myTypeHandler() {
984       return new MyTypeHandler();
985     }
986 
987   }
988 
989   @Configuration
990   static class MyBatisConfigurationCustomizerConfiguration {
991     @Bean
992     ConfigurationCustomizer typeHandlerConfigurationCustomizer() {
993       return configuration -> configuration.getTypeHandlerRegistry().register(new DummyTypeHandler());
994     }
995 
996     @Bean
997     ConfigurationCustomizer cacheConfigurationCustomizer() {
998       return configuration -> configuration.addCache(new PerpetualCache("test"));
999     }
1000   }
1001 
1002   @Configuration
1003   static class SqlSessionFactoryBeanCustomizerConfiguration {
1004     @Bean
1005     SqlSessionFactoryBeanCustomizer typeHandlerSqlSessionFactoryBeanCustomizer() {
1006       return factoryBean -> factoryBean.setTypeHandlers(new DummyTypeHandler());
1007     }
1008 
1009     @Bean
1010     SqlSessionFactoryBeanCustomizer cacheSqlSessionFactoryBeanCustomizer() {
1011       return factoryBean -> factoryBean.setCache(new PerpetualCache("test"));
1012     }
1013   }
1014 
1015   @Configuration
1016   static class SingleLanguageDriverConfiguration {
1017     @Bean
1018     ThymeleafLanguageDriver myThymeleafLanguageDriver() {
1019       return new ThymeleafLanguageDriver();
1020     }
1021   }
1022 
1023   @Intercepts(@Signature(type = Map.class, method = "get", args = { Object.class }))
1024   static class MyInterceptor implements Interceptor {
1025 
1026     @Override
1027     public Object intercept(Invocation invocation) {
1028       return "Test";
1029     }
1030 
1031     @Override
1032     public Object plugin(Object target) {
1033       return Plugin.wrap(target, this);
1034     }
1035 
1036     @Override
1037     public void setProperties(Properties properties) {
1038 
1039     }
1040   }
1041 
1042   @Intercepts(@Signature(type = Map.class, method = "get", args = { Object.class }))
1043   static class MyInterceptor2 implements Interceptor {
1044 
1045     @Override
1046     public Object intercept(Invocation invocation) {
1047       return "Test2";
1048     }
1049 
1050     @Override
1051     public Object plugin(Object target) {
1052       return Plugin.wrap(target, this);
1053     }
1054 
1055     @Override
1056     public void setProperties(Properties properties) {
1057 
1058     }
1059   }
1060 
1061   @Configuration
1062   static class DatabaseProvidersConfiguration {
1063 
1064     @Bean
1065     public PropertiesFactoryBean vendorProperties() {
1066       Properties properties = new Properties();
1067       properties.put("SQL Server", "sqlserver");
1068       properties.put("DB2", "db2");
1069       properties.put("H2", "h2");
1070 
1071       PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
1072       propertiesFactoryBean.setProperties(properties);
1073       return propertiesFactoryBean;
1074     }
1075 
1076     @Bean
1077     public VendorDatabaseIdProvider vendorDatabaseIdProvider(
1078         @Qualifier("vendorProperties") Properties vendorProperties) {
1079       VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
1080       databaseIdProvider.setProperties(vendorProperties);
1081       return databaseIdProvider;
1082     }
1083 
1084   }
1085 
1086   @Configuration
1087   static class CustomSqlSessionFactoryConfiguration {
1088     @Bean
1089     public SqlSessionFactory customSqlSessionFactory(DataSource dataSource) throws Exception {
1090       SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
1091       sqlSessionFactoryBean.setDataSource(dataSource);
1092       Properties props = new Properties();
1093       props.setProperty("key", "value");
1094       sqlSessionFactoryBean.setConfigurationProperties(props);
1095       return sqlSessionFactoryBean.getObject();
1096     }
1097   }
1098 
1099   @Configuration
1100   static class MySqlSessionFactoryConfiguration {
1101     @Bean
1102     public SqlSessionFactory sqlSessionFactory(DataSource dataSource) {
1103       MySqlSessionFactory sqlSessionFactory = new MySqlSessionFactory(new org.apache.ibatis.session.Configuration());
1104       sqlSessionFactory.getConfiguration()
1105           .setEnvironment(new Environment("", new SpringManagedTransactionFactory(), dataSource));
1106       return sqlSessionFactory;
1107     }
1108   }
1109 
1110   @Configuration
1111   static class CustomSqlSessionTemplateConfiguration {
1112     @Bean
1113     public SqlSessionTemplate customSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
1114       return new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);
1115     }
1116   }
1117 
1118   @Configuration
1119   static class MySqlSessionTemplateConfiguration {
1120     @Bean
1121     public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
1122       return new MySqlSessionTemplate(sqlSessionFactory);
1123     }
1124   }
1125 
1126   static class MySqlSessionFactory extends DefaultSqlSessionFactory {
1127     MySqlSessionFactory(org.apache.ibatis.session.Configuration configuration) {
1128       super(configuration);
1129     }
1130   }
1131 
1132   static class MySqlSessionTemplate extends SqlSessionTemplate {
1133     MySqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
1134       super(sqlSessionFactory);
1135     }
1136   }
1137 
1138   static class MyTypeHandler extends BaseTypeHandler<UUID> {
1139 
1140     @Override
1141     public void setNonNullParameter(PreparedStatement ps, int i, UUID parameter, JdbcType jdbcType) {
1142 
1143     }
1144 
1145     @Override
1146     public UUID getNullableResult(ResultSet rs, String columnName) {
1147       return null;
1148     }
1149 
1150     @Override
1151     public UUID getNullableResult(ResultSet rs, int columnIndex) {
1152       return null;
1153     }
1154 
1155     @Override
1156     public UUID getNullableResult(CallableStatement cs, int columnIndex) {
1157       return null;
1158     }
1159 
1160   }
1161 
1162   @Configuration
1163   @TestAutoConfigurationPackage(CityMapper.class)
1164   static class CityMapperRepositoryConfiguration {
1165 
1166   }
1167 
1168   @Configuration(proxyBeanMethods = false)
1169   static class CustomFlywayMigrationInitializer {
1170 
1171     @Bean
1172     FlywayMigrationInitializer flywayMigrationInitializer(Flyway flyway) {
1173       FlywayMigrationInitializer initializer = new FlywayMigrationInitializer(flyway);
1174       initializer.setOrder(Ordered.HIGHEST_PRECEDENCE);
1175       return initializer;
1176     }
1177 
1178   }
1179 
1180   @Configuration(proxyBeanMethods = false)
1181   static class CustomFlyway {
1182 
1183     @Bean
1184     Flyway customFlyway() {
1185       return Flyway.configure().load();
1186     }
1187 
1188   }
1189 
1190   @Configuration(proxyBeanMethods = false)
1191   static class LiquibaseUserConfiguration {
1192 
1193     @Bean
1194     SpringLiquibase springLiquibase(DataSource dataSource) {
1195       SpringLiquibase liquibase = new SpringLiquibase();
1196       liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.yaml");
1197       liquibase.setShouldRun(true);
1198       liquibase.setDataSource(dataSource);
1199       return liquibase;
1200     }
1201 
1202   }
1203 
1204 }