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();
 // 执行sql
 Object 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 configuration
        PUBLIC "-//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_user
        where 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 {
    @Autowired
    private TestMyBatisMapper testMyBatisMapper;

    @Autowired
    private 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 {
    @Override
    public 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);
    }

    @Override
    public 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
     */
    @Override
    protected boolean  isCandidateComponent(AnnotatedBeanDefinition beanDefinition){
        return beanDefinition.getMetadata().isInterface();
    }
    /**
     * 覆盖bean 的扫描定义 --所有的类都返回
     * @param metadataReader
     * @return
     */
    @Override
    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
        return true;
    }
	 /**
     * 对扫描到的bean 添加 bean 的生产工厂类
     * @param basePackages
     * @return
     */
    @Override
    protected 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 类来生成 bean
            beanDefinition.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 进行注入
    @Autowired
    public 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;
    }

    @Override
    public 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);
    }

    @Override
    public 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
 @Bean
 public 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 SqlSessionFactorySqlSessionTemplate

在Spring Mybatis中,SqlSessionFactorySqlSessionTemplate都是常重要的组件。

(1). SqlSessionFactory
在Mybatis中,SqlSessionFactory是一个非常重重要的构建,所有的操作都是需要通过SqlSessionFactory去创建一个SqlSession,然后通过SqlSession进行数据库的CURD操作。通过配置文件或java代码可以构建出SqlSessionFactory象。这个 SqlSession 是DefaultSqlSession 多个线程 如果使用同一个 SqlSession 过过某个线程对其属性进行了修改,则会造成线程安全;

(2). SqlSessionTemplate
SqlSessionTemplateSqlSession的一个实现,它是线程安全的,可以在多个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 层注入代理对象完成方法的调用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/203889.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vr智慧党建展厅超强参与感增强党员群众认同感、归属感

党建教育与VR虚拟现实技术的结合&#xff0c;是顺应现代信息化发展趋势的要求&#xff0c;不仅打破了传统党建教育的束缚&#xff0c;还丰富了党建宣传教育的渠道&#xff0c;党建教育VR云课堂平台是基于深圳华锐视点自主研发的VR云课堂平台中去体验各种VR党建教育软件或者视频…

水利遥测终端机RTU的重要作用

水利遥测终端机RTU是一种在水利行业中广泛应用的设备&#xff0c;它利用先进的传感技术和远程通信技术&#xff0c;实时监测和采集水利系统的各项指标数据。它的出现不仅提高了水利行业的运行效率&#xff0c;同时也使水利管理更加科学和精细化。 ■水利遥测终端机RTU具备高精度…

如何有效的进行 E2E

一、前言 本文作者介绍了什么是E2E测试以及E2E测试测什么&#xff0c;并从对于被测系统、测试用例、测试自动化工具、测试者四个方面的要求&#xff0c;介绍了如何保证E2E测试有效性&#xff0c;干货满满&#xff0c;值得学习。 二、什么是E2E测试 相信每一个对自动化测试感…

记录一次前后端传参方式不一致异常

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 报错以及Bug ✨特色专栏&#xff1a; …

同城按摩理疗APP小程序开发制作流程;

同城按摩理疗APP小程序开发制作流程&#xff1b; 开发同城按摩理疗APP小程序&#xff0c;首先需要进行市场调研&#xff0c;深入了解用户需求&#xff0c;明确小程序的定位和服务对象。接着&#xff0c;根据需求分析结果&#xff0c;制定详细的设计方案和开发计划。然后&#…

python中的enumerate函数

enumerate函数是Python内置builtins模块中的一个函数&#xff0c;用于将一个可迭代对象转换为一个索引-元素对的枚举对象&#xff0c;从而方便地同时获得索引和元素&#xff0c;并在循环迭代中使用。 enumerate函数的语法格式为&#xff1a;enumerate(iterable, start0) itera…

Sui与阿联酋科技孵化器Hub71合作支持生态项目建设,扩大全球影响力

近日&#xff0c;总部位于阿联酋&#xff08; United Arab Emirates &#xff0c;UAE&#xff09;的科技孵化器Hub71宣布与Mysten Labs合作&#xff0c;将支持Sui上的新项目。通过本次合作&#xff0c;孵化项目的开发者们不仅可以获得Mysten Labs的技术专业知识和支持&#xff…

基于Java SSM框架+Vue实现垃圾分类网站系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架Vue实现垃圾分类网站系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个垃圾分类网站 &#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述…

北斗卫星助力乡村治理,走进数字化新时代

