目录
一、示例代码
1.核心依赖
2.配置(application.yml)
3.核心代码
二、源码分析SpringBoot整合Mybatis的过程
1.SpringBoot集成Mybatis
2.创建MapperScannerConfigurer的bean定义及扫描mapper接口
3.实例化SqlSessionFactory、SqlSessionTemplate及mapper三个bean
3.1. 实例化SqlSessionFactory
3.2. 实例化sqlSessionTemplate
3.3. 实例化mapper
4.执行mapper操作原理
三、总结
1.如何对mapper实例化bean
1.1. 初始化
1.2. 实例化bean
2.如何执行mapper
在众多ORM框架中,mybatis算是比较常用的一个了,基于SpringBoot框架整合,mybatis的使用非常的方便,那么它的整合原理是怎样的呢?本篇将结合实例,通过源码分析SpringBoot整合Mybatis的过程。
以下实例及源码使用的SpringBoot、Mybatis及相关依赖版本如下:
SpringBoot:spring-boot-dependencies-2.3.5.RELEASE
Mybatis:mybatis-spring-boot-starter-2.2.0
JDK:1.8
为了更容易理解本篇的内容,建议提前了解下spring容器初始化过程以及jdk动态代理相关知识点。相关文章如下:
什么是spring容器和容器初始化;
JDK动态代理和CGLIB代理源码分析;
org.springframework.boot
spring-boot-dependencies
2.3.5.RELEASE
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.2.0
mysql
mysql-connector-java
5.1.35
com.alibaba
druid-spring-boot-starter
1.1.9
com.github.pagehelper
pagehelper-spring-boot-starter
1.3.0
spring:
datasource:
name: test
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false&useOldAliasMetadataBehavior=true
username: root
password: ******
# 使用druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
filters: stat
maxActive: 20
initialSize: 1
maxWait: 60000
minIdle: 1
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxOpenPreparedStatements: 20
mybatis:
#注意:一定要对应mapper映射xml文件的所在路径
mapper-locations: classpath:mapping/*.xml
# 注意:对应实体类的路径
type-aliases-package: com.micro.demo.entity
启动类ProviderApplication.java
package com.micro.provider;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.micro.provider.dao")
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
UserMapper.java类
package com.micro.provider.dao;
import com.micro.provider.dto.RequestParamDto;
import com.micro.provider.entity.User;
import java.util.List;
public interface UserMapper {
int deleteByPrimaryKey(Long id);
int insert(User record);
int insertSelective(User record);
User selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(User record);
int updateByPrimaryKey(User record);
User validateUser(User user);
List getUserLsit(RequestParamDto requestParamDto);
}
mapping/UserMapper.xml
ID, USER_NAME, USER_NICKNAME, USER_PASSWORD, TELEPHONE, MAIL, CREATE_TIME, UPDATE_TIME,
IS_DEL
delete from ims_user
where ID = #{id,jdbcType=BIGINT}
insert into ims_user (ID, USER_NAME, USER_NICKNAME,
USER_PASSWORD, TELEPHONE, MAIL,
CREATE_TIME, UPDATE_TIME, IS_DEL
)
values (#{id,jdbcType=BIGINT}, #{userName,jdbcType=VARCHAR}, #{userNickname,jdbcType=VARCHAR},
#{userPassword,jdbcType=VARCHAR}, #{telephone,jdbcType=VARCHAR}, #{mail,jdbcType=VARCHAR},
#{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, #{isDel,jdbcType=BIT}
)
insert into ims_user
ID,
USER_NAME,
USER_NICKNAME,
USER_PASSWORD,
TELEPHONE,
MAIL,
CREATE_TIME,
UPDATE_TIME,
IS_DEL,
#{id,jdbcType=BIGINT},
#{userName,jdbcType=VARCHAR},
#{userNickname,jdbcType=VARCHAR},
#{userPassword,jdbcType=VARCHAR},
#{telephone,jdbcType=VARCHAR},
#{mail,jdbcType=VARCHAR},
#{createTime,jdbcType=TIMESTAMP},
#{updateTime,jdbcType=TIMESTAMP},
#{isDel,jdbcType=BIT},
update ims_user
USER_NAME = #{userName,jdbcType=VARCHAR},
USER_NICKNAME = #{userNickname,jdbcType=VARCHAR},
USER_PASSWORD = #{userPassword,jdbcType=VARCHAR},
TELEPHONE = #{telephone,jdbcType=VARCHAR},
MAIL = #{mail,jdbcType=VARCHAR},
CREATE_TIME = #{createTime,jdbcType=TIMESTAMP},
UPDATE_TIME = #{updateTime,jdbcType=TIMESTAMP},
IS_DEL = #{isDel,jdbcType=BIT},
where ID = #{id,jdbcType=BIGINT}
update ims_user
set USER_NAME = #{userName,jdbcType=VARCHAR},
USER_NICKNAME = #{userNickname,jdbcType=VARCHAR},
USER_PASSWORD = #{userPassword,jdbcType=VARCHAR},
TELEPHONE = #{telephone,jdbcType=VARCHAR},
MAIL = #{mail,jdbcType=VARCHAR},
CREATE_TIME = #{createTime,jdbcType=TIMESTAMP},
UPDATE_TIME = #{updateTime,jdbcType=TIMESTAMP},
IS_DEL = #{isDel,jdbcType=BIT}
where ID = #{id,jdbcType=BIGINT}
public interface UserService {
PageInfo getUserList(RequestParamDto requestParamDto);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
/**
* 获取用户列表
* @param requestParamDto
* @return
*/
@Override
public PageInfo getUserList(RequestParamDto requestParamDto){
if(requestParamDto == null){
requestParamDto = new RequestParamDto();
}
PageHelper.startPage(requestParamDto.getPageNum(), requestParamDto.getPageSize());
List getUserLsit = userMapper.getUserLsit(requestParamDto);
PageInfo pageInfo = new PageInfo<>(getUserLsit);
return pageInfo;
}
}
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 用户首页
* @return
*/
@ResponseBody
@RequestMapping("/index")
public List index(@RequestParam(value = "pageNum", defaultValue = "1") int pageNum , @RequestParam(value = "keyword", defaultValue = "") String keyword){
RequestParamDto requestParamDto = new RequestParamDto();
requestParamDto.setKeyword(keyword);
requestParamDto.setPageNum(pageNum);
PageInfo getUserList = userService.getUserList(requestParamDto);
return getUserList.getList();
}
}
基于上面的示例,接下来一起通过分析源码,看下SpringBoot整合Mybatis的过程。
由springboot引入包mybatis-spring-boot-starter后,该包中引入了mybatis-spring-boot-autoconfigure包,该包下的META-INF\spring.factories有如下配置,
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
而启动类ProviderApplication添加了@SpringBootApplication注解,内部源码如下,
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class>[] exclude() default {};
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class>[] scanBasePackageClasses() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "nameGenerator"
)
Class extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class>[] exclude() default {};
String[] excludeName() default {};
}
注解@EnableAutoConfiguration的作用就是支持自动化配置机制,真正执行自动化配置是在SpringApplication的run方法里,执行run,(在执行到AbstractApplicationContext.refresh->invokeBeanFactoryPostProcessors方法里)类AutoConfigurationImportSelector会读取所有引入包下的META-INF\spring.factories文件,然后加载其中的类,加载之前会通过META-INF\spring-autoconfigure-metadata.properties文件中的条件进行判断,判断是否要加载META-INF\spring.factories中的类,在这里肯定时加载的(加载类中重点是MybatisAutoConfiguration类,我们稍后进行分析),详细加载过程在这里不做细究,感兴趣同学可以翻阅其他资料。
提前说明下,在加载MybatisAutoConfiguration类之后(注意此处并非实例化操作),因为SqlSessionTemplate、SqlSessionFactory都用了@Bean注解,所以,在执行refresh->obtainFreshBeanFactory()方法时,会把这两个类生成beanDefinition,并放入IOC容器(beanFactory)的bean定义注册表中。
在启动类使用了@MapperScan注解,先看下核心源码,
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan {
String[] value() default {};
String[] basePackages() default {};
Class>[] basePackageClasses() default {};
}
@MapperScan注解通过@Import引入MapperScannerRegistrar类,MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,当执行refresh的invokeBeanFactoryPostProcessors会加载所有实现了ImportBeanDefinitionRegistrar接口的类,并执行它的registerBeanDefinitions方法,即执行MapperScannerRegistrar.registerBeanDefinitions,其源码如下,
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取@MapperScan注解属性信息
AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
this.registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
// 使用BeanDefinitionBuilder来构造MapperScannerConfigurer的BeanDefinition对象
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
Class extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}
// 从@MapperScan注解属性中获取设置的值,然后设置给MapperScannerConfigurer的bd
Class> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
builder.addPropertyValue("markerInterface", markerInterface);
}
Class extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
}
Class extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
}
String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
if (StringUtils.hasText(sqlSessionTemplateRef)) {
builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
}
String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
if (StringUtils.hasText(sqlSessionFactoryRef)) {
builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
}
// 获取@MapperScan注解的属性值,然后添加到basePackages集合中
List basePackages = new ArrayList();
basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
if (basePackages.isEmpty()) {
basePackages.add(getDefaultBasePackage(annoMeta));
}
String lazyInitialization = annoAttrs.getString("lazyInitialization");
if (StringUtils.hasText(lazyInitialization)) {
builder.addPropertyValue("lazyInitialization", lazyInitialization);
}
String defaultScope = annoAttrs.getString("defaultScope");
if (!"".equals(defaultScope)) {
builder.addPropertyValue("defaultScope", defaultScope);
}
// 给BeanDefinition设置属性
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
// 注册该MapperScannerConfigurer的bd
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
上面创建MapperScannerConfigurer的bean定义到beanFactory中,其中basePackages的值包含@MapperScan注解中的:com.micro.provider.dao。MapperScannerConfigurer的类继承关系如下图,
MapperScannerConfigurer实现的BeanDefinitionRegistryPostProcessor接口,因为在invokeBeanFactoryPostProcessors方法里会执行两个接口,按先后执行顺序为:
第一个是BeanDefinitionRegistryPostProcessor;
第二个是BeanFactoryPostProcessor。
而执行BeanDefinitionRegistryPostProcessor,会执行postProcessBeanDefinitionRegistry方法,在这里就是MapperScannerConfigurer.postProcessBeanDefinitionRegistry方法,源码如下,
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
this.processPropertyPlaceHolders();
}
// 构建一个ClassPathMapperScanner,并填充相应属性
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(this.lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(this.lazyInitialization));
}
if (StringUtils.hasText(this.defaultScope)) {
scanner.setDefaultScope(this.defaultScope);
}
// 注册Filter,因为上面构造函数我们没有使用默认的Filter,
// 有两种Filter,includeFilters:要扫描的;excludeFilters:要排除的
scanner.registerFilters();
// 扫描basePackage,basePackage可通过",; \t\n"来填写多个,
// ClassPathMapperScanner重写了doScan方法
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 进行真正的扫描,调用的ClassPathMapperScanner中的doScan
this.doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
// 返回注册BeanDefinition的个数
return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
}
public Set doScan(String... basePackages) {
// 调用父类(ClassPathBeanDefinitionScanner)去扫描指定包下面的类
Set beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> {
return "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.";
});
} else {
// 将扫描到的beanDefinitions进行处理,把beanDefinition的beanClass替换为
this.processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
protected Set doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set beanDefinitions = new LinkedHashSet();
String[] var3 = basePackages;
int var4 = basePackages.length;
// 遍历传入的包名
for(int var5 = 0; var5 < var4; ++var5) {
String basePackage = var3[var5];
// 扫描包路径下的所有mapper
// 比如,则根据示例,我们配置的包为:com.micro.provider.dao,则会把包下UserMapper接口转换成BeanDefinition,并放入集合中返回
Set candidates = this.findCandidateComponents(basePackage);
Iterator var8 = candidates.iterator();
while(var8.hasNext()) {
BeanDefinition candidate = (BeanDefinition)var8.next();
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
// 获取bean的名字
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
//设置bean初始化相关属性
this.postProcessBeanDefinition((AbstractBeanDefinition)candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition)candidate);
}
// 判断beanFactory是否包含beanName的bean的定义,不包含则添加
if (this.checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
//把该BeanDefinition加到beanFactory的bean定义注册表中
this.registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
上面主要的作用是扫描指定包下的类,并生成bean定义,然后注册到beanFactory的bean定义注册表中,并返回beanDefinitions。
默认情况下,spring是扫描所有基于@Component注解的类,并生成bean定义,然后放到bean定义注册表中。而我们这里的mapper没有加@Component注解,所以在执行上述逻辑之前,实际的bean定义注册表中是没有这些mapper的bean定义的。执行上述逻辑之后,才在bean定义注册表中加入了这些mapper的beanDefinition。
返回这些刚生成的beanDefinitions后,继续执行processBeanDefinitions(beanDefinitions),源码如下,
private void processBeanDefinitions(Set beanDefinitions) {
BeanDefinitionRegistry registry = this.getRegistry();
Iterator var4 = beanDefinitions.iterator();
while(var4.hasNext()) {
BeanDefinitionHolder holder = (BeanDefinitionHolder)var4.next();
AbstractBeanDefinition definition = (AbstractBeanDefinition)holder.getBeanDefinition();
boolean scopedProxy = false;
if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
definition = (AbstractBeanDefinition)Optional.ofNullable(((RootBeanDefinition)definition).getDecoratedDefinition()).map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> {
return new IllegalStateException("The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]");
});
scopedProxy = true;
}
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> {
return "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface";
});
// 获取参数构造器的,并未其添加值,其值为beanClassName--类的全限定名
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// 设置其BeanClass为mapperFactoryBeanClass,注意这个类实现了FactoryBean接口,
// 在创建bean时,会根据BeanDefinition的BeanClass和构造方法进行创建
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
definition.setAttribute("factoryBeanObjectType", beanClassName);
boolean explicitFactoryUsed = false;
// 因为sqlSessionFactoryBeanName、sqlSessionTemplateBeanName在@MapperScan中没有执行,所以下面的判断都不会进
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(() -> {
return "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(() -> {
return "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.";
});
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
LOGGER.debug(() -> {
return "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.";
});
definition.setAutowireMode(2);
}
definition.setLazyInit(this.lazyInitialization);
if (!scopedProxy) {
if ("singleton".equals(definition.getScope()) && this.defaultScope != null) {
definition.setScope(this.defaultScope);
}
if (!definition.isSingleton()) {
BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
registry.removeBeanDefinition(proxyHolder.getBeanName());
}
registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
}
}
}
}
这里是把每个mapper的Bean定义的BeanClass设置为mapperFactoryBeanClass,这样做是为了让后面创建bean时,可以使用MapperFactoryBean来创建bean。
一般情况下,mapper是接口类。默认情况下,spring对接口interface是不会生成BeanDefinition 对象。在mybatis里,为了生成BeanDefinition 对象,则重写了isCandidateComponent方法,
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
为什么要把mapper的BeanClass设置为mapperFactoryBeanClass?
因为mapper是接口,但是接口是不能实例化的,所以mybatis中就把mapper的beanDefinition的beanClass定义为mapperFactoryBeanClass,利用mapperFactoryBeanClass是通过getObject()来进行实例化,即通过jdk代理的方式,生成的代理对象,这块后面再具体分析。
我们可以下面图示看下,mapper生成的bean定义,转换BeanClass前后的对比,
转换后
简单地说,这里就是先生成MapperScannerConfigurer的bean定义,并放入bean定义注册表中。然后通过MapperScannerConfigurer.postProcessBeanDefinitionRegistry扫描所有mapper类,创建mapper的bean定义,也是放入bean定义注册表中,并将mapper的bean定义的BeanClass属性值设置为mapperFactoryBeanClass,为后面创建bean做准备。
扩展:如果不使用@MapperScan,会怎么处理?
加载MybatisAutoConfiguration类后,因为MybatisAutoConfiguration内部类AutoConfiguredMapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,当执行refresh的invokeBeanFactoryPostProcessors会加载所有实现了ImportBeanDefinitionRegistrar接口的类,并执行它的registerBeanDefinitions方法,即执行AutoConfiguredMapperScannerRegistrar.registerBeanDefinitions,其源码如下,
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!AutoConfigurationPackages.has(this.beanFactory)) {
MybatisAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
} else {
MybatisAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");
// 获取要扫描的包名,即在哪里找mapper
// 对照示例,就是{"com.micro.provider"}
List packages = AutoConfigurationPackages.get(this.beanFactory);
if (MybatisAutoConfiguration.logger.isDebugEnabled()) {
packages.forEach((pkg) -> {
MybatisAutoConfiguration.logger.debug("Using auto-configuration base package '{}'", pkg);
});
}
// 使用BeanDefinitionBuilder来构造MapperScannerConfigurer的BeanDefinition对象
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
// 这就是要扫描的注解类型,就是@Mapper
builder.addPropertyValue("annotationClass", Mapper.class);
// 这里是要扫描的包的路径
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Set propertyNames = (Set)Stream.of(beanWrapper.getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet());
if (propertyNames.contains("lazyInitialization")) {
builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
}
if (propertyNames.contains("defaultScope")) {
builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}");
}
// 注册该MapperScannerConfigurer的bd
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
}
这里也是生成MapperScannerConfigurer的bean定义,放到IOC容器(beanFactory)中,后面扫描mapper的逻辑和上面的是一个方法,但是执行后,发现没有找到mapper,报错:“Field userMapper in com.micro.provider.service.impl.UserServiceImpl required a bean of type 'com.micro.provider.dao.UserMapper' that could not be found.”,什么原因呢?
请看上面的代码,已经说明了会扫描带@Mapper注解的作为mapper,所以需要在mapper接口上添加@Mapper注解,经测试OK。
接下来,执行到refresh.finishBeanFactoryInitialization(beanFactory),在这里将会把bean定义注册表中的所有的beanDefinition进行实例化,然后放到bean缓存池中,供应用程序调用。
注意这里是把bean定义注册表中的所有beanDefinition遍历处理挨个进行实例化,那么mapper、SqlSessionFactory、SqlSessionTemplate实例化的先后顺序是怎样的呢? 这里需注意一点,在每个创建bean实例之后,初始化bean之前,会执行populateBean进行属性赋值,如果依赖其他的bean,那么会先创建依赖的bean,所以,即使先实例化的是controller(正常controller依赖的service,service依赖mapper,mapper依赖sqlSessionTemplate,sqlSessionTemplate依赖SqlSessionFactory,但实例化的顺序是反过来的),最终还是先实例化SqlSessionFactory。
因为SqlSessionFactory在开始的MybatisAutoConfiguration就已经用@Bean进行注解,所以会执行到MybatisAutoConfiguration.sqlSessionFactory,此时dataSource已经初始化好了,这个不是重点,来看下sqlSessionFactory源码,
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
// 获取配置文件中设置的mybatis配置文件的路径
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
this.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);
}
// 获取配置文件中设置的mapper配置文件的路径
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
Set factoryPropertyNames = (Set)Stream.of((new BeanWrapperImpl(SqlSessionFactoryBean.class)).getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet());
Class extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
return factory.getObject();
}
public SqlSessionFactory getObject() throws Exception {
// 如果没有sqlSessionFactory,则进行创建
if (this.sqlSessionFactory == null) {
this.afterPropertiesSet();
}
return this.sqlSessionFactory;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.dataSource, "Property 'dataSource' is required");
Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
// 创建sqlSessionFactory工厂
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
XMLConfigBuilder xmlConfigBuilder = null;
Configuration targetConfiguration;
Optional var10000;
if (this.configuration != null) {
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
// 创建 xmlConfigBuilder 对象 : 用于解析 mybatis-config.xml 数据
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
LOGGER.debug(() -> {
return "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration";
});
targetConfiguration = new Configuration();
var10000 = Optional.ofNullable(this.configurationProperties);
Objects.requireNonNull(targetConfiguration);
var10000.ifPresent(targetConfiguration::setVariables);
}
var10000 = Optional.ofNullable(this.objectFactory);
Objects.requireNonNull(targetConfiguration);
var10000.ifPresent(targetConfiguration::setObjectFactory);
var10000 = Optional.ofNullable(this.objectWrapperFactory);
Objects.requireNonNull(targetConfiguration);
var10000.ifPresent(targetConfiguration::setObjectWrapperFactory);
var10000 = Optional.ofNullable(this.vfs);
Objects.requireNonNull(targetConfiguration);
var10000.ifPresent(targetConfiguration::setVfsImpl);
Stream var24;
if (StringUtils.hasLength(this.typeAliasesPackage)) {
var24 = this.scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream().filter((clazz) -> {
return !clazz.isAnonymousClass();
}).filter((clazz) -> {
return !clazz.isInterface();
}).filter((clazz) -> {
return !clazz.isMemberClass();
});
TypeAliasRegistry var10001 = targetConfiguration.getTypeAliasRegistry();
Objects.requireNonNull(var10001);
var24.forEach(var10001::registerAlias);
}
if (!ObjectUtils.isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach((typeAlias) -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> {
return "Registered type alias: '" + typeAlias + "'";
});
});
}
if (!ObjectUtils.isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach((plugin) -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> {
return "Registered plugin: '" + plugin + "'";
});
});
}
if (StringUtils.hasLength(this.typeHandlersPackage)) {
var24 = this.scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter((clazz) -> {
return !clazz.isAnonymousClass();
}).filter((clazz) -> {
return !clazz.isInterface();
}).filter((clazz) -> {
return !Modifier.isAbstract(clazz.getModifiers());
});
TypeHandlerRegistry var25 = targetConfiguration.getTypeHandlerRegistry();
Objects.requireNonNull(var25);
var24.forEach(var25::register);
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach((typeHandler) -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> {
return "Registered type handler: '" + typeHandler + "'";
});
});
}
targetConfiguration.setDefaultEnumTypeHandler(this.defaultEnumTypeHandler);
if (!ObjectUtils.isEmpty(this.scriptingLanguageDrivers)) {
Stream.of(this.scriptingLanguageDrivers).forEach((languageDriver) -> {
targetConfiguration.getLanguageRegistry().register(languageDriver);
LOGGER.debug(() -> {
return "Registered scripting language driver: '" + languageDriver + "'";
});
});
}
var10000 = Optional.ofNullable(this.defaultScriptingLanguageDriver);
Objects.requireNonNull(targetConfiguration);
var10000.ifPresent(targetConfiguration::setDefaultScriptingLanguage);
if (this.databaseIdProvider != null) {
try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException var23) {
throw new NestedIOException("Failed getting a databaseId", var23);
}
}
var10000 = Optional.ofNullable(this.cache);
Objects.requireNonNull(targetConfiguration);
var10000.ifPresent(targetConfiguration::addCache);
if (xmlConfigBuilder != null) {
try {
// 执行 XmlConfigBuilder 解析方法
xmlConfigBuilder.parse();
LOGGER.debug(() -> {
return "Parsed configuration file: '" + this.configLocation + "'";
});
} catch (Exception var21) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var21);
} finally {
ErrorContext.instance().reset();
}
}
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;
// 遍历所有的mapper文件
for(int var5 = 0; var5 < var4; ++var5) {
Resource mapperLocation = var3[var5];
if (mapperLocation != null) {
try {
// 创建 XMLMapperBuilder 对象 : 用于解析 mapper.xml 数据
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 + "'";
});
}
}
}
} else {
LOGGER.debug(() -> {
return "Property 'mapperLocations' was not specified.";
});
}
// 创建sqlSessionFactory对象
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
总结,buildSqlSessionFactory就是通过XMLConfigBuilder解析mybatis配置,通过XMLMapperBuilder解析mapper.xml的配置(重点是生成mappedStatements、resultMaps、sqlFragments,结果看下图),以及其他的配置,最终放到Configuration里,供后面使用。
mappedStatements存放所有mapper.xml(上图是示例UserMapper.xml生成的mappedStatements)中的 select|delete|insert|update 操作,注意每个id操作生成两个value一样的MappedStatement,区别在于key不一样,一个使用的是方法名(sql语句中的id),一个使用的全限定名,这么做的原因是为了兼容早期版本的用法,早期不是通过的接口的,而是通过方法名的方式来进行查询的。而现在使用接口方式的话,在configuration.getMappedStatement的时候,是根据全限定名来获取MappedStatement。
resultMaps存放的就是resultMap。
sqlFragments存放的sql信息。
另外,通过XMLMapperBuilder解析生成的还有mapperRegistry、typeHandlerRegistry。
在XMLMapperBuilder.parse()里通过mapperRegistry.addMapper方法,会把mapper接口添加到knownMappers中(knownMappers结构为HashMap,每个mapper的value为MapperProxyFactory对象),那么后面调用getMapper的时候就可以直接获取了。所以XMLMapperBuilder.parse()方法,完成了mapper接口和xml映射文件的绑定。
在实际的入参或出参时,根据定义的参数类型,自动选择合适的typeHandler进行数据映射,typeHandler在mybatis里有默认的,当然也可以自定义typeHandler,需继承BaseTypeHandler,并重写相应的方法即可。
接着开始实例化sqlSessionTemplate,同上面的SqlSessionFactory,调用的MybatisAutoConfiguration.sqlSessionTemplate,源码如下,
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
Assert.notNull(executorType, "Property 'executorType' is required");
// 设置sqlSessionFactory对象
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 使用jdk动态代理生成SqlSession代理对象
// 顶一个参数表示生成代理对象的类加载器,第二个参数表示被代理类对象,第三个参数表示代理实现类(里面主要是执行代理对象时要做的事情)
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}
可以看出,使用jdk代理方式生成SqlSessionTemplate代理对象,代理实现是SqlSessionTemplate内部类SqlSessionInterceptor,
private class SqlSessionInterceptor implements InvocationHandler {
private SqlSessionInterceptor() {
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取会话SqlSession(这里是DefaultSqlSession),该SqlSession可能是新创建的,也可能是上一次请求的SqlSession(属于同一事务)
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
// 利用反射机制执行SqlSession方法
Object result = method.invoke(sqlSession, args);
// 判断当前的 SqlSession 是否有事务,如果没有则commit
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) {
// 关闭当前会话,并且将sqlSession置为空,防止finally重复关闭
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 {
// 回话sqlSession不为空,则关闭回话sqlSession操作
if (sqlSession != null) {
// 如果当前sqlSession有事务,则进行释放;否则直接关闭sqlSession
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
return unwrapped;
}
}
上面已经实例化完成SqlSessionFactory和SqlSessionTemplate两个bean,现在终于到实例化mapper了。继续执行到initializeBean方法,然后执行到初始化方法invokeInitMethods。
因为mapper的beanClass是MapperFactoryBean,由上图可以看出,MapperFactoryBean实现了InitializingBean接口,所以先执行afterPropertiesSet,最终执行MapperFactoryBean.checkDaoConfig,源码如下,
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
this.checkDaoConfig();
try {
this.initDao();
} catch (Exception var2) {
throw new BeanInitializationException("Initialization of DAO failed", var2);
}
}
protected void checkDaoConfig() {
super.checkDaoConfig();
Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = this.getSqlSession().getConfiguration();
// 如果configuration中没有该mapper接口,则加载
// 注意,上面通过xmlMapperBuilder.parse()已经加载了mapper接口,所以这一步判断不会进入
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 通过 addMapper() 方法加载到configuration中,内部走的也是mapperRegistry.addMapper方法
configuration.addMapper(this.mapperInterface);
} catch (Exception var6) {
this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
throw new IllegalArgumentException(var6);
} finally {
ErrorContext.instance().reset();
}
}
}
初始化之后,因为MapperFactoryBean是FactoryBean,所以会执行MapperFactoryBean.getObject,
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
上面的getSqlSession()获取的就是上面创建的SqlSessionTemplate对象,这是mapper的SqlSession,然后继续执行SqlSessionTemplate.getMapper,源码如下,
public T getMapper(Class type) {
return this.getConfiguration().getMapper(type, this);
}
mapper是什么时候放到Configuration中的呢,请看上面的checkDaoConfig(也可能是在实例化sqlSessionFactory过程中,通过xmlMapperBuilder加载进来的,两种方式最终都是调用mapperRegistry.addMapper方法加载进来),明白了吧。继续往下看,
public T getMapper(Class type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
public T getMapper(Class type, SqlSession sqlSession) {
// 从knownMappers中获取MapperProxyFactory
MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
// 通过 MapperProxyFactory.newInstance 创建 MapperProxy
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
public T newInstance(SqlSession sqlSession) {
MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
protected T newInstance(MapperProxy mapperProxy) {
// 通过jdk动态代理方式创建mapper代理对象
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
可以看出,也是通过jdk代理方式创建的mapper代理对象,代理实现类为MapperProxy,
public class MapperProxy implements InvocationHandler, Serializable {
private static final long serialVersionUID = -4724728412955527868L;
private static final int ALLOWED_MODES = 15;
private static final Constructor lookupConstructor;
private static final Method privateLookupInMethod;
private final SqlSession sqlSession;
private final Class mapperInterface;
private final Map methodCache;
public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果调用的方法是 Object 种定义的方法,直接执行;否则获取缓存的MapperMethod方法,如果没有则创建
return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
private MapperProxy.MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return (MapperProxy.MapperMethodInvoker)MapUtil.computeIfAbsent(this.methodCache, method, (m) -> {
if (m.isDefault()) {
try {
return privateLookupInMethod == null ? new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava8(method)) : new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava9(method));
} catch (InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException var4) {
throw new RuntimeException(var4);
}
} else {
return new MapperProxy.PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));
}
});
} catch (RuntimeException var4) {
Throwable cause = var4.getCause();
throw (Throwable)(cause == null ? var4 : cause);
}
}
private MethodHandle getMethodHandleJava9(Method method) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Class> declaringClass = method.getDeclaringClass();
return ((Lookup)privateLookupInMethod.invoke((Object)null, declaringClass, MethodHandles.lookup())).findSpecial(declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()), declaringClass);
}
private MethodHandle getMethodHandleJava8(Method method) throws IllegalAccessException, InstantiationException, InvocationTargetException {
Class> declaringClass = method.getDeclaringClass();
return ((Lookup)lookupConstructor.newInstance(declaringClass, 15)).unreflectSpecial(method, declaringClass);
}
static {
Method privateLookupIn;
try {
privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, Lookup.class);
} catch (NoSuchMethodException var5) {
privateLookupIn = null;
}
privateLookupInMethod = privateLookupIn;
Constructor lookup = null;
if (privateLookupInMethod == null) {
try {
lookup = Lookup.class.getDeclaredConstructor(Class.class, Integer.TYPE);
lookup.setAccessible(true);
} catch (NoSuchMethodException var3) {
throw new IllegalStateException("There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.", var3);
} catch (Exception var4) {
lookup = null;
}
}
lookupConstructor = lookup;
}
private static class DefaultMethodInvoker implements MapperProxy.MapperMethodInvoker {
private final MethodHandle methodHandle;
public DefaultMethodInvoker(MethodHandle methodHandle) {
this.methodHandle = methodHandle;
}
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return this.methodHandle.bindTo(proxy).invokeWithArguments(args);
}
}
private static class PlainMethodInvoker implements MapperProxy.MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
this.mapperMethod = mapperMethod;
}
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
// 执行MapperMethod的execute方法
return this.mapperMethod.execute(sqlSession, args);
}
}
interface MapperMethodInvoker {
Object invoke(Object var1, Method var2, Object[] var3, SqlSession var4) throws Throwable;
}
}
最后把实例化mapper的bean放到bean缓存池中,至此,实例化mapper结束。
以上所有步骤,已经把SqlSessionFactory、SqlSessionTemplate、mapper这三个bean创建好了,那么实际使用mapper执行sql,是怎么实现的呢?接着往下看。
还记得前面创建的代理对象吗,通过Proxy和MapperProxy创建了mapper代理对象,所以执行mapper是调用的MapperProxy.invoke,最终调用的MapperMethod的execute方法,
这里我们使用controller里的/user/index请求为例,最终执行到userMapper.getUserLsit方法,
execute方法如下,
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
execute里的sqlSession其实就是上面生成的sqlSessionTemplate,在初始化MapperMethod时候,通过SqlCommand从Configuration中的MappedStatement获取限定名和SQL类型(select|insert|update|delete)。
public MapperMethod(Class> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
}
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class> mapperInterface, Method method) {
String methodName = method.getName();
Class> declaringClass = method.getDeclaringClass();
MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) == null) {
throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName);
}
this.name = null;
this.type = SqlCommandType.FLUSH;
} else {
this.name = ms.getId();
this.type = ms.getSqlCommandType();
if (this.type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + this.name);
}
}
}
}
获取到的限定名name为“com.micro.provider.dao.UserMapper.getUserLsit”,SQL类型为“SELECT”。
然后根据类型判断,执行的executeForMany(sqlSession, args)方法,
private Object executeForMany(SqlSession sqlSession, Object[] args) {
Object param = this.method.convertArgsToSqlCommandParam(args);
List result;
if (this.method.hasRowBounds()) {
RowBounds rowBounds = this.method.extractRowBounds(args);
result = sqlSession.selectList(this.command.getName(), param, rowBounds);
} else {
// 到这里基本上就是到了真正处理逻辑的时候了,
// sqlSession是通过SqlSessionTemplate构造器创建的,里面使用了jdk动态代理生成SqlSession代理对象
result = sqlSession.selectList(this.command.getName(), param);
}
if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
} else {
return result;
}
}
因为sqlSessionTemplate是SqlSessionInterceptor代理创建的,所以,接下来走SqlSessionInterceptor.invoke方法,通过上面实例化sqlSessionTemplate可以看出,实际用的是反射机制,执行sqlSession方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取会话SqlSession(这里是DefaultSqlSession),该SqlSession可能是新创建的,也可能是上一次请求的SqlSession(属于同一事务)
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
// 忽略部分代码...
// 利用反射机制执行SqlSession方法
Object result = method.invoke(sqlSession, args);
// 忽略部分代码...
return unwrapped;
}
这里创建的sqlSession是与数据库交互的会话sqlSession,注意与sqlSessionTemplate这个sqlSession的区别,然后通过method.invoke反射调用到具体的 DefaultSqlSession.selectList 方法。
public List selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
return this.selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
private List selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
List var6;
try {
// statement是由MapperMethod中的SqlCommand的name传下来的,即“com.micro.provider.dao.UserMapper.getUserLsit”
// getMappedStatement利用限定名来获取对应的MappedStatement,这里就完成了从mapper接口到sql的连接了
MappedStatement ms = this.configuration.getMappedStatement(statement);
// 通过 Executor.query 执行数据库操作
var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
} catch (Exception var10) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var10, var10);
} finally {
ErrorContext.instance().reset();
}
return var6;
}
最终调用Executor方法执行,Executor有两个方法:BaseExecutor 和 CachingExecutor,分别代表两种缓存机制,BaseExecutor 是一级缓存机制使用,CachingExecutor是二级缓存机制使用(如果查询二级缓存中没有结果,则会调用BaseExecutor的方法查询一级缓存,然后把查询结果放到二级缓存)。
所以,一级缓存或二级缓存机制都会调用BaseExecutor,
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 从 MappedStatement 中获取到 BoundSql(实际上是通过 调用 MappedStatement 中的 SqlSource 的 getBoundSql() 获取)
BoundSql boundSql = ms.getBoundSql(parameter);
// 通过参数解析出 cacheKey ,这个是一级缓存的key
CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
Mybatis默认的 BaseExecutor 实现是 SimpleExecutor,最终调用SimpleExecutor的doQuery方法,
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
// 从 MappedStatement 中获取到 Configuration
Configuration configuration = ms.getConfiguration();
// 通过 Configuration 的 newStatementHandler() 方法创建了一个 StatementHandler 对象
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 调用 prepareStatement() 方法 获取到 Statement 对象 (真正执行静态SQl的接口)
stmt = this.prepareStatement(handler, ms.getStatementLog());
// 调用 StatementHandler.query() 方法执行
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
StatementHandler的类继承关系,
这几个子类的意义:
SimpleStatementHandler ,这个对应的 就是JDBC 中常用到的 Statement 接口,用于简单SQL的处理
PreparedStatementHandler , 这个对应的就是JDBC中的 PreparedStatement,用于预编译SQL的处理
CallableStatementHandler , 这个对应JDBC中 CallableStatement ,用于执行存储过程相关的处理
RoutingStatementHandler,这个接口是以上三个接口的路由,没有实际操作,只是负责上面三个StatementHandler的创建及调用
由上面的configuration.newStatementHandler可以知道,创建的是RoutingStatementHandler,并且其内部 的 delegate 默认是 PreparedStatementHandler (MappedStatement builder方法指定了默认的 statementType = StatementType.PREPARED )
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch(ms.getStatementType()) {
case STATEMENT:
this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
prepareStatement底层调用方法如下,
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
// 创建 Connection 链接,查看源码其是通过 transaction.getConnection() 获取到的
Connection connection = this.getConnection(statementLog);
// 预编译获取到 PrepareStatement ,即最终会调用到 connection.prepareStatement() 方法
Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
// 设置参数信息,其参数是通过从 BoundSql 获取
handler.parameterize(stmt);
return stmt;
}
执行handler.query方法,以PreparedStatementHandler为例,源码如下,
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
// 执行SQL
ps.execute();
// 通过 ResultSetHandler 的 handleResultSets() 方法解析返回数据
return this.resultSetHandler.handleResultSets(ps);
}
这里的 ResultSetHandler就是用来处理 JDBC中的 ResultSet (相信用过JDBC 的同学对这个不陌生),关于如何解析返回数据的逻辑这里就不再详细分析了。
所以,Executor执行的操作,本质是底层封装了jdbc的一个执行操作。
返回结果后,如果当前SqlSession是否被事务所管控,如果是则不commit,最后再调用 closeSqlSession() 方法进行SqlSession “关闭”,这个关闭表示,判断当前SqlSession是否被事务所管控,是的话仅仅将引用计数器减一,并未真正将SqlSession 关闭(这也是为了下次能够使用同一个SqlSession),如果不被事务管控则执行正在的 session.close() 操作。
简单的说,执行mapper方法,实际就是通过jdk代理机制,执行MapperMethod的execute方法。然后通过sqlSessionTemplate的jdk代理机制,执行method.invoke方法。然后通过反射机制,执行DefaultSqlSession.selectList方法。通过configuration.getMappedStatement获取MappedStatement,这时找到了实际的sql了,然后到了Executor模块。Executor通过封装的jdbc操作,最终调用jdbc操作执行sql,最后解析结果并返回。
整个SpringBoot整合Mybatis的过程,核心就两点:如何对mapper的bean实例化和如何执行mapper。
对mapper实例化bean,重点就是如何将mapper接口和mapper.xml映射文件的绑定,以及实例化mapper,具体分为两步。
首先了解几个比较重要的类:
SqlSessionFactoryBean:用于生成SqlSessionFactory 的FactoryBean。
Configuration:存放所有mybatis配置信息,包括mapper接口、mapper.xml、 mybatis-config.xml等;
XMLConfigBuilder: 解析 mybatis-config.xml 配置并存放到Configuration中;
XMLMapperBuilder: 解析 mapper.xml 配置并存放到Configuration中,在这里完成了mapper接口与mapper.xml的绑定;
SqlSessionFactoryBuilder: 实际用于创建 SqlSessionFactory
SqlSessionFactory: 用于创建 SqlSession
SqlSession: Mybatis工作的最顶层API会话接口,所有访问数据库的操作都是通过SqlSession来的。
SqlSessionTemplate: 内部维护有 SqlSession 的代理对象,解耦Mapper和SqlSession的关键对象。
MapperScannerConfigurer:用于扫描所有mapper接口,并将mapper接口生成beanDefinition放到beanFactory的bean定义注册表中,然后再把beanDefinition中的mapper的beanClass转换成MapperFactoryBean,这么做是为了:第一,可以通过遍历bean定义注册表,找到mapper的beanDefinition,用于实例化bean;第二,可以通过MapperFactoryBean的getObject方法来实例化bean(通过jdk代理生成了bean的代理对象)。
初始化的过程,创建SqlSessionFactory、SqlSessionTemplate、MapperScannerConfigurer的bean定义,放到IOC容器(beanFactory)中,这是基础。在此过程,通过MapperScannerConfigurer扫描指定包下的所有mapper接口生成beanDefinition,并放到bean定义注册表中。
第一步,使用SqlSessionFactoryBean来生成SqlSessionFactory。生成过程中,使用了XMLConfigBuilder、XMLMapperBuilder解析mybatis相关的xml配置,放到Configuration中,然后放到SqlSessionFactory里,把创建的SqlSessionFactory实例放到bean缓存池中。
第二步,使用使用SqlSessionTemplate构造器创建SqlSessionTemplate对象,其中用了jdk代理方式创建了SqlSession代理对象。需说明,SqlSessionTemplate采用单例模式,并通过TransactionSynchronizationManager中的ThreadLocal
第三步,通过MapperFactoryBean来实例化mapper接口,也是通过jdk代理方式创建的mapper代理对象,并把依赖的SqlSessionFactory和SqlSessionTemplate注入mapper中。
执行mapper方法的过程,主要是先通过两个代理类,即先执行mapper代理实现类MapperProxy的invoke方法,然后执行SqlSessionTemplate代理实现类的invoke方法,然后进入DefaultSqlSession相应方法中,这里会根据mapper的限定名获取MappedStatement,然后调用Executor相应方法,而Executor是封装了jdbc的操作,所以最终是通过jdbc执行sql,最后再把执行的结果解析返回。
综上所述,整个SpringBoot整合Mybatis的过程,就是在spring容器初始化的过程中生成mapper的代理对象,然后在执行mapper方法的过程,利用代理机制,执行目标方法,最终底层通过jdbc执行sql。
参考文章:
https://blog.csdn.net/qq_35634181/article/details/106396443
https://www.cnblogs.com/wbo112/p/15025348.html
https://www.cnblogs.com/bug9/p/11793728.html
https://www.cnblogs.com/bug9/p/11822093.html
https://www.cnblogs.com/bug9/p/11867615.html
https://www.cnblogs.com/bug9/p/11910005.html