Spring之整合Mybatis底层源码

文章目录

    • 一、整体核心思路
      • 1 . 简介
      • 2. 整合思路
    • 二、源码分析
      • 1. 环境准备
      • 2. 源码分析

一、整体核心思路

1 . 简介

有很多框架需要与Spring进行整合,而整合的核心思路就是把其他框架所产生的对象放到Spring容器中,让其成为一个bean。比如Mybatis,Mybatis框架本身是可以单独使用的,而单独使用Mybtis框架就需要用到Mybatis所提供的一些类构造出对应的对象,然后使用该对象,就能使用到Mybatis框架给我们提供的功能,和Mybatis整合SPring就是为了将这些对象放入Spring中成为Bean,在我们的Spring项目中就能很方便的使用这些对象了,也就能很方便的使用Mybatis框架提供的功能了。

2. 整合思路

  • 安装

导入jar包:

<dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.3</version>
</dependency>
  • 快速上手

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。在 MyBatis-Spring 中,可使用 SqlSessionFactoryBean 来创建 SqlSessionFactory。 要配置这个工厂 Bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

<bean id="dataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"></property>  <property name="url" value="jdbc:mysql://localhost/mysql"></property> <property name="username" value="root"></property> <property name="password" value="1234"></property>   
</bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource" />
</bean>

SqlSessionFactory 需要一个 DataSource(数据源)。 这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。

假设你定义了一个如下的 mapper 接口:

public interface UserMapper {@Select("SELECT * FROM users WHERE id = #{userId}")User getUser(@Param("userId") String userId);
} 

那么可以通过 MapperFactoryBean 将接口加入到 Spring 中:

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"><property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" /><property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

需要注意的是:所指定的映射器类必须是一个接口,而不是具体的实现类。在这个示例中,通过注解来指定 SQL 语句,但是也可以使用 MyBatis 映射器的 XML 配置文件。

配置好之后,你就可以像 Spring 中普通的 Bean 注入方法那样,将映射器注入到你的业务或服务对象中。MapperFactoryBean 将会负责 SqlSession 的创建和关闭。如果使用了 Spring 的事务功能,那么当事务完成时,Session 将会被提交或回滚。最终任何异常都会被转换成 Spring 的 DataAccessException 异常。

要调用 MyBatis 的数据方法,只需一行代码:

public class FooServiceImpl implements FooService {private final UserMapper userMapper;public FooServiceImpl(UserMapper userMapper) {this.userMapper = userMapper;}public User doSomeBusinessStuff(String userId) {return this.userMapper.getUser(userId);}
}

二、源码分析

1. 环境准备

  • 准备个mapper
public interface UserMapper {@Select("select 'user'")String selectById();
}
  • 准备个UserService
@Component
public class UserService {@AutowiredUserMapper userMapper;public void test(){System.out.println(userMapper.selectById());}
}
  • 测试类代码
	public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();applicationContext.register(AppConfig.class);applicationContext.refresh();UserService aService = (UserService) applicationContext.getBean("userService");aService.test();applicationContext.close();}

允许上面代码我们知道肯定会报错,报错的原因是什么,主要是我们的spring容器中是没有userMapper这个bean的,所以依赖注入这里会失败。那我们分析,userMapper这里要注入的到底是什么对象?由Mybatis的工作原理我们知道,userMapper要注入的应该是mybatis为该接口产生的一个代理对象,一旦我们拿到这个Mybatis产生的代理对象,我们就可以真正的做到操作Mybatis框架了,这也是Spring和Mybatis整合的一个关键点,这也是Spring整合其它框架的思路。而创建这个对象的关键就是,FactoryBean。