北斗卫星助力乡村治理&#xff0c;走进数字化新时代 随着国家对乡村治理越来越重视&#xff0c;为了进一步提升乡村治理水平&#xff0c;我国已经启动了全面建设现代化强国的大计划&#xff0c;其中数字化成为了重要的一环。而北斗卫星作为我国自主研制的卫星导航系统&#xff…

【带头学C++】----- 九、类和对象 ---- 9.1 类和对象的基本概念----(9.1.1---9.1.3)

目录 9.1 类和对象的基本概念 9.1.1 类的封装性 9.1.2 定义类的步骤和方法 9.1.3 设计一个学生类 Student 9.1 类和对象的基本概念 9.1.1 类的封装性 类是一种用户自定义的数据类型&#xff0c;它定义了一组数据成员和成员函数。类可以看作是一个模板或者蓝图&#xff0c;用…

echarts实现3D柱状图

效果如图 let setData function(data, constData, showData) {data.filter(function(item) {if (item) {constData.push(1);showData.push(item);} else {constData.push(0);showData.push({value: 1,itemStyle: {normal: {borderColor: "rgba(0,0,0,0)",borderWidt…

BUUCTF [GXYCTF2019]BabySQli 1 详解!(MD5与SQL之间的碰撞)

题目环境burp抓包 随便输入值 repeater放包 在注释那里发现某种编码 MMZFM422K5HDASKDN5TVU3SKOZRFGQRRMMZFM6KJJBSG6WSYJJWESSCWPJNFQSTVLFLTC3CJIQYGOSTZKJ2VSVZRNRFHOPJ5 看着像是base编码格式 通过测试发现是套加密&#xff08;二次加密&#xff09; 首先使用base32对此编码…

修复 Apache Kafka 中的远程代码执行漏洞CVE-2023-25194

文章目录 前言一、Log4Shell connection二、DisclosureUpdates, mitigations 前言 Possible RCE and denial-of-service issue discovered in Kafka Connect 在 Kafka Connect 中发现可能的 RCE 和拒绝服务问题。 更新 阿帕奇软件基金会 (ASF) 已解决了一个漏洞&#xff0c;…

enum常用方法 - Java

六、enum常用方法 0、准备工作1、name()2、ordinal()3、values()4、valueOf()5、compareTo()6、toString() 说明&#xff1a;使用关键字enum时&#xff0c;会隐式 继承 Enum类&#xff0c;这样我们就可以使用 Enum 类相关的方法。 0、准备工作 enum Season2 {SPRING("…

富必达API:一站式无代码开发集成电商平台、CRM和营销系统

一站式无代码开发的连接解决方案 电子商务、客户服务系统以及其它商业应用&#xff0c;是现代企业运营的重要部分。然而&#xff0c;将这些系统进行有效的整合往往需要复杂的API开发&#xff0c;这对很多企业来说是一个巨大的挑战。富必达API以其一站式的无代码开发解决方案&a…

一键上传,无限容量!打造高效图床工具,利用Electron和Gitee搭建自己的私人云存储空间

说在前面 平时写文章或写代码的时候&#xff0c;都少不了需要将本地图片转成在线图片链接&#xff0c;大家都是使用什么工具进行转换的呢&#xff1f;相信很多人都有自己的图床工具&#xff0c;今天来给大家介绍一下&#xff0c;怎么基于Gitee和Electron来开发一个便捷的图床工…

Docker本地部署Firefox火狐浏览器并远程访问

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《Linux》《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;…

上市公司数字化转型及同群效应数据集合(四种测算方法)

数据简介&#xff1a;当今世界处于高速发展的信息时代中&#xff0c;数字革命的产生催生出大量数字技术和数字信息。在数字经济时代&#xff0c;数字化转型赋予了企业新的发展动能&#xff0c;数字化转型已经成为诸多企业高质量发展的重要路径。是否需要进行数字化转型、能否及…

windows启动后直接进入指定程序并且不显示欢迎界面和windows桌面

windows启动后直接进入指定程序并且不显示欢迎界面和windows桌面 前言开机进入指定程序方法问题 浅尝GINA和Credential Providers关闭欢迎屏幕 前言 由于系统需求需要做到电脑开机后显示完windows加载页面就直接进入自己系统的界面&#xff0c;并且不显示登录欢迎页面&#xf…

IDE1007:当前上下文中不存在名称“xxx“

这种在Halcon中直接导出的代码不能直接放程序中&#xff0c;应该在控件中比如一个按钮中&#xff0c;就不会出错了。