分析的是mybatis plus 2.0的代码,现在mybatis plus都3.0了。
分析之前,我们想,
如果要做一个mybatis plus这样的建立在mybatis之上,自动生成crud的框架,则可以从这个MapperStatement入手,解析mapper接口,MapperStatement过程中,自动生成各种crud的MapperStatement加入configuration的变量mappedStatements中,但是mybatis的MapperStatement都是通过xml或者注解解析而来,而mybatis plus是没有注解和xml的。所以,看看发生了什么。
maven依赖。
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>最新版本号</version>
</dependency>
</dependencies>
![](https://img.haomeiwen.com/i15792750/3c50a0a707df1660.png)
看一下这个配置类。
其实这个配置类,跟mybatis跟spring boot集成的配置的类MybatisAutoConfiguration差不多,只是改了几个类。
@EnableConfigurationProperties(MybatisPlusProperties.class)
MybatisPlusProperties注入了application.yml的mybatis plus配置。
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
这个跟spring boot集成mybatis的一样。
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({SqlSessionFactory.class, MybatisSqlSessionFactoryBean.class})
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisPlusProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisPlusAutoConfiguration {
看一下改了什么
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// 这个换了,原来的是:
// SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
MybatisConfiguration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new MybatisConfiguration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
customizer.customize(configuration);
}
}
configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
factory.setConfiguration(configuration);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
// TODO 自定义枚举包
if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) {
factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
if (!ObjectUtils.isEmpty(this.properties.getGlobalConfig())) {
factory.setGlobalConfig(this.properties.getGlobalConfig().convertGlobalConfiguration());
}
return factory.getObject();
}
看一下MybatisSqlSessionFactoryBean,这里作者的注释就说了,这个和SqlSessionFactoryBean相差就是buildSqlSessionFactory方法。
/**
* <p>
* 拷贝类 org.mybatis.spring.SqlSessionFactoryBean 修改方法 buildSqlSessionFactory()
* 加载自定义 MybatisXmlConfigBuilder
* </p>
*
* @author hubin
* @Date 2017-01-04
*/
public class MybatisSqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
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;
// 这里把原来的XMLConfigBuilder换成了MybatisXmlConfigBuilder,这两
//个的区别就是MybatisXmlConfigBuilder使用的mybatis plus自己的
//MybatisConfiguration,XMLConfigBuilder使用的是mybatis 的configuration。
// 作者在MybatisXmlConfigBuilder注解上有写
// 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");
}
// 这里改了,原来的是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)) {
// 下面三个if也是新增的,用处作者有注释
// 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是mybatis plus的全局的缓存类
// 设置元数据相关
GlobalConfigUtils.setMetaData(dataSource, globalConfig);
SqlSessionFactory sqlSessionFactory = this.sqlSessionFactoryBuilder.build(configuration);
// SqlRunner是mybatis plus的执行sql的类
// TODO SqlRunner
SqlRunner.FACTORY = sqlSessionFactory;
// TODO 缓存 sqlSessionFactory
globalConfig.setSqlSessionFactory(sqlSessionFactory);
// TODO 设置全局参数属性
globalConfig.signGlobalConfig(sqlSessionFactory);
// 下面是解析mapper了,和mybatis差不多,没什么大变化,就是正常的解析mapper.xml文件
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;
}
可见,到这里,如果mybatis plus没有xml文件,那么到这里,都没有生成mapper的代理类,也没有生成mapperStatement对象注入configuration中。
那么其实,生成mapper代理类,并且自动生成CRUD 的mapperStatement对象的逻辑,在@MapperScan注解中
看这个mybatis plus的配置类,使用的MapperScan注解是mybatis的注解。
@Configuration
@MapperScan(basePackages = {"com.mit.community.mapper", "com.mit.community.*.*.mapper"})
public class MybatisPlusConfig {
同样,看MapperScannerRegistrar这个类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
省略MapperScannerRegistrar的registerBeanDefinitions方法的内部的详情,之前分析过,关注重点
最终doScan方法会调用下面的方法,修改注入到spring中的mapper的bean。
definition.setBeanClass(this.mapperFactoryBean.getClass());
看mapperFactoryBean这个类。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
MapperFactoryBean是DaoSupport的子类的子类,而DaoSupport实现了InitializingBean 接口,所以自然,服务器一启动,就会调用InitializingBeanafterPropertiesSet方法。
public abstract class DaoSupport implements InitializingBean
这个方法,会调用抽象方法checkDaoConfig,子类实现checkDaoConfig这个方法,模板方法设计模式。
@Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// Let abstract subclasses check their configuration.
checkDaoConfig();
// Let concrete implementations initialize themselves.
try {
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
然后看一下MapperFactoryBean的实现的checkDaoConfig
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
// 这里获取到的Configuration是mybatis plus的MybatisConfiguration
Configuration configuration = getSqlSession().getConfiguration();
// 如果Configuration中没有注入过这个mapper,则从新注入。这里,自然前面没有注入过,所以会走入if里。
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 执行MybatisConfiguration的addMapper方法,MybatisConfiguration重写了addMapper方法。
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
MybatisConfiguration的addMapper方法
mybatis plus重写了mybatisMapperRegistry
/**
* Mapper 注册
*/
public final mybatisMapperRegistry = new MybatisMapperRegistry(this);
@Override
public <T> void addMapper(Class<T> type) {
mybatisMapperRegistry.addMapper(type);
}
mybatisMapperRegistry.addMapper(type)方法
@Override
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
// TODO 如果之前注入 直接返回
return;
// throw new BindingException("Type " + type +
// " is already known to the MybatisPlusMapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
// TODO 自定义无 XML 注入
// mybtais plus的MybatisMapperAnnotationBuilder,下面就是生成CRUD的maperStatement了
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
@Override
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
// TODO 注入 CURD 动态 SQL (应该在注解之前注入)
// 如果mapper接口实现了BaseMapper接口,则生成CRUD的mapperStatement自动注入
if (BaseMapper.class.isAssignableFrom(type)) {
// 从全局缓存中取SqlInjector,这在注入mybatis plus的时候,已经注入进去了,前面说过 GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
/**
* <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);
}
}
AutoSqlInjector的inject方法,注入
/**
* 注入单点 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());
}*/
// 获取mapper的泛型,因为必须要直到生成哪个类的crud MapperStatement
Class<?> modelClass = extractModelClass(mapperClass);
if (null != modelClass) {
/**
* 初始化 SQL 解析
*/
if (this.getGlobalConfig().isSqlParserCache()) {
PluginUtils.initSqlParserInfoCache(mapperClass);
}
// 分析泛型中关于表的注解的信息,最终生成TableInfo对象,tableInfo对象包含了生成CRUD的所有数据库表信息
TableInfo table = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
// 之后,生成
injectSql(builderAssistant, mapperClass, modelClass, table);
}
}
看一下注入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);
}
找一个删除的sql注入分析。
/**
* <p>
* 注入删除 SQL 语句
* </p>
*
* @param mapperClass
* @param modelClass
* @param table
*/
protected void injectDeleteByIdSql(boolean batch, Class<?> mapperClass, Class<?> modelClass, TableInfo table) {
// SqlMethod 是一个枚举,里面定义了各个crud等方法的sql语句,你会发现里面的sql语句都是<script></script>包裹的,但是其实原生的mybatis是<select><insert>等包裹的xnode节点。
// 看 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);就会发现,里面是会解析<script>的节点的
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("\n<foreach item=\"item\" index=\"index\" collection=\"coll\" separator=\",\">");
ids.append("#{item}");
ids.append("\n</foreach>");
idStr = ids.toString();
}
// 替换一下sql中的参数
String sql = String.format(sqlMethod.getSql(), table.getTableName(), table.getKeyColumn(), idStr);
// 生成mybatis的SqlSource
sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
// 构建MappedStatement并添加到configuration中
this.addDeleteMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource);
}
至此,就分析完了。
大概总结一下:替换了Configuration为MybatisConfiguration,MybatisConfiguration的mapperRegistry变量替换成MybatisMapperRegistry。
这样当@MapperScan注解导入的处理类MapperScannerRegistrar注入了mapperFactoryBean工厂类之后,这个工厂类,父类实现了 InitializingBean接口,所以服务器一启动,就会调用mapperFactoryBean的checkDaoConfig方法,判断mapper接口是否被注入到configuration中,自然是没有的,所以会调用替换过的MybatisConfiguration的addMapper方法注入到configuration中,再这个注入过程中,生成CRUD等MapperStatement,然后注入到Configuration中。
网友评论