spring源码学习之整合Mybatis原理分析

article/2025/9/1 12:59:19

本文主要解析spring是如何与mybatis进行整合,整合的过程中需要哪些组件的支持。以前面提到过的配置例子《spring源码学习之aop事物标签解析》

整合的过程中需要使用以下这个依赖包:

<!-- mybatis-spring 整合 -->
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.2.2</version>
</dependency>

spring启动的时候需要使用一个bean.xml配置文件,

ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");

bean.xml文件中就涉及到整合的配置信息,整合使用到以下的配置:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!-- 加载数据源 --><property name="dataSource" ref="dataSource"/><property name="mapperLocations" value="classpath*:mappers/*Mapper.xml"/>
</bean><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><!-- 指定扫描的包,如果存在多个包使用(逗号,)分割 --><property name="basePackage" value="com.test.bean"/><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

创建MapperScannerConfigurer

首先我们分析一下,这个MapperScannerConfigurer类在实例化过程中做了什么主要的事情。首先看看它的类图:

这个类中主要的方法就是这个postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法,看看具体内容:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {if (this.processPropertyPlaceHolders) {this.processPropertyPlaceHolders();}ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);scanner.setAddToConfig(this.addToConfig);scanner.setAnnotationClass(this.annotationClass);scanner.setMarkerInterface(this.markerInterface);scanner.setSqlSessionFactory(this.sqlSessionFactory);scanner.setSqlSessionTemplate(this.sqlSessionTemplate);scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);scanner.setResourceLoader(this.applicationContext);scanner.setBeanNameGenerator(this.nameGenerator);scanner.registerFilters();scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}

这个方法中创建了一个spring-mybatis.jar包中的ClassPathMapperScanner扫描器,这个扫描器继承了spring的ClassPathBeanDefinitionScanner。

ClassPathMapperScanner这个扫描器的主要的作用有以下几个:

  • 第一扫描basePackage包下面所有的class类
  • 第二将所有的class类封装成为spring的ScannedGenericBeanDefinition sbd对象 
  • 第三过滤sbd对象,只接受接口类,从下面的代码中可以看出。
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
  • 第四完成sbd对象属性的设置,比如设置sqlSessionFactory、BeanClass等,这个sqlSessionFactory是本文接下来要解析的SqlSessionFactoryBean
sbd.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
sbd.setBeanClass(MapperFactoryBean.class);
sbd.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
  • 第五将过滤出来的sbd对象通过这个BeanDefinitionRegistry registry注册器注册到DefaultListableBeanFactory中,这个registry就是方法postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)中的参数。

以上就是实例化MapperScannerConfigurer类的主要工作,总结起来就是扫描basePackage包下所有的mapper接口类,并将mapper接口类封装成为BeanDefinition对象,注册到spring的BeanFactory容器中。以下时序图不代表实际过程。

mapper接口注册之后,在什么地方实例化和使用呢?后面在分析。

接着看看spring和mybatis整合的另外一个标签。

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!-- 加载数据源 --><property name="dataSource" ref="dataSource"/><property name="mapperLocations" value="classpath*:mappers/*Mapper.xml"/>
</bean>

创建SqlSessionFactoryBean

这个sqlSessionFactory的实现类中做了什么事情。首先看看这个类的声明:

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {//省略代码
}

从配置来看,这个Bean至少提供了两个方法,一个是和dataSource有关,一个和mapperLocations有关,

public void setDataSource(DataSource dataSource) {if (dataSource instanceof TransactionAwareDataSourceProxy) {this.dataSource = ((TransactionAwareDataSourceProxy)dataSource).getTargetDataSource();} else {this.dataSource = dataSource;}}
public void setMapperLocations(Resource[] mapperLocations) {this.mapperLocations = mapperLocations;
}

这个两个方法,啥也没干,就是做了一次赋值操作。在来一张Bean的类图,实现了几个接口,那就必须实现这几个接口的方法。那就继续看看实现的方法中,都干了什么事情。

执行afterPropertiesSet方法

在这个bean被创建的过程中,首先被调用的方法是afterPropertiesSet,这个方法是接口InitializingBean中的方法。

