Spring源码解析(十):spring整合mybatis源码

news2024/12/23 4:07:35

Spring源码系列文章

Spring源码解析(一):环境搭建

Spring源码解析(二):bean容器的创建、默认后置处理器、扫描包路径bean

Spring源码解析(三):bean容器的刷新

Spring源码解析(四):单例bean的创建流程

Spring源码解析(五):循环依赖

Spring源码解析(六):bean工厂后置处理器ConfigurationClassPostProcessor

Spring源码解析(七):bean后置处理器AutowiredAnnotationBeanPostProcessor

Spring源码解析(八):bean后置处理器CommonAnnotationBeanPostProcessor

Spring源码解析(九):AOP源码之@Aspect所有相关注解解析

Spring源码解析(十):spring整合mybatis源码


目录

  • 一、Mybatis的使用介绍
    • 1、mybatis框架单独使用
    • 2、spring整合mybatis使用
  • 二、spring整合mybatis原理
    • 1、@MapperScan注解原理
      • 1.1、MapperScannerRegistrar类的核心方法(实现ImportBeanDefinitionRegistrar接口方法)
      • 1.2、MapperScannerConfigurer类的作用
    • 2、SqlSessionFactoryBean类
      • 2.1、FactoryBean接口
    • 3、MapperFactoryBean类
      • 3.1、FactoryBean接口
      • 3.2、DaoSupport抽象类
      • 3.3、SqlSessionDaoSupport抽象类
      • 3.4、MapperFactoryBean的checkDaoConfig方法
    • 4、总结Mapper对象的创建过程

一、Mybatis的使用介绍

1、mybatis框架单独使用

  • 通过sqlSession获取对应的代理对象
  • 代理对象执行sql完成数据库操作
public class MybatisMain {
    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        InputStream resourceAsStream = Resources.getResourceAsStream(resource);
        // 1.解析XML配置
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        // 2.基于解析好的XML配置创建一个SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = builder.build(resourceAsStream);
        // 3.通过SqlSessionFactory,创建一个SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 4.获取一个代理对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 5.调用代理对象的方法
        System.out.println("代理对象查询结果:" + mapper.selectOne(1));
    }
}

2、spring整合mybatis使用

  • SqlSession对象以及代理对象都交给spring容器来创建
  • 直接从容器中获取Mapper接口的实现类(代理对象)即可
@Configuration //声明该类是核心配置类
@ComponentScan("com.xc") //开启spring注解扫描
@MapperScan("com.xc.mapper") //MyBatis扫描Mapper接口
public class MybatisConfig {
    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setPassword("root");
        driverManagerDataSource.setUsername("root");
        driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false");
        return driverManagerDataSource;
    }
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean() {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource());
        return sqlSessionFactoryBean;
    }
	// 执行操作
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MybatisConfig.class);
        UserMapper userMapper = context.getBean(UserMapper.class);
        User user = userMapper.selectOne(100);
        System.out.println(user);
    }
}

二、spring整合mybatis原理

  • 根据上面spring整合mybatis的配置类的信息,要想清楚Spring是如何整合Mybatis的,我们只要弄明白两点
    1. @MapperScan注解的作用
    2. SqlSessionFactoryBean对象的作用

1、@MapperScan注解原理

  • @Import注解,那它的作用很明显,就是注册bean
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
 
  // basePackages属性的别名,等价于basePackages
  String[] value() default {};
  
  // 扫描的包名
  String[] basePackages() default {};
 
  // 可以提供一个类,以类的包名作为扫描的包  
  Class<?>[] basePackageClasses() default {};
 
  // BeanName的生成器,一般用默认的就好啦
  Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
 
  // 指定要扫描的注解
  Class<? extends Annotation> annotationClass() default Annotation.class;
 
  // 指定标记接口,只有继承了这个接口才会被扫描
  Class<?> markerInterface() default Class.class;
 
  // 指定SqlSessionTemplate的名称,
  // SqlSessionTemplate是Spring对Mybatis中SqlSession的封装
  String sqlSessionTemplateRef() default "";
 
  //  指定SqlSessionFactory的名称
  String sqlSessionFactoryRef() default "";
 
  // 这个属性是什么意思呢?Spring跟Mybatis整合
  // 最重要的事情就是将Mybatis生成的代理对象交由Spring来管理
  // 实现这个功能的就是这个MapperFactoryBean
  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
 
  // 是否对mapper进行懒加载,默认为false
  String lazyInitialization() default "";
 
}

