前言
注意:閱讀本文需要有一定的Spring和SpringBoot基礎
先上一個Mybatis-Spring官網鏈接,打開一個SSM整合的案例專案一起食用本文效果更佳哦,
官網上說的很清楚,要和 Spring 一起使用 MyBatis,需要在 Spring 應用背景關系中定義至少兩樣東西:一個SqlSessionFactory 和至少一個資料映射器類,
在 MyBatis-Spring 中,使用 SqlSessionFactoryBean來創建 SqlSessionFactory,
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
至于在SpringBoot中SqlSessionFactoryBean是怎么被創建并且被執行的,就不贅述了,感興趣的可以去看 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration#sqlSessionFactory 方法,簡單來說是利用的SpringBoot的自動裝配機制
需要更多Java知識點和大廠面試題的朋友可以點一點下方鏈接免費領取
鏈接:1103806531暗號:CSDN

SqlSessionFactoryBean是怎么創建SqlSessionFactory的?
SqlSessionFactory是MyBatis中的一個重要的物件,它是用來創建SqlSession物件的,而SqlSession用來操作資料庫的,我們通過SqlSessionFactoryBean的繼承體系可以看出,它實作了FactoryBean和InitialzingBean,這兩個類的作用我也不贅述了

我們直接來看重要的代碼
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
// afterPropertiesSet方法在Bean的生命周期中會被呼叫,這里為什么會手動呼叫一次我也不知道
// 或許為了防止getObject被提前呼叫,也可能是兼容SpringBoot和Spring整合的區別
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
// 一直往下追就是下面的build方法
this.sqlSessionFactory = buildSqlSessionFactory();
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
走到buildSqlSessionFactory方法,最后還是通過 SqlSessionFactoryBuilder#build 去構建的DefaultSqlSessionFactory
@MapperScan注解到底做了什么?
@MapperScan負責Spring和Mybatis整合時mapper的掃描和注冊
@Import(MapperScannerRegistrar.class)
@Import注解也是spring Framework提供的將普通javaBean注冊到容器中,該注解匯入了MapperScannerRegistrar 類來實作功能,我們看這個類的繼承結構
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware
它實作了ImportBeanDefinitionRegistrar,Spring中手動注冊bean的兩種方式:
- 實作ImportBeanDefinitionRegistrar
- 實作BeanDefinitionRegistryPostProcessor
需要注意的是,如果某一個Bean實作了BeanDefinitionRegistryPostProcessor或者ImportBeanDefinitionRegistrar介面,那我們在這個類中使用@Autowired或者@Value注解,我們會發現失效了,原因是,spring容器執行介面的方法時,此時還沒有去決議@Autowired或者@Value注解,如果我們要使用獲取組態檔屬性,可以通過原始方式,直接用IO讀取組態檔,然后得到Properties物件,然后再獲取配置值,
MapperScannerRegistrar是何方神圣?
這里會呼叫registerBeanDefinitions方法
ImportBeanDefinitionRegistrar在ConfigurationClassPostProcessor處理Configuration類期間被呼叫,用來生成該Configuration類所需要的BeanDefinition,而ConfigurationClassPostProcessor正實作了BeanDefinitionRegistryPostProcessor介面(所以支持mapper注冊成bean,并注入到spring容器中),
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(mapperScanAttrs, registry);
}
}
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
...
/**
* 這里會將basePackages下的mapper全部掃描成bd并實體化成bean
* 注意這里注冊的所有bean實際均為MapperFactoryBean[代理]
*/
scanner.doScan(StringUtils.toStringArray(basePackages));
}
我們接著看doScan實作的細節:
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 掃描出所有mybatis的mapper,此時的bd里的class還是自己
// 假設UserMapper,那么此時bd中的beanclass = UserMapper.class
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
...
} else {
// 對bd進行處理
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
...
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// bean的實際類是MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
...
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
這里特別要注意最后一行setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);Spring默認情況下的自動裝配級別為AUTOWIRE_NO,需要修改為AUTOWIRE_BY_TYPE按型別注入,這樣Spring就會通過set方法,并且再根據bean的型別幫我們注入屬性,比如后會說到的MapperFactoryBean中的sqlSessionFactory和sqlSessionTemplate,
Spring中自動裝配有四種
// 默認不注入,也是默認級別 = 0
public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;
// 按name = 1
public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
// 按type = 2
public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
// 按構造器 = 3
public static final int AUTOWIRE_CONSTRUCTOR = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;
// 還有一種已經不建議使用了,這里就不贅述自動裝配的細節了
為什么mybatis不用@AutoWired呢? 主要是解耦吧,可以不依賴Spring進行編譯,加了Spring的注解,那么就是強耦合了,
到了這一步,那么此時的mapper,我們可以認為都變成了bd加入到spring中了,那么下一步就是實體化了,那么我們就要來看看MapperFactoryBean了
MapperFactoryBean
因為MapperFactoryBean extends SqlSessionDaoSupport extends DaoSupport,DaoSupport implements InitializingBean ,所以在mfb實體化之后會執行afterPropertiesSet
@Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// Let abstract subclasses check their configuration.
checkDaoConfig();
// Let concrete implementations initialize themselves.
try {b
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
afterPropertiesSet其中checkDaoConfig()方法很重要,我們來看看,在每一個mfb實體化的時候,其實方法和sql是在實體化的時候就已經系結并且放在Map<String, MappedStatement> mappedStatements當中,而不是在你呼叫的時候才去系結,
protected void checkDaoConfig() {
super.checkDaoConfig();
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 在這一步,最后會呼叫一個parse決議,將所有的方法和sql陳述句系結
// 放進一個map當中,所以實際上當你執行一個mapper的方法時,底層其實是去一個map當中get
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();
}
}
}
this.mapperInterface這個實際上就是指的當前類,比如UserMapper在實體化的時候,這個mfb里面的mapperInterface就是它自己,因為mfb實作了FactoryBean,所以當你使用注解注入mapper的時候,此時的mapper是由getObject()方法產生的
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
getSqlSession()在和Spring整合中,拿到的一直是SqlSessionTemplate
org.mybatis.spring.support.SqlSessionDaoSupport#getSqlSession
// 這個SqlSession是由Spring管理,它會自動注入
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
最后通過SqlSession的getMapper去獲得代理類
// 底層呼叫的是MapperRegistry的getMapper方法
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);
}
}
說到這里,我們大概知道了mybatis的dao介面是怎么變成代理類并且放入Spring容器中的程序了,
代理類執行邏輯
放入到Spring容器后,我們就可以注入代理物件進行資料庫操作了,由于介面方法會被代理邏輯攔截,所以下面我們把目光聚焦在代理邏輯上面,看看代理邏輯會做哪些事情,
public 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);
}
// 從快取中獲取 MapperMethod 物件,若快取未命中,則創建 MapperMethod 物件
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 這里的sqlSession在不同情況下不同,比如有defaultSqlSession 和 sqlSessionManger
// 最后還是呼叫的sqlSession的方法,但是sqlSessionManger解決自動關閉問題和執行緒安全問題
// 呼叫 execute 方法執行 SQL
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
/**
* 創建映射方法
*/
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
// 創建 SqlCommand 物件,該物件包含一些和 SQL 相關的資訊
this.command = new SqlCommand(config, mapperInterface, method);
// 創建 MethodSignature 物件,由類名可知,該物件包含了被攔截方法的一些資訊
this.method = new MethodSignature(config, mapperInterface, method);
}
/**
* 該物件包含一些和 SQL 相關的資訊
* 通過它可以找到MappedStatement
* 我們可以看到熟悉的 Invalid bound statement (not found) 例外
*/
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
// 決議 MappedStatement
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
// 檢測當前方法是否有對應的 MappedStatement
if (ms == null) {
if(method.getAnnotation(Flush.class) != null){
name = null;
type = SqlCommandType.FLUSH;
} else {
// 熟悉的例外
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
前面已經分析了 MapperMethod 的初始化程序,現在 MapperMethod 創建好了,那么,接下來要做的事情是呼叫 MapperMethod 的 execute 方法,執行 SQL,后面的就不分析了,看到這,你應該知道Mybatis是怎么實作的和Spring的整合流程了,
這篇文章到這就結束啦,喜歡的話就給個贊 + 收藏 + 關注吧!🤓 有什么想看的歡迎留言!!!
我這邊也整理了一份 架構師全套視頻教程和關于java的系統化資料,包括java核心知識點、面試專題和20年最新的互聯網真題、電子書等都有,有需要的朋友可以點一點下方鏈接免費領取!
鏈接:1103806531暗號:CSDN


轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/206190.html
標籤:其他
上一篇:從易到難,我回答了面試官的JVM奪命連環10問,結果太酸爽了!
下一篇:Java中的方法