public void afterPropertiesSet() throws Exception {Assert.notNull(this.dataSource, "Property 'dataSource' is required");Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");this.sqlSessionFactory = this.buildSqlSessionFactory();
}

在这个buildSqlSessionFactory()方法中,做了很多事情,但是有些不是必须做的,为了简化分析,我们只关心必须要做的事情。

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {XMLConfigBuilder xmlConfigBuilder = null;Configuration configuration;configuration = new Configuration();configuration.setVariables(this.configurationProperties);//省略代码if (this.transactionFactory == null) {this.transactionFactory = new SpringManagedTransactionFactory();}Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);configuration.setEnvironment(environment);if (!ObjectUtils.isEmpty(this.mapperLocations)) {Resource[] arr$ = this.mapperLocations;len$ = arr$.length;for(i$ = 0; i$ < len$; ++i$) {Resource mapperLocation = arr$[i$];if (mapperLocation != null) {try {XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());xmlMapperBuilder.parse();} catch (Exception var20) {throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var20);} finally {ErrorContext.instance().reset();}}}}return this.sqlSessionFactoryBuilder.build(configuration);
}

首先通过一个关键字new创建了对象Configuration,这个对象是mybatis框架的一个核心类,在这里我们不做详细介绍,以后再剖析。接着又创建了new SpringManagedTransactionFactory(),后面介绍这个类的作用,此处略过。接着继续创建new Environment(this.environment, this.transactionFactory, this.dataSource),这个Environment类中持有事物工厂和数据源的引用。接下来就是创建XMLMapperBuilder对象,并且调用了xmlMapperBuilder.parse()方法,这个方法的详细,不在此分析,也不是我们这篇文章要记录的重点,否则会偏离我们的主题,parse()这个方法就是在解析mapperLocation变量所代表的就是mybatis的一个xml配置文件,mapperLocation-->AuthUserMapper.xml,AuthUserMapper.xml的部分代码如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.test.bean.AuthUserMapper" ><resultMap id="BaseResultMap" type="com.test.bean.AuthUser" ><id column="id" property="id" jdbcType="BIGINT" /><result column="user_account" property="userAccount" jdbcType="VARCHAR" /><result column="password" property="password" jdbcType="VARCHAR" /><result column="create_time" property="createTime" jdbcType="TIMESTAMP" /><result column="update_time" property="updateTime" jdbcType="TIMESTAMP" /><result column="is_delete" property="isDelete" jdbcType="TINYINT" /></resultMap>//此处代码有省略<update id="updateByPrimaryKey" parameterType="com.test.bean.AuthUser" >update t_auth_userset user_account = #{userAccount,jdbcType=VARCHAR},password = #{password,jdbcType=VARCHAR},create_time = #{createTime,jdbcType=TIMESTAMP},update_time = #{updateTime,jdbcType=TIMESTAMP},is_delete = #{isDelete,jdbcType=TINYINT}where id = #{id,jdbcType=BIGINT}</update>
</mapper>

解析这个xml文件内容,然后将解析的结果封装成不同的对象,存放到Configuration对象中。

xmlMapperBuilder.parse()方法执行完成之后,调用this.sqlSessionFactoryBuilder.build(configuration),这个sqlSessionFactoryBuilder 构造器在哪儿创建的呢?其他它就是SqlSessionFactoryBean的一个私有类变量,初始化SqlSessionFactoryBean的时候,就实例化了这个sqlSessionFactoryBuilder。

private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

看看这个build()方法做什么了。

public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);
}

构造器做的事情很简单,直接创建了DefaultSqlSessionFactory,其中还持有Configuration对象的引用。到此afterPropertiesSet()方法做的做的事情就结束了。

总结起来,就是创建了几个对象,依次是mybatis的核心类Configuration、spring和mybatis集成的事物工厂类SpringManagedTransactionFactory、mybatis的Environment类、mybatis的DefaultSqlSessionFactory类,同时还完成了对mybatis的xml文件解析,并将解析结果封装在Configuration类中。以下时序图不代表实际的过程。

执行onApplicationEvent方法

调用完上面的afterPropertiesSet方法之后,第二个被调用的就是onApplicationEvent方法,这个方法的调用时机是,spring容器初始化完成之后,该方法是接口ApplicationListener<ApplicationEvent>中的方法。

