網站首頁 編程語言 正文
本文將結合示例并閱讀源碼,分析mybatis與spring的集成原理,將重點分析@MapperScan注解掃描、插件注冊等內容。
示例代碼
Configuration配置類
@MapperScan(basePackages = {"org.net5ijy.mybatis.test.mapper"})
@Configuration
public class MybatisConfig {
// bean config
}
數據源配置
使用的是Druid數據源。
@Bean
public DataSource druidDataSource()
throws IOException, InvocationTargetException,
IllegalAccessException, IntrospectionException,
InstantiationException, ClassNotFoundException {
DruidDataSource dataSource = new DruidDataSource();
// 讀取數據源配置文件
Properties properties = new Properties();
properties.load(
MybatisConfig.class.getClassLoader().getResourceAsStream("jdbc.properties"));
// 設置數據源核心參數
dataSource.setUsername(properties.getProperty("username"));
dataSource.setPassword(properties.getProperty("password"));
dataSource.setDriverClassName(properties.getProperty("driver"));
dataSource.setUrl(properties.getProperty("url"));
Set<Object> keys = properties.keySet();
for (Object key : keys) {
String k = (String) key;
if (k.startsWith("druid.")) {
String propertyName = k.substring(6);
String propertyValue = properties.getProperty(k);
if (propertyName.equals("proxyFilters")) {
dataSource.setProxyFilters(createFilters(propertyValue));
} else {
PropertyDescriptor propertyDescriptor =
new PropertyDescriptor(propertyName, DruidDataSource.class);
Method writeMethod = propertyDescriptor.getWriteMethod();
Class<?> type = writeMethod.getParameterTypes()[0];
Object v = transferValue(type, propertyValue);
writeMethod.invoke(dataSource, v);
}
}
}
return dataSource;
}
SqlSessionFactory配置
SqlSessionFactoryBean實現了FactoryBean接口。
@Bean
public SqlSessionFactoryBean sessionFactory(@Autowired DataSource dataSource) {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
// 設置數據源
sessionFactoryBean.setDataSource(dataSource);
// 設置mapper xml配置文件路徑
sessionFactoryBean.setMapperLocations(new ClassPathResource("mapper/BlogMapper.xml"));
return sessionFactoryBean;
}
SqlSessionTemplate配置(可選)
@Bean
public SqlSession session(@Autowired SqlSessionFactory sessionFactory) {
return new SqlSessionTemplate(sessionFactory);
}
Mapper接口
正常編寫。
Mapper xml配置文件
正常編寫。
jdbc參數配置
jdbc.properties配置文件:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis_source_analysis
username=root
password=123456
druid.proxyFilters=stat
druid.maxActive=20
druid.initialSize=1
druid.maxWait=60000
druid.minIdle=1
druid.timeBetweenEvictionRunsMillis=60000
druid.minEvictableIdleTimeMillis=300000
druid.validationQuery=SELECT 1 FROM DUAL
druid.testWhileIdle=true
druid.testOnBorrow=false
druid.testOnReturn=false
druid.poolPreparedStatements=true
druid.maxOpenPreparedStatements=20
druid.defaultAutoCommit=false
測試代碼
public class BlogMapper3Test {
private BlogMapper blogMapper;
@Before
public void before() {
// 創建ApplicationContext
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(MybatisConfig.class);
// 獲取mapper對象
this.blogMapper = applicationContext.getBean(BlogMapper.class);
}
@Test
public void testInsertBlog() {
Blog blog = new Blog();
blog.setTitle("spring學習");
blog.setContent("spring深入 - 源碼分析");
int rows = this.blogMapper.insertBlog(blog);
System.out.println(rows);
System.out.println(blog.getId());
}
@Test
public void testSelectBlogById() {
Blog blog = this.blogMapper.selectBlogById(1);
System.out.println(blog);
}
@Test
public void testSelectBlogByBlogSearchParameter() throws ParseException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
List<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
Blog blog = new Blog();
blog.setContent("mybatis源碼分析");
blog.setTitle("mybatis");
BlogSearchParameter parameter = new BlogSearchParameter();
parameter.setId(1);
parameter.setIds(ids);
parameter.setCreateTime(format.parse("2020-01-01 00:00:00"));
parameter.setBlog(blog);
List<Blog> list = this.blogMapper.selectBlogByParameter(parameter);
for (Blog b : list) {
System.out.println(b);
}
}
}
重點分析的內容
- @MapperScan注解掃描Mapper接口的原理
- 插件注冊的原理
- 與獨立運行時的不同點
SqlSessionFactoryBean類
這個類實現了FactoryBean接口,是spring工廠模式獲取bean的方式,使用getObject()方法獲取bean并放入bean factory中。
FactoryBean接口:
public interface FactoryBean<T> {
T getObject() throws Exception;
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
SqlSessionFactoryBean類提供了絕大多數mybatis配置的入口。
可以配置數據源:
sessionFactoryBean.setDataSource(dataSource);
可以配置mapper xml配置路徑:
sessionFactoryBean.setMapperLocations(new ClassPathResource("mapper/BlogMapper.xml"));
可以配置主配置文件路徑:
void setConfigLocation(Resource configLocation);
可以配置枚舉類型處理器:
void setDefaultEnumTypeHandler(Class<? extends TypeHandler> defaultEnumTypeHandler);
可以配置插件:
void setPlugins(Interceptor... plugins);
可以配置事務管理器:
void setTransactionFactory(TransactionFactory transactionFactory);
可以配置別名:
void setTypeAliases(Class<?>... typeAliases);
void setTypeAliasesPackage(String typeAliasesPackage);
可以配置類型處理器:
void setTypeHandlers(TypeHandler<?>... typeHandlers);
SqlSessionTemplate類
實現SqlSession接口,內部維護著一個SqlSessionFactory對象和一個SqlSession的代理對象,所有的SQL執行都是使用這個SqlSession代理對象來做的。
構造方法
看一下構造方法:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType,
new MyBatisExceptionTranslator(
sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 這里創建了一個SqlSession的代理
// 代理邏輯在SqlSessionInterceptor中
// SqlSessionInterceptor是SqlSessionTemplate的內部類,可以訪問SqlSessionTemplate的所有成員
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
SqlSessionInterceptor類
前面已經說明,SqlSessionInterceptor實現了SqlSessionTemplate的sqlSessionProxy的代理邏輯,看一下invoke代碼:
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 為了解決DefaultSqlSession的非線程安全問題
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
// 調用查詢方法
Object result = method.invoke(sqlSession, args);
// 判斷手動提交事務
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
// 這里關閉連接
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
// Checks if SqlSession passed as an argument is managed by Spring TransactionSynchronizationManager
// If it is not, it closes it, otherwise it just updates the reference counter and lets Spring call
// the close callback when the managed transaction ends
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
if ((holder != null) && (holder.getSqlSession() == session)) {
holder.released();
} else {
session.close();
}
}
// SqlSessionUtils.getSqlSession(SqlSessionFactory, ExecutorType, PersistenceExceptionTranslator)
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory,
ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
// TransactionSynchronizationManager類:
// Central delegate that manages resources and transaction synchronizations per thread.
// To be used by resource management code but not by typical application code.
// 這是spring-tx提供的一個工具類,用于管理資源和事務的同步
// 底層使用ThreadLocal實現
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 獲取當前線程上的SqlSession
// 第一次執行是沒有的
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
// 如果沒有就open一個,內部代碼已經分析過
// 返回的是一個DefaultSqlSession對象
session = sessionFactory.openSession(executorType);
// 注冊到當前線程上
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
// 這里記錄一下openSession方法,因為此時使用的事務管理器和之前不同,其余內容都一樣
private SqlSession openSessionFromDataSource(
ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 這里使用的是SpringManagedTransactionFactory類型
// 此處涉及到spring的事務管理,后續深入分析補充
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@MapperScan注解掃描原理
之前的 Autowired注入Service變成了biaomidou的Mapper代理 一文的 MapperScan注解 章節對此問題做過詳細分析,此處不再做分析。
https://blog.csdn.net/xuguofeng2016/article/details/120515536
執行SQL查詢的流程
MapperFactoryBean類和FactoryBean接口
FactoryBean接口是spring工廠模式創建bean的方式,spring在創建完FactoryBean的實現類對象后,會調用getObject()方法來獲取真正的bean對象,然后將這個對象放入factory中:
public interface FactoryBean<T> {
// 使用這個方法獲取bean對象
T getObject() throws Exception;
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
看一下MapperFactoryBean的getObject()方法:
public T getObject() throws Exception {
// 獲取到SqlSession成員變量,用他來getMapper
// this.mapperInterface也是成員變量,就是我們的mapper接口
return getSqlSession().getMapper(this.mapperInterface);
}
getSqlSession()獲取到的是MapperFactoryBean持有的sqlSessionTemplate對象,這個是spring幫忙注入進來的。
getMapper(Class)流程
入口在SqlSessionTemplate.getMapper(Class)中:
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
進入到Configuration.getMapper()方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
進入到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源碼分析_mapper接口源碼分析 一文中看到過,內部的邏輯不再分析,不過這里有一個問題,knownMappers是什么時候put元素的?
knownMappers的初始化
在 mybatis源碼分析_mapper接口源碼分析 一文中,我們了解到XMLMapperBuilder.parse方法調用bindMapperForNamespace()方法,最后會調用MapperRegistry的addMapper(Class)方法將mapper接口的方法轉為Statement保存到Configuration中。
在spring環境中是如何處理的?
還是要回到SqlSessionFactoryBean的getObject()方法,我們之前分析過這個類,但是沒有展開分析getObject()方法,此處看一下這個方法:
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
public void afterPropertiesSet() throws Exception {
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
// 讀取configuration配置,是null分支進不來
if (this.configuration != null) {
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
// 從配置的主配置文件路徑解析配置,是null分支進不來
xmlConfigBuilder =
new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
// 別名包配置解析,略
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
.filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
.filter(clazz -> !clazz.isMemberClass())
.forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
// 類型別名配置解析,略
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
});
}
// 插件在這里,代碼比較熟悉
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
});
}
// 解析類型處理器
if (hasLength(this.typeHandlersPackage)) {
scanClasses(this.typeHandlersPackage, TypeHandler.class)
.stream().filter(clazz -> !clazz.isAnonymousClass())
.filter(clazz -> !clazz.isInterface())
.filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
.forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}
// 解析類型處理器
if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
});
}
// 默認的枚舉類型處理器
targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);
// 腳本
if (!isEmpty(this.scriptingLanguageDrivers)) {
Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
targetConfiguration.getLanguageRegistry().register(languageDriver);
});
}
Optional.ofNullable(this.defaultScriptingLanguageDriver)
.ifPresent(targetConfiguration::setDefaultScriptingLanguage);
if (this.databaseIdProvider != null) {
try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
// 分支進不來
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
// 這里是重點
// 這里在解析mapper xml配置文件
// 就是之前使用setMapperLocations(...)方法配置的
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
// ...
} else {
for (Resource mapperLocation : this.mapperLocations) {
try {
// 這里就比較熟悉了
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
// 這里調用parse()方法
// 這樣就和之前的分析連上了
// OK
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
}
}
} else {
// ...
}
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
查詢流程
和 mybatis源碼分析_mapper接口源碼分析 一文中的分析基本一致,只是最后使用的是SqlSessionTemplate的方法,而不是DefaultSqlSession的方法,而SqlSessionTemplate內部又是使用一個SqlSession代理做的,代理的實現邏輯在SqlSessionInterceptor類中,前文做過分析,此處不再記錄。
原文鏈接:https://blog.csdn.net/xuguofeng2016/article/details/124832716
相關推薦
- 2022-07-12 weui省市區三級聯動
- 2022-05-06 SQL語句獲取表結構
- 2022-08-21 golang字符串本質與原理詳解_Golang
- 2022-04-23 小程序云函數解析encryptedData
- 2022-06-01 Android實現簡單的照相功能_Android
- 2023-06-18 詳解Go語言中make和new的區別_Golang
- 2023-10-30 Spring的BeanFactory與FactoryBean的區別
- 2023-11-13 linux平臺下ZeroMQ zmq(C++)編譯安裝以及調用
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支