Spring boot,Springboot整合mybatis原理

 2023-10-31 阅读 32 评论 0

摘要:文章目錄加載過程1、讀取META-INF/spring.factories配置文件里需要自動裝載的類2、解析MybatisAutoConfiguration類里的注解信息,將需要管理的Bean注冊到Spring容器2.1 注冊SqlSessionFactory,并根據mapper配置文件解析出dao與具體jdbc操作、resultMap與實體類

文章目錄

  • 加載過程
    • 1、讀取META-INF/spring.factories配置文件里需要自動裝載的類
    • 2、解析MybatisAutoConfiguration類里的注解信息,將需要管理的Bean注冊到Spring容器
      • 2.1 注冊SqlSessionFactory,并根據mapper配置文件解析出dao與具體jdbc操作、resultMap與實體類等的映射關系
      • 2.2 注冊實現了CRUD操作的SqlSessionTemplate
      • 2.3 注冊AutoConfiguredMapperScannerRegistrar類來掃描被@Mapper標注的類
    • 3 在注入dao時,觸發該dao對應的MapperFactoryBean.getObject()方法來注入動態代理類
  • 調用過程

我們在Springboot中使用mybatis時只需要簡單的幾個配置就可以了:1、在pom文件中引入mybatis的starter。2、配置數據庫連接池。3、在Springboot配置文件里配置mybatis相關參數。4、編寫自己的dao以及mapper配置文件。那么在我們Spring項目里注入dao并進行CRUD時,mybatis是怎么被Springboot加載以及怎么執行jdbc操作的?這里先貼出配置:

  • 配置datasource,比如用druid鏈接池配置
spring:datasource:#druid相關配置type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.jdbc.Driver#配置數據庫連接druid:url: jdbc:mysql://localhost:3306/test-db?useUnicode=true&allowMultiQueries=true&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456initial-size: 10max-active: 100min-idle: 10max-wait: 60000pool-prepared-statements: truemax-pool-prepared-statement-per-connection-size: 20time-between-eviction-runs-millis: 60000min-evictable-idle-time-millis: 300000validation-query: SELECT 1 FROM DUALtest-while-idle: truetest-on-borrow: falsetest-on-return: falseconnectionInitSqls: set names utf8mb4
  • 配置mybatis參數,指定mapper文件路徑