public void onApplicationEvent(ApplicationEvent event) {if (this.failFast && event instanceof ContextRefreshedEvent) {this.sqlSessionFactory.getConfiguration().getMappedStatementNames();}
}

调试发现this.failFast这个变量的值是false,所以这个方法不会执行,在此就不做重点分析了。

执行getObject方法

调用完上面的onApplicationEvent方法之后,第三个被调用的就是getObject方法,该方法是接口FactoryBean<SqlSessionFactory>中的方法。这个方法的调用时机是?首先需要简单介绍一下这个FactoryBean的作用。

一般情况下,Spring通过反射机制利用<bean>标签的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂,如果按照反射机制的方式,则需要在<bean>标签中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.beans.factory.FactoryBean<T>的工厂类接口,我们可以通过实现该接口定制实例化Bean的逻辑。

实现了FactoryBean<T>接口的Bean,不同于普通Bean,根据该Bean的ID从BeanFactory中获取的实际上是FactoryBean的getObject()返回的对象,而不是FactoryBean本身,如果要获取FactoryBean对象,请在id前面加一个&符号来获取。

通过这段对FactoryBean的简短介绍就知道,getObject()方法被调用时机是在spring实例化SqlSessionFactoryBean的过程中。那么调用的getObject()方法返回的是什么对象呢,看看方法内容:

public SqlSessionFactory getObject() throws Exception {if (this.sqlSessionFactory == null) {this.afterPropertiesSet();}return this.sqlSessionFactory;
}

从内容上看,是直接返回了一个SqlSessionFactory类的实例,这个sqlSessionFactory对象是在什么时候创建的呢?其实就是本文上面介绍的afterPropertiesSet()方法中,sqlSessionFactory引用的对象就是mybatis的DefaultSqlSessionFactory类的实例。至于这个类是什么作用,在哪儿用的,后面在作介绍。

上面两个标签的解析过程分析完成,现在有两个问题没有答案,第一个是basePackage基础包下面扫描出来的mapper接口怎么实例化的?第二个是这两个标签创建出来的对象怎么配合使用的?

整合之后实例化过程

在本系列的例子中,spring容器在启动的过程中,需要去实例化AuthUserServiceImpl服务类,

@Service
public class AuthUserServiceImpl implements IAuthUserService {@Resourceprivate AuthUserMapper authUserMapper;public AuthUserServiceImpl(){logger.info("创建 com.test.bean.AuthUserServiceImpl");}
}

这个类依赖了AuthUserMapper接口,在实例化AuthUserServiceImpl的过程中需要,首先去实例化这个AuthUserMapper接口,但是接口是不能被实例化的,接下来分析这个接口实现类的创建过程。以下时序图不代表实际执行过程。

对上面的图做一个简单的说明,spring在初始化的过程中,会去创建AuthUserServiceImpl类,创建完成之后,会进行属性赋值,AuthUserMapper这个mybatis接口就是AuthUserServiceImpl的一个属性,

首先根据这个mapper的名字从spring的BeanFactory中获取它的BeanDefinition,再从BeanDefinition中获取BeanClass,AuthUserMapper对应的BeanClass就是MapperFactoryBean,这是为什么呢?在上面分析的内容中提到过,也就是在创建MapperScannerConfigurer对象的时候设置的。

接着就是创建MapperFactoryBean对象了,创建完成之后,就需要对属性进行赋值,他有哪些属性需要赋值呢?这是在创建AuthUserMapper所对应BeanDefinition对象的时候决定的,回顾上面创建MapperScannerConfigurer对象的那部分内容就知道。其中有一个属性就是SqlSessionFactoryBean,要实例化这个对象,这个就需要用到下面这个标签了

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!-- 加载数据源 --><property name="dataSource" ref="dataSource"/><property name="mapperLocations" value="classpath*:mappers/*Mapper.xml"/>
</bean>

上面的内容也已经分析过这个SqlSessionFactoryBean的创建过程。

MapperFactoryBean对象的属性设置完成之后,就调用它的getObject()方法,来获取authUserMapper对应的实现类,从上面图中可以看出来,最后返回的就是一个代理类,这个代理类使用jdk的动态代理创建出来的。