@Component
public class JackFactoryBean implements FactoryBean {@Overridepublic Object getObject() throws Exception {//使用jdk动态代理的技术Object ProxyInstance= Proxy.newProxyInstance(JackFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return null;}});return ProxyInstance;}@Overridepublic Class<?> getObjectType() {return UserMapper.class;}
}

在分析Bean的生命周期源码的时候,我们知道FacotryBean底层的beanName为jackFactoryBean,但它所对应的Bean类型为getObject方法所返回的对象。这里我们以jdk动态代理技术为UserMapper生成了一个代理对象,测试一下上面的测试代码。

现在我们就可以成功注入UserMapper对象到UserService中了

在这里插入图片描述
但我们发现一个问题,如果我们每需要一个Mapper我们都去创建一个FactoryBean,这是很不合理的,所以我做一下改进。

@Component
public class JackFactoryBean implements FactoryBean {private Class MapperInterface;@Overridepublic Object getObject() throws Exception {//使用jdk动态代理的技术Object ProxyInstance= Proxy.newProxyInstance(JackFactoryBean.class.getClassLoader(), new Class[]{MapperInterface}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return null;}});return ProxyInstance;}@Overridepublic Class<?> getObjectType() {return MapperInterface;}
}

但新的问题出来了,由于JackFactoryBean上面加了@Component注解,这是一个单例bean,而我们基于上面的改进代码我们知道,我们是要用这个FactoryBean去创建多个mapper的,所以我们不能这么定义bean,这里我们在测试类中改用以前用过的方式来创建bean。

public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();applicationContext.register(AppConfig.class);AbstractBeanDefinition beanDefinition=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();beanDefinition.setBeanClass(JackFactoryBean.class);beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);applicationContext.registerBeanDefinition("userMapper",beanDefinition);AbstractBeanDefinition beanDefinition1=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();beanDefinition1.setBeanClass(JackFactoryBean.class);beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);applicationContext.registerBeanDefinition("orderMapper",beanDefinition1);applicationContext.refresh();UserService userService = (UserService) applicationContext.getBean("userService");userService.test();applicationContext.close();}

使用上面方法就给JackFactoryBean类型创建了多个bean,注意userMapperorderMapper所对应的类型都是userMapperorderMapper的代理对象。但上面这些代码需要程序员在启动Spring之前自己写,这同样是不可取的。现在我们详细我们的根本目的是往容器中注入BeanDefinition,然后生成我们需要的bean,但仔细回顾我们前面学的知识,还有哪些方式可以注册BeanDefinition,这里我们就想到了前一章学到的BeanDefinitionRegistryPostProcessor接口。

@Component
public class JackBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {AbstractBeanDefinition beanDefinition= BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();beanDefinition.setBeanClass(JackFactoryBean.class);beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);registry.registerBeanDefinition("userMapper",beanDefinition);AbstractBeanDefinition beanDefinition1=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();beanDefinition1.setBeanClass(JackFactoryBean.class);beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);registry.registerBeanDefinition("orderMapper",beanDefinition1);}
} 

在这里插入图片描述
但上面代码的灵活性还是不高,因为我们还是把OrderMapper和UserMapper给写死了,所以我们能不能提供一种包扫描的方式,自动拿到所有的Mapper,所以思路有了下面就开始实现。首先我们自定义一个注解:


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface JackMapperScan {String value();
}

这里我们要知道其实是在实现@MapperScan注解

然后在配置类上加上该注解,现在我们就通过注解拿到我们的路径值了


@ComponentScan("com.zhouyu")
@EnableScheduling
@PropertySource("classpath:spring.properties")
@EnableTransactionManagement
@JackMapperScan("com.zhouyu.mapper")
public class AppConfig {}

现在我们采用前面学习过的另一种思路来注入BeanDefinition,即使用ImportBeanDefinitionRegistrar接口,(使用这个类的原因是,我们可以很方便拿到注解信息)

public class JackImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {AbstractBeanDefinition beanDefinition= BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();beanDefinition.setBeanClass(JackFactoryBean.class);beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);registry.registerBeanDefinition("userMapper",beanDefinition);AbstractBeanDefinition beanDefinition1=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();beanDefinition1.setBeanClass(JackFactoryBean.class);beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);registry.registerBeanDefinition("orderMapper",beanDefinition1);}
}

然后在配置类上导入该类,我们看过源码知道这个类加@Component是没有效果的,所以我们在配置类上导入该类。

@ComponentScan("com.zhouyu")
@EnableScheduling
@PropertySource("classpath:spring.properties")
@EnableTransactionManagement
@JackMapperScan("com.zhouyu.mapper")
@Import(JackImportBeanDefinitionRegistrar.class)
public class AppConfig {}