1.1、MapperScannerRegistrar类的核心方法(实现ImportBeanDefinitionRegistrar接口方法)

  • 核心内容就是注册MapperScannerConfigurer.class的bean
  • @MapperScan注解中的属性都添加到这个bean中
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    // 获取到@MapperScan这个注解中的属性
    AnnotchaationAttributes mapperScanAttrs = AnnotationAttributes
      .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
        // 紧接着开始向Spring容器中注册bd
        registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
                                generateBaseBeanName(importingClassMetadata, 0));
    }
}
 
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
                             BeanDefinitionRegistry registry, String beanName) {
 
    // 打算注册到容器中的bd的beanClass属性为MapperScannerConfigurer.class
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);
 
   // 省略部分代码
   // ....
   // 这部分代码就是将注解中的属性获取出来
   // 放到MapperScannerConfigurer这个beanDefinition中
    
   // 最后将这个beanDefinition注册到容器中
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
 
}

1.2、MapperScannerConfigurer类的作用

类图:

  • 由图可知,MapperScannerConfigurer是一个Bean工厂的后置处理器,bean初始化也会做一些事情

在这里插入图片描述

初始化操作:

  • 内容简单,就是校验下@MapperScan是否有basePackage属性,没有抛异常
@Override
public void afterPropertiesSet() throws Exception {
  notNull(this.basePackage, "Property 'basePackage' is required");
}

public static void notNull(Object object, String message) {
    if (object == null) {
        throw new IllegalArgumentException(message);
    }
}

bean工厂后置处理器操作bean定义:

  • ClassPathMapperScanner继承ClassPathBeanDefinitionScanner
  • ClassPathBeanDefinitionScanner有扫描包注册bean的功能
    • 默认扫描规则@Component的bean,包括@Controller @Service @Repository
    • 所以这里需要重新设置扫描规则
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
        // 处理@MaperScan注解属性中的占位符
        processPropertyPlaceHolders();
    }
 	// 在这里创建了一个ClassPathMapperScanner
    // 这个类继承了ClassPathBeanDefinitionScanner,并复写了它的doScan、registerFilters等方法
 	// 其整体行为跟ClassPathBeanDefinitionScanner差不多,
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
        scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    // 这里设置了扫描规则
    scanner.registerFilters();
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, 
        ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
  • 注册指定包扫描规则(指定注解指定接口全部
public void registerFilters() {
    boolean acceptAllInterfaces = true;
 
    // 第一步,判断是否要扫描指定的注解
    // 也就是判断在@MapperScan注解中是否指定了要扫描的注解
    if (this.annotationClass != null) {
        addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
        acceptAllInterfaces = false;
    }
 
    // 第二步,判断是否要扫描指定的接口
    // 同样也是根据@MapperScan注解中的属性做判断
    if (this.markerInterface != null) {
        addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
            @Override
            protected boolean matchClassName(String className) {
                return false;
            }
        });
        acceptAllInterfaces = false;
    }
 
    // 如果既没有指定注解也没有指定标记接口
    // 那么所有.class文件都会被扫描
    if (acceptAllInterfaces) {
        addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
    }
 
    // 排除package-info文件
    addExcludeFilter((metadataReader, metadataReaderFactory) -> {
        String className = metadataReader.getClassMetadata().getClassName();
        return className.endsWith("package-info");
    });
}
  • 扫描指定package的类路径下的接口
  • ClassPathMapperScanner重新ClassPathBeanDefinitionScanner的doScan方法
    • 调用父类doScan方法、对扫描出来的BeanDefinition做处理
  • super.doScan以前讲过Spring源码解析(二):bean容器的创建、默认后置处理器、扫描包路径bean
    • 核心内容就是扫描指定包将符合要求Class注册bean定义
