META-INF/spring.factories
配置文件里需要自動裝載的類MybatisAutoConfiguration
類里的注解信息,將需要管理的Bean注冊到Spring容器SqlSessionFactory
,并根據mapper配置文件解析出dao與具體jdbc操作、resultMap與實體類等的映射關系SqlSessionTemplate
類AutoConfiguredMapperScannerRegistrar
類來掃描被@Mapper
標注的類MapperFactoryBean.getObject()
方法來注入動態代理類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-locations: classpath:mapper/*.xml
@Mapper
注解@Mapper
public interface UserInfoDao {int insert(UserInfoDO userInfoDO)UserInfoDO getById(long id);int update(UserInfoDO userInfoDO);int delete(long id);
}
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>
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下流程。
MybatisAutoConfiguration
類里的注解信息,將需要管理的Bean注冊到Spring容器Spring boot?在《Springboot之Bean的加載過程》中講到將類解析成BeanDefinition
并最終實例化成Bean的過程,這里會向Spring容器注冊幾個重要的類:
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與具體實體類的映射等,并放到SqlSessionFactory
的Configuration
中緩存下來,在后續調用過程中會通過這些信息來匹配jdbc操作。
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);}}
AutoConfiguredMapperScannerRegistrar
類來掃描被@Mapper
標注的類該類負責遍歷被@Mapper標注的類,并將這些掃描到的類解析成BeanDefinition
注冊到Spring容器中,核心邏輯在registerBeanDefinitions
中,需要注意的一點是,在掃描到被@Mapper標注的類時,會將這些類解析成beanClass為MapperFactoryBean
的BeanDefinition,同時會告知Spring容器在將這個BeanDefinition
實例化成Bean時,需要注入SqlSessionFactory
和SqlSessionTemplate
對象,截圖如下:
從這里可以看出我們在代碼中注入的dao實際上是一個動態代理類,由MapperFactoryBean
這個FactoryBean
的getObject()
方法生成
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)
方法里,主要邏輯如下:
通過SqlSessionFactory
從DataSource
連接池中獲取sqlSession
,這里會先從一個ThreadLocal
中獲取,因為開啟了事務的話,sqlSession
會通過ThreadLocal
來傳遞,如果沒有開啟事務,則從連接池中獲取新的Session
通過反射來調用獲取到的sqlSession
對象(這里獲取到的是DefaultSqlSession
)的selectList
方法
以dao的接口名+方法名為key獲取之前解析到的元數據信息,包括對應的sql、返回類型等,debug截圖如下:
通過四大組件之一的Executor的實現類CachingExecutor
類(因為mybatis默認開啟緩存,所以會使用這個實現類)來執行jdbc操作,該類封裝了cache相關操作,先解析出該方法需要執行的sql,debug圖如下:
這里會首先去查詢是否開啟了二級緩存(需要在mapper文件里家在<cache/>配置,二級緩存是namespace粒度的),如果開啟了緩存,則會直接從緩存中查詢,debug圖如下:
spring mybatis原理?然后會查詢一級緩存(session粒度),如果沒有命中緩存則繼續后續操作,debug圖如下:
通過四大組件之一的statementHandler的實現類RoutingStatementHandler
類來執行CRUD操作,這個類主要是封裝類,不提供具體的實現,只是根據Executor的類型,創建不同的類型StatementHandler,默認創建帶有預編譯功能的PreparedStatementHandler
類,debug圖如下:
通過四大組件之一的ParameterHandler來拼接sql中的參數,debug圖如下:
通過四大組件之一的ResultSetHandler來處理返回值,將數據庫返回值綁定到對應的實體類,debug圖如下:
處理緩存信息、釋放資源等邏輯
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态