现在BeanDefinition是可以注入进容器的。

在这里插入图片描述
我们知道,在Spring解析我们的注解,它会解析该注解和注解底层使用的注解,所以现在我们可以把Import注解加入到我们自定义的注解里面。所以现在我们就可以真正的开始解析扫描路径了。

public class JackImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(JackMapperScan.class.getName());//然后拿到扫描路径String value = (String) annotationAttributes.get("value");System.out.println(value);AbstractBeanDefinition beanDefinition= BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();beanDefinition.setBeanClass(JackFactoryBean.class);beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);registry.registerBeanDefinition("userMapper",beanDefinition);AbstractBeanDefinition beanDefinition1=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();beanDefinition1.setBeanClass(JackFactoryBean.class);beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);registry.registerBeanDefinition("orderMapper",beanDefinition1);}
}

我们就成功拿到了扫描路径
在这里插入图片描述
下面的关键是如何实现扫描逻辑,前面我们说过Spring是提供了一个扫描器的,它在扫描BeanDefinition的时候起了关键作用,即ClassPathBeanDefinitionScanner,下面自定义一个Scanner。

public class JackScanner extends ClassPathBeanDefinitionScanner {public JackScanner(BeanDefinitionRegistry registry) {super(registry);}
}

有了扫描器,现在的问题是我们知道Spring底层的扫描器是不会扫接口的,所以我们要重写isCandidateComponent方法让其支持扫描借口。

public class JackScanner extends ClassPathBeanDefinitionScanner {public JackScanner(BeanDefinitionRegistry registry) {super(registry);}//我们是不能使用Spring提供的扫描逻辑的@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {//我们只关心接口return beanDefinition.getMetadata().isInterface();}
}

另外一点从源码我们知道,源码中会判断includefilters属性,这是Component的一个属性,即加了Component注解会自动包含在includefilters属性中,所以我们也要把自己定义的这些接口全部加入到includefilters属性中。

public class JackImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(JackMapperScan.class.getName());//然后拿到扫描路径String value = (String) annotationAttributes.get("value");JackScanner scanner=new JackScanner(registry);scanner.addIncludeFilter(new TypeFilter() {@Overridepublic boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {//这样写扫描路径下的所有类和接口都会反在IncludeFileter属性中return true;}});scanner.scan(value);AbstractBeanDefinition beanDefinition= BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();beanDefinition.setBeanClass(JackFactoryBean.class);beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);registry.registerBeanDefinition("userMapper",beanDefinition);AbstractBeanDefinition beanDefinition1=BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();beanDefinition1.setBeanClass(JackFactoryBean.class);beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);registry.registerBeanDefinition("orderMapper",beanDefinition1);}
}

我们测试一下,现在能否拿到mapper生成的BeanDefinition,首先完善一下代码:

public class JackScanner extends ClassPathBeanDefinitionScanner {public JackScanner(BeanDefinitionRegistry registry) {super(registry);}//我们是不能使用Spring提供的扫描逻辑的@Overrideprotected Set<BeanDefinitionHolder> doScan(String... basePackages) {Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);System.out.println(beanDefinitionHolders);return beanDefinitionHolders;}@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {//我们只关心接口return beanDefinition.getMetadata().isInterface();}
}

现在我们通过打破Spring原始扫描的逻辑,确实是拿到了自己需要的mapper的bean

在这里插入图片描述

但现在的问题是,我们需要的不是接口的实际类型,但是我们实际需要的是这些借口的代理对象,所以我们还需要对BeanDefinition做一些处理。

public class JackScanner extends ClassPathBeanDefinitionScanner {public JackScanner(BeanDefinitionRegistry registry) {super(registry);}//我们是不能使用Spring提供的扫描逻辑的@Overrideprotected Set<BeanDefinitionHolder> doScan(String... basePackages) {Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {GenericBeanDefinition beanDefinition= (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());beanDefinition.setBeanClassName(JackFactoryBean.class.getName());beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);}return beanDefinitionHolders;}@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {//我们只关心接口return beanDefinition.getMetadata().isInterface();}
}

再对JackImportBeanDefinitionRegistrar作一些修改:

