Spring-Mybatis源码解析--手写代码实现Spring整合Mybatis
文章目录
- 前言
- 一、引入:
- 二、准备工作:
- 2.1 引入依赖
- 2.2 数据源的文件:
- 2.1 数据源:
- 2.3 业务文件:
- 三、整合的实现:
- 3.1 xxxMapper 接口的扫描:
- 3.2 xxxMapper 接口代理对象的生成:
- 3.2 SqlSessionFactory 的定义 :
- 四、Spring 整合 Mybatis 对比 :
- 4.1 扫描路径定义:
- 4.2 bean 的生成:
- 五、扩展 :
- 5.1 `SqlSessionFactory`和`SqlSessionTemplate`:
- 5.2 `SqlSessionTemplate` 线程安全源码概览:
- 5.3 Spring整合Mybatis后一级缓存失效问题:
- 总结
前言
Spring 项目中我们只需要通过简单的注解及bean 定义就可以实现对Mybatis 的整合,便我们直接进行CRUD的,那么Spring 底层是如何对Mybatis进行处理的,本文通过手写代码实现Spring整合Mybatis。
一、引入:
在 Spring-Mybatis源码解析–Mybatis配置文件解析 一文中 我们通过以下代码:
String resource ="mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);// 解析数据源 解析 xml 中的sql 语句SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);// 解析 Executor 执行器SqlSession sqlSession = sqlSessionFactory.openSession();// 执行sqlObject abc = sqlSession.selectOne("com.example.springdabaihua.mapper.TestMapper.selectOne","123");System.out.println("abc = " + abc);Map abc1 =(Map) sqlSession.selectOne("com.example.springdabaihua.mapper.TestMapper.selectById","1");abc1.entrySet().stream().forEach(e->{System.out.println("e.toString() = " + e.toString());});
SqlSessionFactoryBuilder().build() 方法来对sql 语句进行解析,然后 sqlSessionFactory.openSession() 获取sql 的一个执行对象,最终通过sqlSession.selectOne() 简单实现了对数据库的操作;在spring 项目中我们通常是通过注入 xxxMapper 接口 的方式来进行数据的操作,所以以下工作围绕 如何注册xxxMapper 来进行;
二、准备工作:
显然为了整合Mybatis ,我们需要首先引入jar 包,配置数据源,新增一些xxxMapper 接口 来实现对数据的操作;
2.1 引入依赖
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version></dependency><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId></dependency>
2.2 数据源的文件:
2.1 数据源:
mybatis-config-test.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><properties resource="dbconfig.properties"/><environments default="development"><environment id="development"><!-- JDBC 和 POOLED 会解析别名--><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><mappers><!-- <package name="com.example.springdabaihua.mapper"/>--><!--<mapper resource="mapper/mybatis/mybatistest.xml"/>--><mapper resource="mapper/mybatis/mybatistest1.xml"/></mappers></configuration>
dbconfig.properties:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3406/mybatis
jdbc.username=root
jdbc.password=ddsoft
2.3 业务文件:
这里模拟注入xxxMpper 的方式来查询数据;
GoodMybatisMapper:
package com.example.springdabaihua.mybatis.mapper;import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;import java.util.Map;public interface GoodMybatisMapper {@Select("select 'good'")String selectOne();public Map selectById(@Param(value = "id") String id);}
TestMyBatisMapper:
package com.example.springdabaihua.mybatis.mapper;import org.apache.ibatis.annotations.Select;public interface TestMyBatisMapper {@Select("select 123")String selectOne();
}
mybatistest1.xml
<?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.example.springdabaihua.mybatis.mapper.GoodMybatisMapper"><select id="selectById" resultType="java.util.Map">select * from tb_userwhere 1=1<if test="id != null and id != ''">and id =#{id}</if></select>
</mapper>
TestMyBatisService:
package com.example.springdabaihua.mybatis.service;import com.example.springdabaihua.mybatis.mapper.GoodMybatisMapper;
import com.example.springdabaihua.mybatis.mapper.TestMyBatisMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class TestMyBatisService {@Autowiredprivate TestMyBatisMapper testMyBatisMapper;@Autowiredprivate GoodMybatisMapper goodMybatisMapper;public void test() {System.out.println(testMyBatisMapper.selectOne());System.out.println(goodMybatisMapper.selectOne());System.out.println(goodMybatisMapper.selectById("1"));}}
spring 启动类:
ConfigMybatis:
package com.example.springdabaihua.mybatis.config;import com.example.springdabaihua.mybatis.anotion.TestMybatisMapperScan;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;import java.io.IOException;
import java.io.Reader;@Configuration
// 定义bean 的扫描路径 方便注入 service 层的 bean
@ComponentScan("com.example.springdabaihua.mybatis")
public class ConfigMybatis {}
MybatisTest:
package com.example.springdabaihua.mybatis;import com.example.springdabaihua.mybatis.config.ConfigMybatis;
import com.example.springdabaihua.mybatis.service.TestMyBatisService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class MybatisTest {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();context.register(ConfigMybatis.class);context.refresh();TestMyBatisService bean = context.getBean(TestMyBatisService.class);bean.test();}
}
这些业务代码都比较简单 ,我们通过 @Configuration 定义启动的配置类 ConfigMybatis ,这里通过 定义@ComponentScan(“com.example.springdabaihua.mybatis.service”) 的扫描路径 方便spring 对service 层生成bean;TestMyBatisService 业务类中我们通过 @Autowired 的方式去注入了两个mapper ,然后在test 方法使用注入的mapper 来执行sql;
此时我们启动后在 TestMyBatisService 对象调用 test() 会报错,因为spring 找不到 我们要注入的 TestMyBatisMapper 和 GoodMybatisMapper 接口对象;
三、整合的实现:
显然我们业务中的xxxMapper 因为其本身是一个接口类,并不能通过new 的方式来产生对象,也无法进行方法的调用;所以要想通过xxxMapper 调用方法,必须生成xxxMapper 的代理对象,通过代理对象来对其方法的执行;
3.1 xxxMapper 接口的扫描:
因为程序并不知道到哪些接口需要被生成代理对象,所以我们需要告诉程序,我们定义一个 TestMybatisMapperScan 注解以此来设置mapper 的位置,
TestMybatisMapperScan:
package com.example.springdabaihua.mybatis.anotion;import com.example.springdabaihua.mybatis.config.TestMybatisImportBeanDefinitionRegister;
import org.springframework.context.annotation.Import;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TestMybatisMapperScan {String basePackages() default "";
}
这样我们在启动类上就可以方便的定义 mapper 的扫描路径:
@Configuration
// 定义bean 的扫描路径 方便注入 service 层的 bean
@ComponentScan("com.example.springdabaihua.mybatis.service")
// 模拟 mybatis 的 @MapperScan 注解 定义 TestMybatisMapperScan 并设置mapper 的扫描路径
@TestMybatisMapperScan(basePackages = "com.example.springdabaihua.mybatis.mapper")
public class ConfigMybatis {}
现在已经定义了mapper 的路径,接下来就是从注解中获取定义的路径值,然后为其生成代理对象了,而spring 框架已经存在的bean 可以大大的方便我们 对其两个工作的完成;
首先我们定义一个 ImportBeanDefinitionRegistrar 类型的类 ,来对扫描路径的获取,并且可以通过 spirng 的scaner 来完成bean 定义的扫描:
TestMybatisImportBeanDefinitionRegister:
package com.example.springdabaihua.mybatis.config;import com.example.springdabaihua.mybatis.anotion.TestMybatisMapperScan;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;import java.util.Map;public class TestMybatisImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {// 扫描路径Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(TestMybatisMapperScan.class.getName());String basePackages = (String) annotationAttributes.get("basePackages");System.out.println(basePackages);// 自定义 扫描器,扫描 basePackages 下的类 并生成bean 定义TestMybatisBeanDefinitionScanner scanner = new TestMybatisBeanDefinitionScanner(registry);// 添加一个 IncludeFilter ,直接返回true
// scanner.addIncludeFilter(new TypeFilter() {
// @Override
// public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
// return true;
// }
// });scanner.scan(basePackages);}@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {ImportBeanDefinitionRegistrar.super.registerBeanDefinitions(importingClassMetadata, registry);}
}
这样我们可以在 TestMybatisMapperScan 注解中通过 import 的方式来对其进行引入:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
// TestMybatisImportBeanDefinitionRegister 方便获取已经解析的注解信息
@Import(TestMybatisImportBeanDefinitionRegister.class)
public @interface TestMybatisMapperScan {String basePackages() default "";
}
接下来重点就是 怎么定义这个扫描器了,这里因为传入的是一个路径,我们可以通过实现Spring 中的 ClassPathBeanDefinitionScanner 来定义自己的扫描器:
TestMybatisBeanDefinitionScanner:
package com.example.springdabaihua.mybatis.config;import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.classreading.MetadataReader;import java.io.IOException;
import java.util.Set;public class TestMybatisBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {public TestMybatisBeanDefinitionScanner(BeanDefinitionRegistry registry) {super(registry);}/*** 覆盖bean 的扫描定义 --只扫描接口* @param beanDefinition* @return*/@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition){return beanDefinition.getMetadata().isInterface();}/*** 覆盖bean 的扫描定义 --所有的类都返回* @param metadataReader* @return*/@Overrideprotected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {return true;}/*** 对扫描到的bean 添加 bean 的生产工厂类* @param basePackages* @return*/@Overrideprotected Set<BeanDefinitionHolder> doScan(String... basePackages) {Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);// 循环遍历为每个bean 定义都 指定生成bean时需要用到的类及参数for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();// 放入参数 要代理的 mapper 接口beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());// 通过 TestMybatisFactory 类来生成 beanbeanDefinition.setBeanClassName(TestMybatisFactory.class.getName());}return beanDefinitionHolders;}}
到这里我们已经完成了对路径下所有mapper 接口的类扫描 ,并通过覆盖 isCandidateComponent 方法 以此来 对 接口生成bean 的定义,有了bean 的定义,接下来就是要为每个mapper 都去生成代理对象;
3.2 xxxMapper 接口代理对象的生成:
我们通过 实现Spring 中的FactoryBean 接口,重写getObject() 接口的方式,来生成xxxMapper 的代理对象;
TestMybatisFactory:
package com.example.springdabaihua.mybatis.config;import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;//@Component
public class TestMybatisFactory implements FactoryBean {// xxxMapper 接口类private Class mapperInterface;// 注入 执行sql 需要的 sqlSession 对象private SqlSession sqlSession;// 通过set 进行注入@Autowiredpublic void setSqlSession(SqlSessionFactory sqlSessionFactory) {// 这里通过Mybatis 来为每个 xxxMapper 接口生成代理对象if (!sqlSessionFactory.getConfiguration().hasMapper(mapperInterface)){sqlSessionFactory.getConfiguration().addMapper(mapperInterface);}this.sqlSession = sqlSessionFactory.openSession();}public TestMybatisFactory(Class mapperInterface) {this.mapperInterface = mapperInterface;}@Overridepublic Object getObject() throws Exception {
// Object proxy = Proxy.newProxyInstance(TestMybatisFactory.class.getClassLoader(), new Class[]{mapperInterface}, new InvocationHandler() {
// @Override
// public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// System.out.println("method.getName() = " + method.getName());
// return null;
// }
// });
// return proxy;// 这里通过Mybatis 直接获取对应xxxMapper 接口的代理对象return sqlSession.getMapper(mapperInterface);
// return sqlSession.getMapper(TestMyBatisMapper.class);}@Overridepublic Class<?> getObjectType() {return mapperInterface;
// return TestMyBatisMapper.class;}
}
到这里我们通过Mybatis jar 包中的方法已经完成了 代理对象的生成,具体的xxxMpper 代理对象的生成,实际上就是通过SqlSessionFactory 对象 的getConfiguration().addMapper(mapperInterface) 方法来解析sql 并且生成代理对象;所以我们只需要定义一个 SqlSessionFactory 即可;
3.2 SqlSessionFactory 的定义 :
我们在启动类的配置文件ConfigMybatis 中来定义SqlSessionFactory 的bean;
// 定义 SqlSessionFactory 方便通过 SqlSessionFactory 执行sql@Beanpublic SqlSessionFactory sqlSessionFactory() throws IOException {String resource = "mybatis-config-test.xml";Reader reader = Resources.getResourceAsReader(resource);// 解析数据源 解析 xml 中的sql 语句SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);return sqlSessionFactory;}
至此我们借助Mybatis jar 中的方法,通过在Spring 中为想要生成代理对象的xxxMapper 生成bean 定义,最终借助Mybatis 的方法来生成最终的代理对象;这样在我们最终@Autowired 就可以来注入代理对象,进而实现对数据的操作;现在我们再次启动并且调用service 层的方法,可以直接获取到结果了;
四、Spring 整合 Mybatis 对比 :
4.1 扫描路径定义:
在实际的 Spring 中 我们通过 @MapperScan 定义路径和 SqlSessionFactory:
在@MapperScan 中 通过 @Import({MapperScannerRegistrar.class}) 来引入 mapper 的扫描和注册:
在 MapperScannerRegistrar 的registerBeanDefinitions 方法中可以看到通过 ClassPathMapperScanner 定义扫描器:
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {// 通过类路径的扫描器 ClassPathMapperScanner ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);Optional var10000 = Optional.ofNullable(this.resourceLoader);Objects.requireNonNull(scanner);var10000.ifPresent(scanner::setResourceLoader);Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");if (!Annotation.class.equals(annotationClass)) {scanner.setAnnotationClass(annotationClass);}Class<?> markerInterface = annoAttrs.getClass("markerInterface");if (!Class.class.equals(markerInterface)) {scanner.setMarkerInterface(markerInterface);}Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");if (!BeanNameGenerator.class.equals(generatorClass)) {scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass));}// 为 scanner 设置 bean 的工厂类Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {scanner.setMapperFactoryBeanClass(mapperFactoryBeanClass);}scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));List<String> basePackages = new ArrayList();basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));// 添加 IncludeFilter 扫描路径下的所以类scanner.registerFilters();// 对bean 的扫描scanner.doScan(StringUtils.toStringArray(basePackages));}
doScan 扫描方法:
public Set<BeanDefinitionHolder> doScan(String... basePackages) {// 获取bean 定义Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);if (beanDefinitions.isEmpty()) {LOGGER.warn(() -> {return "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.";});} else {// bean 定义的后置处理,这个方法为每个bean 定义都设置了 bean 工厂的类以及设置bean 的参数this.processBeanDefinitions(beanDefinitions);}return beanDefinitions;
}
4.2 bean 的生成:
通过源码可以看到Spring 中通过 MapperFactoryBean 中的 getObject() 方法来获取代理对象:
与我们首先实现的不同点在于Spring 中getSqlSession() 获取的 SqlSessionTemplate 对象,而我们代码里获取的是 SqlSession 对象,关于这两个对象的区别和联系,放到本文中扩展的章节进行阐述;
至此我们看到Spring 中整合 Mybatis 和我们手写的基本一致,实际上手写的实现也只是仿照 Spring 中整合 Mybatis 罢了;
五、扩展 :
这里对Spring 整合Mybatis 遇到的知识点儿进行额外的阐述:
5.1 SqlSessionFactory
和SqlSessionTemplate
:
在Spring Mybatis中,SqlSessionFactory
和SqlSessionTemplate
都是常重要的组件。
(1). SqlSessionFactory
:
在Mybatis中,SqlSessionFactory
是一个非常重重要的构建,所有的操作都是需要通过SqlSessionFactory
去创建一个SqlSession
,然后通过SqlSession
进行数据库的CURD操作。通过配置文件或java代码可以构建出SqlSessionFactory象。这个 SqlSession
是DefaultSqlSession 多个线程 如果使用同一个 SqlSession
过过某个线程对其属性进行了修改,则会造成线程安全;
(2). SqlSessionTemplate
:
SqlSessionTemplate
是SqlSession
的一个实现,它是线程安全的,可以在多个DAO间共享。它使用了Spring的事务管理,可以在service层配置事务后,完成对数据库的一系列操作(一系列的增删改查),当出现异常时,会自动进行回滚操作,保证数据的一致性。每个线程在执行的时候都会生产自己的DefaultSqlSession SqlSession
对象使用ThreadLocal 进行缓存进行了线程隔离,所有不会有线程安全的问题;
简言之,当我们在使用Spring集成Mybatis进行开发时,首先我们需要创建一个SqlSessionFactory
,然后再通过SqlSessionFactory
创建一个SqlSession
。但是这样做需要手动管理事务,且使用不便。而SqlSessionTemplate
可以解决这个问题,它在内部维护了一个SqlSession
,并且自动管理会话的生命周期,包括开启,提交,回滚,关闭等。此外,它还提供了非常方便的CRUD操作方法,非常易于使用。
5.2 SqlSessionTemplate
线程安全源码概览:
在创建SqlSessionTemplate 时 可以看到其内部使用了一个拦截器:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");Assert.notNull(executorType, "Property 'executorType' is required");this.sqlSessionFactory = sqlSessionFactory;this.executorType = executorType;this.exceptionTranslator = exceptionTranslator;this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionInterceptor());
}
在 SqlSessionTemplate 调用方法时 会进入 SqlSessionInterceptor 拦截器中:
private class SqlSessionInterceptor implements InvocationHandler {private SqlSessionInterceptor() {}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 重点在于 sqlSession 对象的获取SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);Object unwrapped;try {// 方法的执行Object result = method.invoke(sqlSession, args);if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {sqlSession.commit(true);}unwrapped = result;} catch (Throwable var11) {unwrapped = ExceptionUtil.unwrapThrowable(var11);if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);sqlSession = null;Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);if (translated != null) {unwrapped = translated;}}throw (Throwable)unwrapped;} finally {if (sqlSession != null) {SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);}}return unwrapped;}
}
重点看下 getSqlSession 方法关于sqlSession 的获取:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {Assert.notNull(sessionFactory, "No SqlSessionFactory specified");Assert.notNull(executorType, "No ExecutorType specified");// 从当前线程中获取 holder SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);SqlSession session = sessionHolder(executorType, holder);// 当前线程的 holder 中的 SqlSession 不为空则直接使用if (session != null) {return session;} else {LOGGER.debug(() -> {return "Creating a new SqlSession";});// 为空则创建一个新的SqlSession 对象session = sessionFactory.openSession(executorType);// 将创建出来的SqlSession 对象放入到当前线程的 holder 中registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);return session;}
}
registerSessionHolder holder 的放入:
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {if (TransactionSynchronizationManager.isSynchronizationActive()) {// 开启了事务 并且被 @Transactional 的事务方法才进入该判断 Environment environment = sessionFactory.getConfiguration().getEnvironment();if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {LOGGER.debug(() -> {return "Registering transaction synchronization for SqlSession [" + session + "]";});SqlSessionHolder holder = new SqlSessionHolder(session, executorType, exceptionTranslator);TransactionSynchronizationManager.bindResource(sessionFactory, holder);TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));holder.setSynchronizedWithTransaction(true);holder.requested();} else {if (TransactionSynchronizationManager.getResource(environment.getDataSource()) != null) {throw new TransientDataAccessResourceException("SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");}LOGGER.debug(() -> {return "SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional";});}} else {LOGGER.debug(() -> {return "SqlSession [" + session + "] was not registered for synchronization because synchronization is not active";});}}
5.3 Spring整合Mybatis后一级缓存失效问题:
Mybatis中的一级缓存是基于SqlSession来实现的,所以在执行同一个sql时,如果使用的是同一个SqlSession对象,那么就能利用到一级缓存,提高sql的执行效率。
但是在Spring整合Mybatis后,如果执行某个方法时,该方法上没有加@Transactional注解,也就是没有开启Spring事务,那么后面在执行具体sql时,没执行一个sql时都会新生成一个SqlSession对象来执行该sql,这就是我们说的一级缓存失效(也就是没有使用同一个SqlSession对象),而如果开启了Spring事务,那么该Spring事务中的多个sql,在执行时会使用同一个SqlSession对象,从而一级缓存生效。
个人理解:实际上Spring整合Mybatis后一级缓存失效并不是问题,是正常的实现,因为,一个方法如果没有开启Spring事务,那么在执行sql时候,那就是每个sql单独一个事务来执行,也就是单独一个SqlSession对象来执行该sql,如果开启了Spring事务,那就是多个sql属于同一个事务,那自然就应该用一个SqlSession来执行这多个sql。所以,在没有开启Spring事务的时候,SqlSession的一级缓存并不是失效了,而是存在的生命周期太短了(执行完一个sql后就被销毁了,下一个sql执行时又是一个新的SqlSession了)。
通常会关闭一级缓存,因为它会影响到 mysql 的事务隔离级别:demo 如果mysql 是读未提交,则在一个被@Transactional注解 修饰的方法中,同样的一个查询,使用了一级缓存则会得到相同的结果,而实际上,数据可能已经被改变;
在Spring中关闭MyBatis的一级缓存通常意味着每次查询都会直接去数据库查询,而不使用MyBatis的内置缓存机制。然而,关闭一级缓存并不影响Spring使用@Transactional
修饰的方法中SqlSession
的创建和使用方式。
即便一级缓存被关闭(比如,通过MyBatis设置localCacheScope=STATEMENT
,或者手动清理缓存),在一个被@Transactional
注解修饰的方法中,所有的数据库操作依然会使用同一个SqlSession
对象。因为在Spring中,SqlSession
的生命周期和Spring的事务绑定是一致的。只要事务是活跃的,就会使用同一个SqlSession
。
关闭一级缓存意味着每次执行查询操作时,不会从SqlSession
的缓存中取数据,而是直接执行SQL语句并返回结果。这对于实现非常严格的数据一致性要求的场景是有用的,比如当你知道数据频繁变化,或者同一事务内需要反复查询最新数据时。
总结一下,在被@Transactional
修饰的方法中:
- 即使关闭了MyBatis的一级缓存,所有的数据库操作仍然使用相同的
SqlSession
对象。 - 关闭一级缓存不影响Spring的事务管理,意味着在事务范围内,
SqlSession
仍然是同一个,并且会在事务结束时关闭。 - 关闭一级缓存确保每次查询都会执行SQL获取最新数据,不从缓存中获取。
总结
本文通过Mybatis jar 的方法 以及结合Spring,通过为其指定路径的接口,生成必要的bean 定义,并通过Mybatis 的addMapper 方法为其接口生成代理对象,最终实现service 层注入代理对象完成方法的调用。
相关文章:

Spring-Mybatis源码解析--手写代码实现Spring整合Mybatis
文章目录 前言一、引入:二、准备工作:2.1 引入依赖2.2 数据源的文件:2.1 数据源: 2.3 业务文件: 三、整合的实现:3.1 xxxMapper 接口的扫描:3.2 xxxMapper 接口代理对象的生成:3.2 S…...

5.2 Windows驱动开发:内核取KERNEL模块基址
模块是程序加载时被动态装载的,模块在装载后其存在于内存中同样存在一个内存基址,当我们需要操作这个模块时,通常第一步就是要得到该模块的内存基址,模块分为用户模块和内核模块,这里的用户模块指的是应用层进程运行后…...
聊聊Go语言的注释
文章目录 聊聊Go语言的注释一、注释的格式1.1 块注释1.2 行注释 二、包注释三、命令注释四、类型注释4.1 实例注释4.2 并发安全注释4.3 零值注释4.4 导出字段注释 五、函数注释5.1 参数/返回值/函数作用注释5.2 bool返回值函数注释5.3 形参/返回值名称注释5.4 并发安全注释5.5 …...

皮肤警告,羊大师讲解身体与环境的默契
皮肤警告,羊大师讲解身体与环境的默契 我们常常忽视身体皮肤所承受的压力和警告信号。皮肤是身体的第一道屏障,也是与外界环境直接接触的组织。我们的皮肤通过各种方式向我们传达信息,警告我们关于身体健康的重要问题。本文小编羊大师将带大…...

使用NVM管理多个Nodejs版同时支持vue2、vue3
1.安装nvm,下载地址: https://github.com/coreybutler/nvm-windows/releases/tag/1.1.12 2.nvm常用命令 Usage:nvm arch : Show if node is running in 32 or 64 bit mode.nvm current : Display active version.nvm debug …...

Android帝国之进程杀手--lmkd
本文概要 这是Android系统启动的第三篇文章,本文以自述的方式来讲解lmkd进程,通过本文您将了解到lmkd进程在安卓系统中存在的意义,以及它是如何杀进程的。(文中的代码是基于android13) 我是谁 init:“大…...

堆栈_队列实现栈
//请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。 // // 实现 MyStack 类: // // // void push(int x) 将元素 x 压入栈顶。 // int pop() 移除…...

好用的json处理工具He3 JSON
官网地址:https://he3app.com/zh/ json格式化 https://portal.he3app.com/home/extension/json-to-pretty 其他 https://portal.he3app.com/home/category...

RabbitMQ消息模型之Routing-Direct
Routing Direct 在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。 在Direct模型下: 队列与交换机的绑定,不能是任意…...

Harmony 应用开发之size 脚本
作者:麦客奥德彪 在应用开发中,最终呈现在用户面前的UI,是用户能否继续使用应用的强力依据之一,在之前的开发中,Android 屏幕碎片化严重,所以出现了很多尺寸适配方案。 最小宽适配、百分比适配等等。 还有一…...

商家门店小程序怎么做?门店小程序的优势和好处
生活服务类商家在当前数字化时代,越来越认识到门店小程序的重要性。门店小程序不仅为商家提供了一个在线展示的窗口,更为其打造了一个与消费者直接互动的平台。有了门店小程序,商家可以更加便捷地管理商品信息、订单流程,同时还能…...

什么是灯塔工厂?灯塔工厂的作用?
什么是灯塔工厂? "灯塔工厂"概念源于德国的工业4.0战略,又称“工业4.0示范工厂”或“标杆工厂”,代表工业领域顶级的智能制造能力。2018年,由世界经济论坛和麦肯锡共同推出。 灯塔工厂是通过数字化、网络化和智能化手…...
【GEO-AI】SAM-Geo库(segment-geospatial)入门教程
今年4月份,Meta公布了它图形分割模型Segment-Anything,简称SAM。当时就想着这个东西用在遥感影像分割上应该效果不错,奈何自己能力有限,没有办法上手实践。偶然间看到有介绍SAM-Geo工具包的文章,决定研究一番ÿ…...

ESP32-Web-Server 实战编程-使用文件系统建立强大的 web 系统
ESP32-Web-Server 实战编程-使用文件系统建立强大的 web 系统 概述 在前述章节我们讲述了在网页端控制多个 GPIO 的案例。当程序开始变得复杂,让一些功能“自动起来”是一个好的选择。 在前面的示例中,我们需要在后端为每个前端代码的 URL 指定一个对…...

kubeadm快速搭建k8s高可用集群
1.安装及优化 1.1基本环境配置 1.环境介绍 (1).高可用集群规划 主机名ip地址说明k8s-master01192.168.2.96master节点k8s-master02192.168.2.97master节点k8s-master03192.168.2.98master节点k8s-node01192.168.2.99node节点k8s-node02192.168.2.100n…...

GoLong的学习之路,进阶,Redis
这个redis和上篇rabbitMQ一样,在之前我用Java从原理上进行了剖析,这里呢,我做项目的时候,也需要用到redis,所以这里也将去从怎么用的角度去写这篇文章。 文章目录 安装redis以及原理redis概念redis的应用场景有很多red…...

Linux重置MySql密码(简洁版)
关闭验证 /etc/my.cnf-->[mysqld]-->skip-grant-tables 重启MySql service mysql restart 登陆MySql mysql -u root 刷新权限 FLUSH PRIVILEGES; 更新密码 ALTER USER rootlocalhost IDENTIFIED BY 123456; 退出MySql exit 打开验证 /etc/my.cnf-->[mysqld]-->skip…...

Ubuntu部署jmeter与ant
为了整合接口自动化的持续集成工具,我将jmeter与ant都部署在了Jenkins容器中,并配置了build.xml 一、ubuntu部署jdk 1:先下载jdk-8u74-linux-x64.tar.gz,上传到服务器,这里上传文件用到了ubuntu 下的 lrzsz。 ubunt…...
如何使用 RestTemplate 进行 Spring Boot 微服务通信示例?
在 Spring Boot 微服务架构中,RestTemplate 是一个强大的工具,用于简化微服务之间的通信。下面是一个简单的示例,演示如何使用 RestTemplate 进行微服务之间的 HTTP 通信。 首先,确保你的 Spring Boot 项目中已经添加了 spring-b…...

新开普掌上校园服务管理平台service.action RCE漏洞复现 [附POC]
文章目录 新开普掌上校园服务管理平台service.action RCE漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 新开普掌上校园服务管理平台service.action RCE漏洞复现 [附POC] 0x01 前言 免责声明:请勿…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...

tauri项目,如何在rust端读取电脑环境变量
如果想在前端通过调用来获取环境变量的值,可以通过标准的依赖: std::env::var(name).ok() 想在前端通过调用来获取,可以写一个command函数: #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...

Linux部署私有文件管理系统MinIO
最近需要用到一个文件管理服务,但是又不想花钱,所以就想着自己搭建一个,刚好我们用的一个开源框架已经集成了MinIO,所以就选了这个 我这边对文件服务性能要求不是太高,单机版就可以 安装非常简单,几个命令就…...
shell脚本质数判断
shell脚本质数判断 shell输入一个正整数,判断是否为质数(素数)shell求1-100内的质数shell求给定数组输出其中的质数 shell输入一个正整数,判断是否为质数(素数) 思路: 1:1 2:1 2 3:1 2 3 4:1 2 3 4 5:1 2 3 4 5-------> 3:2 4:2 3 5:2 3…...

鸿蒙Navigation路由导航-基本使用介绍
1. Navigation介绍 Navigation组件是路由导航的根视图容器,一般作为Page页面的根容器使用,其内部默认包含了标题栏、内容区和工具栏,其中内容区默认首页显示导航内容(Navigation的子组件)或非首页显示(Nav…...
基于Uniapp的HarmonyOS 5.0体育应用开发攻略
一、技术架构设计 1.混合开发框架选型 (1)使用Uniapp 3.8版本支持ArkTS编译 (2)通过uni-harmony插件调用原生能力 (3)分层架构设计: graph TDA[UI层] -->|Vue语法| B(Uniapp框架)B --&g…...
接口 RESTful 中的超媒体:REST 架构的灵魂驱动
在 RESTful 架构中,** 超媒体(Hypermedia)** 是一个核心概念,它体现了 REST 的 “表述性状态转移(Representational State Transfer)” 的本质,也是区分 “真 RESTful API” 与 “伪 RESTful AP…...
MyBatis-Plus 常用条件构造方法
1.常用条件方法 方法 说明eq等于 ne不等于 <>gt大于 >ge大于等于 >lt小于 <le小于等于 <betweenBETWEEN 值1 AND 值2notBetweenNOT BETWEEN 值1 AND 值2likeLIKE %值%notLikeNOT LIKE %值%likeLeftLIKE %值likeRightLIKE 值%isNull字段 IS NULLisNotNull字段…...

STM32 低功耗设计全攻略:PWR 模块原理 + 睡眠 / 停止 / 待机模式实战(串口 + 红外 + RTC 应用全解析)
文章目录 PWRPWR(电源控制模块)核心功能 电源框图上电复位和掉电复位可编程电压监测器低功耗模式模式选择睡眠模式停止模式待机模式 修改主频一、准备工作二、修改主频的核心步骤:宏定义配置三、程序流程:时钟配置函数解析四、注意…...