springboot整合mybatis以及mybatis源码分析
1、@ComponentScan basePackages与value: 用于指定包的路径,进行扫描 basePackageClasses: 用于指定某个类的包的路径进行扫描 nameGenerator: bean的名称的生成器 useDefaultFilters: 是否开启对@Component,@Repository,@Service,@Controller的类进行检测 url: https://blog.csdn.net/u012326462/article/details/82765485
2、@ImportResource注解 用于导入 Spring 的 xml 配置文件,让该配置文件中定义的 bean 对象加载到Spring容器中。 参考链接:https://blog.csdn.net/lzb348110175/article/details/105148214
3、 引入依赖
<dependency> <groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId> </dependency>
在上述BeanFactory的后置处理器中就会读取mybatis-spring-boot-autoconfigure-***.jar包中的spring.factories文件 /org/mybatis/spring/boot/mybatis-spring-boot-autoconfigure/2.1.0/mybatis-spring-boot-autoconfigure-2.1.0.jar!/META-INF/spring.factories org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
<!-- 通过扫描的模式,扫描目录在mapper目录下,所有的mapper都继承SqlMapper接口的接口, 这样一个bean就可以了-->
@MapperScan和@Mapper都可以,@Mapper每个接口都要写 为什么没有加@MapperScan注解也可以扫描mapper接口 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.hsjry.convert.dal.dao.mapper.**" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean>
扫描调用栈 1.org.mybatis.spring.annotation.MapperScannerRegistrar#registerBeanDefinitions(org.springframework.core.annotation.AnnotationAttributes, org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.String)
2.org.mybatis.spring.annotation.MapperScannerRegistrar#registerBeanDefinitions(org.springframework.core.annotation.AnnotationAttributes, org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.String) 声明要注册的对象 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
3. 向这个注册中心注册一个新的 bean 定义 要用到BeanDefinitionRegistry org.springframework.beans.factory.support.BeanDefinitionRegistry#registerBeanDefinition
4.org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry
5.org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan
6.org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan @return 注册的 bean set集 Set<BeanDefinitionHolder>
7.org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
8.ClassPathMapperScanner extends ClassPathBeanDefinitionScanner
9.扫描到mapper接口了 org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions
10.ClassPathMapperScanner#processBeanDefinitions // 处理BeanDefinitions // 入参的beanDefinitions是从mapper接口产生的 private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); // the mapper interface is the original class of the bean // but, the actual class of the bean is MapperFactoryBean // 是为了创建mapperFactoryBean对象时,给mapperInterface赋值 definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // 设置beanClass = mapperFactoryBean.class definition.setBeanClass(this.mapperFactoryBean.getClass()); ....... // 按类型注入,作用是在创建MapperFactoryBean对象时,通过set方法按类型实现装配 definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } } }
11.org.mybatis.spring.mapper.MapperFactoryBean#getObject public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> 很多都是在spring做扩展
12.org.apache.ibatis.session.SqlSession#getMapper interface
13.org.mybatis.spring.SqlSessionTemplate#getMapper
14.org.apache.ibatis.session.Configuration#getMapper 这个就是SqlSession接口的方法
15.org.apache.ibatis.binding.MapperRegistry#getMapper
16.org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
17.org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy<T>),在这个方法中,使用JDK动态代理,创建代理对象,该代理对象就是我们Service层的代码中注入的对象 dao层接口。
二.MyBatis的SqlSession是非线程安全的,那Spring的SqlSessionTemplate是怎么解决的这个问题?
1.org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke
2.org.mybatis.spring.SqlSessionUtils#getSqlSession(org.apache.ibatis.session.SqlSessionFactory, org.apache.ibatis.session.ExecutorType, org.springframework.dao.support.PersistenceExceptionTranslator):
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
//从事务同步管理器中取sqlSession
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
//有就返回 if (session != null) { return session; } LOGGER.debug(() -> "Creating a new SqlSession");
//没有就创建 session = sessionFactory.openSession(executorType);
//放到事务同步管理器中 registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; }
MyBatis 方法调用路由到从 Spring 的事务管理器获得的正确 SqlSession 事务同步管理器可以认为是一个ThreadLocal的map,这样同一个线程在使用SqlSessionTemplate多次对数据库操作的时候,取到的是同一个SqlSession;而不同的线程取到的是不同的SqlSession,所以说SqlSessionTemplate是线程安全的。
三 MyBatis怎么跟Spring声明式事务配合完成的数据库事务内的增删改查操作?
1.设置数据库连接的自动提交为false
2.通过这个数据库连接进行sql语句执行
3.异常回滚或者提交 以上三步中的1和3是Spring声明式事务做的,2是MyBatis做的,它们是怎么配合的呢?其实只需要保证1、2、3这三步中获取到的是同一个数据库连接就好,其实也是通过事务同部管理器这个ThreadLocal的map来实现的 1.org.springframework.transaction.interceptor.TransactionInterceptor#invoke>>
TransactionInterceptor extends TransactionAspectSupport
//存在在threadLocal里面去
org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction 》》
org.springframework.transaction.interceptor.TransactionAspectSupport.TransactionInfo#bindToThread
2.org.mybatis.spring.SqlSessionUtils#getSqlSession(org.apache.ibatis.session.SqlSessionFactory, org.apache.ibatis.session.ExecutorType, org.springframework.dao.support.PersistenceExceptionTranslator)
去spring 管理的事务同步管理器中拿sqlSession 拿不到就拿传进来的SqlSessionFactory去开一个session org.apache.ibatis.session.SqlSessionFactory#openSession(org.apache.ibatis.session.ExecutorType) >>> org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession(org.apache.ibatis.session.ExecutorType)>>>
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource>>>
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null; try { final Environment environment = configuration.getEnvironment();
//创建的是 就是TransactionFactory实现类org.apache.ibatis.transaction.managed.ManagedTransactionFactory
//从环境配置中拿到的是org.apache.ibatis.transaction.TransactionFactory final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// autoCommit是false
//new org.mybatis.spring.transaction.SpringManagedTransaction 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();
}
}
3.org.mybatis.spring.transaction.SpringManagedTransaction#getConnection>>
org.mybatis.spring.transaction.SpringManagedTransaction#openConnection>>
org.springframework.jdbc.datasource.DataSourceUtils#getConnection>>
org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection
四.springboot整合mybatis是怎么做到不用配置mybatis-config.xml的呢?
1.https://mybatis.org/mybatis-3/configuration.html mybatis-config.xml属性参考官网文档
2.用mybatis-config.xml配置文件 org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.Reader, java.lang.String, java.util.Properties)》》
//解析配置文件得到Configuration对象,得到DefaultSqlSessionFactory对象
org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)》》
org.apache.ibatis.builder.xml.XMLConfigBuilder#parse》》
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration 这里的顺序有依赖关系 properties先解析 后面environments,databaseIdProvider都可以用到
properties》settings》typeAliases》plugins》objectFactory》objectWrapperFactory》reflectorFactory》environments》databaseIdProvider》typeHandlers》mappers
3.springboot自动配置 引入依赖 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> 要使配置生效两种方式
1、@EnableConfigurationProperties(MybatisProperties.class)org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration @EnableConfigurationProperties 注解的作用是:让使用了 @ConfigurationProperties 注解的类生效,并且将该类注入到 IOC 容器中,交由 IOC 容器进行管理
2、使用 @ConfigurationProperties + @Component 注解 用于自定义默认设置的配置对象。如果指定了 {@link #configLocation},则 * 不使用此属性
@NestedConfigurationProperty private Configuration configuration; 表示 {@link ConfigurationProperties} 对象中的字段应该被视为 * 如果它是嵌套类型。此注释与实际绑定 * 进程无关,但它被 {@code spring-boot-configuration-processor} 用作提示 * 字段未绑定为单个值。指定时,*为该字段创建一个嵌套组,并获取其类型。 * <p> * 这对collections and maps没有影响,因为这些类型是自动识别的 前缀是mybatis加@NestedConfigurationProperty 我们就可以表明它是嵌套组了 可以在配置设置这个对象里面的值
mybatis:
configuration:
cache-enabled:
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
提示应该在其他指定的自动配置类之后应用 {@link EnableAutoConfiguration 自动配置} 这个注解表示DataSourceAutoConfiguration MybatisLanguageDriverAutoConfiguration要在MybatisAutoConfiguration 后执行 org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration》》
org.springframework.boot.autoconfigure.jdbc.DataSourceProperties 前缀是spring.datasource 动态代理
spring整合mybatis的debug的步骤:
org.apache.ibatis.session.defaults.DefaultSqlSession@14d6f58f
org.apache.ibatis.session.SqlSession#insert(java.lang.String, java.lang.Object)
org.apache.ibatis.binding.MapperProxy#invoke》》
org.apache.ibatis.binding.MapperMethod#execute》》
org.mybatis.spring.SqlSessionTemplate#insert(java.lang.String, java.lang.Object)》》
org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke》》
org.apache.ibatis.session.defaults.DefaultSqlSession#insert(java.lang.String, java.lang.Object)》》也是通过代理生成的,传统的操作是直接工厂模式new
org.apache.ibatis.session.defaults.DefaultSqlSession#update(java.lang.String, java.lang.Object)》》
org.apache.ibatis.plugin.Plugin#invoke》》
org.apache.ibatis.executor.CachingExecutor#update》》这里面不用创建缓存只有query才需要
org.apache.ibatis.executor.BaseExecutor#update》》
org.apache.ibatis.executor.BaseExecutor#doUpdate》》
org.apache.ibatis.executor.SimpleExecutor#doUpdate》》
org.apache.ibatis.executor.statement.SimpleStatementHandler#update》》
java.sql.Statement#execute(java.lang.String)
jdbc里内容了
@Override public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.update(stmt); } finally { closeStatement(stmt); } }
Plugin里的invoke方法
一堆代理
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~