
先前我们讲了springboot 集成mybatis的过程,只需要通过几步简单的配置,我们就能够通过操作我们的mapper映射器来完成对数据库的操作。不知道大家是否会产生这样的疑惑:我写的映射器只是一个自定义的接口,为啥我就能直接用它来对数据库进行增删改查呢?接下来咱们就来探讨一下这块的原理。
由于mybatis相关的原理涉及的东西较多,所以咱们就对内部的知识点进行逐个的讲解。本篇主要讲解下mybatis starter的作用,以为映射器实例的注册过程。另外,本篇将基于我之前写的《springboot集成mybatis》文章,如果有还没看过的小伙伴,可以先去我的历史文章中查看下。
1、mybatis-spring-boot-starter的自动配置类
我们用springboot集成mybatis时,会在pom文件中引入下面的依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
引入之后,maven就会自动帮我们导入mybatis-spring-boot-autoconfigure包。看这个名字,我们能猜想到这是个自动配置包,那我们就免不了要去看下这个jar包里面的META-INF/spring.factories文件了,该文件有如下内容:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
MybatisAutoConfiguration这个配置类帮我们以下功能:
(1)往spring容器注册SqlSessionFactory和SqlSessionTemplate
(2)往spring容器注册AutoConfiguredMapperScannerRegistrar,后面需要它来完成标记有@Mapper注解的映射器的注册工作
为方便大家预览,此处呈现MybatisAutoConfiguration的代码如下:
package org.mybatis.spring.boot.autoconfigure;
import java.beans.PropertyDescriptor;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.sql.DataSource;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandler;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* {@link EnableAutoConfiguration Auto-Configuration} for Mybatis. Contributes a {@link SqlSessionFactory} and a
* {@link SqlSessionTemplate}.
*
* If {@link org.mybatis.spring.annotation.MapperScan} is used, or a configuration file is specified as a property,
* those will be considered, otherwise this auto-configuration will attempt to register mappers based on the interface
* definitions in or under the root auto-configuration package.
*
* @author Eddú Meléndez
* @author Josh Long
* @author Kazuki Shimizu
* @author Eduardo Macarrón
*/
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
private final MybatisProperties properties;
private final Interceptor[] interceptors;
private final TypeHandler[] typeHandlers;
private final LanguageDriver[] languageDrivers;
private final ResourceLoader resourceLoader;
private final DatabaseIdProvider databaseIdProvider;
private final List<ConfigurationCustomizer> configurationCustomizers;
private final List<SqlSessionFactoryBeanCustomizer> sqlSessionFactoryBeanCustomizers;
public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,
ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,
ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers) {
this.properties = properties;
this.interceptors = interceptorsProvider.getIfAvailable();
this.typeHandlers = typeHandlersProvider.getIfAvailable();
this.languageDrivers = languageDriversProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = databaseIdProvider.getIfAvailable();
this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
this.sqlSessionFactoryBeanCustomizers = sqlSessionFactoryBeanCustomizers.getIfAvailable();
}
@Override
public void afterPropertiesSet() {
checkConfigFileExists();
}
private void checkConfigFileExists() {
if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
Assert.state(resource.exists(),
"Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
}
}
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
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());
}
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());
}
if (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
Set<String> factoryPropertyNames = Stream
.of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
// Need to mybatis-spring 2.0.2+
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
// Need to mybatis-spring 2.0.2+
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
applySqlSessionFactoryBeanCustomizers(factory);
return factory.getObject();
}
private void applyConfiguration(SqlSessionFactoryBean factory) {
Configuration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new Configuration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
}
private void applySqlSessionFactoryBeanCustomizers(SqlSessionFactoryBean factory) {
if (!CollectionUtils.isEmpty(this.sqlSessionFactoryBeanCustomizers)) {
for (SqlSessionFactoryBeanCustomizer customizer : this.sqlSessionFactoryBeanCustomizers) {
customizer.customize(factory);
}
}
}
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
/**
* This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use
* {@link org.mybatis.spring.annotation.MapperScan} but this will get typed mappers working correctly, out-of-the-box,
* similar to using Spring Data JPA repositories.
*/
public static class AutoConfiguredMapperScannerRegistrar
implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar {
private BeanFactory beanFactory;
private Environment environment;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!AutoConfigurationPackages.has(this.beanFactory)) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
return;
}
logger.debug("Searching for mappers annotated with @Mapper");
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
builder.addPropertyValue("annotationClass", Mapper.class);
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Set<String> propertyNames = Stream.of(beanWrapper.getPropertyDescriptors()).map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
if (propertyNames.contains("lazyInitialization")) {
// Need to mybatis-spring 2.0.2+
builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
}
if (propertyNames.contains("defaultScope")) {
// Need to mybatis-spring 2.0.6+
builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}");
}
// for spring-native
boolean injectSqlSession = environment.getProperty("mybatis.inject-sql-session-on-mapper-scan", Boolean.class,
Boolean.TRUE);
if (injectSqlSession && this.beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) this.beanFactory;
Optional<String> sqlSessionTemplateBeanName = Optional
.ofNullable(getBeanNameForType(SqlSessionTemplate.class, listableBeanFactory));
Optional<String> sqlSessionFactoryBeanName = Optional
.ofNullable(getBeanNameForType(SqlSessionFactory.class, listableBeanFactory));
if (sqlSessionTemplateBeanName.isPresent() || !sqlSessionFactoryBeanName.isPresent()) {
builder.addPropertyValue("sqlSessionTemplateBeanName",
sqlSessionTemplateBeanName.orElse("sqlSessionTemplate"));
} else {
builder.addPropertyValue("sqlSessionFactoryBeanName", sqlSessionFactoryBeanName.get());
}
}
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
private String getBeanNameForType(Class<?> type, ListableBeanFactory factory) {
String[] beanNames = factory.getBeanNamesForType(type);
return beanNames.length > 0 ? beanNames[0] : null;
}
}
/**
* If mapper registering configuration or mapper scanning configuration not present, this configuration allow to scan
* mappers based on the same component-scanning path as Spring Boot itself.
*/
@org.springframework.context.annotation.Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
@Override
public void afterPropertiesSet() {
logger.debug(
"Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}
}
}
2、为映射器接口生成BeanDefinition对象
MybatisAutoConfiguration的内部类MapperScannerRegistrarNotFoundConfiguration通过@Import的方式,将AutoConfiguredMapperScannerRegistrar注册到了spring容器中,代码如下所示:
@org.springframework.context.annotation.Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
@Override
public void afterPropertiesSet() {
logger.debug(
"Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}
}
AutoConfiguredMapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口。我在之前的文章中说过,通过@Import方式导入的类,都可以被当做一个配置类。AutoConfiguredMapperScannerRegistrar只完成了一件事,就是将MapperScannerConfigurer注册到了spring容器中,当然这时spring中的MapperScannerConfigurer还只是一个BeanDefinition对象。那么MapperScannerConfigurer又是用来干嘛的呢?
2.1、MapperScannerConfigurer映射器扫描器的配置器
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,在它的postProcessBeanDefinitionRegistry方法中,它通过手动创建ClassPathMapperScanner对象,由ClassPathMapperScanner对象来完成映射器的注册工作。此处简单说下BeanDefinitionRegistryPostProcessor接口,它的作用就是提供一个拓展点,允许我们实现该接口,然后覆盖它的postProcessBeanDefinitionRegistry方法,并在该方法中完成BeanDefinition对象的注册工作。记住哦,是BeanDefinition对象,spring在初始化bean的阶段就会根据BeanDefinition对象来完成bean的初始化工作。
2.2、ClassPathMapperScanner类路径映射器扫描器
它的全路径是org.mybatis.spring.mapper.ClassPathMapperScanner,专门用来扫描类路径下指定包目录下标有@Mapper注解的映射器接口,然后为我们的每一个映射器接口都生成一个BeanDefinition对象,存放到spring容器中。其中每一个映射器接口对应的BeanDefinition对象的bean类型为MyFactoryBean类型,代码如下:
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
。。。省略部分代码
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
definition.setAttribute("factoryBeanObjectType", beanClassName);
。。。省略部分代码
}
其中mapperFactoryBeanClass是ClassPathMapperScanner的成员变量,它的定义如下:
private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
后续spring在初始化bean的阶段,会调用MyFactoryBean的getObject方法生成我们的映射器接口实例。
3、初始化bean阶段执行的流程
经过前面的步骤,spring容器中已经有了完整的BeanDefinition对象列表,接下来spring会继续执行,根据这些BeanDefinition对象生成实际的bean对象。
3.1、生成SqlSessionFactory的bean实例对象
spring容器中存储着SqlSessionFactoryBean的bean定义信息,spring初始化bean的时候,会根据该bean定义信息,调用它的getObject方法,生成真正的sqlSessionFactory对象。在这过程中,会涉及到下列操作:
(1)生成mybatis的全局Configuration对象,configuration对象可以是根据mybatis的xml配置文件解析而来
(2)如果有自定义的mybatis插件拦截器plugins,则将它们加到configuration的拦截器列表。
if (!ObjectUtils.isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach((plugin) -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> {
return "Registered plugin: '" + plugin + "'";
});
});
}
(3)如果有自定义的类型处理器typeHandlers,则将它们加到configuration的类型处理器注册中心对象中
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach((typeHandler) -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> {
return "Registered type handler: '" + typeHandler + "'";
});
});
}
(4)根据我们在application.yml文件中配置的mybatis.mapper-locations属性值,使用XMLMapperBuilder解析对应路径下的xml格式的mybatis映射文件,根据映射文件中的命名空间namespace的值,找到对应的类(或接口)的class对象,并将其加入到configuration的映射器注册中心mapperRegistry。而且针对映射文件中的每个sql语句,都会将它解析成一个MappedStatement对象,并将这个MappedStatement对象加入到configuration的mappedStatements(Map类型)对象中,mappedStatements的key就是每个MappedStatement对象的id,而id的值就是映射文件中namespace的值+”.”+sql语句的id。我们以orderMapper.xml文件中的findbyId这个映射语句为例,它被解析后生成的MappedStatement对象id就是com.xk.mybatis.springboot.mapper.OrderMapper.findById
<mapper namespace="com.xk.mybatis.springboot.mapper.OrderMapper">
<select id="findById" parameterType="Long" resultType="com.xk.mybatis.springboot.entity.Order">
select * from t_order where id=#{id};
</select>
</mapper>
核心代码如下:
targetConfiguration.setEnvironment(new Environment(this.environment, (TransactionFactory)(this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory), this.dataSource));
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> {
return "Property 'mapperLocations' was specified but matching resources are not found.";
});
} else {
Resource[] var3 = this.mapperLocations;
int var4 = var3.length;
for(int var5 = 0; var5 < var4; ++var5) {
Resource mapperLocation = var3[var5];
if (mapperLocation != null) {
try {
//解析映射文件
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception var19) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var19);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> {
return "Parsed mapper file: '" + mapperLocation + "'";
});
}
}
}
}
其中xmlMapperBuilder.parse()的实现如下,详情大家可以自己去看一下:
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//根据映射文件中的命名空间namespace的值,找到对应的类(或接口)的class对象,并将其加入到configuration的映射器注册中心mapperRegistry
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
//将映射文件中的每一个sql语句都解析成一个MappedStatement对象
parsePendingStatements();
}
3.2、生成SqlSessionTemplate的bean实例对象
SqlSessionTemplate实现了SqlSession接口,内部维护了一个sqlSessionProxy对象,而sqlSessionProxy本身是通过jdk动态代理创建的一个SqlSession的代理对象。SqlSessionTemplate的构造方法如下:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
Assert.notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}
其中SqlSessionInterceptor类的实现如下:
private class SqlSessionInterceptor implements InvocationHandler {
private SqlSessionInterceptor() {
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//通过sqlSessionFactory生成sqlSession对象
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
//通过反射执行目标方法
Object result = method.invoke(sqlSession, args);
if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
unwrapped = result;
} catch (Throwable var11) {
unwrapped = ExceptionUtil.unwrapThrowable(var11);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw (Throwable)unwrapped;
} finally {
if (sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
return unwrapped;
}
}
当我们在代码中调用sqlSessionTemplate的select方法时,会执行如下的方法:
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}
对sqlSessionTemplate的所有操作,都会由它内部的sqlSessionProxy代理对象完成,而这又会进一步执行上面SqlSessionInterceptor类的invoke方法。invoke内部就是通过sqlSessionFactory对象创建新的sqlsession对象,然后由这个新的sqlSession对象完成对数据库的操作。
3.3、生成映射器接口对应的bean实例对象
前面我们说了,spring容器中存储的关于映射器接口的bean定义信息指定的bean的类型是MyFactoryBean类型,MyFactoryBean是一个工厂bean,spring根据映射器接口的BeanDefinition对象初始化bean的时候,就会调用MyFactoryBean的getObject方法生成真正的映射器接口实例。getObject方法实现如下:
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
其中getSqlSession()就是返回我们3.2节生成的sqlSessionTemplate对象。
3.3.1、进入SqlSessionTemplate的getMapper方法,
public <T> T getMapper(Class<T> type) {
return this.getConfiguration().getMapper(type, this);
}
其中getConfiguration()就是3.1节中创建SqlSessionFactory对象时生成的mybatis全局的configuration对象。
3.3.2、进入Configuration类的getMapper方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
3.3.3、进入MapperRegistry的getMapper方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 返回mapper接口的代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
上面这里会获取MapperProxyFactory对象,MapperProxyFactory通过调用它自身的newInstance方法,创建映射器接口(mapper接口)的代理对象,
3.3.4、进入MapperProxyFactory的newInstance方法
代码如下所示:
// 第二步
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
//第一步
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 调用第二步
return newInstance(mapperProxy);
}
到这里,我们自定义的映射器接口的实例对象(代理对象)就通过JDK动态代理的方式创建成功了,接下来spring就会将其注入到容器中,供我们在应用系统层面直接使用,比如在我们的OrderService中直接调用OrderMapper。有一点要说下,其实映射器接口的实例对象的真实类型是org.apache.ibatis.binding.MapperProxy,经过了动态代理这一步,我们可以理解为这个代理对象也已经实现了映射器接口,比如实现了我们的OrderMapper接口,所以spring才能帮我们把代理对象注入到我们的OrderService中。
4、执行映射器接口方法
本部分我们以在orderService中调用orderMapper.findById()方法为例,讲解映射器接口方法的执行流程。
上面3.3.4部分我们提到:spring容器中映射器实例对象的真实类型是org.apache.ibatis.binding.MapperProxy,那么MapperProxy又是个啥呢?
4.1、MapperProxy
MapperProxy实现了InvocationHandler接口,上面3.3.4部分newInstance方法创建MapperProxy对象,就是为了通过JDK自身的Proxy类创建映射器接口的代理对象(这就是jdk的动态代理的实现)。当被代理的映射器接口的方法被调用时,就会执行MapperProxy内部的invoke方法,invoke内部实现如下:
@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 {
//调用MapperMethodInvoker的invoke方法,内部再调用MapperMethod,最终会执行我们的sql语句
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//获取MapperMethod方法的调用器,通过调用器完成对底层MappedStatement的调用
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return MapUtil.computeIfAbsent(methodCache, method, m -> {
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
4.2、PlainMethodInvoker
因为我们映射器接口里面一般不会写默认方法,所以上面MapperProxy的invoke方法中cachedInvoker(method)方法一般会返回一个PlainMethodInvoker对象,并且给PlainMethodInvoker传递了一个MapperMethod对象。
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
PlainMethodInvoker的invoke方法会调用MapperMethod.execute方法,invoke实现如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
4.3、MapperMethod
MapperMethod是对我们当前调用的映射器接口内部方法的一个包装,上面PlainMethodInvoker通过调用new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())构造器来创建MapperMethod对象,我们看下它的构造方法实现:
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
构造函数的参数mapperInterface就是我们的映射器接口名,我们观察到构造函数里面创建了一个SqlCommand对象,我们来看下SqlCommand的实现:
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
...省略部分代码
}
SqlCommand是MapperMethod的一个内部类,用来维护映射器接口中的方法的sql操作类型,它会通过获取映射器接口的全限定名+”.”+接口方法名来从configuration中获取MappedStatement对象,并把MappedStatement对象的名称赋值给SqlCommand对象的name属性,把MappedStatement对象的sql操作类型(select、update、insert或delete)赋值给SqlCommand对象的type属性。为啥要提这两个属性呢?因为下面要用到。
MapperMethod的execute方法实现如下:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
在execute中,我们又看到了一个熟悉的身影:sqlSession,它就是我们前面讲的sqlSessionTemplate对象。我们调用orderMapper.findById方法时,就会执行到下面这里:
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
前面提到过command.getName()的值就是MappedStatement对象的id值,再细一点说就是:映射器接口的全限定名+”.”+接口方法名,而执行sqlSession.selectOne的流程就相当于直接操作sqlSessionTemplate了。
5、总结
本篇主要讲了映射器自动注册的流程,以及调用映射器接口方法时涉及到的一些流程。我们也提到了一个很重要的概念:MappedStatement。映射文件中的每个sql映射语句都对应一个MappedStatement对象,而通过映射器接口的方法名,我们又能找到唯一的MappedStatement对象,进而执行最终的sql映射语句。
结束语
觉得有收获的朋友,麻烦点击下“关注”,或者进行分享或收藏,多谢支持~
原文始发于微信公众号(IT人的天地):springboot环境下mybatis映射器自动注册的原理
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/186548.html