1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.mybatis.spring;
17
18 import static org.springframework.util.Assert.notNull;
19 import static org.springframework.util.Assert.state;
20 import static org.springframework.util.ObjectUtils.isEmpty;
21 import static org.springframework.util.StringUtils.hasLength;
22 import static org.springframework.util.StringUtils.tokenizeToStringArray;
23
24 import java.io.IOException;
25 import java.lang.reflect.Modifier;
26 import java.sql.SQLException;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Optional;
32 import java.util.Properties;
33 import java.util.Set;
34 import java.util.function.IntFunction;
35 import java.util.stream.Stream;
36
37 import javax.sql.DataSource;
38
39 import org.apache.ibatis.builder.xml.XMLConfigBuilder;
40 import org.apache.ibatis.builder.xml.XMLMapperBuilder;
41 import org.apache.ibatis.cache.Cache;
42 import org.apache.ibatis.executor.ErrorContext;
43 import org.apache.ibatis.io.Resources;
44 import org.apache.ibatis.io.VFS;
45 import org.apache.ibatis.mapping.DatabaseIdProvider;
46 import org.apache.ibatis.mapping.Environment;
47 import org.apache.ibatis.plugin.Interceptor;
48 import org.apache.ibatis.reflection.factory.ObjectFactory;
49 import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
50 import org.apache.ibatis.scripting.LanguageDriver;
51 import org.apache.ibatis.session.Configuration;
52 import org.apache.ibatis.session.SqlSessionFactory;
53 import org.apache.ibatis.session.SqlSessionFactoryBuilder;
54 import org.apache.ibatis.transaction.TransactionFactory;
55 import org.apache.ibatis.type.TypeHandler;
56 import org.mybatis.logging.Logger;
57 import org.mybatis.logging.LoggerFactory;
58 import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
59 import org.springframework.beans.factory.FactoryBean;
60 import org.springframework.beans.factory.InitializingBean;
61 import org.springframework.context.ApplicationListener;
62 import org.springframework.context.ConfigurableApplicationContext;
63 import org.springframework.context.event.ContextRefreshedEvent;
64 import org.springframework.core.io.Resource;
65 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
66 import org.springframework.core.io.support.ResourcePatternResolver;
67 import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
68 import org.springframework.core.type.classreading.MetadataReaderFactory;
69 import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
70 import org.springframework.util.ClassUtils;
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 public class SqlSessionFactoryBean
92 implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ContextRefreshedEvent> {
93
94 private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class);
95
96 private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
97 private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();
98
99 private Resource configLocation;
100
101 private Configuration configuration;
102
103 private Resource[] mapperLocations;
104
105 private DataSource dataSource;
106
107 private TransactionFactory transactionFactory;
108
109 private Properties configurationProperties;
110
111 private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
112
113 private SqlSessionFactory sqlSessionFactory;
114
115
116 private String environment = SqlSessionFactoryBean.class.getSimpleName();
117
118 private boolean failFast;
119
120 private Interceptor[] plugins;
121
122 private TypeHandler<?>[] typeHandlers;
123
124 private String typeHandlersPackage;
125
126 @SuppressWarnings("rawtypes")
127 private Class<? extends TypeHandler> defaultEnumTypeHandler;
128
129 private Class<?>[] typeAliases;
130
131 private String typeAliasesPackage;
132
133 private Class<?> typeAliasesSuperType;
134
135 private LanguageDriver[] scriptingLanguageDrivers;
136
137 private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;
138
139
140 private DatabaseIdProvider databaseIdProvider;
141
142 private Class<? extends VFS> vfs;
143
144 private Cache cache;
145
146 private ObjectFactory objectFactory;
147
148 private ObjectWrapperFactory objectWrapperFactory;
149
150
151
152
153
154
155
156
157
158 public void setObjectFactory(ObjectFactory objectFactory) {
159 this.objectFactory = objectFactory;
160 }
161
162
163
164
165
166
167
168
169
170 public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) {
171 this.objectWrapperFactory = objectWrapperFactory;
172 }
173
174
175
176
177
178
179
180
181 public DatabaseIdProvider getDatabaseIdProvider() {
182 return databaseIdProvider;
183 }
184
185
186
187
188
189
190
191
192
193 public void setDatabaseIdProvider(DatabaseIdProvider databaseIdProvider) {
194 this.databaseIdProvider = databaseIdProvider;
195 }
196
197
198
199
200
201
202 public Class<? extends VFS> getVfs() {
203 return this.vfs;
204 }
205
206
207
208
209
210
211
212 public void setVfs(Class<? extends VFS> vfs) {
213 this.vfs = vfs;
214 }
215
216
217
218
219
220
221 public Cache getCache() {
222 return this.cache;
223 }
224
225
226
227
228
229
230
231 public void setCache(Cache cache) {
232 this.cache = cache;
233 }
234
235
236
237
238
239
240
241
242
243 public void setPlugins(Interceptor... plugins) {
244 this.plugins = plugins;
245 }
246
247
248
249
250
251
252
253
254
255
256
257 public void setTypeAliasesPackage(String typeAliasesPackage) {
258 this.typeAliasesPackage = typeAliasesPackage;
259 }
260
261
262
263
264
265
266
267
268
269
270 public void setTypeAliasesSuperType(Class<?> typeAliasesSuperType) {
271 this.typeAliasesSuperType = typeAliasesSuperType;
272 }
273
274
275
276
277
278
279
280
281
282
283
284 public void setTypeHandlersPackage(String typeHandlersPackage) {
285 this.typeHandlersPackage = typeHandlersPackage;
286 }
287
288
289
290
291
292
293
294
295
296 public void setTypeHandlers(TypeHandler<?>... typeHandlers) {
297 this.typeHandlers = typeHandlers;
298 }
299
300
301
302
303
304
305
306
307
308 public void setDefaultEnumTypeHandler(
309 @SuppressWarnings("rawtypes") Class<? extends TypeHandler> defaultEnumTypeHandler) {
310 this.defaultEnumTypeHandler = defaultEnumTypeHandler;
311 }
312
313
314
315
316
317
318
319
320
321 public void setTypeAliases(Class<?>... typeAliases) {
322 this.typeAliases = typeAliases;
323 }
324
325
326
327
328
329
330
331
332
333
334 public void setFailFast(boolean failFast) {
335 this.failFast = failFast;
336 }
337
338
339
340
341
342
343
344
345 public void setConfigLocation(Resource configLocation) {
346 this.configLocation = configLocation;
347 }
348
349
350
351
352
353
354
355
356
357 public void setConfiguration(Configuration configuration) {
358 this.configuration = configuration;
359 }
360
361
362
363
364
365
366
367
368
369
370
371
372 public void setMapperLocations(Resource... mapperLocations) {
373 this.mapperLocations = mapperLocations;
374 }
375
376
377
378
379
380
381
382
383
384 public void setConfigurationProperties(Properties sqlSessionFactoryProperties) {
385 this.configurationProperties = sqlSessionFactoryProperties;
386 }
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405 public void setDataSource(DataSource dataSource) {
406 if (dataSource instanceof TransactionAwareDataSourceProxy) {
407
408
409
410
411 this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
412 } else {
413 this.dataSource = dataSource;
414 }
415 }
416
417
418
419
420
421
422
423
424
425
426 public void setSqlSessionFactoryBuilder(SqlSessionFactoryBuilder sqlSessionFactoryBuilder) {
427 this.sqlSessionFactoryBuilder = sqlSessionFactoryBuilder;
428 }
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445 public void setTransactionFactory(TransactionFactory transactionFactory) {
446 this.transactionFactory = transactionFactory;
447 }
448
449
450
451
452
453
454
455
456 public void setEnvironment(String environment) {
457 this.environment = environment;
458 }
459
460
461
462
463
464
465
466
467
468 public void setScriptingLanguageDrivers(LanguageDriver... scriptingLanguageDrivers) {
469 this.scriptingLanguageDrivers = scriptingLanguageDrivers;
470 }
471
472
473
474
475
476
477
478
479
480 public void setDefaultScriptingLanguageDriver(Class<? extends LanguageDriver> defaultScriptingLanguageDriver) {
481 this.defaultScriptingLanguageDriver = defaultScriptingLanguageDriver;
482 }
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499 public void addMapperLocations(Resource... mapperLocations) {
500 setMapperLocations(appendArrays(this.mapperLocations, mapperLocations, Resource[]::new));
501 }
502
503
504
505
506
507
508
509
510
511 public void addTypeHandlers(TypeHandler<?>... typeHandlers) {
512 setTypeHandlers(appendArrays(this.typeHandlers, typeHandlers, TypeHandler[]::new));
513 }
514
515
516
517
518
519
520
521
522
523 public void addScriptingLanguageDrivers(LanguageDriver... scriptingLanguageDrivers) {
524 setScriptingLanguageDrivers(
525 appendArrays(this.scriptingLanguageDrivers, scriptingLanguageDrivers, LanguageDriver[]::new));
526 }
527
528
529
530
531
532
533
534
535
536 public void addPlugins(Interceptor... plugins) {
537 setPlugins(appendArrays(this.plugins, plugins, Interceptor[]::new));
538 }
539
540
541
542
543
544
545
546
547
548 public void addTypeAliases(Class<?>... typeAliases) {
549 setTypeAliases(appendArrays(this.typeAliases, typeAliases, Class[]::new));
550 }
551
552 private <T> T[] appendArrays(T[] oldArrays, T[] newArrays, IntFunction<T[]> generator) {
553 if (oldArrays == null) {
554 return newArrays;
555 }
556 if (newArrays == null) {
557 return oldArrays;
558 }
559 List<T> newList = new ArrayList<>(Arrays.asList(oldArrays));
560 newList.addAll(Arrays.asList(newArrays));
561 return newList.toArray(generator.apply(0));
562 }
563
564 @Override
565 public void afterPropertiesSet() throws Exception {
566 notNull(dataSource, "Property 'dataSource' is required");
567 notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
568
569 state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
570 "Property 'configuration' and 'configLocation' can not specified with together");
571
572 this.sqlSessionFactory = buildSqlSessionFactory();
573 }
574
575
576
577
578
579
580
581
582
583
584
585
586
587 protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
588
589 final Configuration targetConfiguration;
590
591 XMLConfigBuilder xmlConfigBuilder = null;
592 if (this.configuration != null) {
593 targetConfiguration = this.configuration;
594 if (targetConfiguration.getVariables() == null) {
595 targetConfiguration.setVariables(this.configurationProperties);
596 } else if (this.configurationProperties != null) {
597 targetConfiguration.getVariables().putAll(this.configurationProperties);
598 }
599 } else if (this.configLocation != null) {
600 xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
601 targetConfiguration = xmlConfigBuilder.getConfiguration();
602 } else {
603 LOGGER.debug(
604 () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
605 targetConfiguration = new Configuration();
606 Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
607 }
608
609 Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
610 Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
611 Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
612
613 if (hasLength(this.typeAliasesPackage)) {
614 scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
615 .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
616 .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
617 }
618
619 if (!isEmpty(this.typeAliases)) {
620 Stream.of(this.typeAliases).forEach(typeAlias -> {
621 targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
622 LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
623 });
624 }
625
626 if (!isEmpty(this.plugins)) {
627 Stream.of(this.plugins).forEach(plugin -> {
628 targetConfiguration.addInterceptor(plugin);
629 LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
630 });
631 }
632
633 if (hasLength(this.typeHandlersPackage)) {
634 scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
635 .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
636 .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
637 }
638
639 if (!isEmpty(this.typeHandlers)) {
640 Stream.of(this.typeHandlers).forEach(typeHandler -> {
641 targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
642 LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
643 });
644 }
645
646 targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);
647
648 if (!isEmpty(this.scriptingLanguageDrivers)) {
649 Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
650 targetConfiguration.getLanguageRegistry().register(languageDriver);
651 LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
652 });
653 }
654 Optional.ofNullable(this.defaultScriptingLanguageDriver)
655 .ifPresent(targetConfiguration::setDefaultScriptingLanguage);
656
657 if (this.databaseIdProvider != null) {
658 try {
659 targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
660 } catch (SQLException e) {
661 throw new IOException("Failed getting a databaseId", e);
662 }
663 }
664
665 Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
666
667 if (xmlConfigBuilder != null) {
668 try {
669 xmlConfigBuilder.parse();
670 LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
671 } catch (Exception ex) {
672 throw new IOException("Failed to parse config resource: " + this.configLocation, ex);
673 } finally {
674 ErrorContext.instance().reset();
675 }
676 }
677
678 targetConfiguration.setEnvironment(new Environment(this.environment,
679 this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
680 this.dataSource));
681
682 if (this.mapperLocations != null) {
683 if (this.mapperLocations.length == 0) {
684 LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
685 } else {
686 for (Resource mapperLocation : this.mapperLocations) {
687 if (mapperLocation == null) {
688 continue;
689 }
690 try {
691 var xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration,
692 mapperLocation.toString(), targetConfiguration.getSqlFragments());
693 xmlMapperBuilder.parse();
694 } catch (Exception e) {
695 throw new IOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
696 } finally {
697 ErrorContext.instance().reset();
698 }
699 LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
700 }
701 }
702 } else {
703 LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
704 }
705
706 return this.sqlSessionFactoryBuilder.build(targetConfiguration);
707 }
708
709 @Override
710 public SqlSessionFactory getObject() throws Exception {
711 if (this.sqlSessionFactory == null) {
712 afterPropertiesSet();
713 }
714
715 return this.sqlSessionFactory;
716 }
717
718 @Override
719 public Class<? extends SqlSessionFactory> getObjectType() {
720 return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
721 }
722
723 @Override
724 public boolean isSingleton() {
725 return true;
726 }
727
728 @Override
729 public void onApplicationEvent(ContextRefreshedEvent event) {
730 if (failFast) {
731
732 this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
733 }
734 }
735
736 private Set<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType) throws IOException {
737 Set<Class<?>> classes = new HashSet<>();
738 var packagePatternArray = tokenizeToStringArray(packagePatterns,
739 ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
740 for (String packagePattern : packagePatternArray) {
741 var resources = RESOURCE_PATTERN_RESOLVER.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
742 + ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class");
743 for (Resource resource : resources) {
744 try {
745 var classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();
746 Class<?> clazz = Resources.classForName(classMetadata.getClassName());
747 if (assignableType == null || assignableType.isAssignableFrom(clazz)) {
748 classes.add(clazz);
749 }
750 } catch (Throwable e) {
751 LOGGER.warn(() -> "Cannot load the '" + resource + "'. Cause by " + e.toString());
752 }
753 }
754 }
755 return classes;
756 }
757
758 }