本文主要关注与 SpringBoot 集成时的初始化过程。
1. 核心组件
- Configuration:MyBatis所有的配置信息都保存在Configuration对象之中,配置文件中的大部分配置都会存储到该类中。
- SqlSession:MyBatis 的顶层 API,表示与数据库交互的会话,完成数据库增删改查操作。
- Executor:执行器是 MyBatis 调度的核心,负责 SQL 语句的生成和查询缓存的维护。
- StatementHandler:封装了 JDBC Statement 操作,如设置参数等。
- ParameterHandler:负责将用户传递的参数转换成 JDBC Statement 所对应的数据类型。
- ResultSetHandler:负责将 JDBC 返回的 ResultSet 结果集转换成 List 类型的集合。
- TypeHandler:负责将 Java 数据类型和 jdbc 数据类型之间的映射与转换。
- MapperFactoryBean:与 Spring 集成时表示一个 Mapper 原型的工厂 bean,用以创建最终的代理。
- MapperProxy:与一个 mapperInterface 对应,维护了 Map<Method, MapperMethod> 映射。
- MapperAnnotationBuilder:基于注解驱动的 MappedStatement 解析器,解析接口类的每个方法,封装成 MappedStatement。
- MappedStatement:维护了一条 <select|update|delete|insert> 节点的封装。
- SqlSource:负责根据用户传递的 parameterObject 动态生成 SQL 语句,将信息封装成 BoundSql 对象。
- BoundSql:表示动态生成的 SQL 语句以及相应的参数信息。
整体架构如下图:
2. 与 SpringBoot 集成的初始化
2.1 概览
SpringBoot 自动组装 MyBatis 的属性(前缀为 “mybatis”)配置成 MybatisProperties
对象。
SpringBoot 的自动配置机制会解析配置类 MybatisAutoConfiguration
,其内部方法定义了两个 Bean 。
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {...}
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {...}
在 sqlSessionFactory(dataSource)
方法里利用 MybatisProperties
初始化 SqlSessionFactoryBean
,通过 SqlSessionFactoryBean.buildSqlSessionFactory()
来创建 Configuration
,该方法的主要逻辑是创建并配置 Configuration
,然后用 Configuration
创建 SqlSessionFactory
。
再用 SqlSessionFactory
创建 SqlSessionTemplate
。
SpringBoot 会解析处理配置类上的 @MapperScan("package.to.scan")
注解,调用该注解上的导入类 MapperScannerRegistrar
; MapperScannerRegistrar.registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)
方法创建一个 ClassPathMapperScanner
,利用 @MapperScan
上的信息初始化这个扫描器,以限定扫描的接口范围,对于扫描到的每个 bean 定义,设置其 beanClass
为 MapperFactoryBean
,如果有指定 sqlSessionFactory/sqlSessionTemplate
bean 的引用则直接使用,否则采用按类型自动注入。
ClassPathMapperScanner
默认扫描所有的接口并进行注册。
2.2. SqlSessionTemplate
SqlSessionTemplate 是线程安全、Spring 管理的 SqlSession,可以注入到其他 Spring 管理的 bean 里去使用。与 Spring 事务管理机制一起工作以保证实际使用的 SqlSession 是与当前的 Spring 事务关联的。另外,它管理着会话的生命周期,包括关闭、提交或回滚会话,基于 Spring 的事务配置。
该模板默认使用 MyBatisExceptionTranslator
把 MyBatis PersistenceExceptions
转换为不受检查的异常 DataAccessExceptions
。
因为 SqlSessionTemplate
是线程安全的,一个实例可以被所有的 DAOs 共享,这样也可以节省点内存。
public class SqlSessionTemplate implements SqlSession, DisposableBean {
// 用于创建 SqlSession
private final SqlSessionFactory sqlSessionFactory;
// 执行器的类型,默认取 sqlSessionFactory 里的
private final ExecutorType executorType;
// 拦截对 SqlSession 接口里声明的方法的调用,以便与 Spring 事务管理集成
private final SqlSession sqlSessionProxy;
// 异常转换器
private final PersistenceExceptionTranslator exceptionTranslator;
}
2.3 Configuration 的构建过程
下面是创建 Configuration
对象的过程:
// SqlSessionFactoryBean
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
}
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (this.vfs != null) {
configuration.setVfsImpl(this.vfs);
}
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
}
}
if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
}
}
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
}
}
if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan);
}
}
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
}
}
if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
if (this.cache != null) {
configuration.addCache(this.cache);
}
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
if (this.transactionFactory == null) {
// 设置事务工厂为 SpringManagedTransactionFactory,以便与 Spring 管理的事务进行协作
this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
// 适用于 Mapper 的 XML 文件集中存放的场景
if (!isEmpty(this.mapperLocations)) {
// 如果指定了 Mapper 存放的路径则解析
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
// SqlSessionFactoryBuilder
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
2.4 Mapper 定义扫描
Mapper 的扫描一是可以通过属性 mybatis.mapperLocations
来指定其存放路径,也可以通过 @MapperScan("package.to.scan")
注解来配置。
该注解扫描指定包下的所有接口、并为其生成代理注册到 Spring 的容器里。
MapperScannerRegistrar
从 @MapperScan
注解获取扫描配置,用以初始化 ClassPathMapperScanner
并完成最终的扫描工作。
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
// 设置bean定义的类为 MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
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) {
// 如果没有显式设置 sqlSessionTemplate 或 sqlSessionFactory 则按类型注入
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
上述代码最重要的是 设置 bean 定义的类为 MapperFactoryBean
。
2.5 Mapper 实例化
Mapper 的 Bean 定义被扫描后会注册到 Spring 容器里,再由 Spring 实例化时调用 MapperFactoryBean.getObject()
方法得到该类型的 Bean 实例,注册到容器里,供应用使用。
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSession sqlSession;
private boolean externalSqlSession;
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}
public SqlSession getSqlSession() {
return this.sqlSession;
}
// 省略其他
}
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
// 该方法在父类的 afterPropertiesSet 里调用
// 用于把接口类型注册到 Configuration 里。供后面创建代理时使用
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
// 省略其他
}
SqlSessionDaoSupport
的主要作用是把 sqlSession
设置为 SqlSessionTemplate
,DaoSupport
提供了属性设置后回调的初始化钩子,触发调用 MapperFactoryBean.checkDaoConfig
方法。
下面的代理的创建调用链:
// SqlSessionTemplate
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
// Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry
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 {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
// MapperProxyFactory
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<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
最终创建了一个 JDK 的动态代理,代理的方法处理器为 MapperProxy
,其持有的 SqlSession
仍然是 SqlSessionTemplate
的实例。
欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。
相关推荐
Spring和MyBatis的整合是Java...Spring使用FactoryBean来创建特定类型的bean,例如MyBatis的Mapper接口的动态代理实例。BeanDefinitionRegistry则用于注册这些动态生成的bean,确保它们可以被Spring容器识别和管理。
MyBatis-006-创建mapper文件.avi MyBatis-007-创建主配置文件.avi MyBatis-008-创建SqlSession执行sql语句.avi MyBatis-009-复习第一个例子.avi MyBatis-010-开发常见问题.avi MyBatis-011-三种处理方式.avi MyBatis...
本文深入分析了MyBatis执行SQL的整个流程,从SqlSessionFactory的创建到SQL语句的执行,揭示了MyBatis工作的内部机制。首先,通过SqlSessionFactoryBuilder,MyBatis解析配置文件生成Configuration对象,并创建...
获取mapper代理对象: - 4. 执行mapper接口方法: - mybatis源码总结 <!-- /TOC --> Mybatis源码分析 1. 解析配置文件,创建SQLSessionFactory InputStream inputStream = CommonTest.class.getClassLoader()...
第四步:创建Dao接口的代理对象 第五步:执行dao中的方法 第六步:释放资源 注意事项: 不要忘记在映射配置中告知mybatis要封装到哪个实体类中 配置的方法:指定实体类的全限定类名 mybatis基于注解的...
第四步:创建Dao接口的代理对象 第五步:执行dao中的方法 第六步:释放资源 注意事项: 不要忘记在映射配置中告知mybatis要封装到哪个实体类中 配置的方法:指定实体类的全限定类名 mybatis基于注解的...
2.4 Mapper动态代理方式 20 2.4.1 实现原理 20 2.4.2 Mapper.xml(映射文件) 20 2.4.3 Mapper.java(接口文件) 21 2.4.4 加载UserMapper.xml文件 22 2.4.5 测试 22 2.4.6 总结 23 3 SqlMapConfig.xml配置文件 24 3.1 ...
第四步:创建Dao接口的代理对象 第五步:执行dao中的方法 第六步:释放资源 注意事项: 不要忘记在映射配置中告知mybatis要封装到哪个实体类中 配置的方法:指定实体类的全限定类名 mybatis基于注解的...
MyBatis 工具:MyBatis通用Mapper插件 MVC 框架:Spring MVC 模板引擎:Thymeleaf Markdown 编辑器:Editor.md 数据库:MySQL、Liquibase、redis 2.部署事宜 1、服务器安装mysql并创建空的数据库blog 2、安装redis...
5.4 创建dao层的代理对象。 第二步: mybatis和spring进行整合 整合的思想:使用spring的ioc的思想来管理mybatis中的对象。 1.引入spring的ioc相关的依赖,还需要引入 spring和mybatis整合的工具包(依赖版本是...
项目简介 2018年6月5日 后台管理系统工程结构: taotao-parent -- 管理依赖jar包的版本,全局,公司级别 taotao-common --- 通用组件、工具类 taotao-manage -- (聚合工程)后台...把mapper的代理对象放到spring容器中
MultiDataSrc基于mybatis的多数据源多数据源概述来源用户需要访问多家供应商的库存,每家供应商库存信息可能在...SqlSession代理对象创建流程核心:将mybatis的模板配置文件内容填充完后以字节流方式让SqlSessionFacto
│ 04.nginx的反向代理及负载均衡.avi │ 05.FastDFS介绍.avi │ 06.FastDFS安装步骤-文件上传.avi │ 07.配置nginx插件访问图片.avi │ 08.测试图片上传.avi │ 09.FastDFS工具类的使用.avi │ 10.图片上传过程分析...
各个子系统前台thymeleaf模板,前端资源模块,使用nginx代理,实现动静分离。 > zheng-upms 本系统是基于RBAC授权和基于用户授权的细粒度权限控制通用平台,并提供单点登录、会话管理和日志管理。接入的系统可自由...
MinorGC 的过程(复制->清空->互换) ....................................................................................... 24 1:eden、servicorFrom 复制到 ServicorTo,年龄+1.................................