移动网站建设多少钱,seo技术培训班,js模版网站,做设计图的网站文章目录 前言一、引入#xff1a;二、准备工作#xff1a;2.1 引入依赖2.2 数据源的文件#xff1a;2.1 数据源#xff1a; 2.3 业务文件#xff1a; 三、整合的实现#xff1a;3.1 xxxMapper 接口的扫描#xff1a;3.2 xxxMapper 接口代理对象的生成#xff1a;3.2 S… 文章目录 前言一、引入二、准备工作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和SqlSessionTemplate5.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 引入依赖 dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.5.2/version/dependency!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --dependencygroupIdcom.mysql/groupIdartifactIdmysql-connector-j/artifactId/dependency2.2 数据源的文件
2.1 数据源
mybatis-config-test.xml
?xml version1.0 encodingUTF-8 ?
!DOCTYPE configurationPUBLIC -//mybatis.org//DTD Config 3.0//ENhttp://mybatis.org/dtd/mybatis-3-config.dtd
configurationproperties resourcedbconfig.properties/environments defaultdevelopmentenvironment iddevelopment!-- JDBC 和 POOLED 会解析别名--transactionManager typeJDBC/dataSource typePOOLEDproperty namedriver value${jdbc.driver}/property nameurl value${jdbc.url}/property nameusername value${jdbc.username}/property namepassword value${jdbc.password}//dataSource/environment/environmentsmappers!-- package namecom.example.springdabaihua.mapper/--!--mapper resourcemapper/mybatis/mybatistest.xml/--mapper resourcemapper/mybatis/mybatistest1.xml//mappers/configuration
dbconfig.properties
jdbc.drivercom.mysql.cj.jdbc.Driver
jdbc.urljdbc:mysql://localhost:3406/mybatis
jdbc.usernameroot
jdbc.passwordddsoft
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 version1.0 encodingUTF-8?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacecom.example.springdabaihua.mybatis.mapper.GoodMybatisMapperselect idselectById resultTypejava.util.Mapselect * from tb_userwhere 11if testid ! null and id ! and id #{id}/if/select
/mapperTestMyBatisService
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 层生成beanTestMyBatisService 业务类中我们通过 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) {// 扫描路径MapString, 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 SetBeanDefinitionHolder doScan(String... basePackages) {SetBeanDefinitionHolder 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 执行sqlBeanpublic 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));ListString 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 SetBeanDefinitionHolder doScan(String... basePackages) {// 获取bean 定义SetBeanDefinitionHolder 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设置localCacheScopeSTATEMENT或者手动清理缓存在一个被Transactional注解修饰的方法中所有的数据库操作依然会使用同一个SqlSession对象。因为在Spring中SqlSession的生命周期和Spring的事务绑定是一致的。只要事务是活跃的就会使用同一个SqlSession。
关闭一级缓存意味着每次执行查询操作时不会从SqlSession的缓存中取数据而是直接执行SQL语句并返回结果。这对于实现非常严格的数据一致性要求的场景是有用的比如当你知道数据频繁变化或者同一事务内需要反复查询最新数据时。
总结一下在被Transactional修饰的方法中
即使关闭了MyBatis的一级缓存所有的数据库操作仍然使用相同的SqlSession对象。关闭一级缓存不影响Spring的事务管理意味着在事务范围内SqlSession仍然是同一个并且会在事务结束时关闭。关闭一级缓存确保每次查询都会执行SQL获取最新数据不从缓存中获取。 总结
本文通过Mybatis jar 的方法 以及结合Spring通过为其指定路径的接口生成必要的bean 定义并通过Mybatis 的addMapper 方法为其接口生成代理对象最终实现service 层注入代理对象完成方法的调用。