网站首页 编程语言 正文
本文将结合示例并阅读源码,分析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-30 if-else和switch的练习及区别比较
- 2022-10-14 element form表单数据未双向绑定
- 2024-01-31 linux下查看文件当下的所有文件的大小和查找大文件
- 2023-02-02 一文带你深入了解C++中的类型转换_C 语言
- 2022-09-28 C语言关于二叉树中堆的创建和使用整理_C 语言
- 2022-11-16 python中如何使用函数改变list_python
- 2022-09-20 linux shell字符串截取的详细总结(实用!)_linux shell
- 2022-10-07 Unity游戏开发实现场景切换示例_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同步修改后的远程分支