# ClassPathBeanDefinitionScanner类方法
public int ClassPathBeanDefinitionScanner.scan(String... basePackages) {
    //获取容器中已经注册BeanDefinition数量
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
    //扫描
	doScan(basePackages);

	// Register annotation config processors, if necessary.
	//是否需要注册注解后置处理,比如注册ConfigurationClassPostProcessor,AutowiredAnnotationBeanPostProcessor等
	//但其实这些处理器已经注册了,这里也不需要再注册了
	if (this.includeAnnotationConfig) {
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}
       //注册了多少mapper
	return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
	
# ClassPathMapperScanner类方法
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //调用父类的扫描方法
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
      //对扫描出来的BeanDefinition做属性处理,比如添加SqlSessionFactory等
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}
  • 扫描出的BeanDefinition修改
  • 核心内容:将真实的BeanClass属性设置为MapperFactoryBean.class
    • BeanDefinition当初创建赋值:beanClass = “com.xc.UserMapper”
    • BeanDefinition的beanName依然是“userMapper
  • BeanDefinition的构造函数添加mapperInterface参数
    • 以后创建MapperFactoryBean时,Mapper接口为构造函数参数
      在这里插入图片描述
// 主要做了这么几件事
// 1.将扫描出来的BeanDefinition的beanClass属性设置为MapperFactoryBeanClass.class
// 2.在BeanDefinition的ConstructorArgumentValues添加一个参数
// 限定实例化时使用MapperFactoryBeanClass的带参构造函数
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
        definition = (GenericBeanDefinition) holder.getBeanDefinition();
        String beanClassName = definition.getBeanClassName();
        
        // 往构造函数的参数集合中添加了一个值,那么在实例化时就会使用带参的构造函数
        // 等价于在XML中配置了
        // <constructor-arg name="mapperInterface" value="mapperFactoryBeanClass"/>
        definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 
        
        // 将真实的BeanClass属性设置为mapperFactoryBeanClass
        definition.setBeanClass(this.mapperFactoryBeanClass);
 
        definition.getPropertyValues().add("addToConfig", this.addToConfig);
  
        // 开始检查是否显示的指定了sqlSessionFactory或者sqlSessionTemplate
        boolean explicitFactoryUsed = false;
        
        // 首先检查是否在@MapperScan注解上配置了sqlSessionFactoryRef属性
        if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
            
            // 如果配置了的话,那么在这个bd的属性集合中添加一个RuntimeBeanReference
            // 等价于在xml中配置了
            // <property name="sqlSessionFactory" ref="sqlSessionFactoryBeanName"/>
            definition.getPropertyValues().add("sqlSessionFactory",
                                               new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
            explicitFactoryUsed = true;
            // 如果@MapperScan上没有进行配置
            // 那么检查是否为这个bean配置了sqlSessionFactory属性
            // 正常来说我们都不会进行配置,会进入自动装配的逻辑
        } else if (this.sqlSessionFactory != null) {
            definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
            explicitFactoryUsed = true;
        }
 
        // 省略sqlSessionTemplate部分代码
        // 逻辑跟sqlSessionFactory属性的处理逻辑一致
        // 需要注意的是,如果同时显示指定了sqlSessionFactory跟sqlSessionTemplate
        // 那么sqlSessionFactory的配置将失效
        // .....
 
        if (!explicitFactoryUsed) {
           // 如果没有显示的配置,那么设置为自动注入
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }
        // 默认不是懒加载
        definition.setLazyInit(lazyInitialization);
    }
}

2、SqlSessionFactoryBean类

类图:

在这里插入图片描述

  • FactoryBean接口:意味着由 Spring 最终创建的 bean 并不是 SqlSessionFactoryBean 本身,而是工厂类(SqlSessionFactoryBean)的 getObject() 方法的返回结果
  • InitializingBean:bean初始化时候做一些操作

2.1、FactoryBean接口

FactoryBean定义了三个方法,其源码如下:

