Mybatis整合Spring源码分析

本文介绍如何在Spring环境中配置MyBatis,并深入探讨MyBatis与Spring事务管理的集成方式,以及Spring如何管理和创建MyBatis的Mapper代理对象。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、整合配置

POM

<!-- mybatis框架 -->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.3</version>
</dependency>

<!--mybatis-spring适配器 -->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.3</version>
</dependency>

需要上面两个依赖的源代码。

版本匹配:

clipboard

在spring上下文中配置SqlSessionFactoryBean,并将一些配置信息都设置到SqlSessionFactoryBean中:

xml

    <!-- 配置数据源 -->
    <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <!--驱动类名 -->
        <property name="driverClass" value="${jdbc.driver}" />
        <!-- url -->
        <property name="jdbcUrl" value="${jdbc.url}" />
        <!-- 用户名 -->
        <property name="user" value="${jdbc.uid}" />
        <!-- 密码 -->
        <property name="password" value="${jdbc.pwd}" />
    </bean>   	

		<!-- 会话工厂bean sqlSessionFactoryBean -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 数据源 -->
        <property name="dataSource" ref="xxxxx"></property>
        <!-- 别名 -->
        <property name="typeAliasesPackage" value="xxxxx"></property>
        <!-- sql映射文件路径 -->
        <property name="mapperLocations" value="xxxxx"></property>
    </bean>

javaConfig

	//<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	@Bean    
	public SqlSessionFactoryBean sqlSessionFactory( ) throws IOException {
		SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
		factoryBean.setDataSource(dataSource());
		// 设置MyBatis配置文件路径
		factoryBean.setConfigLocation(new ClassPathResource("mybatis/mybatis-config.xml"));
		// 设置SQL映射文件路径
		factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));
    // 设置别名
		factoryBean.setTypeAliases(User.class);

		return factoryBean;
	}


		//<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");
        return dataSource;
    }

二、源码分析

1.构建SqlSessionFactory

image-20211216173215281

SqlSessionFactoryBean实现InitializingBean,会在初始化Bean时调用afterPropertiesSet,其中的buildSqlSessionFactory就等同于SqlSessionFactoryBuilder().build()方法,用于构建SqlSessionFactory

  @Override
  public void afterPropertiesSet() throws Exception {
    ...
    //通过sqlSessionFactoryBuilder来构建sqlSessionFactory
    //等同于new SqlSessionFactoryBuilder().build();  
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

buildSqlSessionFactory,将SqlSessionFactoryBean中设置的属性设置到Configuration对象中,Configuration对象用于保存mybatis的所有的配置信息。

  protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    final Configuration targetConfiguration;

    XMLConfigBuilder xmlConfigBuilder = null;
    ...
      			//解析mapper映射文件
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            xmlMapperBuilder.parse();
    ...
    //通过建造者模式构建DefaultSqlSessionFactory
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }      

2.mybatis-spring事务集成

如果在Mybatis中配置了事务,那么Mybatis中的事务要和Spring中的事务集成在一起。

Spring开启事务时会获取Connection,并最终存到TransactionSynchronizationManager中。Mybatis想要集成Spring的事务,就需要拿到Spring开启事务时获取的Connection,具体逻辑源码:

1.当buildSqlSessionFactory时,会设置environment,当transactionFactory为null的时候会创建一个新的mybatis-spring适配的事务工厂类SpringManagedTransactionFactory

    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));

2.当调用SpringManagedTransactionFactory的newTransaction时,会创建一个SpringManagedTransaction

public class SpringManagedTransactionFactory implements TransactionFactory {

  @Override
  public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
    return new SpringManagedTransaction(dataSource);
    ...
  }

3.当mybatis获取connection时就会调用SpringManagedTransaction的getConnection,最终获取到的connection就是TransactionSynchronizationManager中的Connection

public class SpringManagedTransaction implements Transaction {
  ...
  @Override
  public Connection getConnection() throws SQLException {
    if (this.connection == null) {
      openConnection();
    }
    return this.connection;
  }
  
  private void openConnection() throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    ....
  }    

DataSourceUtils

public abstract class DataSourceUtils {
    ...

    public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
        try {
            return doGetConnection(dataSource);
        }...
    }
  
  
    public static Connection doGetConnection(DataSource dataSource) throws SQLException {
        ...
        ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
        ....
    }  

3.Spring管理Mapper代理对象

Mapper接口底层实现