public class JackImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(JackMapperScan.class.getName());//然后拿到扫描路径String value = (String) annotationAttributes.get("value");JackScanner scanner=new JackScanner(registry);scanner.addIncludeFilter(new TypeFilter() {@Overridepublic boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {//这样写扫描路径下的所有类和接口都会反在IncludeFileter属性中return true;}});scanner.scan(value);}
}

在这里插入图片描述

现在我们的代码就可以成功运行了

现在还有一个问题,就是现在mapper的代理对象是我们自己使用jdk代理生成的,但是我们现在需要的使用Mybatis来生成我们的代理对象。所以在代理这一块我们需要重新写一下。在使用Mybatis时,我们使用的使用的SqlSession对象来获取代理对象的。

public class JackFactoryBean implements FactoryBean {private Class MapperInterface;private SqlSession sqlSession;public void setSqlSession(SqlSessionFactory sqlSessionFactory) {sqlSessionFactory.getConfiguration().addMapper(MapperInterface);this.sqlSession = sqlSessionFactory.openSession();}public JackFactoryBean(Class mapperInterface) {this.MapperInterface = mapperInterface;}@Overridepublic Object getObject() throws Exception {return sqlSession.getMapper(MapperInterface);}@Overridepublic Class<?> getObjectType() {return MapperInterface;}
}

在注入SqlSession时,我们使用的是Set方法注入,但是我们需要一个SqlSessionFactory对象在Spring容器中,现在的关键是如何向Spring容器中加入SqlSessionFactory的对象。

@ComponentScan("com.zhouyu")
@EnableScheduling
@PropertySource("classpath:spring.properties")
@EnableTransactionManagement
@JackMapperScan("com.zhouyu.mapper")
public class AppConfig {@BeanSqlSessionFactory sqlSessionFactory() throws IOException {InputStream inputStream= Resources.getResourceAsStream("mybatis.xml");SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);return  sqlSessionFactory;}
}
<?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><environments default="development"><environment id="development"><!-- 使用jdbc事务管理 --><transactionManager type="JDBC"/><!-- 数据库连接池 --><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url"value="jdbc:mysql://127.0.0.1:3306/mysql_learn?characterEncoding=utf-8&amp;useSSL=false"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments></configuration>

现在就可以成功的输出sql语句的结果了

在这里插入图片描述
现在Spring和Mybatis已经整合完了,关键点是如何将Mybatis的代理对象注入到Spring容器中。

2. 源码分析

上面我们自动动手整合了Spring和Mybatis,其实spring-mybatis包底层原理几乎差不多,只是过程更复杂,现在我们基于spring-mybatis的版本1.3.2来分析一下源码。

首先我们从@MapperScan注解开始

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {String[] value() default {};String[] basePackages() default {};Class<?>[] basePackageClasses() default {};Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;Class<? extends Annotation> annotationClass() default Annotation.class;Class<?> markerInterface() default Class.class;String sqlSessionTemplateRef() default "";String sqlSessionFactoryRef() default "";Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;String lazyInitialization() default "";String defaultScope() default AbstractBeanDefinition.SCOPE_DEFAULT;}

上面就用了@Import(MapperScannerRegistrar.class)注解,这里就对应我们案例中的@Import(JackImportBeanDefinitionRegistrar.class),我们进去看一下这个类。

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {private ResourceLoader resourceLoader;/*** {@inheritDoc}*/@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);// this check is needed in Spring 3.1if (resourceLoader != null) {scanner.setResourceLoader(resourceLoader);}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(BeanUtils.instantiateClass(generatorClass));}Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));}scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));List<String> basePackages = new ArrayList<String>();for (String pkg : annoAttrs.getStringArray("value")) {if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}for (String pkg : annoAttrs.getStringArray("basePackages")) {if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {basePackages.add(ClassUtils.getPackageName(clazz));}scanner.registerFilters();scanner.doScan(StringUtils.toStringArray(basePackages));}/*** {@inheritDoc}*/@Overridepublic void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader = resourceLoader;}}

上面这个类同样实现ImportBeanDefinitionRegistrar接口,重写了registerBeanDefinitions方法。

 AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));

上面代码和案例中一样是拿@MapperScan注解的一些信息。

ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