return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(),new Class[]{this.mapperInterface}, mapperProxy);

这个MapperProxy类就是InvocationHandler的实现类:


public class MapperProxy<T> implements InvocationHandler, Serializable {public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else {MapperMethod mapperMethod = this.cachedMapperMethod(method);return mapperMethod.execute(this.sqlSession, args);}}
}

程序在调用authUserMapper对象的某个方法的时候,就会调用到MapperProxy对象的invoke()方法,去完成对数据库的操作。

最后附上一张类图,spring和mybatis整合过程中创建的类:


http://chatgpt.dhexx.cn/article/TNxZRRXp.shtml

相关文章

MyBatis架构原理详解

1. Mybatis架构设计 MyBatis 是 Java 生态中非常著名的一款 ORM 框架&#xff0c;目前在一线互联网大厂中应用广泛&#xff0c;Mybatis已经成为了一个必会框架。 Mybatis的功能架构分为三层&#xff1a; API接口层数据处理层基础支撑层 2. API接口层 接口层&#xff1a;主要…

springboot集成mybatis原理剖析

前言&#xff1a;本文主要基于springboot项目&#xff0c;从springboot的拓展角度来分析mybatis是如何与springboot集成的。 1、首先搭建springboot集成mybatis环境 1.1、maven pom文件依赖 <?xml version"1.0" encoding"UTF-8"?> <project x…

MyBatis原理分析(通俗易懂)

MyBatis原理分析 MyBatis工作流程简述原生MyBatis原理分析初始化工作解析配置文件配置类方式 执行SQLSqlSession API方式接口方式 真正掌握一个框架源码分析是少不了的~ 在讲解整合Spring的原理之前理解原生的MyBatis执行原理是非常有必要的 MyBatis工作流程简述 传统工作模…

【MyBatis学习笔记九】——MyBatis原理

一、Mybatis工作原理图 mybatis 原理图如下所示&#xff1a; 二、工作原理解析 mybatis应用程序通过SqlSessionFactoryBuilder从mybatis-config.xml配置文件&#xff08;也可以用Java文件配置的方式&#xff0c;需要添加Configuration&#xff09;来构建SqlSessionFactory&…

【mybatis原理工作原理】

文章目录 mybatis原理&#xff1a;mybatis缓存机制 mybatis原理&#xff1a; mybatis的工作原理就是&#xff1a;先封装sql&#xff0c;接着调用jdbc操作数据库&#xff0c;最后把数据库返回的表结果封装成java类。 通过代码实现jdbc查询操作&#xff1a; mybatis-config.xm…

mybatis原理(含图)

上面中流程就是MyBatis内部核心流程&#xff0c;每一步流程的详细说明如下文所述&#xff1a; &#xff08;1&#xff09;读取MyBatis的配置文件。mybatis-config.xml为MyBatis的全局配置文件&#xff0c;用于配置数据库连接信息。 &#xff08;2&#xff09;加载映射文件。映…

MyBatis原理分析

是什么? MyBatis 是一款优秀的持久层框架&#xff0c;它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO&#xff08;Plain Old Java …

Spring 整合 Mybatis 原理

目录 Mybatis的基本工作原理分析需要解决的问题Spring中Bean的产生过程解决问题解决方案FactoryBean Import总结优化 Mybatis的基本工作原理 在 Mybatis 中&#xff0c;我们可以使用一个接口去定义要执行 sql&#xff0c;简化代码如下&#xff1a;定义一个接口&#xff0c;Sel…

Springboot整合mybatis原理

文章目录 加载过程1、读取META-INF/spring.factories配置文件里需要自动装载的类2、解析MybatisAutoConfiguration类里的注解信息&#xff0c;将需要管理的Bean注册到Spring容器2.1 注册SqlSessionFactory&#xff0c;并根据mapper配置文件解析出dao与具体jdbc操作、resultMap与…

【mybatis原理】

mybatis mybatis原理mybatis框架分层架构核心接口和对象mapper接口与xml的映射mybatis执行过程mybatis执行时序图一级缓存和二级缓存一级缓存二级缓存 mybatis核心流程1、初始化阶段2、代理阶段3、数据读写阶段 mybatis如何获取数据源mybatis如何获取执行SQLMyBatis 如何执行 s…

