View Javadoc
1   /*
2    *    Copyright 2015-2022 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.autoconfigure.flyway.FlywayAutoConfiguration;
80  import org.springframework.boot.autoconfigure.flyway.FlywayMigrationInitializer;
81  import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
82  import org.springframework.boot.autoconfigure.liquibase.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)
311             .hasMessageContainingAll("Error creating bean with name 'mybatisAutoConfiguration':",
312                 "Cannot find config location: class path resource [foo.xml] (please add config file or check your Mybatis configuration)"));
313   }
314 
315   @Test
316   void testWithTypeHandlersPackage() {
317     this.contextRunner
318         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
319         .withPropertyValues("mybatis.type-handlers-package:org.mybatis.spring.boot.autoconfigure.handler")
320         .run(context -> {
321           TypeHandlerRegistry typeHandlerRegistry = context.getBean(SqlSessionFactory.class).getConfiguration()
322               .getTypeHandlerRegistry();
323           assertThat(typeHandlerRegistry.hasTypeHandler(BigInteger.class)).isTrue();
324           assertThat(typeHandlerRegistry.hasTypeHandler(AtomicInteger.class)).isTrue();
325           assertThat(typeHandlerRegistry.hasTypeHandler(AtomicLong.class)).isTrue();
326         });
327   }
328 
329   @Test
330   void testWithMapperLocation() {
331     this.contextRunner
332         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
333         .withPropertyValues("mybatis.type-aliases-package:org.mybatis.spring.boot.autoconfigure.domain",
334             "mybatis.mapper-locations:classpath:org/mybatis/spring/boot/autoconfigure/repository/CityMapper.xml")
335         .run(
336             context -> assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getMappedStatementNames())
337                 .hasSize(2));
338   }
339 
340   @Test
341   void testWithExecutorType() {
342     this.contextRunner
343         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisMapperConfiguration.class,
344             PropertyPlaceholderAutoConfiguration.class)
345         .withPropertyValues("mybatis.config-location:mybatis-config.xml", "mybatis.executor-type:REUSE")
346         .run(context -> assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType())
347             .isEqualTo(ExecutorType.REUSE));
348   }
349 
350   @Test
351   void testDefaultBootConfiguration() {
352     this.contextRunner
353         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
354             PropertyPlaceholderAutoConfiguration.class, CityMapperRepositoryConfiguration.class)
355         .run(context -> {
356           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
357           assertThat(sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers()).hasSize(1);
358           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
359           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
360           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
361         });
362   }
363 
364   @Test
365   void testWithInterceptorsOrder1() {
366     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class,
367         MybatisInterceptorConfiguration.class, PropertyPlaceholderAutoConfiguration.class).run(context -> {
368           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
369           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
370           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getInterceptors()).hasSize(2);
371           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getInterceptors().get(0))
372               .isInstanceOf(MyInterceptor2.class);
373           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getInterceptors().get(1))
374               .isInstanceOf(MyInterceptor.class);
375         });
376   }
377 
378   @Test
379   void testWithInterceptorsOrder2() {
380     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class,
381         MybatisInterceptorConfiguration2.class, PropertyPlaceholderAutoConfiguration.class).run(context -> {
382           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
383           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
384           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getInterceptors()).hasSize(2);
385           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getInterceptors().get(0))
386               .isInstanceOf(MyInterceptor.class);
387           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getInterceptors().get(1))
388               .isInstanceOf(MyInterceptor2.class);
389         });
390   }
391 
392   @Test
393   void testWithTypeHandlers() {
394     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class,
395         MybatisTypeHandlerConfiguration.class, PropertyPlaceholderAutoConfiguration.class).run(context -> {
396           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
397           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
398           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getTypeHandlerRegistry()
399               .getTypeHandler(UUID.class)).isInstanceOf(MyTypeHandler.class);
400         });
401   }
402 
403   @Test
404   void testWithDatabaseIdProvider() {
405     this.contextRunner
406         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, DatabaseProvidersConfiguration.class,
407             PropertyPlaceholderAutoConfiguration.class)
408         .run(context -> assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getDatabaseId())
409             .isEqualTo("h2"));
410   }
411 
412   @Test
413   void testMixedWithConfigurationFileAndInterceptor() {
414     this.contextRunner
415         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisInterceptorConfiguration.class,
416             CityMapperRepositoryConfiguration.class)
417         .withPropertyValues("mybatis.config-location:mybatis-config-settings-only.xml").run(context -> {
418           org.apache.ibatis.session.Configuration configuration = context.getBean(SqlSessionFactory.class)
419               .getConfiguration();
420           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
421           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
422           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
423           assertThat(configuration.getDefaultFetchSize()).isEqualTo(1000);
424           assertThat(configuration.getInterceptors()).hasSize(2);
425           assertThat(configuration.getInterceptors().get(0)).isInstanceOf(MyInterceptor2.class);
426           assertThat(configuration.getInterceptors().get(1)).isInstanceOf(MyInterceptor.class);
427         });
428   }
429 
430   @Test
431   void testMixedWithConfigurationFileAndDatabaseIdProvider() {
432     this.contextRunner
433         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
434             DatabaseProvidersConfiguration.class, CityMapperRepositoryConfiguration.class)
435         .withPropertyValues("mybatis.config-location:mybatis-config-settings-only.xml").run(context -> {
436           org.apache.ibatis.session.Configuration configuration = context.getBean(SqlSessionFactory.class)
437               .getConfiguration();
438           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
439           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
440           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
441           assertThat(configuration.getDefaultFetchSize()).isEqualTo(1000);
442           assertThat(configuration.getDatabaseId()).isEqualTo("h2");
443         });
444   }
445 
446   @Test
447   void testMixedWithConfigurationFileAndTypeHandlersPackage() {
448     this.contextRunner
449         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
450             CityMapperRepositoryConfiguration.class)
451         .withPropertyValues("mybatis.config-location:mybatis-config-settings-only.xml",
452             "mybatis.type-handlers-package:org.mybatis.spring.boot.autoconfigure.handler.")
453         .run(context -> {
454           org.apache.ibatis.session.Configuration configuration = context.getBean(SqlSessionFactory.class)
455               .getConfiguration();
456           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
457           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
458           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
459           assertThat(configuration.getDefaultFetchSize()).isEqualTo(1000);
460           assertThat(configuration.getTypeHandlerRegistry().getTypeHandler(BigInteger.class))
461               .isInstanceOf(DummyTypeHandler.class);
462           assertThat(configuration.getTypeHandlerRegistry().getTypeHandler(AtomicInteger.class))
463               .isInstanceOf(AtomicNumberTypeHandler.class);
464           assertThat(configuration.getTypeHandlerRegistry().getTypeHandler(AtomicLong.class))
465               .isInstanceOf(AtomicNumberTypeHandler.class);
466           assertThat(configuration.getTypeHandlerRegistry().getTypeHandler(AtomicInteger.class))
467               .hasToString("type=" + AtomicInteger.class);
468         });
469   }
470 
471   @Test
472   void testMixedWithConfigurationFileAndTypeAliasesPackageAndMapperLocations() {
473     this.contextRunner
474         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
475             CityMapperRepositoryConfiguration.class)
476         .withPropertyValues("mybatis.config-location:mybatis-config-settings-only.xml",
477             "mybatis.type-aliases-package:org.mybatis.spring.boot.autoconfigure.domain",
478             "mybatis.mapper-locations:classpath:org/mybatis/spring/boot/autoconfigure/repository/CityMapper.xml")
479         .run(context -> {
480           org.apache.ibatis.session.Configuration configuration = context.getBean(SqlSessionFactory.class)
481               .getConfiguration();
482           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
483           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
484           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
485           assertThat(configuration.getDefaultFetchSize()).isEqualTo(1000);
486           assertThat(configuration.getMappedStatementNames()).contains("selectCityById");
487           assertThat(configuration.getMappedStatementNames())
488               .contains("org.mybatis.spring.boot.autoconfigure.repository.CityMapperImpl.selectCityById");
489           assertThat(configuration.getTypeAliasRegistry().getTypeAliases()).containsKey("city");
490           assertThat(configuration.getTypeAliasRegistry().getTypeAliases()).containsKey("name");
491         });
492   }
493 
494   @Test
495   void testMixedWithFullConfigurations() {
496     this.contextRunner
497         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
498             MybatisInterceptorConfiguration.class, DatabaseProvidersConfiguration.class,
499             CityMapperRepositoryConfiguration.class)
500         .withPropertyValues("mybatis.config-location:mybatis-config-settings-only.xml",
501             "mybatis.type-handlers-package:org.mybatis.spring.**.handler",
502             "mybatis.type-aliases-package:org.mybatis.spring.boot.autoconfigure.domain",
503             "mybatis.mapper-locations:classpath:org/mybatis/spring/boot/autoconfigure/repository/CityMapper.xml",
504             "mybatis.executor-type=REUSE")
505         .run(context -> {
506           org.apache.ibatis.session.Configuration configuration = context.getBean(SqlSessionFactory.class)
507               .getConfiguration();
508           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
509           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
510           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
511           assertThat(configuration.getDefaultFetchSize()).isEqualTo(1000);
512           assertThat(configuration.getTypeHandlerRegistry().getTypeHandler(BigInteger.class))
513               .isInstanceOf(DummyTypeHandler.class);
514           assertThat(configuration.getTypeHandlerRegistry().getTypeHandler(AtomicInteger.class))
515               .isInstanceOf(AtomicNumberTypeHandler.class);
516           assertThat(configuration.getTypeHandlerRegistry().getTypeHandler(AtomicLong.class))
517               .isInstanceOf(AtomicNumberTypeHandler.class);
518           assertThat(configuration.getMappedStatementNames()).hasSize(4);
519           assertThat(configuration.getMappedStatementNames()).contains("selectCityById");
520           assertThat(configuration.getMappedStatementNames())
521               .contains("org.mybatis.spring.boot.autoconfigure.repository.CityMapperImpl.selectCityById");
522           assertThat(configuration.getMappedStatementNames()).contains("findById");
523           assertThat(configuration.getMappedStatementNames())
524               .contains("org.mybatis.spring.boot.autoconfigure.mapper.CityMapper.findById");
525           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.REUSE);
526           assertThat(configuration.getInterceptors()).hasSize(2);
527           assertThat(configuration.getInterceptors().get(0)).isInstanceOf(MyInterceptor2.class);
528           assertThat(configuration.getInterceptors().get(1)).isInstanceOf(MyInterceptor.class);
529           assertThat(configuration.getDatabaseId()).isEqualTo("h2");
530         });
531   }
532 
533   @Test
534   void testWithMyBatisConfiguration() {
535     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
536         .withPropertyValues("mybatis.configuration.map-underscore-to-camel-case:true").run(context -> assertThat(
537             context.getBean(SqlSessionFactory.class).getConfiguration().isMapUnderscoreToCamelCase()).isTrue());
538   }
539 
540   @Test
541   void testWithMyBatisConfigurationCustomizer() {
542     this.contextRunner
543         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MyBatisConfigurationCustomizerConfiguration.class)
544         .run(context -> {
545           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
546           assertThat(sqlSessionFactory.getConfiguration().getTypeHandlerRegistry().getTypeHandler(BigInteger.class))
547               .isInstanceOf(DummyTypeHandler.class);
548           assertThat(sqlSessionFactory.getConfiguration().getCache("test")).isNotNull();
549         });
550   }
551 
552   @Test
553   void testWithSqlSessionFactoryBeanCustomizer() {
554     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class,
555         SqlSessionFactoryBeanCustomizerConfiguration.class).run(context -> {
556           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
557           assertThat(sqlSessionFactory.getConfiguration().getTypeHandlerRegistry().getTypeHandler(BigInteger.class))
558               .isInstanceOf(DummyTypeHandler.class);
559           assertThat(sqlSessionFactory.getConfiguration().getCache("test")).isNotNull();
560         });
561   }
562 
563   @Test
564   void testConfigFileAndConfigurationWithTogether() {
565     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
566         .withPropertyValues("mybatis.config-location:mybatis-config.xml",
567             "mybatis.configuration.default-statement-timeout:30")
568         .run(context -> {
569           assertThat(context).hasFailed();
570           assertThat(context).getFailure().isInstanceOf(BeanCreationException.class)
571               .hasMessageContaining("Property 'configuration' and 'configLocation' can not specified with together");
572         });
573   }
574 
575   @Test
576   void testWithoutConfigurationVariablesAndProperties() {
577     this.contextRunner
578         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
579         .run(context -> {
580           Properties variables = context.getBean(SqlSessionFactory.class).getConfiguration().getVariables();
581           assertThat(variables).isEmpty();
582         });
583   }
584 
585   @Test
586   void testWithConfigurationVariablesOnly() {
587     this.contextRunner
588         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
589         .withPropertyValues("mybatis.configuration.variables.key1:value1").run(context -> {
590           Properties variables = context.getBean(SqlSessionFactory.class).getConfiguration().getVariables();
591           assertThat(variables).hasSize(1);
592           assertThat(variables.getProperty("key1")).isEqualTo("value1");
593         });
594   }
595 
596   @Test
597   void testWithConfigurationPropertiesOnly() {
598     this.contextRunner
599         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
600         .withPropertyValues("mybatis.configuration-properties.key2:value2").run(context -> {
601           Properties variables = context.getBean(SqlSessionFactory.class).getConfiguration().getVariables();
602           assertThat(variables).hasSize(1);
603           assertThat(variables.getProperty("key2")).isEqualTo("value2");
604         });
605   }
606 
607   @Test
608   void testWithConfigurationVariablesAndPropertiesOtherKey() {
609     this.contextRunner
610         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
611         .withPropertyValues("mybatis.configuration.variables.key1:value1",
612             "mybatis.configuration-properties.key2:value2")
613         .run(context -> {
614           Properties variables = context.getBean(SqlSessionFactory.class).getConfiguration().getVariables();
615           assertThat(variables).hasSize(2);
616           assertThat(variables.getProperty("key1")).isEqualTo("value1");
617           assertThat(variables.getProperty("key2")).isEqualTo("value2");
618         });
619   }
620 
621   @Test
622   void testWithConfigurationVariablesAndPropertiesSameKey() {
623     this.contextRunner
624         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
625         .withPropertyValues("mybatis.configuration.variables.key:value1", "mybatis.configuration-properties.key:value2")
626         .run(context -> {
627           Properties variables = context.getBean(SqlSessionFactory.class).getConfiguration().getVariables();
628           assertThat(variables).hasSize(1);
629           assertThat(variables.getProperty("key")).isEqualTo("value2");
630         });
631   }
632 
633   @Test
634   void testCustomSqlSessionFactory() {
635     this.contextRunner
636         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
637             CustomSqlSessionFactoryConfiguration.class, CityMapperRepositoryConfiguration.class)
638         .run(context -> {
639           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
640           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().getVariables().getProperty("key"))
641               .isEqualTo("value");
642           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
643           assertThat(((RuntimeBeanReference) context.getBeanFactory().getBeanDefinition("cityMapper")
644               .getPropertyValues().getPropertyValue("sqlSessionFactory").getValue()).getBeanName())
645                   .isEqualTo("customSqlSessionFactory");
646         });
647   }
648 
649   @Test
650   void testMySqlSessionFactory() {
651     this.contextRunner
652         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MySqlSessionFactoryConfiguration.class)
653         .run(context -> {
654           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
655           assertThat(context.getBean(SqlSessionFactory.class)).isInstanceOf(MySqlSessionFactory.class);
656         });
657   }
658 
659   @Test
660   void testCustomSqlSessionTemplate() {
661     this.contextRunner
662         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class,
663             CustomSqlSessionTemplateConfiguration.class, CityMapperRepositoryConfiguration.class)
664         .run(context -> {
665           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
666           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.BATCH);
667           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
668           assertThat(((RuntimeBeanReference) context.getBeanFactory().getBeanDefinition("cityMapper")
669               .getPropertyValues().getPropertyValue("sqlSessionTemplate").getValue()).getBeanName())
670                   .isEqualTo("customSqlSessionTemplate");
671         });
672   }
673 
674   @Test
675   void testMySqlSessionTemplate() {
676     this.contextRunner
677         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MySqlSessionTemplateConfiguration.class)
678         .run(context -> {
679           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
680           assertThat(context.getBean(SqlSessionTemplate.class)).isInstanceOf(MySqlSessionTemplate.class);
681         });
682   }
683 
684   @Test
685   void testCustomSqlSessionTemplateAndSqlSessionFactory() {
686     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class,
687         MybatisBootMapperScanAutoConfiguration.class, CustomSqlSessionFactoryConfiguration.class,
688         CustomSqlSessionTemplateConfiguration.class, CityMapperRepositoryConfiguration.class).run(context -> {
689           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
690           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.BATCH);
691           assertThat(context.getBeanNamesForType(CityMapper.class)).hasSize(1);
692           assertThat(((RuntimeBeanReference) context.getBeanFactory().getBeanDefinition("cityMapper")
693               .getPropertyValues().getPropertyValue("sqlSessionTemplate").getValue()).getBeanName())
694                   .isEqualTo("customSqlSessionTemplate");
695         });
696   }
697 
698   @Test
699   void testTypeAliasesSuperTypeIsSpecify() {
700     this.contextRunner
701         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class)
702         .withPropertyValues("mybatis.type-aliases-package:org.mybatis.spring.boot.autoconfigure.domain",
703             "mybatis.type-aliases-super-type:org.mybatis.spring.boot.autoconfigure.domain.Domain")
704         .run(context -> {
705           org.apache.ibatis.session.Configuration configuration = context.getBean(SqlSessionFactory.class)
706               .getConfiguration();
707           assertThat(configuration.getTypeAliasRegistry().getTypeAliases()).containsKey("city");
708           assertThat(configuration.getTypeAliasRegistry().getTypeAliases()).doesNotContainKey("name");
709         });
710   }
711 
712   @Test
713   void testMapperFactoryBean() {
714     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class,
715         MapperFactoryBeanConfiguration.class, PropertyPlaceholderAutoConfiguration.class).run(context -> {
716           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
717           assertThat(sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers()).hasSize(1);
718           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
719           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
720           assertThat(context.getBeanNamesForType(DateTimeMapper.class)).hasSize(1);
721           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.SIMPLE);
722           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().isMapUnderscoreToCamelCase())
723               .isFalse();
724         });
725   }
726 
727   @Test
728   void testMapperScannerConfigurer() {
729     this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class,
730         MapperScannerConfigurerConfiguration.class, PropertyPlaceholderAutoConfiguration.class).run(context -> {
731           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
732           assertThat(sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers()).hasSize(1);
733           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
734           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
735           assertThat(context.getBeanNamesForType(DateTimeMapper.class)).hasSize(1);
736           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.SIMPLE);
737           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().isMapUnderscoreToCamelCase())
738               .isFalse();
739         });
740   }
741 
742   @Test
743   void testDefaultScriptingLanguageIsSpecify() {
744     this.contextRunner
745         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisScanMapperConfiguration.class,
746             PropertyPlaceholderAutoConfiguration.class)
747         .withPropertyValues(
748             "mybatis.default-scripting-language-driver:org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriver")
749         .run(context -> {
750           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
751           LanguageDriverRegistry languageDriverRegistry = sqlSessionFactory.getConfiguration().getLanguageRegistry();
752           assertThat(languageDriverRegistry.getDefaultDriverClass()).isEqualTo(ThymeleafLanguageDriver.class);
753           assertThat(languageDriverRegistry.getDefaultDriver()).isInstanceOf(ThymeleafLanguageDriver.class);
754           assertThat(languageDriverRegistry.getDriver(ThymeleafLanguageDriver.class)).isNotNull();
755         });
756   }
757 
758   @Test
759   void testExcludeMybatisLanguageDriverAutoConfiguration() {
760     this.contextRunner
761         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisScanMapperConfiguration.class,
762             PropertyPlaceholderAutoConfiguration.class)
763         .withPropertyValues(
764             "spring.autoconfigure.exclude:org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration")
765         .run(context -> {
766           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
767           assertThat(sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers()).hasSize(1);
768           assertThat(context.getBeanNamesForType(SqlSessionFactory.class)).hasSize(1);
769           assertThat(context.getBeanNamesForType(SqlSessionTemplate.class)).hasSize(1);
770           assertThat(context.getBeanNamesForType(DateTimeMapper.class)).hasSize(1);
771           assertThat(context.getBean(SqlSessionTemplate.class).getExecutorType()).isEqualTo(ExecutorType.SIMPLE);
772           assertThat(context.getBean(SqlSessionFactory.class).getConfiguration().isMapUnderscoreToCamelCase())
773               .isFalse();
774           assertThat(context.getBeanNamesForType(LanguageDriver.class)).hasSize(0);
775         });
776   }
777 
778   @Test
779   void testMybatisLanguageDriverAutoConfigurationWithSingleCandidate() {
780     this.contextRunner
781         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisScanMapperConfiguration.class,
782             SingleLanguageDriverConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
783         .withPropertyValues(
784             "spring.autoconfigure.exclude:org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration")
785         .run(context -> {
786           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
787           LanguageDriverRegistry languageDriverRegistry = sqlSessionFactory.getConfiguration().getLanguageRegistry();
788           assertThat(context.getBeanNamesForType(LanguageDriver.class)).hasSize(1);
789           assertThat(languageDriverRegistry.getDefaultDriverClass()).isEqualTo(ThymeleafLanguageDriver.class);
790           assertThat(languageDriverRegistry.getDefaultDriver()).isInstanceOf(ThymeleafLanguageDriver.class);
791           assertThat(languageDriverRegistry.getDriver(ThymeleafLanguageDriver.class)).isNotNull();
792         });
793   }
794 
795   @Test
796   void testMybatisLanguageDriverAutoConfigurationWithSingleCandidateWhenDefaultLanguageDriverIsSpecify() {
797     this.contextRunner
798         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisScanMapperConfiguration.class,
799             SingleLanguageDriverConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
800         .withPropertyValues(
801             "mybatis.default-scripting-language-driver:org.apache.ibatis.scripting.xmltags.XMLLanguageDriver",
802             "spring.autoconfigure.exclude:org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration")
803         .run(context -> {
804           SqlSessionFactory sqlSessionFactory = context.getBean(SqlSessionFactory.class);
805           LanguageDriverRegistry languageDriverRegistry = sqlSessionFactory.getConfiguration().getLanguageRegistry();
806           assertThat(context.getBeanNamesForType(LanguageDriver.class)).hasSize(1);
807           assertThat(languageDriverRegistry.getDefaultDriverClass()).isEqualTo(XMLLanguageDriver.class);
808           assertThat(languageDriverRegistry.getDefaultDriver()).isInstanceOf(XMLLanguageDriver.class);
809           assertThat(languageDriverRegistry.getDriver(ThymeleafLanguageDriver.class)).isNotNull();
810         });
811   }
812 
813   @Test
814   void whenFlywayIsAutoConfiguredThenMybatisSqlSessionTemplateDependsOnFlywayBeans() {
815     ApplicationContextRunner contextRunner = new ApplicationContextRunner()
816         .withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class, MybatisAutoConfiguration.class));
817     contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> {
818       BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("sqlSessionTemplate");
819       assertThat(beanDefinition.getDependsOn()).containsExactlyInAnyOrder("flywayInitializer", "flyway");
820     });
821   }
822 
823   @Test
824   void whenCustomMigrationInitializerIsDefinedThenMybatisSqlSessionTemplateDependsOnIt() {
825     ApplicationContextRunner contextRunner = new ApplicationContextRunner()
826         .withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class, MybatisAutoConfiguration.class));
827     contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, CustomFlywayMigrationInitializer.class)
828         .run((context) -> {
829           BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("sqlSessionTemplate");
830           assertThat(beanDefinition.getDependsOn()).containsExactlyInAnyOrder("flywayMigrationInitializer", "flyway");
831         });
832   }
833 
834   @Test
835   void whenCustomFlywayIsDefinedThenMybatisSqlSessionTemplateDependsOnIt() {
836     ApplicationContextRunner contextRunner = new ApplicationContextRunner()
837         .withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class, MybatisAutoConfiguration.class));
838     contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, CustomFlyway.class).run((context) -> {
839       BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("sqlSessionTemplate");
840       assertThat(beanDefinition.getDependsOn()).containsExactlyInAnyOrder("customFlyway");
841     });
842   }
843 
844   @Test
845   void whenLiquibaseIsAutoConfiguredThenMybatisSqlSessionTemplateDependsOnSpringLiquibaseBeans() {
846     ApplicationContextRunner contextRunner = new ApplicationContextRunner()
847         .withConfiguration(AutoConfigurations.of(LiquibaseAutoConfiguration.class, MybatisAutoConfiguration.class));
848     contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class).run((context) -> {
849       BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("sqlSessionTemplate");
850       assertThat(beanDefinition.getDependsOn()).containsExactly("liquibase");
851     });
852   }
853 
854   @Test
855   void whenCustomSpringLiquibaseIsDefinedThenMybatisSqlSessionTemplateDependsOnSpringLiquibaseBeans() {
856     ApplicationContextRunner contextRunner = new ApplicationContextRunner()
857         .withConfiguration(AutoConfigurations.of(LiquibaseAutoConfiguration.class, MybatisAutoConfiguration.class));
858     contextRunner.withUserConfiguration(LiquibaseUserConfiguration.class, EmbeddedDataSourceConfiguration.class)
859         .run((context) -> {
860           BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("sqlSessionTemplate");
861           assertThat(beanDefinition.getDependsOn()).containsExactly("springLiquibase");
862         });
863   }
864 
865   @Test
866   void testTypeAliasesWithMultiByteCharacterInPackageName() {
867     this.contextRunner
868         .withUserConfiguration(EmbeddedDataSourceConfiguration.class, MybatisBootMapperScanAutoConfiguration.class)
869         .withPropertyValues("mybatis.config-location:mybatis-config2.xml").run(context -> {
870           org.apache.ibatis.session.Configuration configuration = context.getBean(SqlSessionFactory.class)
871               .getConfiguration();
872           assertThat(configuration.getTypeAliasRegistry().getTypeAliases()).containsKey("シティー");
873         });
874   }
875 
876   @Configuration
877   static class MultipleDataSourceConfiguration {
878     @Bean
879     DataSource dataSourcePrimary() {
880       return Mockito.mock(DataSource.class);
881     }
882 
883     @Bean
884     DataSource dataSourceReplica() {
885       return Mockito.mock(DataSource.class);
886     }
887   }
888 
889   @Configuration
890   static class SingleCandidateDataSourceConfiguration {
891     @Bean
892     @Primary
893     DataSource dataSourcePrimary() {
894       return Mockito.mock(DataSource.class);
895     }
896 
897     @Bean
898     DataSource dataSourceReplica() {
899       return Mockito.mock(DataSource.class);
900     }
901   }
902 
903   @Configuration
904   @MapperScan(basePackages = "com.example.mapper", lazyInitialization = "${mybatis.lazy-initialization:false}")
905   static class MybatisScanMapperConfiguration {
906   }
907 
908   @Configuration
909   static class MapperFactoryBeanConfiguration {
910     @Bean
911     MapperFactoryBean<DateTimeMapper> dateTimeMapper(SqlSessionFactory sqlSessionFactory) {
912       MapperFactoryBean<DateTimeMapper> factoryBean = new MapperFactoryBean<>(DateTimeMapper.class);
913       factoryBean.setSqlSessionFactory(sqlSessionFactory);
914       return factoryBean;
915     }
916   }
917 
918   @Configuration
919   static class MapperScannerConfigurerConfiguration {
920     @Bean
921     static MapperScannerConfigurer mapperScannerConfigurer() {
922       MapperScannerConfigurer configurer = new MapperScannerConfigurer();
923       configurer.setBasePackage("com.example.mapper");
924       return configurer;
925     }
926   }
927 
928   @Configuration
929   static class MybatisBootMapperScanAutoConfiguration implements BeanFactoryPostProcessor {
930     @Override
931     public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
932       beanFactory.registerScope("thread", new SimpleThreadScope());
933     }
934   }
935 
936   @Configuration
937   static class MybatisMapperConfiguration {
938 
939     @Bean
940     public CityMapperImpl cityMapper() {
941       return new CityMapperImpl();
942     }
943 
944   }
945 
946   @Configuration
947   static class MybatisInterceptorConfiguration {
948 
949     @Bean
950     @Order(2)
951     public MyInterceptor myInterceptor() {
952       return new MyInterceptor();
953     }
954 
955     @Bean
956     @Order(1)
957     public MyInterceptor2 myInterceptor2() {
958       return new MyInterceptor2();
959     }
960 
961   }
962 
963   @Configuration
964   static class MybatisInterceptorConfiguration2 {
965 
966     @Bean
967     @Order(1)
968     public MyInterceptor myInterceptor() {
969       return new MyInterceptor();
970     }
971 
972     @Bean
973     @Order(2)
974     public MyInterceptor2 myInterceptor2() {
975       return new MyInterceptor2();
976     }
977 
978   }
979 
980   @Configuration
981   static class MybatisTypeHandlerConfiguration {
982 
983     @Bean
984     public MyTypeHandler myTypeHandler() {
985       return new MyTypeHandler();
986     }
987 
988   }
989 
990   @Configuration
991   static class MyBatisConfigurationCustomizerConfiguration {
992     @Bean
993     ConfigurationCustomizer typeHandlerConfigurationCustomizer() {
994       return configuration -> configuration.getTypeHandlerRegistry().register(new DummyTypeHandler());
995     }
996 
997     @Bean
998     ConfigurationCustomizer cacheConfigurationCustomizer() {
999       return configuration -> configuration.addCache(new PerpetualCache("test"));
1000     }
1001   }
1002 
1003   @Configuration
1004   static class SqlSessionFactoryBeanCustomizerConfiguration {
1005     @Bean
1006     SqlSessionFactoryBeanCustomizer typeHandlerSqlSessionFactoryBeanCustomizer() {
1007       return factoryBean -> factoryBean.setTypeHandlers(new DummyTypeHandler());
1008     }
1009 
1010     @Bean
1011     SqlSessionFactoryBeanCustomizer cacheSqlSessionFactoryBeanCustomizer() {
1012       return factoryBean -> factoryBean.setCache(new PerpetualCache("test"));
1013     }
1014   }
1015 
1016   @Configuration
1017   static class SingleLanguageDriverConfiguration {
1018     @Bean
1019     ThymeleafLanguageDriver myThymeleafLanguageDriver() {
1020       return new ThymeleafLanguageDriver();
1021     }
1022   }
1023 
1024   @Intercepts(@Signature(type = Map.class, method = "get", args = { Object.class }))
1025   static class MyInterceptor implements Interceptor {
1026 
1027     @Override
1028     public Object intercept(Invocation invocation) {
1029       return "Test";
1030     }
1031 
1032     @Override
1033     public Object plugin(Object target) {
1034       return Plugin.wrap(target, this);
1035     }
1036 
1037     @Override
1038     public void setProperties(Properties properties) {
1039 
1040     }
1041   }
1042 
1043   @Intercepts(@Signature(type = Map.class, method = "get", args = { Object.class }))
1044   static class MyInterceptor2 implements Interceptor {
1045 
1046     @Override
1047     public Object intercept(Invocation invocation) {
1048       return "Test2";
1049     }
1050 
1051     @Override
1052     public Object plugin(Object target) {
1053       return Plugin.wrap(target, this);
1054     }
1055 
1056     @Override
1057     public void setProperties(Properties properties) {
1058 
1059     }
1060   }
1061 
1062   @Configuration
1063   static class DatabaseProvidersConfiguration {
1064 
1065     @Bean
1066     public PropertiesFactoryBean vendorProperties() {
1067       Properties properties = new Properties();
1068       properties.put("SQL Server", "sqlserver");
1069       properties.put("DB2", "db2");
1070       properties.put("H2", "h2");
1071 
1072       PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
1073       propertiesFactoryBean.setProperties(properties);
1074       return propertiesFactoryBean;
1075     }
1076 
1077     @Bean
1078     public VendorDatabaseIdProvider vendorDatabaseIdProvider(
1079         @Qualifier("vendorProperties") Properties vendorProperties) {
1080       VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
1081       databaseIdProvider.setProperties(vendorProperties);
1082       return databaseIdProvider;
1083     }
1084 
1085   }
1086 
1087   @Configuration
1088   static class CustomSqlSessionFactoryConfiguration {
1089     @Bean
1090     public SqlSessionFactory customSqlSessionFactory(DataSource dataSource) throws Exception {
1091       SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
1092       sqlSessionFactoryBean.setDataSource(dataSource);
1093       Properties props = new Properties();
1094       props.setProperty("key", "value");
1095       sqlSessionFactoryBean.setConfigurationProperties(props);
1096       return sqlSessionFactoryBean.getObject();
1097     }
1098   }
1099 
1100   @Configuration
1101   static class MySqlSessionFactoryConfiguration {
1102     @Bean
1103     public SqlSessionFactory sqlSessionFactory(DataSource dataSource) {
1104       MySqlSessionFactory sqlSessionFactory = new MySqlSessionFactory(new org.apache.ibatis.session.Configuration());
1105       sqlSessionFactory.getConfiguration()
1106           .setEnvironment(new Environment("", new SpringManagedTransactionFactory(), dataSource));
1107       return sqlSessionFactory;
1108     }
1109   }
1110 
1111   @Configuration
1112   static class CustomSqlSessionTemplateConfiguration {
1113     @Bean
1114     public SqlSessionTemplate customSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
1115       return new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);
1116     }
1117   }
1118 
1119   @Configuration
1120   static class MySqlSessionTemplateConfiguration {
1121     @Bean
1122     public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
1123       return new MySqlSessionTemplate(sqlSessionFactory);
1124     }
1125   }
1126 
1127   static class MySqlSessionFactory extends DefaultSqlSessionFactory {
1128     MySqlSessionFactory(org.apache.ibatis.session.Configuration configuration) {
1129       super(configuration);
1130     }
1131   }
1132 
1133   static class MySqlSessionTemplate extends SqlSessionTemplate {
1134     MySqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
1135       super(sqlSessionFactory);
1136     }
1137   }
1138 
1139   static class MyTypeHandler extends BaseTypeHandler<UUID> {
1140 
1141     @Override
1142     public void setNonNullParameter(PreparedStatement ps, int i, UUID parameter, JdbcType jdbcType) {
1143 
1144     }
1145 
1146     @Override
1147     public UUID getNullableResult(ResultSet rs, String columnName) {
1148       return null;
1149     }
1150 
1151     @Override
1152     public UUID getNullableResult(ResultSet rs, int columnIndex) {
1153       return null;
1154     }
1155 
1156     @Override
1157     public UUID getNullableResult(CallableStatement cs, int columnIndex) {
1158       return null;
1159     }
1160 
1161   }
1162 
1163   @Configuration
1164   @TestAutoConfigurationPackage(CityMapper.class)
1165   static class CityMapperRepositoryConfiguration {
1166 
1167   }
1168 
1169   @Configuration(proxyBeanMethods = false)
1170   static class CustomFlywayMigrationInitializer {
1171 
1172     @Bean
1173     FlywayMigrationInitializer flywayMigrationInitializer(Flyway flyway) {
1174       FlywayMigrationInitializer initializer = new FlywayMigrationInitializer(flyway);
1175       initializer.setOrder(Ordered.HIGHEST_PRECEDENCE);
1176       return initializer;
1177     }
1178 
1179   }
1180 
1181   @Configuration(proxyBeanMethods = false)
1182   static class CustomFlyway {
1183 
1184     @Bean
1185     Flyway customFlyway() {
1186       return Flyway.configure().load();
1187     }
1188 
1189   }
1190 
1191   @Configuration(proxyBeanMethods = false)
1192   static class LiquibaseUserConfiguration {
1193 
1194     @Bean
1195     SpringLiquibase springLiquibase(DataSource dataSource) {
1196       SpringLiquibase liquibase = new SpringLiquibase();
1197       liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.yaml");
1198       liquibase.setShouldRun(true);
1199       liquibase.setDataSource(dataSource);
1200       return liquibase;
1201     }
1202 
1203   }
1204 
1205 }