上面就是mybatis自定义的扫描器

 scanner.doScan(StringUtils.toStringArray(basePackages));

然后这里就开始使用子定义的扫描器开始扫描

@Overridepublic 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 {processBeanDefinitions(beanDefinitions);}return beanDefinitions;}private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {GenericBeanDefinition definition;for (BeanDefinitionHolder holder : beanDefinitions) {definition = (GenericBeanDefinition) holder.getBeanDefinition();if (logger.isDebugEnabled()) {logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");}// the mapper interface is the original class of the bean// but, the actual class of the bean is MapperFactoryBeandefinition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59definition.setBeanClass(this.mapperFactoryBean.getClass());definition.getPropertyValues().add("addToConfig", this.addToConfig);boolean explicitFactoryUsed = false;if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionFactory != null) {definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);explicitFactoryUsed = true;}if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {if (explicitFactoryUsed) {logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");}definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionTemplate != null) {if (explicitFactoryUsed) {logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");}definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);explicitFactoryUsed = true;}if (!explicitFactoryUsed) {if (logger.isDebugEnabled()) {logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");}definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);}}}

上面就是Mybatis重写了spring框架的doscan方法:

Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

上面代码就是调用Spring的doScan方法拿到BeanDefinition

 processBeanDefinitions(beanDefinitions);

上面代码就是去修改BeanDefinition

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {GenericBeanDefinition definition;for (BeanDefinitionHolder holder : beanDefinitions) {definition = (GenericBeanDefinition) holder.getBeanDefinition();if (logger.isDebugEnabled()) {logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");}// the mapper interface is the original class of the bean// but, the actual class of the bean is MapperFactoryBeandefinition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59definition.setBeanClass(this.mapperFactoryBean.getClass());definition.getPropertyValues().add("addToConfig", this.addToConfig);boolean explicitFactoryUsed = false;if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionFactory != null) {definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);explicitFactoryUsed = true;}if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {if (explicitFactoryUsed) {logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");}definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));explicitFactoryUsed = true;} else if (this.sqlSessionTemplate != null) {if (explicitFactoryUsed) {logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");}definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);explicitFactoryUsed = true;}if (!explicitFactoryUsed) {if (logger.isDebugEnabled()) {logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");}definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);}}}

上面代码思路和我们一样同样是遍历并修改BeanDefinition,关键代码就是下面这几行:

definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());definition.setBeanClass(this.mapperFactoryBean.getClass());definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

然后this.mapperFactoryBean就是Mybatis写的一个FactoryBean,我们看一下这个类。

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {//要生成代理对象的接口的类型private Class<T> mapperInterface;private boolean addToConfig = true;public MapperFactoryBean() {//intentionally empty }public MapperFactoryBean(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}/*** {@inheritDoc}*/@Overrideprotected void checkDaoConfig() {super.checkDaoConfig();notNull(this.mapperInterface, "Property 'mapperInterface' is required");//设置SqlSessionConfiguration configuration = getSqlSession().getConfiguration();if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {try {configuration.addMapper(this.mapperInterface);} catch (Exception e) {logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);throw new IllegalArgumentException(e);} finally {ErrorContext.instance().reset();}}}@Overridepublic T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}/*** {@inheritDoc}*/@Overridepublic Class<T> getObjectType() {return this.mapperInterface;}.......
}

上面代码和我们一样首先定义了要生成代理对象的接口的类型

 private Class<T> mapperInterface;