Mybatis原理

文章目录 - 什么是Mybatis&#xff1f;- Mybaits的优点&#xff1a;- MyBatis框架的缺点&#xff1a;- MyBatis与Hibernate有哪些不同&#xff1f;- 架构MyBatis缓存一级缓存一级缓存和sqlsession之间的关系一级缓存的生命周期有多长&#xff1f;SqlSession 一级缓存的工作流程…

深入详解Mybatis的架构原理与6大核心流程

MyBatis 是 Java 生态中非常著名的一款 ORM 框架&#xff0c;目前在一线互联网大厂中应用广泛&#xff0c;Mybatis已经成为了一个必会框架。 如果你想要进入一线大厂&#xff0c;能够熟练使用 MyBatis 开发已经是一项非常基本的技能&#xff0c;同时大厂也更希望自己的开发人员…

总结Mybatis的原理

一、什么是Mybatis Mybatis是一个半ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;底层封装了JDBC&#xff0c;是程序员在开发时只需要关注SQL语句本身&#xff0c;不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。MyBatis 可以使用简单的 XM…

MyBatis基本工作原理介绍

1、MyBatis基本工作原理介绍 计算机的基本工作就是存储和计算&#xff0c;而MyBatis是存储领域的利器。MyBatis的基本工作原理就是&#xff1a;先封装SQL&#xff0c;接着调用JDBC操作数据库&#xff0c;最后把数据库返回的表结果封装成Java类。 2、MyBatis的核心流程介绍 m…

Mybatis 工作原理详解

目录 Mybatis持久层框架 结果集进行ORM映射 步骤解析 1、获取结果集及结果映射入口 2、开始ORM映射接口 3、数据库结果集解析&#xff0c;完成ORM映射 4、保存并获取ORM映射后的结果集 参数传递方式 顺序传参法 Param注解传参法 Map传参法 Java Bean传参法 mybat…

《深入理解mybatis原理》 MyBatis的架构设计以及实例分析

MyBatis是目前非常流行的ORM框架&#xff0c;它的功能很强大&#xff0c;然而其实现却比较简单、优雅。本文主要讲述MyBatis的架构设计思路&#xff0c;并且讨论MyBatis的几个核心部件&#xff0c;然后结合一个select查询实例&#xff0c;深入代码&#xff0c;来探究MyBatis的实…

【图片压缩】三个方法压缩图片体积

图片体积过大&#xff0c;会占用电脑过多的容量&#xff0c;也有可能无法发送给其他人。我们可以压缩图片体积来解决问题。分享三个方法压缩图片体积&#xff1a; 方法一&#xff1a;改变图片格式 首先是改变图片的格式&#xff0c;如果你不介意压缩之后的图片画质损失&#…

Windows不同压缩软件、压缩算法、压缩率详细对比测试与选择

上次写了图片压缩&#xff0c;这倒让我想起几年前看过的一个很有意思的东西 那就是这张鸭子图&#xff1a; 不过微信会压缩图片&#xff0c;你可以打开这个链接&#xff1a;http://2.im.guokr.com/F70Kn-4wz7aF5Yejf9W3g6kO4exDBqVEb0TumQmxy5MiAQAAEAEAAEpQ.jpg 来获取原图 …

为什么压缩图片和压缩

为什么要压缩图片&#xff1f; 表示图像需要大量的数据&#xff0c;但图像数据是高度相关的&#xff0c;或者说存在冗余(Redundancy)信息&#xff0c;去掉这些冗余信息后可以有效压缩图像&#xff0c;同时又不会损害图像的有效信息。 视网膜上有两种感光细胞&#xff0c;能够…

2款免费的图片压缩工具

今天写这篇文章的目的&#xff0c;主要是为大家介绍2款免费的图片压缩工具&#xff0c;在工作和学习中这也是一项必备的技能。比如当你遇到比较大的图片需要发送的时候&#xff0c;或者对图片的大小有强制要求的时候&#xff0c;这些小工具就派上了用场。 网上也有一些付费的压…