之前简单研究了Mybatis 的源码,现在简单研究下MybatisPlus 的源码。大体分析其执行过程。Mybatisplus 执行逻辑大体和mybatis一样,只是在启动过程中会生成一些默认的SQL下面研究其生成默认SQL的过程。
1. 自动配置
查看源码按自动配置的套路,先查看AutoConfiguration和Properties文件。
1. com.baomidou.mybatisplus.spring.boot.starter.MybatisPlusProperties 属性文件
1 package com.baomidou.mybatisplus.spring.boot.starter; 2 3 import java.io.IOException; 4 import java.util.ArrayList; 5 import java.util.Arrays; 6 import java.util.List; 7 import java.util.Properties; 8 9 import org.apache.ibatis.session.ExecutorType; 10 import org.springframework.boot.context.properties.ConfigurationProperties; 11 import org.springframework.boot.context.properties.NestedConfigurationProperty; 12 import org.springframework.core.io.Resource; 13 import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 14 import org.springframework.core.io.support.ResourcePatternResolver; 15 16 import com.baomidou.mybatisplus.MybatisConfiguration; 17 18 /** 19 * Configuration properties for MyBatis. 20 * 21 * @author Eddú Meléndez 22 * @author Kazuki Shimizu 23 */ 24 @ConfigurationProperties(prefix = MybatisPlusProperties.MYBATIS_PLUS_PREFIX) 25 public class MybatisPlusProperties { 26 27 public static final String MYBATIS_PLUS_PREFIX = "mybatis-plus"; 28 29 /** 30 * Location of MyBatis xml config file. 31 */ 32 private String configLocation; 33 34 /** 35 * Locations of MyBatis mapper files. 36 */ 37 private String[] mapperLocations; 38 39 /** 40 * Packages to search type aliases. (Package delimiters are ",; ") 41 */ 42 private String typeAliasesPackage; 43 44 // TODO 自定义枚举包 45 private String typeEnumsPackage; 46 47 /** 48 * Packages to search for type handlers. (Package delimiters are ",; ") 49 */ 50 private String typeHandlersPackage; 51 52 /** 53 * Indicates whether perform presence check of the MyBatis xml config file. 54 */ 55 private boolean checkConfigLocation = false; 56 57 /** 58 * Execution mode for {@link org.mybatis.spring.SqlSessionTemplate}. 59 */ 60 private ExecutorType executorType; 61 62 /** 63 * Externalized properties for MyBatis configuration. 64 */ 65 private Properties configurationProperties; 66 /** 67 * Externalized properties for MyBatis configuration. 68 */ 69 @NestedConfigurationProperty 70 private GlobalConfig globalConfig; 71 72 /** 73 * A Configuration object for customize default settings. If {@link #configLocation} 74 * is specified, this property is not used. 75 */ 76 @NestedConfigurationProperty 77 private MybatisConfiguration configuration; 78 79 /** 80 * @since 1.1.0 81 */ 82 public String getConfigLocation() { 83 return this.configLocation; 84 } 85 86 /** 87 * @since 1.1.0 88 */ 89 public void setConfigLocation(String configLocation) { 90 this.configLocation = configLocation; 91 } 92 93 public String[] getMapperLocations() { 94 return this.mapperLocations; 95 } 96 97 public void setMapperLocations(String[] mapperLocations) { 98 this.mapperLocations = mapperLocations; 99 } 100 101 public String getTypeHandlersPackage() { 102 return this.typeHandlersPackage; 103 } 104 105 public void setTypeHandlersPackage(String typeHandlersPackage) { 106 this.typeHandlersPackage = typeHandlersPackage; 107 } 108 109 public String getTypeAliasesPackage() { 110 return this.typeAliasesPackage; 111 } 112 113 public void setTypeAliasesPackage(String typeAliasesPackage) { 114 this.typeAliasesPackage = typeAliasesPackage; 115 } 116 117 public String getTypeEnumsPackage() { 118 return typeEnumsPackage; 119 } 120 121 public void setTypeEnumsPackage(String typeEnumsPackage) { 122 this.typeEnumsPackage = typeEnumsPackage; 123 } 124 125 public boolean isCheckConfigLocation() { 126 return this.checkConfigLocation; 127 } 128 129 public void setCheckConfigLocation(boolean checkConfigLocation) { 130 this.checkConfigLocation = checkConfigLocation; 131 } 132 133 public ExecutorType getExecutorType() { 134 return this.executorType; 135 } 136 137 public void setExecutorType(ExecutorType executorType) { 138 this.executorType = executorType; 139 } 140 141 /** 142 * @since 1.2.0 143 */ 144 public Properties getConfigurationProperties() { 145 return configurationProperties; 146 } 147 148 /** 149 * @since 1.2.0 150 */ 151 public void setConfigurationProperties(Properties configurationProperties) { 152 this.configurationProperties = configurationProperties; 153 } 154 155 public MybatisConfiguration getConfiguration() { 156 return configuration; 157 } 158 159 public void setConfiguration(MybatisConfiguration configuration) { 160 this.configuration = configuration; 161 } 162 163 public Resource[] resolveMapperLocations() { 164 ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); 165 List<Resource> resources = new ArrayList<Resource>(); 166 if (this.mapperLocations != null) { 167 for (String mapperLocation : this.mapperLocations) { 168 try { 169 Resource[] mappers = resourceResolver.getResources(mapperLocation); 170 resources.addAll(Arrays.asList(mappers)); 171 } catch (IOException e) { 172 // ignore 173 } 174 } 175 } 176 return resources.toArray(new Resource[resources.size()]); 177 } 178 179 public GlobalConfig getGlobalConfig() { 180 return globalConfig; 181 } 182 183 public void setGlobalConfig(GlobalConfig globalConfig) { 184 this.globalConfig = globalConfig; 185 } 186 }
可以看到有一些别名、配置文件所在包等的配置。还有一个重要的属性 GlobalConfig, 是mybatisplus的全局配置类。源码如下:
1 package com.baomidou.mybatisplus.spring.boot.starter; 2 3 import com.baomidou.mybatisplus.entity.GlobalConfiguration; 4 import com.baomidou.mybatisplus.incrementer.IKeyGenerator; 5 import com.baomidou.mybatisplus.mapper.ISqlInjector; 6 import com.baomidou.mybatisplus.mapper.MetaObjectHandler; 7 import com.baomidou.mybatisplus.toolkit.StringUtils; 8 9 /** 10 * <p> 11 * Mybatis全局缓存 12 * </p> 13 * 14 * @author Caratacus 15 * @since 2017-05-01 16 */ 17 public class GlobalConfig { 18 19 /** 20 * 主键类型 21 */ 22 private Integer idType; 23 /** 24 * 表前缀 25 */ 26 private String tablePrefix; 27 /** 28 * 表名、字段名、是否使用下划线命名 29 */ 30 private Boolean dbColumnUnderline; 31 /** 32 * SQL注入器 33 */ 34 @Deprecated 35 private String sqlInjector; 36 /** 37 * 元对象字段填充控制器 38 */ 39 @Deprecated 40 private String metaObjectHandler; 41 /** 42 * 字段验证策略 43 */ 44 private Integer fieldStrategy; 45 /** 46 * 方便调试 47 */ 48 private Boolean refreshMapper; 49 /** 50 * 是否大写命名 51 */ 52 private Boolean isCapitalMode; 53 /** 54 * 标识符 55 */ 56 private String identifierQuote; 57 /** 58 * 逻辑删除全局值 59 */ 60 private String logicDeleteValue = null; 61 /** 62 * 逻辑未删除全局值 63 */ 64 private String logicNotDeleteValue = null; 65 /** 66 * 表关键词 key 生成器 67 */ 68 @Deprecated 69 private String keyGenerator; 70 /** 71 * 缓存 Sql 解析初始化 72 */ 73 private Boolean sqlParserCache; 74 75 public Integer getIdType() { 76 return idType; 77 } 78 79 public void setIdType(Integer idType) { 80 this.idType = idType; 81 } 82 83 public String getTablePrefix() { 84 return tablePrefix; 85 } 86 87 public void setTablePrefix(String tablePrefix) { 88 this.tablePrefix = tablePrefix; 89 } 90 91 public Boolean getDbColumnUnderline() { 92 return dbColumnUnderline; 93 } 94 95 public void setDbColumnUnderline(Boolean dbColumnUnderline) { 96 this.dbColumnUnderline = dbColumnUnderline; 97 } 98 99 public String getSqlInjector() { 100 return sqlInjector; 101 } 102 103 public void setSqlInjector(String sqlInjector) { 104 this.sqlInjector = sqlInjector; 105 } 106 107 public String getMetaObjectHandler() { 108 return metaObjectHandler; 109 } 110 111 public void setMetaObjectHandler(String metaObjectHandler) { 112 this.metaObjectHandler = metaObjectHandler; 113 } 114 115 public Integer getFieldStrategy() { 116 return fieldStrategy; 117 } 118 119 public void setFieldStrategy(Integer fieldStrategy) { 120 this.fieldStrategy = fieldStrategy; 121 } 122 123 public Boolean getCapitalMode() { 124 return isCapitalMode; 125 } 126 127 public void setCapitalMode(Boolean capitalMode) { 128 isCapitalMode = capitalMode; 129 } 130 131 public String getIdentifierQuote() { 132 return identifierQuote; 133 } 134 135 public void setIdentifierQuote(String identifierQuote) { 136 this.identifierQuote = identifierQuote; 137 } 138 139 public Boolean getRefreshMapper() { 140 return refreshMapper; 141 } 142 143 public void setRefreshMapper(Boolean refreshMapper) { 144 this.refreshMapper = refreshMapper; 145 } 146 147 public String getLogicDeleteValue() { 148 return logicDeleteValue; 149 } 150 151 public void setLogicDeleteValue(String logicDeleteValue) { 152 this.logicDeleteValue = logicDeleteValue; 153 } 154 155 public String getLogicNotDeleteValue() { 156 return logicNotDeleteValue; 157 } 158 159 public void setLogicNotDeleteValue(String logicNotDeleteValue) { 160 this.logicNotDeleteValue = logicNotDeleteValue; 161 } 162 163 public String getKeyGenerator() { 164 return keyGenerator; 165 } 166 167 public void setKeyGenerator(String keyGenerator) { 168 this.keyGenerator = keyGenerator; 169 } 170 171 public Boolean getSqlParserCache() { 172 return sqlParserCache; 173 } 174 175 public void setSqlParserCache(Boolean sqlParserCache) { 176 this.sqlParserCache = sqlParserCache; 177 } 178 179 public GlobalConfiguration convertGlobalConfiguration() throws ClassNotFoundException, IllegalAccessException, InstantiationException { 180 GlobalConfiguration globalConfiguration = new GlobalConfiguration(); 181 if (StringUtils.isNotEmpty(this.getIdentifierQuote())) { 182 globalConfiguration.setIdentifierQuote(this.getIdentifierQuote()); 183 } 184 if (StringUtils.isNotEmpty(this.getLogicDeleteValue())) { 185 globalConfiguration.setLogicDeleteValue(this.getLogicDeleteValue()); 186 } 187 if (StringUtils.isNotEmpty(this.getLogicNotDeleteValue())) { 188 globalConfiguration.setLogicNotDeleteValue(this.getLogicNotDeleteValue()); 189 } 190 if (StringUtils.isNotEmpty(this.getSqlInjector())) { 191 globalConfiguration.setSqlInjector((ISqlInjector) Class.forName(this.getSqlInjector()).newInstance()); 192 } 193 if (StringUtils.isNotEmpty(this.getMetaObjectHandler())) { 194 globalConfiguration.setMetaObjectHandler((MetaObjectHandler) Class.forName(this.getMetaObjectHandler()).newInstance()); 195 } 196 if (StringUtils.isNotEmpty(this.getKeyGenerator())) { 197 globalConfiguration.setKeyGenerator((IKeyGenerator) Class.forName(this.getKeyGenerator()).newInstance()); 198 } 199 if (StringUtils.checkValNotNull(this.getIdType())) { 200 globalConfiguration.setIdType(this.getIdType()); 201 } 202 if (StringUtils.checkValNotNull(this.getTablePrefix())) { 203 globalConfiguration.setTablePrefix(this.getTablePrefix()); 204 } 205 if (null != this.getDbColumnUnderline()) { 206 globalConfiguration.setDbColumnUnderline(this.getDbColumnUnderline()); 207 } 208 if (StringUtils.checkValNotNull(this.getFieldStrategy())) { 209 globalConfiguration.setFieldStrategy(this.getFieldStrategy()); 210 } 211 if (StringUtils.checkValNotNull(this.getRefreshMapper())) { 212 globalConfiguration.setRefresh(this.getRefreshMapper()); 213 } 214 if (StringUtils.checkValNotNull(this.getCapitalMode())) { 215 globalConfiguration.setCapitalMode(this.getCapitalMode()); 216 } 217 if (null != this.getSqlParserCache()) { 218 globalConfiguration.setSqlParserCache(this.getSqlParserCache()); 219 } 220 return globalConfiguration; 221 } 222 223 }
这里面有一些全局的配置,包括表名的前缀、是否使用下换线命名方式、sql注入器等属性
2. com.baomidou.mybatisplus.spring.boot.starter.MybatisPlusAutoConfiguration 自动配置类
1 package com.baomidou.mybatisplus.spring.boot.starter; 2 3 import java.util.List; 4 5 import javax.annotation.PostConstruct; 6 import javax.sql.DataSource; 7 8 import org.apache.ibatis.annotations.Mapper; 9 import org.apache.ibatis.logging.Log; 10 import org.apache.ibatis.logging.LogFactory; 11 import org.apache.ibatis.mapping.DatabaseIdProvider; 12 import org.apache.ibatis.plugin.Interceptor; 13 import org.apache.ibatis.session.ExecutorType; 14 import org.apache.ibatis.session.SqlSessionFactory; 15 import org.mybatis.spring.SqlSessionTemplate; 16 import org.mybatis.spring.mapper.ClassPathMapperScanner; 17 import org.mybatis.spring.mapper.MapperFactoryBean; 18 import org.springframework.beans.BeansException; 19 import org.springframework.beans.factory.BeanFactory; 20 import org.springframework.beans.factory.BeanFactoryAware; 21 import org.springframework.beans.factory.ObjectProvider; 22 import org.springframework.beans.factory.support.BeanDefinitionRegistry; 23 import org.springframework.boot.autoconfigure.AutoConfigurationPackages; 24 import org.springframework.boot.autoconfigure.AutoConfigureAfter; 25 import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 26 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 27 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 28 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 29 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 30 import org.springframework.boot.context.properties.EnableConfigurationProperties; 31 import org.springframework.context.ApplicationContext; 32 import org.springframework.context.ResourceLoaderAware; 33 import org.springframework.context.annotation.Bean; 34 import org.springframework.context.annotation.Import; 35 import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 36 import org.springframework.core.io.Resource; 37 import org.springframework.core.io.ResourceLoader; 38 import org.springframework.core.type.AnnotationMetadata; 39 import org.springframework.util.Assert; 40 import org.springframework.util.CollectionUtils; 41 import org.springframework.util.ObjectUtils; 42 import org.springframework.util.StringUtils; 43 44 import com.baomidou.mybatisplus.MybatisConfiguration; 45 import com.baomidou.mybatisplus.MybatisXMLLanguageDriver; 46 import com.baomidou.mybatisplus.entity.GlobalConfiguration; 47 import com.baomidou.mybatisplus.incrementer.IKeyGenerator; 48 import com.baomidou.mybatisplus.mapper.ISqlInjector; 49 import com.baomidou.mybatisplus.mapper.MetaObjectHandler; 50 import com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean; 51 52 /** 53 * {@link EnableAutoConfiguration Auto-Configuration} for Mybatis. Contributes a 54 * {@link SqlSessionFactory} and a {@link SqlSessionTemplate}. 55 * <p> 56 * If {@link org.mybatis.spring.annotation.MapperScan} is used, or a 57 * configuration file is specified as a property, those will be considered, 58 * otherwise this auto-configuration will attempt to register mappers based on 59 * the interface definitions in or under the root auto-configuration package. 60 * 61 * @author Eddú Meléndez 62 * @author Josh Long 63 * @author Kazuki Shimizu 64 * @author Eduardo Macarrón 65 */ 66 @SuppressWarnings("ConstantConditions") 67 @org.springframework.context.annotation.Configuration 68 @ConditionalOnClass({SqlSessionFactory.class, MybatisSqlSessionFactoryBean.class}) 69 @ConditionalOnBean(DataSource.class) 70 @EnableConfigurationProperties(MybatisPlusProperties.class) 71 @AutoConfigureAfter(DataSourceAutoConfiguration.class) 72 public class MybatisPlusAutoConfiguration { 73 74 private static final Log logger = LogFactory.getLog(MybatisPlusAutoConfiguration.class); 75 76 private final MybatisPlusProperties properties; 77 78 private final Interceptor[] interceptors; 79 80 private final ResourceLoader resourceLoader; 81 82 private final DatabaseIdProvider databaseIdProvider; 83 84 private final List<ConfigurationCustomizer> configurationCustomizers; 85 86 private final ApplicationContext applicationContext; 87 88 public MybatisPlusAutoConfiguration(MybatisPlusProperties properties, 89 ObjectProvider<Interceptor[]> interceptorsProvider, 90 ResourceLoader resourceLoader, 91 ObjectProvider<DatabaseIdProvider> databaseIdProvider, 92 ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider, ApplicationContext applicationContext) { 93 this.properties = properties; 94 this.interceptors = interceptorsProvider.getIfAvailable(); 95 this.resourceLoader = resourceLoader; 96 this.databaseIdProvider = databaseIdProvider.getIfAvailable(); 97 this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable(); 98 this.applicationContext = applicationContext; 99 } 100 101 @PostConstruct 102 public void checkConfigFileExists() { 103 if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) { 104 Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation()); 105 Assert.state(resource.exists(), "Cannot find config location: " + resource 106 + " (please add config file or check your Mybatis configuration)"); 107 } 108 } 109 110 @Bean 111 @ConditionalOnMissingBean 112 public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { 113 MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean(); 114 factory.setDataSource(dataSource); 115 factory.setVfs(SpringBootVFS.class); 116 if (StringUtils.hasText(this.properties.getConfigLocation())) { 117 factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); 118 } 119 MybatisConfiguration configuration = this.properties.getConfiguration(); 120 if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) { 121 configuration = new MybatisConfiguration(); 122 } 123 if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) { 124 for (ConfigurationCustomizer customizer : this.configurationCustomizers) { 125 customizer.customize(configuration); 126 } 127 } 128 // TODO 自定义配置 129 if (null != configuration) { 130 configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class); 131 } 132 factory.setConfiguration(configuration); 133 if (this.properties.getConfigurationProperties() != null) { 134 factory.setConfigurationProperties(this.properties.getConfigurationProperties()); 135 } 136 if (!ObjectUtils.isEmpty(this.interceptors)) { 137 factory.setPlugins(this.interceptors); 138 } 139 if (this.databaseIdProvider != null) { 140 factory.setDatabaseIdProvider(this.databaseIdProvider); 141 } 142 if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { 143 factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); 144 } 145 // TODO 自定义枚举包 146 if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) { 147 factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage()); 148 } 149 if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { 150 factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); 151 } 152 if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { 153 factory.setMapperLocations(this.properties.resolveMapperLocations()); 154 } 155 GlobalConfiguration globalConfig; 156 if (!ObjectUtils.isEmpty(this.properties.getGlobalConfig())) { 157 globalConfig = this.properties.getGlobalConfig().convertGlobalConfiguration(); 158 } else { 159 globalConfig = new GlobalConfiguration(); 160 } 161 //注入填充器 162 if (this.applicationContext.getBeanNamesForType(MetaObjectHandler.class, false, 163 false).length > 0) { 164 MetaObjectHandler metaObjectHandler = this.applicationContext.getBean(MetaObjectHandler.class); 165 globalConfig.setMetaObjectHandler(metaObjectHandler); 166 } 167 //注入主键生成器 168 if (this.applicationContext.getBeanNamesForType(IKeyGenerator.class, false, 169 false).length > 0) { 170 IKeyGenerator keyGenerator = this.applicationContext.getBean(IKeyGenerator.class); 171 globalConfig.setKeyGenerator(keyGenerator); 172 } 173 //注入sql注入器 174 if (this.applicationContext.getBeanNamesForType(ISqlInjector.class, false, 175 false).length > 0) { 176 ISqlInjector iSqlInjector = this.applicationContext.getBean(ISqlInjector.class); 177 globalConfig.setSqlInjector(iSqlInjector); 178 } 179 factory.setGlobalConfig(globalConfig); 180 return factory.getObject(); 181 } 182 183 @Bean 184 @ConditionalOnMissingBean 185 public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { 186 ExecutorType executorType = this.properties.getExecutorType(); 187 if (executorType != null) { 188 return new SqlSessionTemplate(sqlSessionFactory, executorType); 189 } else { 190 return new SqlSessionTemplate(sqlSessionFactory); 191 } 192 } 193 194 /** 195 * This will just scan the same base package as Spring Boot does. If you want 196 * more power, you can explicitly use 197 * {@link org.mybatis.spring.annotation.MapperScan} but this will get typed 198 * mappers working correctly, out-of-the-box, similar to using Spring Data JPA 199 * repositories. 200 */ 201 public static class AutoConfiguredMapperScannerRegistrar 202 implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware { 203 204 private BeanFactory beanFactory; 205 206 private ResourceLoader resourceLoader; 207 208 @Override 209 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 210 211 logger.debug("Searching for mappers annotated with @Mapper"); 212 213 ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); 214 215 try { 216 if (this.resourceLoader != null) { 217 scanner.setResourceLoader(this.resourceLoader); 218 } 219 220 List<String> packages = AutoConfigurationPackages.get(this.beanFactory); 221 if (logger.isDebugEnabled()) { 222 for (String pkg : packages) { 223 logger.debug("Using auto-configuration base package '" + pkg + "'"); 224 } 225 } 226 227 scanner.setAnnotationClass(Mapper.class); 228 scanner.registerFilters(); 229 scanner.doScan(StringUtils.toStringArray(packages)); 230 } catch (IllegalStateException ex) { 231 logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled." + ex); 232 } 233 } 234 235 @Override 236 public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 237 this.beanFactory = beanFactory; 238 } 239 240 @Override 241 public void setResourceLoader(ResourceLoader resourceLoader) { 242 this.resourceLoader = resourceLoader; 243 } 244 } 245 246 /** 247 * {@link org.mybatis.spring.annotation.MapperScan} ultimately ends up 248 * creating instances of {@link MapperFactoryBean}. If 249 * {@link org.mybatis.spring.annotation.MapperScan} is used then this 250 * auto-configuration is not needed. If it is _not_ used, however, then this 251 * will bring in a bean registrar and automatically register components based 252 * on the same component-scanning path as Spring Boot itself. 253 */ 254 @org.springframework.context.annotation.Configuration 255 @Import({AutoConfiguredMapperScannerRegistrar.class}) 256 @ConditionalOnMissingBean(MapperFactoryBean.class) 257 public static class MapperScannerRegistrarNotFoundConfiguration { 258 259 @PostConstruct 260 public void afterPropertiesSet() { 261 logger.debug("No " + MapperFactoryBean.class.getName() + " found."); 262 } 263 } 264 265 }
类似于Mybatis,注入了一些重要的配置。其中包括:
(1) SqlSessionTamplate : 之前在研究Mybatis 源码的时候研究过,是一个SeqSession,内部包含一个SqlSession 代理对象,代理对象采用JDK动态代理,代理对象内部每次都是调用SqlSessionUtils.getSqlSession 获取一个实际工作的三种会话工厂中的一个SqlSession 对象。
(2) SqlSessionFactory 会话工厂。和Mybatis一样,需要一个会话工厂,用于每次获取SqlSession 对象。这个SqlSessionFactory 是通过mybatisplus的MybatisSqlSessionFactoryBean 工厂Bean提供的。 其中配置了MybatisConfiguration (继承自Mybatis的Configuration 对象)、GlobalConfiguration 信息,重要的包括主键生成器、sql注入器、填充器。
com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean 源码如下:
/** * Copyright (c) 2011-2014, hubin (jobob@qq.com). * <p> * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.baomidou.mybatisplus.spring; import static org.springframework.util.Assert.notNull; import static org.springframework.util.Assert.state; import static org.springframework.util.ObjectUtils.isEmpty; import static org.springframework.util.StringUtils.hasLength; import static org.springframework.util.StringUtils.tokenizeToStringArray; import java.io.IOException; import java.sql.SQLException; import java.util.HashSet; import java.util.Properties; import java.util.Set; import javax.sql.DataSource; import org.apache.ibatis.builder.xml.XMLMapperBuilder; import org.apache.ibatis.cache.Cache; import org.apache.ibatis.executor.ErrorContext; import org.apache.ibatis.io.VFS; import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.LogFactory; import org.apache.ibatis.mapping.DatabaseIdProvider; import org.apache.ibatis.mapping.Environment; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.reflection.factory.ObjectFactory; import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.apache.ibatis.transaction.TransactionFactory; import org.apache.ibatis.type.TypeHandler; import org.apache.ibatis.type.TypeHandlerRegistry; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.transaction.SpringManagedTransactionFactory; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.NestedIOException; import org.springframework.core.io.Resource; import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; import com.baomidou.mybatisplus.MybatisConfiguration; import com.baomidou.mybatisplus.MybatisXMLConfigBuilder; import com.baomidou.mybatisplus.entity.GlobalConfiguration; import com.baomidou.mybatisplus.enums.IEnum; import com.baomidou.mybatisplus.exceptions.MybatisPlusException; import com.baomidou.mybatisplus.mapper.SqlRunner; import com.baomidou.mybatisplus.toolkit.GlobalConfigUtils; import com.baomidou.mybatisplus.toolkit.PackageHelper; /** * <p> * 拷贝类 org.mybatis.spring.SqlSessionFactoryBean 修改方法 buildSqlSessionFactory() * 加载自定义 MybatisXmlConfigBuilder * </p> * * @author hubin * @Date 2017-01-04 */ public class MybatisSqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class); private Resource configLocation; private Configuration configuration; private Resource[] mapperLocations; private DataSource dataSource; private TransactionFactory transactionFactory; private Properties configurationProperties; private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); private SqlSessionFactory sqlSessionFactory; //EnvironmentAware requires spring 3.1 private String environment = MybatisSqlSessionFactoryBean.class.getSimpleName(); private boolean failFast; private Interceptor[] plugins; private TypeHandler<?>[] typeHandlers; private String typeHandlersPackage; private Class<?>[] typeAliases; private String typeAliasesPackage; // TODO 自定义枚举包 private String typeEnumsPackage; private Class<?> typeAliasesSuperType; //issue #19. No default provider. private DatabaseIdProvider databaseIdProvider; private Class<? extends VFS> vfs; private Cache cache; private ObjectFactory objectFactory; private ObjectWrapperFactory objectWrapperFactory; private GlobalConfiguration globalConfig = GlobalConfigUtils.defaults(); // TODO 注入全局配置 public void setGlobalConfig(GlobalConfiguration globalConfig) { this.globalConfig = globalConfig; } /** * Sets the ObjectFactory. * * @param objectFactory * @since 1.1.2 */ public void setObjectFactory(ObjectFactory objectFactory) { this.objectFactory = objectFactory; } /** * Sets the ObjectWrapperFactory. * * @param objectWrapperFactory * @since 1.1.2 */ public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) { this.objectWrapperFactory = objectWrapperFactory; } /** * Gets the DatabaseIdProvider * * @return * @since 1.1.0 */ public DatabaseIdProvider getDatabaseIdProvider() { return databaseIdProvider; } /** * Sets the DatabaseIdProvider. * As of version 1.2.2 this variable is not initialized by default. * * @param databaseIdProvider * @since 1.1.0 */ public void setDatabaseIdProvider(DatabaseIdProvider databaseIdProvider) { this.databaseIdProvider = databaseIdProvider; } public Class<? extends VFS> getVfs() { return this.vfs; } public void setVfs(Class<? extends VFS> vfs) { this.vfs = vfs; } public Cache getCache() { return this.cache; } public void setCache(Cache cache) { this.cache = cache; } /** * Mybatis plugin list. * * @param plugins list of plugins * @since 1.0.1 */ public void setPlugins(Interceptor[] plugins) { this.plugins = plugins; } /** * Packages to search for type aliases. * * @param typeAliasesPackage package to scan for domain objects * @since 1.0.1 */ public void setTypeAliasesPackage(String typeAliasesPackage) { this.typeAliasesPackage = typeAliasesPackage; } public void setTypeEnumsPackage(String typeEnumsPackage) { this.typeEnumsPackage = typeEnumsPackage; } /** * Super class which domain objects have to extend to have a type alias created. * No effect if there is no package to scan configured. * * @param typeAliasesSuperType super class for domain objects * @since 1.1.2 */ public void setTypeAliasesSuperType(Class<?> typeAliasesSuperType) { this.typeAliasesSuperType = typeAliasesSuperType; } /** * Packages to search for type handlers. * * @param typeHandlersPackage package to scan for type handlers * @since 1.0.1 */ public void setTypeHandlersPackage(String typeHandlersPackage) { this.typeHandlersPackage = typeHandlersPackage; } /** * Set type handlers. They must be annotated with {@code MappedTypes} and optionally with {@code MappedJdbcTypes} * * @param typeHandlers Type handler list * @since 1.0.1 */ public void setTypeHandlers(TypeHandler<?>[] typeHandlers) { this.typeHandlers = typeHandlers; } /** * List of type aliases to register. They can be annotated with {@code Alias} * * @param typeAliases Type aliases list * @since 1.0.1 */ public void setTypeAliases(Class<?>[] typeAliases) { this.typeAliases = typeAliases; } /** * If true, a final check is done on Configuration to assure that all mapped * statements are fully loaded and there is no one still pending to resolve * includes. Defaults to false. * * @param failFast enable failFast * @since 1.0.1 */ public void setFailFast(boolean failFast) { this.failFast = failFast; } /** * Set the location of the MyBatis {@code SqlSessionFactory} config file. A typical value is * "WEB-INF/mybatis-configuration.xml". */ public void setConfigLocation(Resource configLocation) { this.configLocation = configLocation; } /** * Set a customized MyBatis configuration. * * @param configuration MyBatis configuration * @since 1.3.0 */ public void setConfiguration(Configuration configuration) { this.configuration = configuration; } /** * Set locations of MyBatis mapper files that are going to be merged into the {@code SqlSessionFactory} * configuration at runtime. * <p> * This is an alternative to specifying "<sqlmapper>" entries in an MyBatis config file. * This property being based on Spring's resource abstraction also allows for specifying * resource patterns here: e.g. "classpath*:sqlmap/*-mapper.xml". */ public void setMapperLocations(Resource[] mapperLocations) { this.mapperLocations = mapperLocations; } /** * Set optional properties to be passed into the SqlSession configuration, as alternative to a * {@code <properties>} tag in the configuration xml file. This will be used to * resolve placeholders in the config file. */ public void setConfigurationProperties(Properties sqlSessionFactoryProperties) { this.configurationProperties = sqlSessionFactoryProperties; } /** * Set the JDBC {@code DataSource} that this instance should manage transactions for. The {@code DataSource} * should match the one used by the {@code SqlSessionFactory}: for example, you could specify the same * JNDI DataSource for both. * <p> * A transactional JDBC {@code Connection} for this {@code DataSource} will be provided to application code * accessing this {@code DataSource} directly via {@code DataSourceUtils} or {@code DataSourceTransactionManager}. * <p> * The {@code DataSource} specified here should be the target {@code DataSource} to manage transactions for, not * a {@code TransactionAwareDataSourceProxy}. Only data access code may work with * {@code TransactionAwareDataSourceProxy}, while the transaction manager needs to work on the * underlying target {@code DataSource}. If there's nevertheless a {@code TransactionAwareDataSourceProxy} * passed in, it will be unwrapped to extract its target {@code DataSource}. */ public void setDataSource(DataSource dataSource) { if (dataSource instanceof TransactionAwareDataSourceProxy) { // If we got a TransactionAwareDataSourceProxy, we need to perform // transactions for its underlying target DataSource, else data // access code won't see properly exposed transactions (i.e. // transactions for the target DataSource). this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource(); } else { this.dataSource = dataSource; } } /** * Sets the {@code SqlSessionFactoryBuilder} to use when creating the {@code SqlSessionFactory}. * <p> * This is mainly meant for testing so that mock SqlSessionFactory classes can be injected. By * default, {@code SqlSessionFactoryBuilder} creates {@code DefaultSqlSessionFactory} instances. */ public void setSqlSessionFactoryBuilder(SqlSessionFactoryBuilder sqlSessionFactoryBuilder) { this.sqlSessionFactoryBuilder = sqlSessionFactoryBuilder; } /** * Set the MyBatis TransactionFactory to use. Default is {@code SpringManagedTransactionFactory} * <p> * The default {@code SpringManagedTransactionFactory} should be appropriate for all cases: * be it Spring transaction management, EJB CMT or plain JTA. If there is no active transaction, * SqlSession operations will execute SQL statements non-transactionally. * <p> * <b>It is strongly recommended to use the default {@code TransactionFactory}.</b> If not used, any * attempt at getting an SqlSession through Spring's MyBatis framework will throw an exception if * a transaction is active. * * @param transactionFactory the MyBatis TransactionFactory * @see SpringManagedTransactionFactory */ public void setTransactionFactory(TransactionFactory transactionFactory) { this.transactionFactory = transactionFactory; } /** * <b>NOTE:</b> This class <em>overrides</em> any {@code Environment} you have set in the MyBatis * config file. This is used only as a placeholder name. The default value is * {@code SqlSessionFactoryBean.class.getSimpleName()}. * * @param environment the environment name */ public void setEnvironment(String environment) { this.environment = environment; } /** * {@inheritDoc} */ @Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property 'dataSource' is required"); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property 'configuration' and 'configLocation' can not specified with together"); this.sqlSessionFactory = buildSqlSessionFactory(); } /** * Build a {@code SqlSessionFactory} instance. * <p> * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a * {@code SqlSessionFactory} instance based on an Reader. * Since 1.3.0, it can be specified a {@link Configuration} instance directly(without config file). * * @return SqlSessionFactory * @throws IOException if loading the config file failed */ protected SqlSessionFactory buildSqlSessionFactory() throws Exception { Configuration configuration; // TODO 加载自定义 MybatisXmlConfigBuilder MybatisXMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { configuration.getVariables().putAll(this.configurationProperties); } } else if (this.configLocation != null) { xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); } // TODO 使用自定义配置 configuration = new MybatisConfiguration(); if (this.configurationProperties != null) { configuration.setVariables(this.configurationProperties); } } if (this.objectFactory != null) { configuration.setObjectFactory(this.objectFactory); } if (this.objectWrapperFactory != null) { configuration.setObjectWrapperFactory(this.objectWrapperFactory); } if (this.vfs != null) { configuration.setVfsImpl(this.vfs); } if (hasLength(this.typeAliasesPackage)) { // TODO 支持自定义通配符 String[] typeAliasPackageArray; if (typeAliasesPackage.contains("*") && !typeAliasesPackage.contains(",") && !typeAliasesPackage.contains(";")) { typeAliasPackageArray = PackageHelper.convertTypeAliasesPackage(typeAliasesPackage); } else { typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); } if (typeAliasPackageArray == null) { throw new MybatisPlusException("not find typeAliasesPackage:" + typeAliasesPackage); } for (String packageToScan : typeAliasPackageArray) { configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases"); } } } // TODO 自定义枚举类扫描处理 if (hasLength(this.typeEnumsPackage)) { Set<Class> classes = null; if (typeEnumsPackage.contains("*") && !typeEnumsPackage.contains(",") && !typeEnumsPackage.contains(";")) { classes = PackageHelper.scanTypePackage(typeEnumsPackage); } else { String[] typeEnumsPackageArray = tokenizeToStringArray(this.typeEnumsPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); if (typeEnumsPackageArray == null) { throw new MybatisPlusException("not find typeEnumsPackage:" + typeEnumsPackage); } classes = new HashSet<Class>(); for (String typePackage : typeEnumsPackageArray) { classes.addAll(PackageHelper.scanTypePackage(typePackage)); } } // 取得类型转换注册器 TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); for (Class cls : classes) { if (cls.isEnum()) { if (IEnum.class.isAssignableFrom(cls)) { typeHandlerRegistry.register(cls.getName(), com.baomidou.mybatisplus.handlers.EnumTypeHandler.class.getCanonicalName()); } else { // 使用原生 EnumOrdinalTypeHandler typeHandlerRegistry.register(cls.getName(), org.apache.ibatis.type.EnumOrdinalTypeHandler.class.getCanonicalName()); } } } } if (!isEmpty(this.typeAliases)) { for (Class<?> typeAlias : this.typeAliases) { configuration.getTypeAliasRegistry().registerAlias(typeAlias); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type alias: '" + typeAlias + "'"); } } } if (!isEmpty(this.plugins)) { for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered plugin: '" + plugin + "'"); } } } if (hasLength(this.typeHandlersPackage)) { String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) { configuration.getTypeHandlerRegistry().register(packageToScan); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers"); } } } if (!isEmpty(this.typeHandlers)) { for (TypeHandler<?> typeHandler : this.typeHandlers) { configuration.getTypeHandlerRegistry().register(typeHandler); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type handler: '" + typeHandler + "'"); } } } if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls try { configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); } catch (SQLException e) { throw new NestedIOException("Failed getting a databaseId", e); } } if (this.cache != null) { configuration.addCache(this.cache); } if (xmlConfigBuilder != null) { try { xmlConfigBuilder.parse(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'"); } } catch (Exception ex) { throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex); } finally { ErrorContext.instance().reset(); } } if (this.transactionFactory == null) { this.transactionFactory = new SpringManagedTransactionFactory(); } configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); // 设置元数据相关 GlobalConfigUtils.setMetaData(dataSource, globalConfig); SqlSessionFactory sqlSessionFactory = this.sqlSessionFactoryBuilder.build(configuration); // TODO SqlRunner SqlRunner.FACTORY = sqlSessionFactory; // TODO 缓存 sqlSessionFactory globalConfig.setSqlSessionFactory(sqlSessionFactory); // TODO 设置全局参数属性 globalConfig.signGlobalConfig(sqlSessionFactory); if (!isEmpty(this.mapperLocations)) { if (globalConfig.isRefresh()) { //TODO 设置自动刷新配置 减少配置 new MybatisMapperRefresh(this.mapperLocations, sqlSessionFactory, 2, 2, true); } for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { // TODO 这里也换了噢噢噢噢 XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found"); } } return sqlSessionFactory; } /** * {@inheritDoc} */ @Override public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { afterPropertiesSet(); } return this.sqlSessionFactory; } /** * {@inheritDoc} */ @Override public Class<? extends SqlSessionFactory> getObjectType() { return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass(); } /** * {@inheritDoc} */ @Override public boolean isSingleton() { return true; } /** * {@inheritDoc} */ @Override public void onApplicationEvent(ApplicationEvent event) { if (failFast && event instanceof ContextRefreshedEvent) { // fail-fast -> check all statements are completed this.sqlSessionFactory.getConfiguration().getMappedStatementNames(); } } }
com.baomidou.mybatisplus.MybatisConfiguration 源码如下:
package com.baomidou.mybatisplus; import org.apache.ibatis.binding.MapperRegistry; import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.LogFactory; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.SqlSession; import com.baomidou.mybatisplus.toolkit.GlobalConfigUtils; /** * <p> * replace default Configuration class * </p> * <p> * Caratacus 2016/9/25 replace mapperRegistry * </p> * * @author hubin * @Date 2016-01-23 */ public class MybatisConfiguration extends Configuration { private static final Log logger = LogFactory.getLog(MybatisConfiguration.class); /** * Mapper 注册 */ public final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this); /** * 初始化调用 */ public MybatisConfiguration() { this.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class); logger.debug("Mybatis-plus init success."); } /** * <p> * MybatisPlus 加载 SQL 顺序: * </p> * 1、加载XML中的SQL<br> * 2、加载sqlProvider中的SQL<br> * 3、xmlSql 与 sqlProvider不能包含相同的SQL<br> * <br> * 调整后的SQL优先级:xmlSql > sqlProvider > curdSql <br> */ @Override public void addMappedStatement(MappedStatement ms) { logger.debug("addMappedStatement: " + ms.getId()); if (GlobalConfigUtils.isRefresh(ms.getConfiguration())) { /* * 支持是否自动刷新 XML 变更内容,开发环境使用【 注:生产环境勿用!】 */ this.mappedStatements.remove(ms.getId()); } else { if (this.mappedStatements.containsKey(ms.getId())) { /* * 说明已加载了xml中的节点; 忽略mapper中的SqlProvider数据 */ logger.error("mapper[" + ms.getId() + "] is ignored, because it's exists, maybe from xml file"); return; } } super.addMappedStatement(ms); } @Override public void setDefaultScriptingLanguage(Class<?> driver) { if (driver == null) { /* 设置自定义 driver */ driver = MybatisXMLLanguageDriver.class; } super.setDefaultScriptingLanguage(driver); } @Override public MapperRegistry getMapperRegistry() { return mybatisMapperRegistry; } @Override public <T> void addMapper(Class<T> type) { mybatisMapperRegistry.addMapper(type); } @Override public void addMappers(String packageName, Class<?> superType) { mybatisMapperRegistry.addMappers(packageName, superType); } @Override public void addMappers(String packageName) { mybatisMapperRegistry.addMappers(packageName); } @Override public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mybatisMapperRegistry.getMapper(type, sqlSession); } @Override public boolean hasMapper(Class<?> type) { return mybatisMapperRegistry.hasMapper(type); } }
com.baomidou.mybatisplus.mapper.AutoSqlInjector 源码如下:
/** * Copyright (c) 2011-2014, hubin (jobob@qq.com). * <p> * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.baomidou.mybatisplus.mapper; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.ibatis.builder.MapperBuilderAssistant; import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator; import org.apache.ibatis.executor.keygen.KeyGenerator; import org.apache.ibatis.executor.keygen.NoKeyGenerator; import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.LogFactory; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ResultMap; import org.apache.ibatis.mapping.ResultMapping; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.mapping.SqlSource; import org.apache.ibatis.mapping.StatementType; import org.apache.ibatis.scripting.LanguageDriver; import org.apache.ibatis.scripting.defaults.RawSqlSource; import org.apache.ibatis.session.Configuration; import com.baomidou.mybatisplus.entity.GlobalConfiguration; import com.baomidou.mybatisplus.entity.TableFieldInfo; import com.baomidou.mybatisplus.entity.TableInfo; import com.baomidou.mybatisplus.enums.FieldFill; import com.baomidou.mybatisplus.enums.FieldStrategy; import com.baomidou.mybatisplus.enums.IdType; import com.baomidou.mybatisplus.enums.SqlMethod; import com.baomidou.mybatisplus.toolkit.ArrayUtils; import com.baomidou.mybatisplus.toolkit.CollectionUtils; import com.baomidou.mybatisplus.toolkit.GlobalConfigUtils; import com.baomidou.mybatisplus.toolkit.PluginUtils; import com.baomidou.mybatisplus.toolkit.SqlReservedWords; import com.baomidou.mybatisplus.toolkit.StringUtils; import com.baomidou.mybatisplus.toolkit.TableInfoHelper; /** * <p> * SQL 自动注入器 * </p> * * @author hubin sjy tantan * @Date 2016-09-09 */ public class AutoSqlInjector implements ISqlInjector { private static final Log logger = LogFactory.getLog(AutoSqlInjector.class); protected Configuration configuration; protected LanguageDriver languageDriver; protected MapperBuilderAssistant builderAssistant; /** * <p> * CRUD 注入后给予标识 注入过后不再注入 * </p> * * @param builderAssistant * @param mapperClass */ @Override public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) { String className = mapperClass.toString(); Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration()); if (!mapperRegistryCache.contains(className)) { inject(builderAssistant, mapperClass); mapperRegistryCache.add(className); } } /** * 注入单点 crudSql */ @Override public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) { this.configuration = builderAssistant.getConfiguration(); this.builderAssistant = builderAssistant; this.languageDriver = configuration.getDefaultScriptingLanguageInstance(); //去除 驼峰设置 PLUS 配置 > 原生配置 (该配置不需要与原生Mybatis混淆) /*if (!globalCache.isDbColumnUnderline()) { globalCache.setDbColumnUnderline(configuration.isMapUnderscoreToCamelCase()); }*/ Class<?> modelClass = extractModelClass(mapperClass); if (null != modelClass) { /** * 初始化 SQL 解析 */ if (this.getGlobalConfig().isSqlParserCache()) { PluginUtils.initSqlParserInfoCache(mapperClass); } TableInfo table = TableInfoHelper.initTableInfo(builderAssistant, modelClass); injectSql(builderAssistant, mapperClass, modelClass, table); } } /** * <p> * 注入SQL * </p> * * @param builderAssistant * @param mapperClass * @param modelClass * @param table */ protected void injectSql(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { /** * #148 表信息包含主键,注入主键相关方法 */ if (StringUtils.isNotEmpty(table.getKeyProperty())) { /** 删除 */ this.injectDeleteByIdSql(false, mapperClass, modelClass, table); this.injectDeleteByIdSql(true, mapperClass, modelClass, table); /** 修改 */ this.injectUpdateByIdSql(true, mapperClass, modelClass, table); this.injectUpdateByIdSql(false, mapperClass, modelClass, table); /** 查询 */ this.injectSelectByIdSql(false, mapperClass, modelClass, table); this.injectSelectByIdSql(true, mapperClass, modelClass, table); } else { // 表不包含主键时 给予警告 logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.", modelClass.toString())); } /** * 正常注入无需主键方法 */ /** 插入 */ this.injectInsertOneSql(true, mapperClass, modelClass, table); this.injectInsertOneSql(false, mapperClass, modelClass, table); /** 删除 */ this.injectDeleteSql(mapperClass, modelClass, table); this.injectDeleteByMapSql(mapperClass, table); /** 修改 */ this.injectUpdateSql(mapperClass, modelClass, table); /** 修改 (自定义 set 属性) */ this.injectUpdateForSetSql(mapperClass, modelClass, table); /** 查询 */ this.injectSelectByMapSql(mapperClass, modelClass, table); this.injectSelectOneSql(mapperClass, modelClass, table); this.injectSelectCountSql(mapperClass, modelClass, table); this.injectSelectListSql(SqlMethod.SELECT_LIST, mapperClass, modelClass, table); this.injectSelectListSql(SqlMethod.SELECT_PAGE, mapperClass, modelClass, table); this.injectSelectMapsSql(SqlMethod.SELECT_MAPS, mapperClass, modelClass, table); this.injectSelectMapsSql(SqlMethod.SELECT_MAPS_PAGE, mapperClass, modelClass, table); this.injectSelectObjsSql(SqlMethod.SELECT_OBJS, mapperClass, modelClass, table); /** 自定义方法 */ this.inject(configuration, builderAssistant, mapperClass, modelClass, table); } /** * 自定义方法,注入点(子类需重写该方法) */ public void inject(Configuration configuration, MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { // to do nothing } /** * 提取泛型模型,多泛型的时候请将泛型T放在第一位 * * @param mapperClass * @return */ protected Class<?> extractModelClass(Class<?> mapperClass) { Type[] types = mapperClass.getGenericInterfaces(); ParameterizedType target = null; for (Type type : types) { if (type instanceof ParameterizedType) { Type[] typeArray = ((ParameterizedType) type).getActualTypeArguments(); if (ArrayUtils.isNotEmpty(typeArray)) { for (Type t : typeArray) { if (t instanceof TypeVariable || t instanceof WildcardType) { target = null; break; } else { target = (ParameterizedType) type; break; } } } break; } } return target == null ? null : (Class<?>) target.getActualTypeArguments()[0]; } /** * <p> * 注入插入 SQL 语句 * </p> * * @param selective 是否选择插入 * @param mapperClass * @param modelClass * @param table */ protected void injectInsertOneSql(boolean selective, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { /* * INSERT INTO table <trim prefix="(" suffix=")" suffixOverrides=","> * <if test="xx != null">xx,</if> </trim> <trim prefix="values (" * suffix=")" suffixOverrides=","> <if test="xx != null">#{xx},</if> * </trim> */ KeyGenerator keyGenerator = new NoKeyGenerator(); StringBuilder fieldBuilder = new StringBuilder(); StringBuilder placeholderBuilder = new StringBuilder(); SqlMethod sqlMethod = selective ? SqlMethod.INSERT_ONE : SqlMethod.INSERT_ONE_ALL_COLUMN; fieldBuilder.append(" <trim prefix="(" suffix=")" suffixOverrides=","> "); placeholderBuilder.append(" <trim prefix="(" suffix=")" suffixOverrides=","> "); String keyProperty = null; String keyColumn = null; // 表包含主键处理逻辑,如果不包含主键当普通字段处理 if (StringUtils.isNotEmpty(table.getKeyProperty())) { if (table.getIdType() == IdType.AUTO) { /** 自增主键 */ keyGenerator = new Jdbc3KeyGenerator(); keyProperty = table.getKeyProperty(); keyColumn = table.getKeyColumn(); } else { if (null != table.getKeySequence()) { keyGenerator = TableInfoHelper.genKeyGenerator(table, builderAssistant, sqlMethod.getMethod(), languageDriver); keyProperty = table.getKeyProperty(); keyColumn = table.getKeyColumn(); fieldBuilder.append(table.getKeyColumn()).append(","); placeholderBuilder.append("#{").append(table.getKeyProperty()).append("},"); } else { /** 用户输入自定义ID */ fieldBuilder.append(table.getKeyColumn()).append(","); // 正常自定义主键策略 placeholderBuilder.append("#{").append(table.getKeyProperty()).append("},"); } } } // 是否 IF 标签判断 boolean ifTag; List<TableFieldInfo> fieldList = table.getFieldList(); for (TableFieldInfo fieldInfo : fieldList) { // 在FieldIgnore,INSERT_UPDATE,INSERT 时设置为false ifTag = !(FieldFill.INSERT == fieldInfo.getFieldFill() || FieldFill.INSERT_UPDATE == fieldInfo.getFieldFill()); if (selective && ifTag) { fieldBuilder.append(convertIfTagIgnored(fieldInfo, false)); fieldBuilder.append(fieldInfo.getColumn()).append(","); fieldBuilder.append(convertIfTagIgnored(fieldInfo, true)); placeholderBuilder.append(convertIfTagIgnored(fieldInfo, false)); placeholderBuilder.append("#{").append(fieldInfo.getEl()).append("},"); placeholderBuilder.append(convertIfTagIgnored(fieldInfo, true)); } else { fieldBuilder.append(fieldInfo.getColumn()).append(","); placeholderBuilder.append("#{").append(fieldInfo.getEl()).append("},"); } } fieldBuilder.append(" </trim>"); placeholderBuilder.append(" </trim>"); String sql = String.format(sqlMethod.getSql(), table.getTableName(), fieldBuilder.toString(), placeholderBuilder.toString()); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); this.addInsertMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource, keyGenerator, keyProperty, keyColumn); } /** * <p> * 注入 entity 条件删除 SQL 语句 * </p> * * @param mapperClass * @param modelClass * @param table */ protected void injectDeleteSql(Class<?> mapperClass, Class<?> modelClass, TableInfo table) { SqlMethod sqlMethod = SqlMethod.DELETE; String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlWhereEntityWrapper(table)); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); this.addDeleteMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource); } /** * <p> * 注入 map 条件删除 SQL 语句 * </p> * * @param mapperClass * @param table */ protected void injectDeleteByMapSql(Class<?> mapperClass, TableInfo table) { SqlMethod sqlMethod = SqlMethod.DELETE_BY_MAP; String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlWhereByMap(table)); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Map.class); this.addDeleteMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource); } /** * <p> * 注入删除 SQL 语句 * </p> * * @param mapperClass * @param modelClass * @param table */ protected void injectDeleteByIdSql(boolean batch, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { SqlMethod sqlMethod = SqlMethod.DELETE_BY_ID; SqlSource sqlSource; // 因为后面要通过get方法获取类型,所以这里要获取key的属性值 String idStr = table.getKeyProperty(); if (batch) { sqlMethod = SqlMethod.DELETE_BATCH_BY_IDS; StringBuilder ids = new StringBuilder(); ids.append(" <foreach item="item" index="index" collection="coll" separator=",">"); ids.append("#{item}"); ids.append(" </foreach>"); idStr = ids.toString(); } String sql = String.format(sqlMethod.getSql(), table.getTableName(), table.getKeyColumn(), idStr); sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); this.addDeleteMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource); } /** * <p> * 注入更新 SQL 语句 * </p> * * @param mapperClass * @param modelClass * @param table */ protected void injectUpdateByIdSql(boolean selective, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { SqlMethod sqlMethod = selective ? SqlMethod.UPDATE_BY_ID : SqlMethod.UPDATE_ALL_COLUMN_BY_ID; String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlSet(selective, table, "et."), table.getKeyColumn(), "et." + table.getKeyProperty(), "<if test="et instanceof java.util.Map">" + "<if test="et.MP_OPTLOCK_VERSION_ORIGINAL!=null">" + "and ${et.MP_OPTLOCK_VERSION_COLUMN}=#{et.MP_OPTLOCK_VERSION_ORIGINAL}" + "</if>" + "</if>" ); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); this.addUpdateMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource); } /** * <p> * 注入批量更新 SQL 语句 * </p> * * @param mapperClass * @param modelClass * @param table */ protected void injectUpdateSql(Class<?> mapperClass, Class<?> modelClass, TableInfo table) { SqlMethod sqlMethod = SqlMethod.UPDATE; String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlSet(true, table, "et."), sqlWhereEntityWrapper(table)); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); this.addUpdateMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource); } /** * <p> * 注入批量更新 SQL 语句 * </p> * * @param mapperClass * @param modelClass * @param table */ protected void injectUpdateForSetSql(Class<?> mapperClass, Class<?> modelClass, TableInfo table) { SqlMethod sqlMethod = SqlMethod.UPDATE_FOR_SET; String sql = String.format(sqlMethod.getSql(), table.getTableName(), customSqlSet(), sqlWhereEntityWrapper(table)); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); this.addUpdateMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource); } /** * <p> * 注入查询 SQL 语句 * </p> * * @param batch 是否为批量插入 * @param mapperClass * @param modelClass * @param table */ protected void injectSelectByIdSql(boolean batch, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID; SqlSource sqlSource; if (batch) { sqlMethod = SqlMethod.SELECT_BATCH_BY_IDS; StringBuilder ids = new StringBuilder(); ids.append(" <foreach item="item" index="index" collection="coll" separator=",">"); ids.append("#{item}"); ids.append(" </foreach>"); sqlSource = languageDriver.createSqlSource(configuration, String.format(sqlMethod.getSql(), sqlSelectColumns(table, false), table.getTableName(), table.getKeyColumn(), ids.toString()), modelClass); } else { sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(), sqlSelectColumns(table, false), table.getTableName(), table.getKeyColumn(), table.getKeyProperty()), Object.class); } this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, modelClass, table); } /** * <p> * 注入 map 查询 SQL 语句 * </p> * * @param mapperClass * @param modelClass * @param table */ protected void injectSelectByMapSql(Class<?> mapperClass, Class<?> modelClass, TableInfo table) { SqlMethod sqlMethod = SqlMethod.SELECT_BY_MAP; String sql = String.format(sqlMethod.getSql(), sqlSelectColumns(table, false), table.getTableName(), sqlWhereByMap(table)); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Map.class); this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, modelClass, table); } /** * <p> * 注入实体查询一条记录 SQL 语句 * </p> * * @param mapperClass * @param modelClass * @param table */ protected void injectSelectOneSql(Class<?> mapperClass, Class<?> modelClass, TableInfo table) { SqlMethod sqlMethod = SqlMethod.SELECT_ONE; String sql = String.format(sqlMethod.getSql(), sqlSelectColumns(table, false), table.getTableName(), sqlWhere(table)); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, modelClass, table); } /** * <p> * 注入EntityWrapper方式查询记录列表 SQL 语句 * </p> * * @param sqlMethod * @param mapperClass * @param modelClass * @param table */ protected void injectSelectListSql(SqlMethod sqlMethod, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { String sql = String.format(sqlMethod.getSql(), sqlSelectColumns(table, true), table.getTableName(), sqlWhereEntityWrapper(table)); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, modelClass, table); } /** * <p> * 注入EntityWrapper方式查询记录列表 SQL 语句 * </p> * * @param sqlMethod * @param mapperClass * @param modelClass * @param table */ protected void injectSelectMapsSql(SqlMethod sqlMethod, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { String sql = String.format(sqlMethod.getSql(), sqlSelectColumns(table, true), table.getTableName(), sqlWhereEntityWrapper(table)); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, Map.class, table); } /** * <p> * 注入EntityWrapper方式查询记录列表 SQL 语句 * </p> * * @param sqlMethod * @param mapperClass * @param modelClass * @param table */ protected void injectSelectObjsSql(SqlMethod sqlMethod, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { String sql = String.format(sqlMethod.getSql(), sqlSelectObjsColumns(table), table.getTableName(), sqlWhereEntityWrapper(table)); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, Object.class, table); } /** * <p> * 注入EntityWrapper查询总记录数 SQL 语句 * </p> * * @param mapperClass * @param modelClass * @param table 表信息 */ protected void injectSelectCountSql(Class<?> mapperClass, Class<?> modelClass, TableInfo table) { SqlMethod sqlMethod = SqlMethod.SELECT_COUNT; String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlWhereEntityWrapper(table)); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, Integer.class, null); } /** * <p> * Sql 运算条件 * </p> */ protected String sqlCondition(String condition, String column, String property) { return String.format(condition, column, property); } /** * <p> * EntityWrapper方式获取select where * </p> * * @param table 表信息 * @return String */ protected String sqlWhereEntityWrapper(TableInfo table) { StringBuilder where = new StringBuilder(128); where.append(" <where>"); where.append(" <if test="ew!=null">"); where.append(" <if test="ew.entity!=null">"); if (StringUtils.isNotEmpty(table.getKeyProperty())) { where.append(" <if test="ew.entity.").append(table.getKeyProperty()).append("!=null"> "); where.append(table.getKeyColumn()).append("=#{ew.entity.").append(table.getKeyProperty()).append("}"); where.append(" </if>"); } List<TableFieldInfo> fieldList = table.getFieldList(); for (TableFieldInfo fieldInfo : fieldList) { where.append(convertIfTag(fieldInfo, "ew.entity.", false)); where.append(" AND ").append(this.sqlCondition(fieldInfo.getCondition(), fieldInfo.getColumn(), "ew.entity." + fieldInfo.getEl())); where.append(convertIfTag(fieldInfo, true)); } where.append(" </if>"); where.append(" <if test="ew!=null and ew.sqlSegment!=null and ew.notEmptyOfWhere"> ${ew.sqlSegment} </if>"); where.append(" </if>"); where.append(" </where>"); where.append(" <if test="ew!=null and ew.sqlSegment!=null and ew.emptyOfWhere"> ${ew.sqlSegment} </if>"); return where.toString(); } /** * <p> * SQL 更新 set 语句 * </p> * * @param selective 是否选择判断 * @param table 表信息 * @param prefix 前缀 * @return */ protected String sqlSet(boolean selective, TableInfo table, String prefix) { StringBuilder set = new StringBuilder(); set.append("<trim prefix="SET" suffixOverrides=",">"); // 是否 IF 标签判断 boolean ifTag; List<TableFieldInfo> fieldList = table.getFieldList(); for (TableFieldInfo fieldInfo : fieldList) { // 判断是否更新忽略,在FieldIgnore,UPDATE,INSERT_UPDATE设置为false ifTag = !(FieldFill.UPDATE == fieldInfo.getFieldFill() || FieldFill.INSERT_UPDATE == fieldInfo.getFieldFill()); if (selective && ifTag) { if (StringUtils.isNotEmpty(fieldInfo.getUpdate())) { set.append(fieldInfo.getColumn()).append("="); set.append(String.format(fieldInfo.getUpdate(), fieldInfo.getColumn())).append(","); } else { set.append(convertIfTag(true, fieldInfo, prefix, false)); set.append(fieldInfo.getColumn()).append("=#{"); if (null != prefix) { set.append(prefix); } set.append(fieldInfo.getEl()).append("},"); set.append(convertIfTag(true, fieldInfo, null, true)); } } else if (FieldFill.INSERT != fieldInfo.getFieldFill()) { // 排除填充注解字段 set.append(fieldInfo.getColumn()).append("=#{"); if (null != prefix) { set.append(prefix); } set.append(fieldInfo.getEl()).append("},"); } } set.append(" </trim>"); return set.toString(); } /** * <p> * SQL 自定义更新 set 语句 * </p> * * @return */ protected String customSqlSet() { StringBuilder set = new StringBuilder(); set.append("<trim prefix="SET" suffixOverrides=",">"); set.append(" ${setStr}"); set.append(" </trim>"); return set.toString(); } /** * <p> * 获取需要转义的SQL字段 * </p> * * @param convertStr * @return */ protected String sqlWordConvert(String convertStr) { GlobalConfiguration globalConfig = GlobalConfigUtils.getGlobalConfig(configuration); return SqlReservedWords.convert(globalConfig, convertStr); } /** * <p> * SQL 查询所有表字段 * </p> * * @param table * @param entityWrapper 是否为包装类型查询 * @return */ protected String sqlSelectColumns(TableInfo table, boolean entityWrapper) { StringBuilder columns = new StringBuilder(); if (null != table.getResultMap()) { /* * 存在 resultMap 映射返回 */ if (entityWrapper) { columns.append("<choose><when test="ew != null and ew.sqlSelect != null">${ew.sqlSelect}</when><otherwise>"); } columns.append("*"); if (entityWrapper) { columns.append("</otherwise></choose>"); } } else { /* * 普通查询 */ if (entityWrapper) { columns.append("<choose><when test="ew != null and ew.sqlSelect != null">${ew.sqlSelect}</when><otherwise>"); } List<TableFieldInfo> fieldList = table.getFieldList(); int size = 0; if (null != fieldList) { size = fieldList.size(); } // 主键处理 if (StringUtils.isNotEmpty(table.getKeyProperty())) { if (table.isKeyRelated()) { columns.append(table.getKeyColumn()).append(" AS ").append(sqlWordConvert(table.getKeyProperty())); } else { columns.append(sqlWordConvert(table.getKeyProperty())); } if (size >= 1) { // 判断其余字段是否存在 columns.append(","); } } if (size >= 1) { // 字段处理 int i = 0; Iterator<TableFieldInfo> iterator = fieldList.iterator(); while (iterator.hasNext()) { TableFieldInfo fieldInfo = iterator.next(); // 匹配转换内容 String wordConvert = sqlWordConvert(fieldInfo.getProperty()); if (fieldInfo.getColumn().equals(wordConvert)) { columns.append(wordConvert); } else { // 字段属性不一致 columns.append(fieldInfo.getColumn()); columns.append(" AS ").append(wordConvert); } if (i + 1 < size) { columns.append(","); } i++; } } if (entityWrapper) { columns.append("</otherwise></choose>"); } } /* * 返回所有查询字段内容 */ return columns.toString(); } /** * <p> * SQL 设置selectObj sqlselect * </p> * * @param table 是否为包装类型查询 * @return */ protected String sqlSelectObjsColumns(TableInfo table) { StringBuilder columns = new StringBuilder(); /* * 普通查询 */ columns.append("<choose><when test="ew != null and ew.sqlSelect != null">${ew.sqlSelect}</when><otherwise>"); // 主键处理 if (StringUtils.isNotEmpty(table.getKeyProperty())) { if (table.isKeyRelated()) { columns.append(table.getKeyColumn()).append(" AS ").append(sqlWordConvert(table.getKeyProperty())); } else { columns.append(sqlWordConvert(table.getKeyProperty())); } } else { // 表字段处理 List<TableFieldInfo> fieldList = table.getFieldList(); if (CollectionUtils.isNotEmpty(fieldList)) { TableFieldInfo fieldInfo = fieldList.get(0); // 匹配转换内容 String wordConvert = sqlWordConvert(fieldInfo.getProperty()); if (fieldInfo.getColumn().equals(wordConvert)) { columns.append(wordConvert); } else { // 字段属性不一致 columns.append(fieldInfo.getColumn()); columns.append(" AS ").append(wordConvert); } } } columns.append("</otherwise></choose>"); return columns.toString(); } /** * <p> * SQL 查询条件 * </p> */ protected String sqlWhere(TableInfo table) { StringBuilder where = new StringBuilder(); where.append(" <where>"); if (StringUtils.isNotEmpty(table.getKeyProperty())) { where.append(" <if test="ew.").append(table.getKeyProperty()).append("!=null"> "); where.append(table.getKeyColumn()).append("=#{ew.").append(table.getKeyProperty()).append("}"); where.append(" </if>"); } List<TableFieldInfo> fieldList = table.getFieldList(); for (TableFieldInfo fieldInfo : fieldList) { where.append(convertIfTag(fieldInfo, "ew.", false)); where.append(" AND ").append(this.sqlCondition(fieldInfo.getCondition(), fieldInfo.getColumn(), "ew." + fieldInfo.getEl())); where.append(convertIfTag(fieldInfo, true)); } where.append(" </where>"); return where.toString(); } /** * <p> * SQL map 查询条件 * </p> */ protected String sqlWhereByMap(TableInfo table) { StringBuilder where = new StringBuilder(); where.append(" <if test="cm!=null and !cm.isEmpty">"); where.append(" <where>"); where.append(" <foreach collection="cm.keys" item="k" separator="AND">"); where.append(" <if test="cm[k] != null">"); where.append(" ").append(SqlReservedWords.convert(getGlobalConfig(), "${k}")).append(" = #{cm[${k}]}"); where.append(" </if>"); where.append(" </foreach>"); where.append(" </where>"); where.append(" </if>"); return where.toString(); } /** * <p> * IF 条件转换方法 * </p> * * @param ignored 允许忽略 * @param fieldInfo 字段信息 * @param prefix 条件前缀 * @param close 是否闭合标签 * @return */ protected String convertIfTag(boolean ignored, TableFieldInfo fieldInfo, String prefix, boolean close) { /** 忽略策略 */ FieldStrategy fieldStrategy = fieldInfo.getFieldStrategy(); if (fieldStrategy == FieldStrategy.IGNORED) { if (ignored) { return ""; } // 查询策略,使用全局策略 fieldStrategy = this.getGlobalConfig().getFieldStrategy(); } // 关闭标签 if (close) { return "</if>"; } /** 前缀处理 */ String property = fieldInfo.getProperty(); Class propertyType = fieldInfo.getPropertyType(); property = StringUtils.removeIsPrefixIfBoolean(property, propertyType); if (null != prefix) { property = prefix + property; } // 验证逻辑 if (fieldStrategy == FieldStrategy.NOT_EMPTY) { if (StringUtils.isCharSequence(propertyType)) { return String.format(" <if test="%s!=null and %s!=''">", property, property); } else { return String.format(" <if test="%s!=null ">", property); } } else { // FieldStrategy.NOT_NULL return String.format(" <if test="%s!=null">", property); } } protected String convertIfTagIgnored(TableFieldInfo fieldInfo, boolean close) { return convertIfTag(true, fieldInfo, null, close); } protected String convertIfTag(TableFieldInfo fieldInfo, String prefix, boolean close) { return convertIfTag(false, fieldInfo, prefix, close); } protected String convertIfTag(TableFieldInfo fieldInfo, boolean close) { return convertIfTag(fieldInfo, null, close); } /** * 查询 */ public MappedStatement addSelectMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource, Class<?> resultType, TableInfo table) { if (null != table) { String resultMap = table.getResultMap(); if (null != resultMap) { /** 返回 resultMap 映射结果集 */ return this.addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.SELECT, null, resultMap, null, new NoKeyGenerator(), null, null); } } /** 普通查询 */ return this.addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.SELECT, null, null, resultType, new NoKeyGenerator(), null, null); } /** * 插入 */ public MappedStatement addInsertMappedStatement(Class<?> mapperClass, Class<?> modelClass, String id, SqlSource sqlSource, KeyGenerator keyGenerator, String keyProperty, String keyColumn) { return this.addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.INSERT, modelClass, null, Integer.class, keyGenerator, keyProperty, keyColumn); } /** * 删除 */ public MappedStatement addDeleteMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource) { return this.addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.DELETE, null, null, Integer.class, new NoKeyGenerator(), null, null); } /** * 更新 */ public MappedStatement addUpdateMappedStatement(Class<?> mapperClass, Class<?> modelClass, String id, SqlSource sqlSource) { return this.addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.UPDATE, modelClass, null, Integer.class, new NoKeyGenerator(), null, null); } public MappedStatement addMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource, SqlCommandType sqlCommandType, Class<?> parameterClass, String resultMap, Class<?> resultType, KeyGenerator keyGenerator, String keyProperty, String keyColumn) { String statementName = mapperClass.getName() + "." + id; if (hasMappedStatement(statementName)) { System.err.println("{" + statementName + "} Has been loaded by XML or SqlProvider, ignoring the injection of the SQL."); return null; } /** 缓存逻辑处理 */ boolean isSelect = false; if (sqlCommandType == SqlCommandType.SELECT) { isSelect = true; } return builderAssistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType, null, null, null, parameterClass, resultMap, resultType, null, !isSelect, isSelect, false, keyGenerator, keyProperty, keyColumn, configuration.getDatabaseId(), languageDriver, null); } // --------------------------------------------------------SqlRunner------------------------------------------------------------ @Override public void injectSqlRunner(Configuration configuration) { this.configuration = configuration; this.languageDriver = configuration.getDefaultScriptingLanguageInstance(); initSelectList(); initSelectObjs(); initInsert(); initUpdate(); initDelete(); initCount(); } /** * 是否已经存在MappedStatement * * @param mappedStatement * @return */ private boolean hasMappedStatement(String mappedStatement) { return configuration.hasStatement(mappedStatement, false); } /** * 创建查询MappedStatement * * @param mappedStatement * @param sqlSource 执行的sqlSource * @param resultType 返回的结果类型 */ @SuppressWarnings("serial") private void createSelectMappedStatement(String mappedStatement, SqlSource sqlSource, final Class<?> resultType) { MappedStatement ms = new MappedStatement.Builder(configuration, mappedStatement, sqlSource, SqlCommandType.SELECT) .resultMaps(new ArrayList<ResultMap>() { { add(new ResultMap.Builder(configuration, "defaultResultMap", resultType, new ArrayList<ResultMapping>(0)) .build()); } }).build(); // 缓存 configuration.addMappedStatement(ms); } /** * 创建一个MappedStatement * * @param mappedStatement * @param sqlSource 执行的sqlSource * @param sqlCommandType 执行的sqlCommandType */ @SuppressWarnings("serial") private void createUpdateMappedStatement(String mappedStatement, SqlSource sqlSource, SqlCommandType sqlCommandType) { MappedStatement ms = new MappedStatement.Builder(configuration, mappedStatement, sqlSource, sqlCommandType).resultMaps( new ArrayList<ResultMap>() { { add(new ResultMap.Builder(configuration, "defaultResultMap", int.class, new ArrayList<ResultMapping>(0)) .build()); } }).build(); // 缓存 configuration.addMappedStatement(ms); } /** * initSelectList */ private void initSelectList() { if (hasMappedStatement(SqlRunner.SELECT_LIST)) { logger.warn("MappedStatement 'SqlRunner.SelectList' Already Exists"); return; } SqlSource sqlSource = languageDriver.createSqlSource(configuration, SqlRunner.SQLScript, Map.class); createSelectMappedStatement(SqlRunner.SELECT_LIST, sqlSource, Map.class); } /** * initSelectObjs */ private void initSelectObjs() { if (hasMappedStatement(SqlRunner.SELECT_OBJS)) { logger.warn("MappedStatement 'SqlRunner.SelectObjs' Already Exists"); return; } SqlSource sqlSource = languageDriver.createSqlSource(configuration, SqlRunner.SQLScript, Object.class); createSelectMappedStatement(SqlRunner.SELECT_OBJS, sqlSource, Object.class); } /** * initCount */ private void initCount() { if (hasMappedStatement(SqlRunner.COUNT)) { logger.warn("MappedStatement 'SqlRunner.Count' Already Exists"); return; } SqlSource sqlSource = languageDriver.createSqlSource(configuration, SqlRunner.SQLScript, Map.class); createSelectMappedStatement(SqlRunner.COUNT, sqlSource, Integer.class); } /** * initInsert */ private void initInsert() { if (hasMappedStatement(SqlRunner.INSERT)) { logger.warn("MappedStatement 'SqlRunner.Insert' Already Exists"); return; } SqlSource sqlSource = languageDriver.createSqlSource(configuration, SqlRunner.SQLScript, Map.class); createUpdateMappedStatement(SqlRunner.INSERT, sqlSource, SqlCommandType.INSERT); } /** * initUpdate */ private void initUpdate() { if (hasMappedStatement(SqlRunner.UPDATE)) { logger.warn("MappedStatement 'SqlRunner.Update' Already Exists"); return; } SqlSource sqlSource = languageDriver.createSqlSource(configuration, SqlRunner.SQLScript, Map.class); createUpdateMappedStatement(SqlRunner.UPDATE, sqlSource, SqlCommandType.UPDATE); } /** * initDelete */ private void initDelete() { if (hasMappedStatement(SqlRunner.DELETE)) { logger.warn("MappedStatement 'SqlRunner.Delete' Already Exists"); return; } SqlSource sqlSource = languageDriver.createSqlSource(configuration, SqlRunner.SQLScript, Map.class); createUpdateMappedStatement(SqlRunner.DELETE, sqlSource, SqlCommandType.DELETE); } /** * <p> * 全局配置 * </p> */ protected GlobalConfiguration getGlobalConfig() { return GlobalConfigUtils.getGlobalConfig(configuration); } }
可以看到MybatisSqlSessionFactoryBean #afterPropertiesSet 方法(Bean 生命周期的InitializingBean 初始化阶段) 调用buildSqlSessionFactory 方法开始执行Mybatis的初始化启动过程。最终经过下面调用: 可以看到走的调用都是MybatisPlus 重写之后的方法
1》 com.baomidou.mybatisplus.MybatisMapperAnnotationBuilder#parse 如下:
public void parse() { String resource = this.type.toString(); if (!this.configuration.isResourceLoaded(resource)) { this.loadXmlResource(); this.configuration.addLoadedResource(resource); this.assistant.setCurrentNamespace(this.type.getName()); this.parseCache(); this.parseCacheRef(); Method[] methods = this.type.getMethods(); if (BaseMapper.class.isAssignableFrom(this.type)) { GlobalConfigUtils.getSqlInjector(this.configuration).inspectInject(this.assistant, this.type); } Method[] arr$ = methods; int len$ = methods.length; for(int i$ = 0; i$ < len$; ++i$) { Method method = arr$[i$]; try { if (!method.isBridge()) { this.parseStatement(method); } } catch (IncompleteElementException var8) { this.configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } this.parsePendingMethods(); }
com.baomidou.mybatisplus.toolkit.GlobalConfigUtils#getSqlInjector 获取SQL注入器
public static ISqlInjector getSqlInjector(Configuration configuration) { GlobalConfiguration globalConfiguration = getGlobalConfig(configuration); ISqlInjector sqlInjector = globalConfiguration.getSqlInjector(); if (sqlInjector == null) { sqlInjector = new AutoSqlInjector(); globalConfiguration.setSqlInjector((ISqlInjector)sqlInjector); } return (ISqlInjector)sqlInjector; }
可以看到是拿设置在GlobalConfiguration 中的SQL注入器,如果没拿到会拿默认的AutoSqlInjector 并设置到GlobalConfiguration 对象属性中。
2》com.baomidou.mybatisplus.mapper.AutoSqlInjector#inject(org.apache.ibatis.builder.MapperBuilderAssistant, java.lang.Class<?>) 如下:
/** * 注入单点 crudSql */ @Override public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) { this.configuration = builderAssistant.getConfiguration(); this.builderAssistant = builderAssistant; this.languageDriver = configuration.getDefaultScriptingLanguageInstance(); //去除 驼峰设置 PLUS 配置 > 原生配置 (该配置不需要与原生Mybatis混淆) /*if (!globalCache.isDbColumnUnderline()) { globalCache.setDbColumnUnderline(configuration.isMapUnderscoreToCamelCase()); }*/ Class<?> modelClass = extractModelClass(mapperClass); if (null != modelClass) { /** * 初始化 SQL 解析 */ if (this.getGlobalConfig().isSqlParserCache()) { PluginUtils.initSqlParserInfoCache(mapperClass); } TableInfo table = TableInfoHelper.initTableInfo(builderAssistant, modelClass); injectSql(builderAssistant, mapperClass, modelClass, table); } } /** * 提取泛型模型,多泛型的时候请将泛型T放在第一位 * * @param mapperClass * @return */ protected Class<?> extractModelClass(Class<?> mapperClass) { Type[] types = mapperClass.getGenericInterfaces(); ParameterizedType target = null; for (Type type : types) { if (type instanceof ParameterizedType) { Type[] typeArray = ((ParameterizedType) type).getActualTypeArguments(); if (ArrayUtils.isNotEmpty(typeArray)) { for (Type t : typeArray) { if (t instanceof TypeVariable || t instanceof WildcardType) { target = null; break; } else { target = (ParameterizedType) type; break; } } } break; } } return target == null ? null : (Class<?>) target.getActualTypeArguments()[0]; }
可以看到是先获取到接口上的泛型的实际类型,也就是我们的Bean 的类型,然后根据Bean 的类型调用 com.baomidou.mybatisplus.toolkit.TableInfoHelper#initTableInfo 构造成Table 信息,com.baomidou.mybatisplus.toolkit.TableInfoHelper 源码如下:(extractModelClass 也可以作为一个工具类作为抽取泛型实际类型)
/** package com.baomidou.mybatisplus.toolkit; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.ibatis.builder.MapperBuilderAssistant; import org.apache.ibatis.executor.keygen.KeyGenerator; import org.apache.ibatis.executor.keygen.NoKeyGenerator; import org.apache.ibatis.executor.keygen.SelectKeyGenerator; import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.LogFactory; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.mapping.SqlSource; import org.apache.ibatis.mapping.StatementType; import org.apache.ibatis.scripting.LanguageDriver; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.SqlSessionFactory; import com.baomidou.mybatisplus.annotations.KeySequence; import com.baomidou.mybatisplus.annotations.TableField; import com.baomidou.mybatisplus.annotations.TableId; import com.baomidou.mybatisplus.annotations.TableName; import com.baomidou.mybatisplus.entity.GlobalConfiguration; import com.baomidou.mybatisplus.entity.TableFieldInfo; import com.baomidou.mybatisplus.entity.TableInfo; import com.baomidou.mybatisplus.enums.IdType; import com.baomidou.mybatisplus.exceptions.MybatisPlusException; import com.baomidou.mybatisplus.incrementer.IKeyGenerator; import com.baomidou.mybatisplus.mapper.SqlRunner; /** * <p> * 实体类反射表辅助类 * </p> * * @author hubin sjy * @Date 2016-09-09 */ public class TableInfoHelper { private static final Log logger = LogFactory.getLog(TableInfoHelper.class); /** * 缓存反射类表信息 */ private static final Map<String, TableInfo> tableInfoCache = new ConcurrentHashMap<>(); /** * 默认表主键 */ private static final String DEFAULT_ID_NAME = "id"; /** * <p> * 获取实体映射表信息 * <p> * * @param clazz 反射实体类 * @return */ public static TableInfo getTableInfo(Class<?> clazz) { return tableInfoCache.get(ClassUtils.getUserClass(clazz).getName()); } /** * <p> * 获取所有实体映射表信息 * <p> * * @return */ public static List<TableInfo> getTableInfos() { return new ArrayList<>(tableInfoCache.values()); } /** * <p> * 实体类反射获取表信息【初始化】 * <p> * * @param clazz 反射实体类 * @return */ public synchronized static TableInfo initTableInfo(MapperBuilderAssistant builderAssistant, Class<?> clazz) { TableInfo tableInfo = tableInfoCache.get(clazz.getName()); if (StringUtils.checkValNotNull(tableInfo)) { if (StringUtils.checkValNotNull(builderAssistant)) { tableInfo.setConfigMark(builderAssistant.getConfiguration()); } return tableInfo; } tableInfo = new TableInfo(); GlobalConfiguration globalConfig; if (null != builderAssistant) { tableInfo.setCurrentNamespace(builderAssistant.getCurrentNamespace()); tableInfo.setConfigMark(builderAssistant.getConfiguration()); globalConfig = GlobalConfigUtils.getGlobalConfig(builderAssistant.getConfiguration()); } else { // 兼容测试场景 globalConfig = GlobalConfigUtils.DEFAULT; } /* 表名 */ TableName table = clazz.getAnnotation(TableName.class); String tableName = clazz.getSimpleName(); if (table != null && StringUtils.isNotEmpty(table.value())) { tableName = table.value(); } else { // 开启字段下划线申明 if (globalConfig.isDbColumnUnderline()) { tableName = StringUtils.camelToUnderline(tableName); } // 大写命名判断 if (globalConfig.isCapitalMode()) { tableName = tableName.toUpperCase(); } else { // 首字母小写 tableName = StringUtils.firstToLowerCase(tableName); } // 存在表前缀 if (null != globalConfig.getTablePrefix()) { tableName = globalConfig.getTablePrefix() + tableName; } } tableInfo.setTableName(tableName); // 开启了自定义 KEY 生成器 if (null != globalConfig.getKeyGenerator()) { tableInfo.setKeySequence(clazz.getAnnotation(KeySequence.class)); } /* 表结果集映射 */ if (table != null && StringUtils.isNotEmpty(table.resultMap())) { tableInfo.setResultMap(table.resultMap()); } List<TableFieldInfo> fieldList = new ArrayList<>(); List<Field> list = getAllFields(clazz); // 标记是否读取到主键 boolean isReadPK = false; boolean existTableId = existTableId(list); for (Field field : list) { /* * 主键ID 初始化 */ if (!isReadPK) { if (existTableId) { isReadPK = initTableId(globalConfig, tableInfo, field, clazz); } else { isReadPK = initFieldId(globalConfig, tableInfo, field, clazz); } if (isReadPK) { continue; } } /* * 字段初始化 */ if (initTableField(globalConfig, tableInfo, fieldList, field, clazz)) { continue; } /* * 字段, 使用 camelToUnderline 转换驼峰写法为下划线分割法, 如果已指定 TableField , 便不会执行这里 */ fieldList.add(new TableFieldInfo(globalConfig, tableInfo, field)); } /* 字段列表 */ tableInfo.setFieldList(globalConfig, fieldList); /* * 未发现主键注解,提示警告信息 */ if (StringUtils.isEmpty(tableInfo.getKeyColumn())) { logger.warn(String.format("Warn: Could not find @TableId in Class: %s.", clazz.getName())); } /* * 注入 */ tableInfoCache.put(clazz.getName(), tableInfo); return tableInfo; } /** * <p> * 判断主键注解是否存在 * </p> * * @param list 字段列表 * @return */ public static boolean existTableId(List<Field> list) { boolean exist = false; for (Field field : list) { TableId tableId = field.getAnnotation(TableId.class); if (tableId != null) { exist = true; break; } } return exist; } /** * <p> * 主键属性初始化 * </p> * * @param tableInfo * @param field * @param clazz * @return true 继续下一个属性判断,返回 continue; */ private static boolean initTableId(GlobalConfiguration globalConfig, TableInfo tableInfo, Field field, Class<?> clazz) { TableId tableId = field.getAnnotation(TableId.class); if (tableId != null) { if (StringUtils.isEmpty(tableInfo.getKeyColumn())) { /* * 主键策略( 注解 > 全局 > 默认 ) */ // 设置 Sequence 其他策略无效 if (IdType.NONE != tableId.type()) { tableInfo.setIdType(tableId.type()); } else { tableInfo.setIdType(globalConfig.getIdType()); } /* 字段 */ String column = field.getName(); if (StringUtils.isNotEmpty(tableId.value())) { column = tableId.value(); tableInfo.setKeyRelated(true); } else { // 开启字段下划线申明 if (globalConfig.isDbColumnUnderline()) { column = StringUtils.camelToUnderline(column); tableInfo.setKeyRelated(true); } // 全局大写命名 if (globalConfig.isCapitalMode()) { column = column.toUpperCase(); } } tableInfo.setKeyColumn(column); tableInfo.setKeyProperty(field.getName()); return true; } else { throwExceptionId(clazz); } } return false; } /** * <p> * 主键属性初始化 * </p> * * @param tableInfo * @param field * @param clazz * @return true 继续下一个属性判断,返回 continue; */ private static boolean initFieldId(GlobalConfiguration globalConfig, TableInfo tableInfo, Field field, Class<?> clazz) { String column = field.getName(); if (globalConfig.isCapitalMode()) { column = column.toUpperCase(); } if (DEFAULT_ID_NAME.equalsIgnoreCase(column)) { if (StringUtils.isEmpty(tableInfo.getKeyColumn())) { tableInfo.setIdType(globalConfig.getIdType()); tableInfo.setKeyColumn(column); tableInfo.setKeyProperty(field.getName()); return true; } else { throwExceptionId(clazz); } } return false; } /** * <p> * 发现设置多个主键注解抛出异常 * </p> */ private static void throwExceptionId(Class<?> clazz) { StringBuilder errorMsg = new StringBuilder(); errorMsg.append("There must be only one, Discover multiple @TableId annotation in "); errorMsg.append(clazz.getName()); throw new MybatisPlusException(errorMsg.toString()); } /** * <p> * 字段属性初始化 * </p> * * @param globalConfig 全局配置 * @param tableInfo 表信息 * @param fieldList 字段列表 * @param clazz 当前表对象类 * @return true 继续下一个属性判断,返回 continue; */ private static boolean initTableField(GlobalConfiguration globalConfig, TableInfo tableInfo, List<TableFieldInfo> fieldList, Field field, Class<?> clazz) { /* 获取注解属性,自定义字段 */ TableField tableField = field.getAnnotation(TableField.class); if (tableField != null) { String columnName = field.getName(); if (StringUtils.isNotEmpty(tableField.value())) { columnName = tableField.value(); } /* * el 语法支持,可以传入多个参数以逗号分开 */ String el = field.getName(); if (StringUtils.isNotEmpty(tableField.el())) { el = tableField.el(); } String[] columns = columnName.split(";"); String[] els = el.split(";"); if (columns.length == els.length) { for (int i = 0; i < columns.length; i++) { fieldList.add(new TableFieldInfo(globalConfig, tableInfo, columns[i], els[i], field, tableField)); } } else { String errorMsg = "Class: %s, Field: %s, 'value' 'el' Length must be consistent."; throw new MybatisPlusException(String.format(errorMsg, clazz.getName(), field.getName())); } return true; } return false; } /** * 获取该类的所有属性列表 * * @param clazz 反射类 * @return */ public static List<Field> getAllFields(Class<?> clazz) { List<Field> fieldList = ReflectionKit.getFieldList(ClassUtils.getUserClass(clazz)); if (CollectionUtils.isNotEmpty(fieldList)) { Iterator<Field> iterator = fieldList.iterator(); while (iterator.hasNext()) { Field field = iterator.next(); /* 过滤注解非表字段属性 */ TableField tableField = field.getAnnotation(TableField.class); if (tableField != null && !tableField.exist()) { iterator.remove(); } } } return fieldList; } /** * 初始化SqlSessionFactory (供Mybatis原生调用) * * @param sqlSessionFactory * @return */ public static void initSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { Configuration configuration = sqlSessionFactory.getConfiguration(); GlobalConfiguration globalConfig = GlobalConfigUtils.getGlobalConfig(configuration); // SqlRunner SqlRunner.FACTORY = sqlSessionFactory; if (globalConfig == null) { GlobalConfiguration defaultCache = GlobalConfigUtils.defaults(); defaultCache.setSqlSessionFactory(sqlSessionFactory); GlobalConfigUtils.setGlobalConfig(configuration, defaultCache); } else { globalConfig.setSqlSessionFactory(sqlSessionFactory); } } /** * <p> * 自定义 KEY 生成器 * </p> */ public static KeyGenerator genKeyGenerator(TableInfo tableInfo, MapperBuilderAssistant builderAssistant, String baseStatementId, LanguageDriver languageDriver) { IKeyGenerator keyGenerator = GlobalConfigUtils.getKeyGenerator(builderAssistant.getConfiguration()); if (null == keyGenerator) { throw new IllegalArgumentException("not configure IKeyGenerator implementation class."); } String id = baseStatementId + SelectKeyGenerator.SELECT_KEY_SUFFIX; Class<?> resultTypeClass = tableInfo.getKeySequence().clazz(); StatementType statementType = StatementType.PREPARED; String keyProperty = tableInfo.getKeyProperty(); String keyColumn = tableInfo.getKeyColumn(); SqlSource sqlSource = languageDriver.createSqlSource(builderAssistant.getConfiguration(), keyGenerator.executeSql(tableInfo.getKeySequence().value()), null); builderAssistant.addMappedStatement(id, sqlSource, statementType, SqlCommandType.SELECT, null, null, null, null, null, resultTypeClass, null, false, false, false, new NoKeyGenerator(), keyProperty, keyColumn, null, languageDriver, null); id = builderAssistant.applyCurrentNamespace(id, false); MappedStatement keyStatement = builderAssistant.getConfiguration().getMappedStatement(id, false); SelectKeyGenerator selectKeyGenerator = new SelectKeyGenerator(keyStatement, true); builderAssistant.getConfiguration().addKeyGenerator(id, selectKeyGenerator); return selectKeyGenerator; } }
最终获取到的TableInfo 如下:(相当于一个抽取注解信息记录类)
3》 接着调用com.baomidou.mybatisplus.mapper.AutoSqlInjector#injectSql 开始注入SQL
/** * <p> * 注入SQL * </p> * * @param builderAssistant * @param mapperClass * @param modelClass * @param table */ protected void injectSql(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { /** * #148 表信息包含主键,注入主键相关方法 */ if (StringUtils.isNotEmpty(table.getKeyProperty())) { /** 删除 */ this.injectDeleteByIdSql(false, mapperClass, modelClass, table); this.injectDeleteByIdSql(true, mapperClass, modelClass, table); /** 修改 */ this.injectUpdateByIdSql(true, mapperClass, modelClass, table); this.injectUpdateByIdSql(false, mapperClass, modelClass, table); /** 查询 */ this.injectSelectByIdSql(false, mapperClass, modelClass, table); this.injectSelectByIdSql(true, mapperClass, modelClass, table); } else { // 表不包含主键时 给予警告 logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.", modelClass.toString())); } /** * 正常注入无需主键方法 */ /** 插入 */ this.injectInsertOneSql(true, mapperClass, modelClass, table); this.injectInsertOneSql(false, mapperClass, modelClass, table); /** 删除 */ this.injectDeleteSql(mapperClass, modelClass, table); this.injectDeleteByMapSql(mapperClass, table); /** 修改 */ this.injectUpdateSql(mapperClass, modelClass, table); /** 修改 (自定义 set 属性) */ this.injectUpdateForSetSql(mapperClass, modelClass, table); /** 查询 */ this.injectSelectByMapSql(mapperClass, modelClass, table); this.injectSelectOneSql(mapperClass, modelClass, table); this.injectSelectCountSql(mapperClass, modelClass, table); this.injectSelectListSql(SqlMethod.SELECT_LIST, mapperClass, modelClass, table); this.injectSelectListSql(SqlMethod.SELECT_PAGE, mapperClass, modelClass, table); this.injectSelectMapsSql(SqlMethod.SELECT_MAPS, mapperClass, modelClass, table); this.injectSelectMapsSql(SqlMethod.SELECT_MAPS_PAGE, mapperClass, modelClass, table); this.injectSelectObjsSql(SqlMethod.SELECT_OBJS, mapperClass, modelClass, table); /** 自定义方法 */ this.inject(configuration, builderAssistant, mapperClass, modelClass, table); } /** * 自定义方法,注入点(子类需重写该方法) */ public void inject(Configuration configuration, MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { // to do nothing }
- 首先注入根据ID增删改查相关的方法
- 然后注入一些非注解的方法
- 调用com.baomidou.mybatisplus.mapper.AutoSqlInjector#inject(org.apache.ibatis.session.Configuration, org.apache.ibatis.builder.MapperBuilderAssistant, java.lang.Class<?>, java.lang.Class<?>, com.baomidou.mybatisplus.entity.TableInfo) 交由子类覆盖自定义注入的方法。
4》以注入根据ID删除为例子进行分析,调用了两次,传入的参数只有第一个有区别(分别代表批量和单个),查看com.baomidou.mybatisplus.mapper.AutoSqlInjector#injectDeleteByIdSql:
protected void injectDeleteByIdSql(boolean batch, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { SqlMethod sqlMethod = SqlMethod.DELETE_BY_ID; SqlSource sqlSource; // 因为后面要通过get方法获取类型,所以这里要获取key的属性值 String idStr = table.getKeyProperty(); if (batch) { sqlMethod = SqlMethod.DELETE_BATCH_BY_IDS; StringBuilder ids = new StringBuilder(); ids.append(" <foreach item="item" index="index" collection="coll" separator=",">"); ids.append("#{item}"); ids.append(" </foreach>"); idStr = ids.toString(); } String sql = String.format(sqlMethod.getSql(), table.getTableName(), table.getKeyColumn(), idStr); sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); this.addDeleteMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource); } public MappedStatement addDeleteMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource) { return this.addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.DELETE, null, null, Integer.class, new NoKeyGenerator(), null, null); } public MappedStatement addMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource, SqlCommandType sqlCommandType, Class<?> parameterClass, String resultMap, Class<?> resultType, KeyGenerator keyGenerator, String keyProperty, String keyColumn) { String statementName = mapperClass.getName() + "." + id; if (hasMappedStatement(statementName)) { System.err.println("{" + statementName + "} Has been loaded by XML or SqlProvider, ignoring the injection of the SQL."); return null; } /** 缓存逻辑处理 */ boolean isSelect = false; if (sqlCommandType == SqlCommandType.SELECT) { isSelect = true; } return builderAssistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType, null, null, null, parameterClass, resultMap, resultType, null, !isSelect, isSelect, false, keyGenerator, keyProperty, keyColumn, configuration.getDatabaseId(), languageDriver, null); }
可以看到先选择对应的SqlMethod 模板,然后根据是否是批量生成不同的id 模板,然后格式化为最后的sql , 最后根据namespace 和 方法名称作为MappedStatement 的ID调用builderAssistant.addMappedStatement 添加到Configuration 对象内部缓存起来。
例如一个delete语句最后生成的两个sql如下:
单个:
<script>DELETE FROM department WHERE id=#{id}</script>
批量:
<script>DELETE FROM department WHERE id IN ( <foreach item="item" index="index" collection="coll" separator=",">#{item} </foreach>)</script>
com.baomidou.mybatisplus.enums.SqlMethod源码如下:里面包含所有的方法名称以及描述
package com.baomidou.mybatisplus.enums; /** * <p> * MybatisPlus 支持 SQL 方法 * </p> * * @author hubin * @Date 2016-01-23 */ public enum SqlMethod { /** * 插入 */ INSERT_ONE("insert", "插入一条数据(选择字段插入)", "<script>INSERT INTO %s %s VALUES %s</script>"), INSERT_ONE_ALL_COLUMN("insertAllColumn", "插入一条数据(全部字段插入)", "<script>INSERT INTO %s %s VALUES %s</script>"), /** * 删除 */ DELETE_BY_ID("deleteById", "根据ID 删除一条数据", "<script>DELETE FROM %s WHERE %s=#{%s}</script>"), DELETE_BY_MAP("deleteByMap", "根据columnMap 条件删除记录", "<script>DELETE FROM %s %s</script>"), DELETE("delete", "根据 entity 条件删除记录", "<script>DELETE FROM %s %s</script>"), DELETE_BATCH_BY_IDS("deleteBatchIds", "根据ID集合,批量删除数据", "<script>DELETE FROM %s WHERE %s IN (%s)</script>"), /** * 逻辑删除 */ LOGIC_DELETE_BY_ID("deleteById", "根据ID 逻辑删除一条数据", "<script>UPDATE %s %s WHERE %s=#{%s}</script>"), LOGIC_DELETE_BY_MAP("deleteByMap", "根据columnMap 条件逻辑删除记录", "<script>UPDATE %s %s %s</script>"), LOGIC_DELETE("delete", "根据 entity 条件逻辑删除记录", "<script>UPDATE %s %s %s</script>"), LOGIC_DELETE_BATCH_BY_IDS("deleteBatchIds", "根据ID集合,批量逻辑删除数据", "<script>UPDATE %s %s WHERE %s IN (%s)</script>"), /** * 修改 */ UPDATE_BY_ID("updateById", "根据ID 选择修改数据", "<script>UPDATE %s %s WHERE %s=#{%s} %s</script>"), UPDATE_ALL_COLUMN_BY_ID("updateAllColumnById", "根据ID 修改全部数据", "<script>UPDATE %s %s WHERE %s=#{%s} %s</script>"), UPDATE("update", "根据 whereEntity 条件,更新记录", "<script>UPDATE %s %s %s</script>"), UPDATE_FOR_SET("updateForSet", "根据 whereEntity 条件,自定义Set值更新记录", "<script>UPDATE %s %s %s</script>"), /** * 逻辑删除 -> 修改 */ LOGIC_UPDATE_BY_ID("updateById", "根据ID 修改数据", "<script>UPDATE %s %s WHERE %s=#{%s} %s</script>"), LOGIC_UPDATE_ALL_COLUMN_BY_ID("updateAllColumnById", "根据ID 选择修改数据", "<script>UPDATE %s %s WHERE %s=#{%s} %s</script>"), /** * 查询 */ SELECT_BY_ID("selectById", "根据ID 查询一条数据", "SELECT %s FROM %s WHERE %s=#{%s}"), SELECT_BY_MAP("selectByMap", "根据columnMap 查询一条数据", "<script>SELECT %s FROM %s %s</script>"), SELECT_BATCH_BY_IDS("selectBatchIds", "根据ID集合,批量查询数据", "<script>SELECT %s FROM %s WHERE %s IN (%s)</script>"), SELECT_ONE("selectOne", "查询满足条件一条数据", "<script>SELECT %s FROM %s %s</script>"), SELECT_COUNT("selectCount", "查询满足条件总记录数", "<script>SELECT COUNT(1) FROM %s %s</script>"), SELECT_LIST("selectList", "查询满足条件所有数据", "<script>SELECT %s FROM %s %s</script>"), SELECT_PAGE("selectPage", "查询满足条件所有数据(并翻页)", "<script>SELECT %s FROM %s %s</script>"), SELECT_MAPS("selectMaps", "查询满足条件所有数据", "<script>SELECT %s FROM %s %s</script>"), SELECT_MAPS_PAGE("selectMapsPage", "查询满足条件所有数据(并翻页)", "<script>SELECT %s FROM %s %s</script>"), SELECT_OBJS("selectObjs", "查询满足条件所有数据", "<script>SELECT %s FROM %s %s</script>"), /** * 逻辑删除 -> 查询 */ LOGIC_SELECT_BY_ID("selectById", "根据ID 查询一条数据", "SELECT %s FROM %s WHERE %s=#{%s} %s"), LOGIC_SELECT_BATCH_BY_IDS("selectBatchIds", "根据ID集合,批量查询数据", "<script>SELECT %s FROM %s WHERE %s IN (%s) %s</script>"); private final String method; private final String desc; private final String sql; SqlMethod(final String method, final String desc, final String sql) { this.method = method; this.desc = desc; this.sql = sql; } public String getMethod() { return this.method; } public String getDesc() { return this.desc; } public String getSql() { return this.sql; } }
5》 可以看到上面3》 结束之后会生成一堆自定义的sql模板,并且生成MappedStatement 对象存到Configuration 对象中。
其对应的就是com.baomidou.mybatisplus.mapper.BaseMapper 接口里面的一些方法, 所以我们自己的Mapper 需要继承该接口,这样调用的时候会根据类全路径加方法名称生成MappedStatement的id,这样来找到上面3》 中生成的MappedStatement 对象。
com.baomidou.mybatisplus.mapper.BaseMapper 接口如下: 下面接口对应的SQL会通过上面步骤进行自动生成。
package com.baomidou.mybatisplus.mapper; import java.io.Serializable; import java.util.Collection; import java.util.List; import java.util.Map; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.session.RowBounds; /** * <p> * Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能 * </p> * <p> * 这个 Mapper 支持 id 泛型 * </p> * * @author hubin * @Date 2016-01-23 */ public interface BaseMapper<T> { /** * <p> * 插入一条记录 * </p> * * @param entity 实体对象 * @return int */ Integer insert(T entity); /** * <p> * 插入一条记录 * </p> * * @param entity 实体对象 * @return int */ Integer insertAllColumn(T entity); /** * <p> * 根据 ID 删除 * </p> * * @param id 主键ID * @return int */ Integer deleteById(Serializable id); /** * <p> * 根据 columnMap 条件,删除记录 * </p> * * @param columnMap 表字段 map 对象 * @return int */ Integer deleteByMap(@Param("cm") Map<String, Object> columnMap); /** * <p> * 根据 entity 条件,删除记录 * </p> * * @param wrapper 实体对象封装操作类(可以为 null) * @return int */ Integer delete(@Param("ew") Wrapper<T> wrapper); /** * <p> * 删除(根据ID 批量删除) * </p> * * @param idList 主键ID列表 * @return int */ Integer deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList); /** * <p> * 根据 ID 修改 * </p> * * @param entity 实体对象 * @return int */ Integer updateById(@Param("et") T entity); /** * <p> * 根据 ID 修改 * </p> * * @param entity 实体对象 * @return int */ Integer updateAllColumnById(@Param("et") T entity); /** * <p> * 根据 whereEntity 条件,更新记录 * </p> * * @param entity 实体对象 * @param wrapper 实体对象封装操作类(可以为 null) * @return */ Integer update(@Param("et") T entity, @Param("ew") Wrapper<T> wrapper); /** * <p> * 根据 whereEntity 条件,更新记录 * </p> * * @param setStr set字符串 * @param wrapper 实体对象封装操作类(可以为 null) * @return */ Integer updateForSet(@Param("setStr") String setStr, @Param("ew") Wrapper<T> wrapper); /** * <p> * 根据 ID 查询 * </p> * * @param id 主键ID * @return T */ T selectById(Serializable id); /** * <p> * 查询(根据ID 批量查询) * </p> * * @param idList 主键ID列表 * @return List<T> */ List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList); /** * <p> * 查询(根据 columnMap 条件) * </p> * * @param columnMap 表字段 map 对象 * @return List<T> */ List<T> selectByMap(@Param("cm") Map<String, Object> columnMap); /** * <p> * 根据 entity 条件,查询一条记录 * </p> * * @param entity 实体对象 * @return T */ T selectOne(@Param("ew") T entity); /** * <p> * 根据 Wrapper 条件,查询总记录数 * </p> * * @param wrapper 实体对象 * @return int */ Integer selectCount(@Param("ew") Wrapper<T> wrapper); /** * <p> * 根据 entity 条件,查询全部记录 * </p> * * @param wrapper 实体对象封装操作类(可以为 null) * @return List<T> */ List<T> selectList(@Param("ew") Wrapper<T> wrapper); /** * <p> * 根据 Wrapper 条件,查询全部记录 * </p> * * @param wrapper 实体对象封装操作类(可以为 null) * @return List<T> */ List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> wrapper); /** * <p> * 根据 Wrapper 条件,查询全部记录 * 注意: 只返回第一个字段的值 * </p> * * @param wrapper 实体对象封装操作类(可以为 null) * @return List<Object> */ List<Object> selectObjs(@Param("ew") Wrapper<T> wrapper); /** * <p> * 根据 entity 条件,查询全部记录(并翻页) * </p> * * @param rowBounds 分页查询条件(可以为 RowBounds.DEFAULT) * @param wrapper 实体对象封装操作类(可以为 null) * @return List<T> */ List<T> selectPage(RowBounds rowBounds, @Param("ew") Wrapper<T> wrapper); /** * <p> * 根据 Wrapper 条件,查询全部记录(并翻页) * </p> * * @param rowBounds 分页查询条件(可以为 RowBounds.DEFAULT) * @param wrapper 实体对象封装操作类 * @return List<Map<String, Object>> */ List<Map<String, Object>> selectMapsPage(RowBounds rowBounds, @Param("ew") Wrapper<T> wrapper); }
补充:上面研究了其主要过程,下面查看其逻辑删除的主要过程。
在上面的过程中,发现其没有对逻辑删除做处理。查看SQL注入器,是其新加了一个LogicSqlInjector, 源码如下:
/** * Copyright (c) 2011-2014, hubin (jobob@qq.com). * <p> * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.baomidou.mybatisplus.mapper; import java.util.List; import java.util.Map; import org.apache.ibatis.mapping.SqlSource; import org.apache.ibatis.scripting.defaults.RawSqlSource; import com.baomidou.mybatisplus.entity.TableFieldInfo; import com.baomidou.mybatisplus.entity.TableInfo; import com.baomidou.mybatisplus.enums.SqlMethod; import com.baomidou.mybatisplus.toolkit.SqlReservedWords; import com.baomidou.mybatisplus.toolkit.StringUtils; /** * <p> * SQL 自动注入逻辑处理器<br> * 1、支持逻辑删除 * </p> * * @author hubin willenfoo * @Date 2017-09-09 */ public class LogicSqlInjector extends AutoSqlInjector { /** * 根据 ID 删除 */ @Override protected void injectDeleteByIdSql(boolean batch, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { if (table.isLogicDelete()) { // 逻辑删除注入 SqlMethod sqlMethod = SqlMethod.LOGIC_DELETE_BY_ID; SqlSource sqlSource; String idStr = table.getKeyProperty(); if (batch) { sqlMethod = SqlMethod.LOGIC_DELETE_BATCH_BY_IDS; StringBuilder ids = new StringBuilder(); ids.append(" <foreach item="item" index="index" collection="coll" separator=",">"); ids.append("#{item}"); ids.append(" </foreach>"); idStr = ids.toString(); } String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlLogicSet(table), table.getKeyColumn(), idStr); sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); this.addUpdateMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource); } else { // 正常删除 super.injectDeleteByIdSql(batch, mapperClass, modelClass, table); } } /** * 根据 SQL 删除 */ @Override protected void injectDeleteSql(Class<?> mapperClass, Class<?> modelClass, TableInfo table) { if (table.isLogicDelete()) { // 逻辑删除注入 SqlMethod sqlMethod = SqlMethod.LOGIC_DELETE; String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlLogicSet(table), sqlWhereEntityWrapper(table)); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); this.addUpdateMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource); } else { // 正常删除 super.injectDeleteSql(mapperClass, modelClass, table); } } /** * 根据 MAP 删除 */ @Override protected void injectDeleteByMapSql(Class<?> mapperClass, TableInfo table) { if (table.isLogicDelete()) { // 逻辑删除注入 SqlMethod sqlMethod = SqlMethod.LOGIC_DELETE_BY_MAP; String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlLogicSet(table), sqlWhereByMap(table)); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Map.class); this.addUpdateMappedStatement(mapperClass, Map.class, sqlMethod.getMethod(), sqlSource); } else { // 正常删除 super.injectDeleteByMapSql(mapperClass, table); } } /** * <p> * 注入查询 SQL 语句 * </p> * * @param batch 是否为批量插入 * @param mapperClass * @param modelClass * @param table */ @Override protected void injectSelectByIdSql(boolean batch, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { if (table.isLogicDelete()) { SqlMethod sqlMethod = SqlMethod.LOGIC_SELECT_BY_ID; SqlSource sqlSource; if (batch) { sqlMethod = SqlMethod.LOGIC_SELECT_BATCH_BY_IDS; StringBuilder ids = new StringBuilder(); ids.append(" <foreach item="item" index="index" collection="coll" separator=",">"); ids.append("#{item}"); ids.append(" </foreach>"); sqlSource = languageDriver.createSqlSource(configuration, String.format(sqlMethod.getSql(), sqlSelectColumns(table, false), table.getTableName(), table.getKeyColumn(), ids.toString(), getLogicDeleteSql(table)), modelClass); } else { sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(), sqlSelectColumns(table, false), table.getTableName(), table.getKeyColumn(), table.getKeyProperty(), getLogicDeleteSql(table)), Object.class); } this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, modelClass, table); } else { // 正常查询 super.injectSelectByIdSql(batch, mapperClass, modelClass, table); } } /** * <p> * 注入更新 SQL 语句 * </p> * * @param mapperClass * @param modelClass * @param table */ @Override protected void injectUpdateByIdSql(boolean selective, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { if (table.isLogicDelete()) { SqlMethod sqlMethod = selective ? SqlMethod.LOGIC_UPDATE_BY_ID : SqlMethod.LOGIC_UPDATE_ALL_COLUMN_BY_ID; String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlSet(selective, table, "et."), table.getKeyColumn(), "et." + table.getKeyProperty(), "<if test="et instanceof java.util.Map">" + "<if test="et.MP_OPTLOCK_VERSION_ORIGINAL!=null">" + "and ${et.MP_OPTLOCK_VERSION_COLUMN}=#{et.MP_OPTLOCK_VERSION_ORIGINAL}" + "</if>" + "</if>" + getLogicDeleteSql(table) ); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); this.addUpdateMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource); } else { super.injectUpdateByIdSql(selective, mapperClass, modelClass, table); } } /** * <p> * SQL 更新 set 语句 * </p> * * @param table 表信息 * @return sql and 片段 */ public String getLogicDeleteSql(TableInfo table) { StringBuilder sql = new StringBuilder(); List<TableFieldInfo> fieldList = table.getFieldList(); for (TableFieldInfo fieldInfo : fieldList) { if (fieldInfo.isLogicDelete()) { sql.append(" AND ").append(fieldInfo.getColumn()); if (StringUtils.isCharSequence(fieldInfo.getPropertyType())) { sql.append("='").append(fieldInfo.getLogicNotDeleteValue()).append("'"); } else { sql.append("=").append(fieldInfo.getLogicNotDeleteValue()); } } } return sql.toString(); } /** * <p> * SQL 更新 set 语句 * </p> * * @param table 表信息 * @return sql set 片段 */ protected String sqlLogicSet(TableInfo table) { List<TableFieldInfo> fieldList = table.getFieldList(); StringBuilder set = new StringBuilder("SET "); int i = 0; for (TableFieldInfo fieldInfo : fieldList) { if (fieldInfo.isLogicDelete()) { if (++i > 1) { set.append(","); } set.append(fieldInfo.getColumn()).append("="); if (StringUtils.isCharSequence(fieldInfo.getPropertyType())) { set.append("'").append(fieldInfo.getLogicDeleteValue()).append("'"); } else { set.append(fieldInfo.getLogicDeleteValue()); } } } return set.toString(); } // ------------ 处理逻辑删除条件过滤 ------------ @Override protected String sqlWhere(TableInfo table) { if (table.isLogicDelete()) { StringBuilder where = new StringBuilder(" <where>"); // 过滤逻辑 List<TableFieldInfo> fieldList = table.getFieldList(); // EW 逻辑 if (StringUtils.isNotEmpty(table.getKeyProperty())) { where.append(" <if test="ew.").append(table.getKeyProperty()).append("!=null">"); where.append(" AND ").append(table.getKeyColumn()).append("=#{ew."); where.append(table.getKeyProperty()).append("}"); where.append("</if>"); } for (TableFieldInfo fieldInfo : fieldList) { where.append(convertIfTag(fieldInfo, "ew.", false)); where.append(" AND ").append(this.sqlCondition(fieldInfo.getCondition(), fieldInfo.getColumn(), "ew." + fieldInfo.getEl())); where.append(convertIfTag(fieldInfo, true)); } // 过滤逻辑 where.append(" ").append(getLogicDeleteSql(table)); where.append(" </where>"); return where.toString(); } // 正常逻辑 return super.sqlWhere(table); } @Override protected String sqlWhereEntityWrapper(TableInfo table) { if (table.isLogicDelete()) { StringBuilder where = new StringBuilder(128); where.append(" <where>"); where.append(" <choose><when test="ew!=null">"); where.append(" <if test="ew.entity!=null">"); if (StringUtils.isNotEmpty(table.getKeyProperty())) { where.append(" <if test="ew.entity.").append(table.getKeyProperty()).append("!=null">"); where.append(" AND ").append(table.getKeyColumn()).append("=#{ew.entity."); where.append(table.getKeyProperty()).append("}"); where.append("</if>"); } List<TableFieldInfo> fieldList = table.getFieldList(); for (TableFieldInfo fieldInfo : fieldList) { where.append(convertIfTag(fieldInfo, "ew.entity.", false)); where.append(" AND ").append(this.sqlCondition(fieldInfo.getCondition(), fieldInfo.getColumn(), "ew.entity." + fieldInfo.getEl())); where.append(convertIfTag(fieldInfo, true)); } where.append(" </if>"); where.append(" ").append(getLogicDeleteSql(table)); where.append(" <if test="ew.sqlSegment!=null">${ew.sqlSegment} </if>"); where.append(" </when><otherwise>"); where.append(" ").append(getLogicDeleteSql(table)); where.append(" </otherwise></choose>"); where.append(" </where>"); return where.toString(); } // 正常逻辑 return super.sqlWhereEntityWrapper(table); } @Override protected String sqlWhereByMap(TableInfo table) { if (table.isLogicDelete()) { StringBuilder where = new StringBuilder(); where.append(" <where>"); // MAP 逻辑 where.append(" <if test="cm!=null and !cm.isEmpty">"); where.append(" <foreach collection="cm.keys" item="k" separator="AND">"); where.append(" <if test="cm[k] != null">"); where.append(SqlReservedWords.convert(getGlobalConfig(), " ${k}")).append(" = #{cm[${k}]}"); where.append("</if>"); where.append(" </foreach>"); where.append(" </if>"); // 过滤逻辑 where.append(" ").append(getLogicDeleteSql(table)); where.append(" </where>"); return where.toString(); } // 正常逻辑 return super.sqlWhereByMap(table); } }
可以看到这里重写了AutoSqlInjector 的delete 相关方法,如果逻辑删除关闭的话,直接调用父类注入实际删除的逻辑;如果是开启的话则改为update 语句,并且调用 sqlLogicSet 从TableInfo 中获取逻辑删除的字段以及相关的值。
2. 加入一些自己定义的全局SQL
上面研究了Mybatisplus 简单的加入sql的过程,那么如果我们想注入一些自己的全局的默认的sql如何操作。我们假设全局增加一个根据 全局唯一码操作的方法。
1. 定义全局Mapper
package com.mapper; import java.util.Collection; import java.util.List; import org.apache.ibatis.annotations.Param; import com.baomidou.mybatisplus.mapper.BaseMapper; public interface MyBaseMapper<T> extends BaseMapper<T> { T selectByUniqueCode(String uniqueCode); List<T> selectBatchUniqueCodes(@Param("coll") Collection<String> uniqueCode); int deleteByUniqueCode(String uniqueCode); int deleteBatchUniqueCodes(@Param("coll") Collection<String> uniqueCode); }
2. 重写SqlInjector, 这里选择继承AutoSqlInjector:
package com.utils.mybatis; import org.apache.ibatis.builder.MapperBuilderAssistant; import org.apache.ibatis.mapping.SqlSource; import org.apache.ibatis.scripting.defaults.RawSqlSource; import com.baomidou.mybatisplus.entity.TableInfo; import com.baomidou.mybatisplus.mapper.AutoSqlInjector; public class MyAutoSqlInjector extends AutoSqlInjector { @Override protected void injectSql(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { super.injectSql(builderAssistant, mapperClass, modelClass, table); // 映射根据唯一编号查询 injectSelectByUniqueCodeSql(false, mapperClass, modelClass, table); injectSelectByUniqueCodeSql(true, mapperClass, modelClass, table); // 映射根据唯一编号删除 injectDeleteByUniqueCodeSql(false, mapperClass, modelClass, table); injectDeleteByUniqueCodeSql(true, mapperClass, modelClass, table); } /** * <p> * 注入查询 SQL 语句 * </p> * * @param batch * 是否为批量插入 * @param mapperClass * @param modelClass * @param table */ protected void injectSelectByUniqueCodeSql(boolean batch, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { String tableName = table.getTableName(); String sql = "SELECT * FROM %s WHERE uniqueCode=#{uniqueCode}"; SqlSource sqlSource; if (batch) { String batchSql = "<script>SELECT * FROM %s WHERE uniqueCode IN (%s)</script>"; StringBuilder ids = new StringBuilder(); ids.append(" <foreach item="item" index="index" collection="coll" separator=",">"); ids.append("#{item}"); ids.append(" </foreach>"); sqlSource = languageDriver.createSqlSource(configuration, String.format(batchSql, tableName, ids.toString()), modelClass); this.addSelectMappedStatement(mapperClass, "selectBatchUniqueCodes", sqlSource, modelClass, table); } else { sqlSource = new RawSqlSource(configuration, String.format(sql, table.getTableName()), Object.class); this.addSelectMappedStatement(mapperClass, "selectByUniqueCode", sqlSource, modelClass, table); } } /** * <p> * 注入删除 SQL 语句 * </p> * * @param mapperClass * @param modelClass * @param table */ protected void injectDeleteByUniqueCodeSql(boolean batch, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { String sql = "<script>DELETE FROM %s WHERE uniqueCode=#{%s}</script>"; String column = "uniqueCode"; String sqlMethod = "deleteByUniqueCode"; if (batch) { sql = "<script>DELETE FROM %s WHERE uniqueCode IN (%s)</script>"; StringBuilder ids = new StringBuilder(); ids.append(" <foreach item="item" index="index" collection="coll" separator=",">"); ids.append("#{item}"); ids.append(" </foreach>"); column = ids.toString(); sqlMethod = "deleteBatchUniqueCodes"; } String sqlFormated = String.format(sql, table.getTableName(), column); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlFormated, modelClass); this.addDeleteMappedStatement(mapperClass, sqlMethod, sqlSource); } }
如果开启逻辑删除,这里需要继承逻辑删除LogicSqlInjector。这里就模拟AutoSqlInjector 进行注入即可。
这里也是重写了com.baomidou.mybatisplus.mapper.AutoSqlInjector#injectSql 方法 , 也可以重写com.baomidou.mybatisplus.mapper.AutoSqlInjector#inject(org.apache.ibatis.session.Configuration, org.apache.ibatis.builder.MapperBuilderAssistant, java.lang.Class<?>, java.lang.Class<?>, com.baomidou.mybatisplus.entity.TableInfo)。 因为父类AutoSqlInjector 的 injectSql 方法执行完最后一步会调用inject 方法,可以说为子类添加sql注入暴露了钩子。
3. 我们的Mapper 继承我们上面的MyBaseMapper
package com.mapper.user; import com.zd.bx.bean.user.Department; import com.zd.bx.bean.user.User; import com.zd.bx.mapper.MyBaseMapper; import com.zd.bx.vo.department.DepartmentVo; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; @Mapper public interface DepartmentMapper extends MyBaseMapper<Department> { /** * 查询部门树 * * @param parentUniqueCode * 父节点唯一编码 * @return */ List<DepartmentVo> selectByParentId(@Param("parentUniqueCode") String parentUniqueCode); /** * 查询部门下的人员 * * @param unCodes * 部门唯一编码list * @return */ List<User> selectUserByList(List<String> unCodes); /** * 查询所有子部门的uniqueCode * * @param parentUniqueCode * 父节点uniqueCode * * @return */ List<String> selectChildrenUnique(@Param("parentUniqueCode") String parentUniqueCode); }
4. 接下来就是使我们的SqlInjector 生效
(1) 方法一: 直接注入到Spring 中,Spring 启动会扫描
(2) 方法二: 注入MybatisSqlSessionFactoryBean 的过程中设置,一般建议这种。因为我们一般会选择重写这个FactoryBean, 加入一些自己的配置。这个FactoryBean 生产的bean是Mybatis 的SqlSqssionFactory(也就是上面自动注入的对象)
@Bean public MybatisSqlSessionFactoryBean sqlSessionFactoryBean() throws Exception { MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean(); /** * 重点,使分页插件生效 */ // 配置数据源,此处配置为关键配置,如果没有将 dynamicDataSource作为数据源则不能实现切换 sessionFactory.setDataSource(dynamicDataSource()); // 扫描Model sessionFactory.setTypeAliasesPackage("com.zd.bx.bean"); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); // 扫描映射文件 sessionFactory.setMapperLocations(resolver.getResources("classpath*:mapper/**/*Mapper.xml")); GlobalConfiguration defaults = GlobalConfigUtils.defaults(); // 设置下划线为false defaults.setDbColumnUnderline(false); // 设置自定义SQL注入器 defaults.setSqlInjector(new MyAutoSqlInjector()); sessionFactory.setGlobalConfig(defaults); // 添加插件 Interceptor[] interceptors = getPlugins(); if (ArrayUtils.isNotEmpty(interceptors)) { sessionFactory.setPlugins(interceptors); } return sessionFactory; }
这种注入方式和com.baomidou.mybatisplus.spring.boot.starter.MybatisPlusAutoConfiguration#sqlSessionFactory 注入的区别是: 这种方式相当于注入的是个FactoryBean,Spring 启动会过程中会调用其getObject 方法获取生产的bean; 而上面自动注入相当于自己调用了其getObject 方法将生产的对象注入到Spring中。
补充:Mybatisplus有一些比较好的反射工具类
主要的工具类位于包com.baomidou.mybatisplus.toolkit。其中比较重要的有:
1. ReflectionKit 反射工具类,可以获取相关字段,方法名等。
2. StringUtils 字符串工具栏,有驼峰转下划线、下划线转驼峰等方法。