然后获取SqlSession对象

  @Overridepublic T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}public abstract class SqlSessionDaoSupport extends DaoSupport {private SqlSession sqlSession;private boolean externalSqlSession;public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {if (!this.externalSqlSession) {this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);}}public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {this.sqlSession = sqlSessionTemplate;this.externalSqlSession = true;}public SqlSession getSqlSession() {return this.sqlSession;}@Overrideprotected void checkDaoConfig() {notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");}}

SqlSessionDaoSupport是MapperFactoryBean的父类,主要就是用来处理SqlSession的,包括设置SqlSessionFactory,前面源码在processBeanDefinitions设置了注入形式为Bytype,所以在注入SqlSession时会自动调用该类的所有set方法,这样SqlSession对象就可以被生成了。

这里有个不同点,我们使用openSession来获取SqlSession对象,但是这里是使用sqlSessionTemplate来创建sqlSession的

这里还是挺重要的,这部分源码需要涉及Mybatis源码分析,这部分在分析Mybatis源码分析。我们现在只需要知道有了sqlSessionTemplate,我们就可以使用sqlSessionTemplate.getMapper()来获取代理对象。前面我们的案例中是直接使用openSession来获取SqlSession对象然后执行selectone等底层函数与数据库进行交互。我们看看sqlSessionTemplate在做什么,因为sqlSessionTemplate在上面代码获得后会被直接返回,所以实际在Mybatis中我们操作的是这个对象来与数据库交互的。

ublic class SqlSessionTemplate implements SqlSession, DisposableBean {private final SqlSessionFactory sqlSessionFactory;private final ExecutorType executorType;private final SqlSession sqlSessionProxy;private final PersistenceExceptionTranslator exceptionTranslator;
.....
}

如果我们现在用SqlSessionTemplate对象去进行查询,例如调用selectOne

  @Overridepublic <T> T selectOne(String statement) {return this.sqlSessionProxy.<T> selectOne(statement);}

我们会发现它会使用内部的一个this.sqlSessionProxy的属性去执行selectone

 public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator) {notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");notNull(executorType, "Property 'executorType' is required");this.sqlSessionFactory = sqlSessionFactory;this.executorType = executorType;this.exceptionTranslator = exceptionTranslator;this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),new Class[] { SqlSession.class },new SqlSessionInterceptor());}

从构造函数中可以发现this.sqlSessionProxy使用jdk代理来SqlSession接口。然后我们进入selectOne方法,可以发现进入了DefaultSqlSession,这就是Mybatis底层sqlSession真正的实现类。Mybatis为上面不直接使用SqlSession而是绕这么大一圈。

我们知道DefaultSqlSession在容器中就这么一个对象,且从其源码看出它是一个线程不安全的类,如果在高并发场景下,多个线程来使用这个DefaultSqlSession那么可能就出现一些难以预料的并非问题。而SqlSessionTemplate却是线程安全的,那么它是如何保证线程安全的?

它的关键点就是使用ThreadLocal,让每个线程都有一份自己的DefaultSqlSession对象。这样就可以保证线程的一个安全性。关键是要理解this.sqlSessionProxy这个对象

 this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),new Class[] { SqlSession.class },new SqlSessionInterceptor());private class SqlSessionInterceptor implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType,SqlSessionTemplate.this.exceptionTranslator);try {Object result = method.invoke(sqlSession, args);if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {// force commit even on non-dirty sessions because some databases require// a commit/rollback before calling close()sqlSession.commit(true);}return result;} catch (Throwable t) {Throwable unwrapped = unwrapThrowable(t);if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {// release the connection to avoid a deadlock if the translator is no loaded. See issue #22closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);sqlSession = null;Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);if (translated != null) {unwrapped = translated;}}throw unwrapped;} finally {if (sqlSession != null) {closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);}}}}}

invoke方法中首先调用下面代码获取了一个真正的SqlSession对象

 SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType,SqlSessionTemplate.this.exceptionTranslator);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;}if (LOGGER.isDebugEnabled()) {LOGGER.debug("Creating a new SqlSession");}session = sessionFactory.openSession(executorType);registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);return session;}

SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);是Spring事务相关的东西,这里我们先不讲Spring事务相关的内容,我们只需要在这句代码中就操作的ThreadLocal对象,然后ThreadLocal中存的对象是SqlSessionHolder,然后就执行下面这段代码:

 SqlSession session = sessionHolder(executorType, holder);if (session != null) {return session;}session = sessionFactory.openSession(executorType);registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