public interface FactoryBean<T> {
	// 当IoC容器通过getBean方法来创建FactoryBean的实例时实际获取的不是FactoryBean 本身
	// 而是具体创建的T泛型实例。
    @Nullable
    T getObject() throws Exception;
	// 返回FactoryBean创建的bean类型。
    @Nullable
    Class<?> getObjectType();
	// 返回由FactoryBean创建的bean实例的作用域是singleton还是prototype。
    default boolean isSingleton() {
        return true;
    }
}

SqlSessionFactoryBean类中对于FactoryBean的实现:

在这里插入图片描述

进入afterPropertiesSet方法

@Override
public void afterPropertiesSet() throws Exception {
  //dataSource是必须要配置的
  notNull(dataSource, "Property 'dataSource' is required");
  notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
  state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
            "Property 'configuration' and 'configLocation' can not specified with together");

  //主要逻辑都在buildSqlSessionFactory方法,创建sqlSessionFactory,getObject就是返回的sqlSessionFactory 
  this.sqlSessionFactory = buildSqlSessionFactory();
}
  • 创建SqlSessionFactory
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
  // 定义了一个Configuration,叫做targetConfiguration。
  final Configuration targetConfiguration;

  XMLConfigBuilder xmlConfigBuilder = null;
  // 判断 Configuration 对象是否已经存在,也就是是否已经解析过。如果已经有对象,就覆盖一下属性
  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);
    }
    // 如果 Configuration 不存在,但是配置了 configLocation 属性,
    // 就根据mybatis-config.xml的文件路径,构建一个xmlConfigBuilder对象。
  } else if (this.configLocation != null) {
    xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
    targetConfiguration = xmlConfigBuilder.getConfiguration();
    // 否则,Configuration 对象不存在,configLocation 路径也没有,
    // 只能使用默认属性去构建去给configurationProperties赋值。
  } else {
    LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified,using default MyBatis Configuration");
    targetConfiguration = new Configuration();
    Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
  }
 

  // configuration的属性赋值
  ...

  // 如果xmlConfigBuilder 不为空,也就是上面的第二种情况,
  if (xmlConfigBuilder != null) {
    try {
      // 调用了xmlConfigBuilder.parse()去解析配置文件,最终会返回解析好的Configuration对象
      xmlConfigBuilder.parse();
      LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
    } catch (Exception ex) {
      throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  // 如果没有明确指定事务工厂 ,默认使用pringManagedTransactionFactory。
  // 它创建的 SpringManagedTransaction 也有getConnection()和close()方法
  // <property name="transactionFactory" value="" />
  targetConfiguration.setEnvironment(new Environment(this.environment,
      this.transactionFactory == null 
      ? new SpringManagedTransactionFactory() 
      : this.transactionFactory,this.dataSource));

  // 指定mybatis-config.xml文件地址,注解开发,不会进去
  if (!isEmpty(this.mapperLocations)) {
    for (Resource mapperLocation : this.mapperLocations) {
      if (mapperLocation == null) {
        continue;
      }
      try {
        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
         targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
        // 调用xmlMapperBuilder.parse(),
        // 它的作用是把接口和对应的MapperProxyFactory 注册到MapperRegistry 中。
        xmlMapperBuilder.parse();
      } catch (Exception e) {
        throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + e);
      } finally {
        ErrorContext.instance().reset();
      }
      LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
    }
  } else {
    LOGGER.debug(() -> "Property 'mapperLocations' was not specified or no matching resources found");
  }

  // 最后调用 sqlSessionFactoryBuilder.build() 返回一个 DefaultSqlSessionFactory。
  return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

// SqlSessionFactoryBuilder类方法
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

总结:

SqlSessionFactoryBean就是创建SqlSessionFactory,为以后创建SqlSession做准备

3、MapperFactoryBean类

MapperFactoryBean即创建 mapper接口的 bean 定义被替换的类型

类图:

在这里插入图片描述

3.1、FactoryBean接口

  • 创建 Mapper的 bean 定义时候将 Class 类型修改为MapperFactoryBean
  • 实际它是FactoryBean对象,真正返回的对象为 getObject()的结果

在这里插入图片描述

3.2、DaoSupport抽象类

  • 初始化时候执行的操作,调用checkDaoConfig方法
public abstract class DaoSupport implements InitializingBean {
	@Override
	public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
		// 子类可以实现这个方法去检查相关的配置信息
		checkDaoConfig();

		// 子类实现,目前没有实现的子类
		try {
			initDao();
		}
		catch (Exception ex) {
			throw new BeanInitializationException("Initialization of DAO failed", ex);
		}
	}
	protected abstract void checkDaoConfig() throws IllegalArgumentException;

	protected void initDao() throws Exception {
	}
}