Mapper接口底层使用JDK动态代理来实现。通过sqlSession.getMapper方法就可以获取对应的Mapper接口,由此查看源码:

  //DefaultSqlSession#getMapper
	@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

	//Configuration#getMapper
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

	//MapperRegistry#getMapper
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    ...
    try {
      //通过MapperProxyFactory来创建实例
      return mapperProxyFactory.newInstance(sqlSession);
    } ...
  }

	//MapperProxyFactory#newInstance
  public T newInstance(SqlSession sqlSession) {
    //创建代理对象
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    //创建Mapper代理对象返回
    return newInstance(mapperProxy);
  }

	//MapperProxyFactory#newInstance
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

JDK动态代理实现类MapperProxy,当我们调用Mapper接口中的某一方法,就会来到MapperProxy的invoke方法。

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    ...
    return mapperMethod.execute(sqlSession, args);
  }

MapperMethod#execute,底层最终还是通过sqlSession的方式来执行。

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //sql命令的类型
    switch (command.getType()) {
      //insert操作
      case INSERT: {
        ...
      }
      //update操作
      case UPDATE: {
        ...
      }
      //delete操作
      case DELETE: {
        ...
      }
      //select操作
      case SELECT:
        ...
      case FLUSH:
        ...
      default:
        ...
    return result;
  }

*Spring管理Mapper代理对象

Mapper代理对象创建后,还需要把Mapper代理对象作为一个bean注入到Spring容器中,使得能够像普通bean一样被@Autowire自动注入。

1.通过@MapperScan(basePackages = {“com.xxx.mapper”})注解指定需要扫描的mapper接口包路径。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

2.通过@Import导入MapperScannerRegistrar,实现了ImportBeanDefinitionRegistrar。它会在beanDefinition扫描的时候调用registerBeanDefinitions()方法往Spring容器中添加beanDefinition对象到 beanDefinitionMap中。

  void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    ...

    //为容器中注册了MapperScannerConfigurer的接口
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }

3.注册MapperScannerConfigurer,实现了BeanDefinitionRegistryPostProcessor,重写其postProcessBeanDefinitionRegistry方法,并会在IOC容器的refresh—>invokeBeanFactoryPostProcessors方法中调用。

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
	...
    
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    ...

    //创建一个ClassPathMapperScanner包扫描器对象
    //mybaits-spring适配器中的,继承了spring中的ClassPathBeanDefinitionScanner
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    ...
    //真正去扫描@MapperScan指定的路径下的bean定义信息
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }    

4.创建ClassPathMapperScanner对象,是mybaits-spring适配器中自定义的一个扫描类,继承了spring中的ClassPathBeanDefinitionScanner。通过doScan进行扫描,将mapper接口都扫描成beanDefinition。

  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //调用父类ClassPathBeanDefinitionScanner 来进行扫描
    //会通过isCandidateComponent判断是否符合条件,该方法被重写
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    ...
      //进行处理,偷天换日操作
      processBeanDefinitions(beanDefinitions);
		...	
  }

	//重写,只有是接口时满足扫描条件
	//spring中默认是不是接口满足条件
  @Override
  protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
  }

5.重点方法,通过processBeanDefinitions对所有mapper接口的beanDefinition循环进行处理,将BeanClass偷换成MapperFactoryBean。

因为接口没有办法进行实例化,所以创建成FactoryBean,在调用getObject时就会给mapper会创建动态代理。

但是创建动态代理需要被代理的mapper接口,MapperFactoryBean中的属性mapperInterface就用来指定mapper接口,通过构造函数的方式进行传入。

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;     
    for (BeanDefinitionHolder holder : beanDefinitions) {
    ...

      // 设置ConstructorArgumentValues 会通过构造器初始化对象
      // 传入String类型的名字,Spring会根据名字获取到class
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 
      // 将BeanClass设置成MapperFactoryBean,实现了FactoryBean
    	// 其中有属性mapperInterface,指向要创建动态代理的接口
    	// MapperFactoryBean通过构造方法设置了mapperInterface
      definition.setBeanClass(this.mapperFactoryBeanClass);

      ...
        //根据类型自动注入,会将容器中已经存在的SqlSessionFactory注入进来
        //MapperFactoryBean继承了SqlSessionDaoSupport,其中有setSqlSessionFactory
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
      ...
    }
  }

6.当我们调用到mapper接口时(getBean),就会调用FactoryBean的getObject方法,创建动态代理。

  @Override
  public T getObject() throws Exception {
    //进入sqlSession.getMapper逻辑
    return getSqlSession().getMapper(this.mapperInterface);
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值