前言
MyBatis-plus
是完全基于MyBatis
开发的一个增强工具,是在MyBatis
的基础上做增强的框架,为简化开发、提高效率而生。它在MyBatis
原本的框架上增加了很多实用性功能,比如乐观锁插件、字段自动填充功能、分页插件、条件构造器、sql 注入器等等。使用 MyBatis-plus
可以完全不写任何 XML
文件,直接使用继承了BaseMapper
接口的类对象完成对数据库的映射操作
基于映射的原理,MyBatis-plus
必然要实现 Mapper
中的方法与 SQL
语句的对应转化,以下即为 MyBatis-plus
重要流程图例
在这里插入图片描述
Mapper 对象方法映射为 SQL 语句
-
在
MyBatis-plus
中,MybatisPlusAutoConfiguration
自动配置类的sqlSessionFactory()
方法为Spring
提供创建sqlSession
的工厂类对象,对sqlSessionFactory
进行定义的定义类变为了MybatisSqlSessionFactoryBean
。在sqlSessionFactory()
方法中,除了注入MyBatis
本身的组件,还会注入MyBatis-plus
的 主键生成器、SQL 注入器等组件,最后通过MybatisSqlSessionFactoryBean#getObject()
方法获取到sqlSessionFactory
对象
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// TODO 使用 MybatisSqlSessionFactoryBean 而不是 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()));
}
applyConfiguration(factory);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
......
// TODO 自定义枚举包
if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) {
factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage());
}
// TODO 此处必为非 NULL
GlobalConfig globalConfig = this.properties.getGlobalConfig();
// TODO 注入填充器
if (this.applicationContext.getBeanNamesForType(MetaObjectHandler.class,
false, false).length > 0) {
MetaObjectHandler metaObjectHandler = this.applicationContext.getBean(MetaObjectHandler.class);
globalConfig.setMetaObjectHandler(metaObjectHandler);
}
// TODO 注入主键生成器
if (this.applicationContext.getBeanNamesForType(IKeyGenerator.class, false,
false).length > 0) {
IKeyGenerator keyGenerator = this.applicationContext.getBean(IKeyGenerator.class);
globalConfig.getDbConfig().setKeyGenerator(keyGenerator);
}
// TODO 注入sql注入器
if (this.applicationContext.getBeanNamesForType(ISqlInjector.class, false,
false).length > 0) {
ISqlInjector iSqlInjector = this.applicationContext.getBean(ISqlInjector.class);
globalConfig.setSqlInjector(iSqlInjector);
}
// TODO 设置 GlobalConfig 到 MybatisSqlSessionFactoryBean
factory.setGlobalConfig(globalConfig);
return factory.getObject();
}
-
MybatisSqlSessionFactoryBean#getObject()
执行懒加载策略,最后通过buildSqlSessionFactory()
方法创建SqlSessionFactory
-
工厂类对象。这个方法的流程很长,不过大致可以分为两个步骤:
❝
❞1.创建 MybatisXMLConfigBuilder 对象,调用其 parse() 方法去解析 XML 配置文件及 Mapper
2.解析获得的信息存储在 targetConfiguration 对象中,根据其信息创建 SqlSessionFactory 对象
-
protected SqlSessionFactory buildSqlSessionFactory() throws Exception { final MybatisConfiguration targetConfiguration; // TODO 使用 MybatisXmlConfigBuilder 而不是 XMLConfigBuilder MybatisXMLConfigBuilder xmlConfigBuilder = null; ...... } else if (this.configLocation != null) { // TODO 使用 MybatisXMLConfigBuilder // 1.1 创建 MybatisConfiguration 对象 xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); // 2.1 将解析获得的信息的引用传递给 targetConfiguration 对象 targetConfiguration = xmlConfigBuilder.getConfiguration(); } else { LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); // TODO 使用 MybatisConfiguration targetConfiguration = new MybatisConfiguration(); Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables); } // TODO 无配置启动所必须的 this.globalConfig = Optional.ofNullable(this.globalConfig).orElseGet(GlobalConfigUtils::defaults); this.globalConfig.setDbConfig(Optional.ofNullable(this.globalConfig.getDbConfig()).orElseGet(GlobalConfig.DbConfig::new)); // TODO 初始化 id-work 以及 打印骚东西 targetConfiguration.setGlobalConfig(this.globalConfig); ...... // 1.2 开始解析 XML 配置文件 及 Mapper 接口 if (xmlConfigBuilder != null) { try { xmlConfigBuilder.parse(); 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(); } } targetConfiguration.setEnvironment(new Environment(MybatisSqlSessionFactoryBean.class.getSimpleName(), this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory, this.dataSource)); if (this.mapperLocations != null) { if (this.mapperLocations.length == 0) { LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found."); } else { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'"); } } } else { LOGGER.debug(() -> "Property 'mapperLocations' was not specified."); } // 2.2 根据 targetConfiguration 对象中保存的信息创建 SqlSessionFactory 对象 final SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(targetConfiguration); ...... return sqlSessionFactory; }
-
MybatisXMLConfigBuilder#parse()
会去解析配置文件,最后会调用到其内部方法mapperElement()
。这个方法完成解析Mapper
工作,并将其添加到配置类MybatisConfiguration
中 -
private void mapperElement(XNode parent) throws Exception { /* * 定义集合 用来分类放置mybatis的Mapper与XML 按顺序依次遍历 */ if (parent != null) { //指定在classpath中的mapper文件 Set<String> resources = new HashSet<>(); //指向一个mapper接口 Set<Class<?>> mapperClasses = new HashSet<>(); setResource(parent, resources, mapperClasses); // 依次遍历 首先 resource 然后 mapper for (String resource : resources) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); //TODO XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } for (Class<?> mapper : mapperClasses) { // 主要关注此处 configuration.addMapper(mapper); } } }
-
MybatisConfiguration#addMapper()
方法其实是去调用MybatisMapperRegistry#addMapper()
方法,其核心是MybatisMapperAnnotationBuilder#parse()
-
public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { // TODO 如果之前注入 直接返回 return; // TODO 这里就不抛异常了 // throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { // TODO 这里也换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory knownMappers.put(type, new MybatisMapperProxyFactory<>(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 这里也换成 MybatisMapperAnnotationBuilder 而不是 MapperAnnotationBuilder MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
-
MybatisMapperAnnotationBuilder#parse()
方法真正开始完成Mapper
接口中的方法与SQL 语句
的映射,其中parseStatement()
方法是解析@Select/@Update
等注解写入的 SQL语句,而代码GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type)
通过MaBatis-plus
的 SQL 注入器完成Mapper
方法与 SQL 语句的转化 -
@Override public void parse() { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { loadXmlResource(); configuration.addLoadedResource(resource); final String typeName = type.getName(); assistant.setCurrentNamespace(typeName); parseCache(); parseCacheRef(); SqlParserHelper.initSqlParserInfoCache(type); Method[] methods = type.getMethods(); for (Method method : methods) { try { // issue #237 if (!method.isBridge()) { // 解析 @Select 注解写入的 SQL parseStatement(method); SqlParserHelper.initSqlParserInfoCache(typeName, method); } } catch (IncompleteElementException e) { // TODO 使用 MybatisMethodResolver 而不是 MethodResolver configuration.addIncompleteMethod(new MybatisMethodResolver(this, method)); } } // TODO 注入 CURD 动态 SQL , 放在在最后, because 可能会有人会用注解重写sql if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) { GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type); } } parsePendingMethods(); }
-
AbstractSqlInjector#inspectInject()
会完成BaseMapper
接口中提供的通用方法对应的 SQL 语句准备,这部分主要通过AbstractMethod#inject()
方法完成 -
@Override public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) { Class<?> modelClass = extractModelClass(mapperClass); if (modelClass != null) { String className = mapperClass.toString(); Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration()); if (!mapperRegistryCache.contains(className)) { List<AbstractMethod> methodList = this.getMethodList(mapperClass); if (CollectionUtils.isNotEmpty(methodList)) { TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass); // 循环注入自定义方法 methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); } else { logger.debug(mapperClass.toString() + ", No effective injection method was found."); } mapperRegistryCache.add(className); } } }
-
AbstractMethod#inject()
方法并没有什么特别的操作,只是调用其子类实现injectMappedStatement()
方法。以SelectOne#injectMappedStatement()
为例,其 SQL 语句的核心在于SqlMethod
类,这个枚举类中缓存了可以动态拼接的 SQL 语句脚本,只需要填上参数 format 就可以得到 SQL 语句的执行脚本。以上过程结束,只需要将所有信息通过addInsertMappedStatement()
方法封装成MappedStatement
对象并将其加入到容器中,这样Mapper
接口方法调用时,就可以通过 动态代理 的方式找到其对应执行的 SQL 脚本,至此 SQL 语句准备及配置解析就完成了。最后拼接的 SQL 语句 脚本形式如下示例,实际执行数据库操作时会解析这个脚本完成变量替换,从而得到可执行的 SQL 语句
<script>
<choose>
<when test="ew != null and ew.sqlFirst != null">
${ew.sqlFirst}
</when>
<otherwise></otherwise>
</choose>
SELECT
<choose>
<when test="ew != null and ew.sqlSelect != null">
${ew.sqlSelect}
</when>
<otherwise>id,name,type</otherwise>
</choose>
FROM node
<if test="ew != null">
<where>
<if test="ew.entity != null">
<if test="ew.entity.id != null">id=#{ew.entity.id}</if>
<if test="ew.entity['name'] != null">AND name=#{ew.entity.name}</if>
<if test="ew.entity['type'] != null">AND type=#{ew.entity.type}</if>
</if>
<if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere">
<if test="ew.nonEmptyOfEntity and ew.nonEmptyOfNormal">AND</if>
${ew.sqlSegment}
</if>
</where>
<if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.emptyOfWhere">
${ew.sqlSegment}
</if>
</if>
<choose>
<when test="ew != null and ew.sqlComment != null">
${ew.sqlComment}
</when>
<otherwise></otherwise>
</choose>
</script>
```
```
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
SqlMethod sqlMethod = SqlMethod.SELECT_ONE;
SqlSource sqlSource = languageDriver.createSqlSource(configuration, String.format(sqlMethod.getSql(),
sqlFirst(), sqlSelectColumns(tableInfo, true), tableInfo.getTableName(),
sqlWhereEntityWrapper(true, tableInfo), sqlComment()), modelClass);
return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo);
}
-
SqlSessionFactory
对象的创建需要回到MybatisSqlSessionFactoryBean#buildSqlSessionFactory()
方法中,很容易追踪到MybatisSqlSessionFactoryBuilder#build()
方法,最后其实是通过SqlSessionFactoryBuilder#build()
方法创建了一个DefaultSqlSessionFactory
对象返回
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
Mapper 操作数据库的流程
-
@MapperScan
注解通过@Import(MapperScannerRegistrar.class)
引入扫描注册的类MapperScannerRegistrar
,该类实现了ImportBeanDefinitionRegistrar
接口并重写registerBeanDefinitions()
方法,在该方法中注册了MapperScannerConfigurer
类
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
......
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
-
MapperScannerConfigurer
是Mapper
接口的扫描配置类,实现了BeanDefinitionRegistryPostProcessor
接口,其postProcessBeanDefinitionRegistry()
方法会在容器启动过程中被回调,通过ClassPathMapperScanner#scan()
方法完成Mapper
的扫描注册public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); scanner.setAddToConfig(this.addToConfig); scanner.setAnnotationClass(this.annotationClass); scanner.setMarkerInterface(this.markerInterface); scanner.setSqlSessionFactory(this.sqlSessionFactory); scanner.setSqlSessionTemplate(this.sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass); if (StringUtils.hasText(lazyInitialization)) { scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization)); } scanner.registerFilters(); scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
3. `ClassPathMapperScanner#processBeanDefinitions()` 将扫描到的 `Mapper`接口生成的对应 `BeanDefinition` 的 `beanClass` 属性替换为 `MapperFactoryBean`,这样每次获取 `Mapper` 实例实际是通过 `MapperFactoryBean` 的实例去获取
> 此处体现了 `FactoryBean` 的定位,即用于获取同一类 bean 的工厂 bean
```java
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
LOGGER.warn(
() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
}
}
-
@Autowired
自动注入Mapper
触发容器获取 bean 的方法,调用到MapperFactoryBean#getObject()
方法,最终调用到sqlSessionTemplate#getMapper()
方法
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
-
MyBatis-plus
使用的配置类是MybatisConfiguration
,最终调用到MybatisMapperRegistry#getMapper()
方法,这里就进入了动态代理获取MapperProxy
实例的流程
@Override
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// TODO 这里换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory
final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
-
MybatisMapperProxyFactory#newInstance()
方法给自动注入返回一个MybatisMapperProxy
代理对象
protected T newInstance(MybatisMapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
-
调用
Mapper
接口的方法触发代理对象的MybatisMapperProxy#invoke()
,此时根据Mapper
对象被调用的方法生成MybatisMapperMethod
对象,通过MybatisMapperMethod#execute()
去真正地执行 SQL 语句,从而完成数据库操作。此后的流程本文就不再分析,
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MybatisMapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
来源:blog.csdn.net/weixin_45505313/article/details/104855453