mybatis:mapper-locations: classpath:mapper/*.xml
  • 按業務需要定義dao接口,并加上@Mapper注解
@Mapper
public interface UserInfoDao {int insert(UserInfoDO userInfoDO)UserInfoDO getById(long id);int update(UserInfoDO userInfoDO);int delete(long id);
}
  • 配置mapper文件,寫好sql與dao接口的映射關系,其中namespace是對應的dao接口完整類名
<?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.hj.dao.UserInfoDao"><resultMap id="BaseResultMap" type="com.hj.DO.UserInfoDO"><id column="id" property="id"/><result column="userId" property="userId"/><result column="name" property="name"/></resultMap><insert id="insert" parameterType="com.hhdd.DO.UserInfoDO">insert ...</insert><select id="getById" resultMap="BaseResultMap" parameterType="java.lang.Long">select ...</select><update id="update" parameterType="com.hj.DO.UserInfoDO">update ...</update><delete id="delete">delete ...</delete></mapper>

加載過程

1、讀取META-INF/spring.factories配置文件里需要自動裝載的類

mybatis-spring-boot-starter依賴的作用實際是提供一個pom文件,該pom文件內有mybatis需要的所有依賴,其中比較重要的有mybatis-spring-boot-autoconfigure,如下圖:
在這里插入圖片描述

mybatis-spring-boot-autoconfigure這個包內包含META-INF/spring.factories配置文件,Springboot就是通過該配置文件內定義的啟動類來拉起mybatis的,如下圖:
在這里插入圖片描述

而Springboot觸發讀取這個配置文件的邏輯在@EnableAutoConfiguration注解上@Import注解引入的AutoConfigurationImportSelector.class類的selectImports方法里,有興趣的可以在這個方法里打個斷點debug下流程。

2、解析MybatisAutoConfiguration類里的注解信息,將需要管理的Bean注冊到Spring容器

Spring boot?在《Springboot之Bean的加載過程》中講到將類解析成BeanDefinition并最終實例化成Bean的過程,這里會向Spring容器注冊幾個重要的類:

2.1 注冊SqlSessionFactory,并根據mapper配置文件解析出dao與具體jdbc操作、resultMap與實體類等的映射關系

代碼如下:

  @Bean@ConditionalOnMissingBeanpublic 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()));}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);...if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {factory.setMapperLocations(this.properties.resolveMapperLocations());}return factory.getObject();}

SqlSessionFactory的實例化是通過SqlSessionFactoryBean.getObject()實現的,該類會被注入DataSource對象(負責管理數據庫連接池,Session指的是一次會話,而這個會話是在DataSource提供的Connection上進行的),SqlSessionFactory.getObject()方法里會根據我們mybatis相關配置(比如上面的mybatis.mapper-locations配置)找到并解析我們的mapper文件,解析出sql與dao方法里的映射、ResultMap與具體實體類的映射等,并放到SqlSessionFactoryConfiguration中緩存下來,在后續調用過程中會通過這些信息來匹配jdbc操作。

2.2 注冊實現了CRUD操作的SqlSessionTemplate

該類實現了我們常用的CRUD操作,在執行CRUD時,會通過SqlSessionFactory對象獲取Session來操作,所以會持有SqlSessionFactory對象

  @Bean@ConditionalOnMissingBeanpublic SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {ExecutorType executorType = this.properties.getExecutorType();if (executorType != null) {return new SqlSessionTemplate(sqlSessionFactory, executorType);} else {return new SqlSessionTemplate(sqlSessionFactory);}}

2.3 注冊AutoConfiguredMapperScannerRegistrar類來掃描被@Mapper標注的類

該類負責遍歷被@Mapper標注的類,并將這些掃描到的類解析成BeanDefinition注冊到Spring容器中,核心邏輯在registerBeanDefinitions中,需要注意的一點是,在掃描到被@Mapper標注的類時,會將這些類解析成beanClass為MapperFactoryBean的BeanDefinition,同時會告知Spring容器在將這個BeanDefinition實例化成Bean時,需要注入SqlSessionFactorySqlSessionTemplate對象,截圖如下:
在這里插入圖片描述

從這里可以看出我們在代碼中注入的dao實際上是一個動態代理類,由MapperFactoryBean這個FactoryBeangetObject()方法生成

3 在注入dao時,觸發該dao對應的MapperFactoryBean.getObject()方法來注入動態代理類

Mybatis、MapperFactoryBean.getObject()邏輯由BeanFactory的getBean(string beanName)觸發,getObject()代碼如下:

    public T getObject() throws Exception {return this.getSqlSession().getMapper(this.mapperInterface);}

getSqlSession()獲取的是SqlSessionTemplate對象,this.mapperInterface就是我們的dao層接口,比如開頭demo里的被@Mapper標注的UserInfoDao,最終會通過MapperProxyFactory來生成動態代理類,代碼如下:

public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}

這里的參數SqlSession就是SqlSessionTemplate對象,可以看出是通過基于接口的JDK的Proxy來生成動態代理,在我們代碼中進行CRUD時候,最后都會通過MapperProxy類(該類實現了InvocationHandler接口)的invoke(Object proxy, Method method, Object[] args)方法來處理.

調用過程

通過上面的加載過程,我們了解到最后注入到業務代碼的是一個動態代理類,我們再看下這個動態代理類的調用過程,主要邏輯在MapperProxy類(該類實現了InvocationHandler接口)的invoke(Object proxy, Method method, Object[] args)方法,我這里以select請求為例,代碼如下:

@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (isDefaultMethod(method)) {return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}final MapperMethod mapperMethod = cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);}

SqlSessionTemplate在加載階段會根據配置的mapper文件解析出對應的映射關系,并封裝好元數據信息(包括需要執行的sql、返回類型等),而MapperMethod的構造器被調用時會通過SqlSessionTemplate里的映射關系拿到這些元數據信息并封裝成SqlCommand對象,在執行jdbc操作時會通過SqlCommand來獲取jdbc信息來執行后續邏輯,debug截圖如下:
在這里插入圖片描述

最終通過SqlSessionTemplate類來實現jdbc操作,debug圖如下:
在這里插入圖片描述

Mybatis框架、而sqlSession.selectOne方法會調用到SqlSessionTemplate的內部類SqlSessionInterceptor.invoke(Object proxy, Method method, Object[] args)方法里,主要邏輯如下:

  1. 通過SqlSessionFactoryDataSource連接池中獲取sqlSession,這里會先從一個ThreadLocal中獲取,因為開啟了事務的話,sqlSession會通過ThreadLocal來傳遞,如果沒有開啟事務,則從連接池中獲取新的Session

  2. 通過反射來調用獲取到的sqlSession對象(這里獲取到的是DefaultSqlSession)的selectList方法

  3. 以dao的接口名+方法名為key獲取之前解析到的元數據信息,包括對應的sql、返回類型等,debug截圖如下:
    在這里插入圖片描述

  4. 通過四大組件之一的Executor的實現類CachingExecutor類(因為mybatis默認開啟緩存,所以會使用這個實現類)來執行jdbc操作,該類封裝了cache相關操作,先解析出該方法需要執行的sql,debug圖如下:
    在這里插入圖片描述

這里會首先去查詢是否開啟了二級緩存(需要在mapper文件里家在<cache/>配置,二級緩存是namespace粒度的),如果開啟了緩存,則會直接從緩存中查詢,debug圖如下:
在這里插入圖片描述

spring mybatis原理?然后會查詢一級緩存(session粒度),如果沒有命中緩存則繼續后續操作,debug圖如下:
在這里插入圖片描述

  1. 通過四大組件之一的statementHandler的實現類RoutingStatementHandler類來執行CRUD操作,這個類主要是封裝類,不提供具體的實現,只是根據Executor的類型,創建不同的類型StatementHandler,默認創建帶有預編譯功能的PreparedStatementHandler類,debug圖如下:
    在這里插入圖片描述

  2. 通過四大組件之一的ParameterHandler來拼接sql中的參數,debug圖如下:
    在這里插入圖片描述

  3. 通過四大組件之一的ResultSetHandler來處理返回值,將數據庫返回值綁定到對應的實體類,debug圖如下:
    在這里插入圖片描述

  4. 處理緩存信息、釋放資源等邏輯

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://808629.com/167474.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 86后生记录生活 Inc. 保留所有权利。

底部版权信息