上面代码就是判断当前线程的ThreadLocal中是否有SqlSessionHolder对象,如果有就直接返回当前线程ThreadLocal中存储的SqlSession对象,如果没有就调用sessionFactory.openSession重新创建一个存入到当前的ThreadLocal中( registerSessionHolder)。

 private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {SqlSessionHolder holder;//判断是否开启了Spring事务if (TransactionSynchronizationManager.isSynchronizationActive()) {Environment environment = sessionFactory.getConfiguration().getEnvironment();if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {if (LOGGER.isDebugEnabled()) {LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");}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) {if (LOGGER.isDebugEnabled()) {LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");}} else {throw new TransientDataAccessResourceException("SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");}}} else {if (LOGGER.isDebugEnabled()) {LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");}}
}

if (TransactionSynchronizationManager.isSynchronizationActive())就是判断我们有没有开启Spring事务,如果我们没有开启Spring事务,因为每次在一个方法中执行一条Sql语句(每个Sql都会看成一个独立的整体)都会创建一个新的SqlSession,这就会导致Mysql一级缓存失效,因为一级缓存有效的保证是同一个SqlSession执行多个sql语句。所以要让一级缓存不失效,就开启Spring事务即可,这样在一个方法中的所有sql就会看成一个整体,只需要一个SqlSession处理即可。

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

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

相关文章

在Colab上测试Mamba

我们在前面的文章介绍了研究人员推出了一种挑战Transformer的新架构Mamba 他们的研究表明&#xff0c;Mamba是一种状态空间模型(SSM)&#xff0c;在不同的模式(如语言、音频和时间序列)中表现出卓越的性能。为了说明这一点&#xff0c;研究人员使用Mamba-3B模型进行了语言建模…

Oladance、南卡、Cleer开放式耳机怎么样?全方位测评大PK!

​开放式耳机作为新兴的音频设备领域中备受欢迎的选择&#xff0c;但市场上琳琅满目的产品汇集了质量千差万别的耳机&#xff0c;其中存在着一些粗制滥造的产品。身为一位音频设备测评博主&#xff0c;我经常收到有关哪个品牌的开放式耳机质量好的疑问。面对市面上众多选择&…

MFC结合GDI+

MFC结合GDI 创建一个空的MFC界面&#xff0c;在确定按钮函数里进行画图&#xff1a; 1、包含头文件与库 在stdafx.h中加入以下三行代码&#xff1a; #include "gdiplus.h" using namespace Gdiplus; #pragma comment(lib, "gdiplus.lib")2、安装GDI 在…

uni-app做A-Z排序通讯录、索引列表

上图是效果图&#xff0c;三个问题 访问电话通讯录&#xff0c;拿数据拿到用户的联系人数组对象&#xff0c;之后根据A-Z排序根据字母索引快速搜索 首先说数据怎么拿 - 社区有指导https://ask.dcloud.net.cn/question/64117 uniapp 调取通讯录 // #ifdef APP-PLUSplus.contac…

安谋科技“周易”NPU与飞桨完成II级兼容性测试,助力实现多样化AI部署

近日&#xff0c;安谋科技&#xff08;中国&#xff09;有限公司&#xff08;以下简称“安谋科技”&#xff09;“周易”NPU系列IP与飞桨已完成II级兼容性测试&#xff0c;测试结果显示&#xff0c;双方兼容性表现良好&#xff0c;整体运行稳定。这是安谋科技加入“硬件生态共创…

【Node.js学习 day3——http模块】

创建HTTP服务端 //1.导入http模块 const http require(http);//2.创建服务对象 const server http.createServer((request, response) > {response.end(Hello HTTP Server);//设置响应体 });//3.监听端口&#xff0c;启动服务 server.listen(9000,()>{console.log(服务…

IMS中如何区分initial INVITE和re-INVITE?

这里就要先看下Dialog的定义。 dialog是两个UA之间持续一段时间的点对点 SIP关系。dialog通过SIP消息建立&#xff0c;例如对 INVITE request的 2xx response。dialog由Call-ID、local tag和remote tag来区分&#xff0c;也就是Call-ID 、from-tag和to-tag就可以确定一个dialog…

java锁的分类

锁定义和特征 乐观锁 VS 悲观锁 区别 乐观锁不会添加锁&#xff0c;无锁算法&#xff0c;没有线程被阻塞。悲观锁拿到资源就加锁&#xff0c;线程被阻塞。 乐观锁&#xff1a;CAS算法 Compare-And-Swap&#xff08;比较并交换&#xff09;的缩写,轻量级锁。 Java中&#xff…