3.3、SqlSessionDaoSupport抽象类

public abstract class SqlSessionDaoSupport extends DaoSupport {
 
  private SqlSessionTemplate sqlSessionTemplate;
 
  //  这个是核心方法
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }
 
  // 省略一些getter/setter方法
 
  // 在初始化时要检查sqlSessionTemplate,确保其不为空
  @Override
  protected void checkDaoConfig() {
    notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
  }
}
  • 创建sqlSessionTemplate对象

在这里插入图片描述

  • Session的代理对象sqlSessionProxy的 invoke 方法
private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    
        // 第一步,获取一个sqlSession
        SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
                                              SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
        try {
            // 第二步,调用sqlSession对应的方法
            Object result = method.invoke(sqlSession, args);
            
            // 检查是否开启了事务,如果没有开启事务那么强制提交
            if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
           
                sqlSession.commit(true);
            }
            return result;
        } catch (Throwable t) {
            // 处理异常
            Throwable unwrapped = unwrapThrowable(t);
            if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
               
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                sqlSession = null;
                Throwable translated = SqlSessionTemplate.this.exceptionTranslator
                    .translateExceptionIfPossible((PersistenceException) unwrapped);
                if (translated != null) {
                    unwrapped = translated;
                }
            }
            throw unwrapped;
        } finally {
            // 关闭sqlSession
            if (sqlSession != null) {
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
        }
    }
}
  • SqlSession的获取,熟悉的味道:sessionFactory.openSession()
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) {
 
	notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
	notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
	
	SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
	
	SqlSession session = sessionHolder(executorType, holder);
	if (session != null) {
	  return session;
	}
	// 看到了吧,在这里调用了SqlSessionFactory创建了一个sqlSession
	LOGGER.debug(() -> "Creating a new SqlSession");
	session = sessionFactory.openSession(executorType);
	// 如果开启了事务的话并且事务是由Spring管理的话,会将sqlSession绑定到当前线程上
	registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
	
	return session;
}

3.4、MapperFactoryBean的checkDaoConfig方法

  • 创建 Mapper接口代理对象初始化时候,会调用此方法
  • 这里就会进入 mybatis 的源码了Mybatis源码解析(八):Mapper代理原理