数字档案安全与高效管理的先锋——亚信安慧AntDB数据库

档案工作在维护历史真实面貌、保障人民利益方面具有至关重要的作用。随着社会的发展&#xff0c;数字化转型成为档案管理领域的不可逆趋势。数字档案的存储和传输已经成为档案工作的重要组成部分&#xff0c;然而&#xff0c;这也伴随着一系列的挑战&#xff0c;其中安全风险是…

【MATLAB】逐次变分模态分解SVMD信号分解算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~ 1 基本定义 逐次变分模态分解&#xff08;Sequential Variational Mode Decomposition&#xff0c;简称SVMD&#xff09;是一种用于信号处理和数据分析的方法。它可以将复杂的信号分解为一系列模态函数&#xff0c;每个…

Java中继承的认识

继承是Java编程语言的三大特征之一&#xff08;就是一个很重要的东西&#xff09;。 想一下我们生活中的继承&#xff0c;比如说张三全部继承了他父亲的留下的50万资产&#xff0c;那张三的资产一定比他的父亲多&#xff08;假设张三打工多年&#xff0c;自己存了100万&#x…

【第33例】IPD体系进阶:市场细分

目录 内容简介 市场细分原因 市场细分主要活动 市场细分流程 作者简介 内容简介 这节内容主要来谈谈 IPD 市场管理篇的市场细分步骤。 其中,市场管理(Market Management)是一套系统的方法。 用于对广泛的机会进行选择性收缩,

Excel删除重复项?4个方法帮你提升效率!

“我在使用Excel处理一些数据时&#xff0c;突然发现有好多重复的项&#xff0c;我想将这些重复的项都删除&#xff0c;有什么快速又简单的操作方法吗&#xff1f;” 在日常的办公中&#xff0c;很多用户都会使用Excel。借助这款软件&#xff0c;用户可以完成对各种数据的处理。…

c++全排列

目录 next_permutation()函数 例 perv_permutation()函数 例 next_permutation()函数 next_pernutation()函数用于生成当前序列的下一个排序。它按照字典序对序列进行重新排序&#xff0c;如果存在下一个排列&#xff0c;则将当前序列更改为下一个排列&#xff0c;并返回t…

uniapp微信小程序投票系统实战 (SpringBoot2+vue3.2+element plus ) -用户投票实现

锋哥原创的uniapp微信小程序投票系统实战&#xff1a; uniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )_哔哩哔哩_bilibiliuniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )共计21条视频…

深入理解 Flink(五)Flink Standalone 集群启动源码剖析

前言 Flink 集群的逻辑概念&#xff1a; JobManager(StandaloneSessionClusterEntrypoint) TaskManager(TaskManagerRunner) Flink 集群的物理概念&#xff1a; ResourceManager(管理集群所有资源&#xff0c;管理集群所有从节点) TaskExecutor(管理从节点资源&#xff0c;接…

ERROR in Plugin “react“ was conflicted .... 天坑留念-turborepo、eslint plugin

前两天项目代码拉下来&#xff0c;装完依赖启动的时候直接报错&#xff1a; [eslint] Plugin "react" was conflicted between ".eslintrc.js eslint-config-custom eslint-config-alloy/react" and "BaseConfig D:\pan\erp\test\business-servic…

迅为RK3588开发板编译 Buildroot单独编译图形化界面三

第三步&#xff1a;编译 Recovery 首先在 linux 源码目录下输入以下命令进入编译的 UI 界面&#xff0c;进入之后如下所示&#xff1a; ./build.sh 然后将光标移动到第四个 recovery&#xff0c;点击回车即可开始 recovery 的编译&#xff0c;编译过程如下所示&#xff1a; 编…

Vue 中修改 Element 组件的 下拉菜单(Dropdown) 的样式

Vue 中修改 Element 组件的 下拉菜单(Dropdown) 的样式 今天在项目中碰到一个 UI 改造的需求&#xff0c;需要根据设计图把页面升级成 UI 设计师提供的设计图样式。 到最后页面改造完了&#xff0c;但是 UI 提供的下拉菜单样式全部是黑色半透明的&#xff0c;只能硬着头皮改了。…