// 之前分析过了,这个方法会在MapperFactoryBean进行初始化的时候调用
protected void checkDaoConfig() {
  super.checkDaoConfig();
  Configuration configuration = getSqlSession().getConfiguration();
   //addToConfig默认为true的,将mapper接口添加到mybatis的配置信息中
  if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
    try {
      configuration.addMapper(this.mapperInterface);
    } catch (Exception e) 
      throw new IllegalArgumentException(e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}
 
// 直接调用了mybatis中现成的方法获取一个代理对象然后放入到容器中
@Override
public T getObject() throws Exception {
  return getSqlSession().getMapper(this.mapperInterface);
}

4、总结Mapper对象的创建过程

  1. 根据@MapperScan注解扫描指定路径下接口
    • 创建BeanDefinition,beanName 为“userMapper”
    • beanClass 修改为MapperFactoryBean.class
  2. 实例化 userMapper也就是MapperFactoryBean
    • 属性注入SqlSessionFactory(由SqlSessionFactoryBean创建而来)

在这里插入图片描述

  1. 初始化 userMapper 会调用上面的checkDaoConfig方法
    • mybatis 源码内容,将接口创建代理对象
    • 代理对象统一放入SqlSession的Configuration对象中
  2. 对于context.getBean(UserMapper.class);
    • 由于userMapper的代理对象是MapperFactoryBeanFactoryBean
    • 获取对象时候,实际是获取getObject()返回的结果
    • 此时会从SqlSession的Configuration中获取mybatis生成的代理对象

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机毕设之基于数据可视化的智慧社区内网平台python+django+mysql(含开题+源码+部署教程)

系统阐述的是一款基于数据可视化的智慧社区内网平台的设计与实现&#xff0c;对于Python、B/S结构、MySql进行了较为深入的学习与应用。主要针对系统的设计&#xff0c;描述&#xff0c;实现和分析与测试方面来表明开发的过程。开发中使用了 django框架和MySql数据库技术搭建系…

el表达式和标签库的简单使用!!!

备工作1&#xff1a;tomcat10.1.13版本&#xff0c;可去官网下载&#xff0c;不会下载的童靴看过来&#xff1a;如何正确下载tomcat&#xff1f;&#xff1f;&#xff1f;_明天更新的博客-CSDN博客 准备工作2&#xff1a;添加架包&#xff08;需要三个架包&#xff09; jstl架包…

老师们快看过来,这里有使用ChatGPT当助教的方法

最近OpenAI官方博客发布了一篇文章How teachers are using ChatGPT&#xff08;老师们如何使用ChatGPT&#xff09;&#xff0c;讲的是老师们如何在教学中使用ChatGPT&#xff0c;其中有几个例子挺好的&#xff0c;我转述一下&#xff0c;希望对你有用。 制定教案 第一个例子…

[git]分支操作

Checkout 相当于切换到该分支&#xff0c;但是因为不能直接操作远程分支&#xff0c;会在本地同步一个完全一样的分支。 注意&#xff1a;切换分支前本地先进行提交&#xff08;addcommit&#xff09;&#xff0c;否则有可能代码会丢失。 New Branch from Selected... 创建一…

【小沐学Unity3d】3ds Max 多维子材质编辑(Multi/Sub-object)

文章目录 1、简介2、精简材质编辑器2.1 先创建多维子材质&#xff0c;后指定它2.2 先指定标准材质&#xff0c;后自动创建多维子材质 3、Slate材质编辑器3.1 编辑器简介3.2 编辑器使用 结语 1、简介 多维子材质&#xff08;Multi/Sub-object&#xff09;是为一个模形&#xff0…

信息安全检测和应用信息系统安全测试

安全测试 信息安全检测&#xff0c;为软件/信息系统出具的软件检测报告&#xff08;或第三方检测报告、软件安全测试报告&#xff09;&#xff0c;是信息系统/软件上线前都需要的测试报告。 信息安全检测的标准&#xff1a; 信息安全检测依据DB31/T272-2008《计算机信息系统…

【ICer的脚本练习】“精通各种语言的hello world!“

系列的目录说明请见&#xff1a;ICer的脚本练习专栏介绍与全流程目录_尼德兰的喵的博客-CSDN博客 前言 这一节呢主要是检查一下Linux和win环境是不是能正常的支持咱们的脚本学习&#xff0c;所以来答应各种语言的hello world!&#xff0c;毕竟打印了就是学会了٩(๑❛ᴗ❛๑)۶…

Nex.js Web 应用程序 SSG 与 SSR——选择正确的渲染方法

Next.js&#xff0c;一个流行的React框架&#xff0c;改变了开发人员构建现代Web应用程序的方式。它提供了强大的功能&#xff0c;例如服务器端渲染 &#xff08;SSR&#xff09; 和静态站点生成 &#xff08;SSG&#xff09;&#xff0c;可优化应用程序的性能和用户体验。在这…

03. 程序在内存中被CPU执行

1. 程序是什么&#xff1f; 程序是由指令和数据组成的。 当我们使用计算机运行一个程序时&#xff0c;计算机会读取程序中的指令一步步执行&#xff0c;直到达到程序结束的地方。 程序的指令&#xff1a;就像一份菜谱&#xff0c;告诉计算机按照哪些步骤来做事情。 程序的数…

【原创】H3C三层交换机的路由模式

网络拓扑图 将三层交换机当路由器使用 交换机配置 <H3C>dis stp briefMST ID Port Role STP State Protection0 GigabitEthernet1/0/1 DESI LEARNING NONE0 GigabitEthernet1/0/2 …

docker常用中间件安装

文章目录 1、前言2、中间件安装2.1、mysql2.2、gitlab容器2.3、nacos2.4、redis2.5、xxljob2.6、zipkin2.7、sentinel2.8、seata2.8.1、获取镜像2.8.2、运行容器并获取配置 2.9、rockerMQ2.9.1、rockerMQ-namesrv2.9.2、rockerMQ-broker2.9.3、rockerMQ-console 2.10、jenkins2…

java企业工程管理系统源码之提高工程项目管理软件的效率

高效的工程项目管理软件不仅能够提高效率还应可以帮你节省成本提升利润 在工程行业中&#xff0c;管理不畅以及不良的项目执行&#xff0c;往往会导致项目延期、成本上升、回款拖后&#xff0c;最终导致项目整体盈利下降。企企管理云业财一体化的项目管理系统&#xff0c;确保…

文件分片上传

概要 在日常开发中上传文件是常见的功能&#xff0c;像使用 SpringBoot 作为服务端接收上传的文件是很方便的&#xff0c;但是默认情况下 SpringBoot 为我限定了单次上传文件的大小&#xff0c;默认是1MB&#xff0c;当我们单次上传的大小超过1MB的时候就会报错&#xff0c;这…

Vmware虚拟机操作系统和本地操作系统互Ping要求、解决方式讲解

Vmware虚拟机操作系统和本地操作系统互Ping讲解 在虚拟化环境中&#xff0c;如VMware&#xff0c;虚拟机&#xff08;Virtual Machine&#xff0c;简称VM&#xff09;和本地操作系统之间进行Ping测试是一项常见的任务。Ping测试可用于检查虚拟机是否能够与本地操作系统或其他网…

wap2app 隐藏系统状态栏

一、首先创建wap2App项目 1、文件》新建》项目 2、选择Wap2App项目&#xff1a;输入项目名称、网站首页地址&#xff08;如果是本地localhost的话改为你的IP地址即可&#xff09;&#xff0c;点击创建 二、创建完wap2App项目后 隐藏系统状态栏只要修改1、2选项即可 1、找到根…

智慧工地源码 智慧大屏、手机APP、SaaS模式

一、智慧工地可以通过安全八要素来提升安全保障&#xff0c;具体措施包括&#xff1a; 1.安全管理制度&#xff1a;建立科学完善的安全管理制度&#xff0c;包括安全标准规范、安全生产手册等&#xff0c;明确各项安全管理职责和要求。 2.安全培训教育&#xff1a;对工地人…

港科夜闻|香港科大商学院李莹莹教授获研究资助局(RGC)授予研资局高级研究员头衔...

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科大商学院李莹莹教授获研究资助局(RGC)授予“研资局高级研究员”头衔。李教授的研究项目“金融风险大数据”将利用金融大数据进行严谨、全面的统计研究&#xff0c;更准确地进行风险评估和预测&#xff0c;构建高质…

Linux内核开启BBR加速

升级内核 BBR 模块从 4.9 版本的内核中开始支持&#xff0c;CentOS7 的发布版标配的是 kernel-3.10&#xff0c;所以首先需要升级内核到大于等于 4.9 的版本&#xff0c;然后再更改设置开启 BBR。 查看内核版本 1) uname -r 3.10.0-1160.71.1.el7.x86_64YUM方式升级安装 1&…

跨境做独立站,如何低成本引流?

大家都知道&#xff0c;海外的消费习惯与国内不同&#xff0c;独立站一向是海外消费者的最喜欢的购物方式之一&#xff0c;这也吸引了许多跨境商家开设独立站。 独立站不同于其他的第三方平台&#xff0c;其他平台可以靠平台自身流量来获得转化&#xff0c;而独立站本身没有流…

django.core.exceptions.AppRegistryNotReady: Apps aren‘t loaded yet.

运行django测试用例报错django.core.exceptions.AppRegistryNotReady: Apps arent loaded yet. 解决&#xff1a;在测试文件上方加上 django.setup() django.setup()是Django框架中的一个函数。它用于在非Django环境下使用Django的各种功能、模型和设置。 在常规的Django应用…