全网讲解 | Mybatis和Spring是怎么整合的 最透彻的文章!(源码解读)

前言

注意:阅读本文须要有必定的Spring和SpringBoot基础java

先上一个Mybatis-Spring官网连接,打开一个SSM整合的案例项目一块儿食用本文效果更佳哦。web

官网上说的很清楚,要和 Spring 一块儿使用 MyBatis,须要在 Spring 应用上下文中定义至少两样东西:一个SqlSessionFactory 和至少一个数据映射器类。面试

在 MyBatis-Spring 中,使用 SqlSessionFactoryBean来建立 SqlSessionFactory。spring

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>

至于在SpringBoot中SqlSessionFactoryBean是怎么被建立而且被执行的,就不赘述了,感兴趣的能够去看 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration#sqlSessionFactory 方法,简单来讲是利用的SpringBoot的自动装配机制sql

须要更多Java知识点和大厂面试题的朋友能够点一点下方连接免费领取数据库

连接:1103806531暗号:CSDN缓存

在这里插入图片描述

SqlSessionFactoryBean是怎么建立SqlSessionFactory的?

SqlSessionFactory是MyBatis中的一个重要的对象,它是用来建立SqlSession对象的,而SqlSession用来操做数据库的。咱们经过SqlSessionFactoryBean的继承体系能够看出,它实现了FactoryBean和InitialzingBean,这两个类的做用我也不赘述了安全

在这里插入图片描述
咱们直接来看重要的代码mybatis

public SqlSessionFactory getObject() throws Exception { 
 
  
    if (this.sqlSessionFactory == null) { 
 
  
      // afterPropertiesSet方法在Bean的生命周期中会被调用,这里为何会手动调用一次我也不知道
      // 或许为了防止getObject被提早调用,也多是兼容SpringBoot和Spring整合的区别
      afterPropertiesSet();
    }
    return this.sqlSessionFactory;
}

public void afterPropertiesSet() throws Exception { 
 
  
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    // 一直往下追就是下面的build方法
    this.sqlSessionFactory = buildSqlSessionFactory();
}
public SqlSessionFactory build(Configuration config) { 
 
  
    return new DefaultSqlSessionFactory(config);
}

org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
走到buildSqlSessionFactory方法,最后仍是经过 SqlSessionFactoryBuilder#build 去构建的DefaultSqlSessionFactory架构

@MapperScan注解到底作了什么?

@MapperScan负责Spring和Mybatis整合时mapper的扫描和注册

@Import(MapperScannerRegistrar.class)

@Import注解也是spring Framework提供的将普通javaBean注册到容器中,该注解导入了MapperScannerRegistrar 类来实现功能,咱们看这个类的继承结构

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware

它实现了ImportBeanDefinitionRegistrar,Spring中手动注册bean的两种方式:

  • 实现ImportBeanDefinitionRegistrar
  • 实现BeanDefinitionRegistryPostProcessor

须要注意的是,若是某一个Bean实现了BeanDefinitionRegistryPostProcessor或者ImportBeanDefinitionRegistrar接口,那咱们在这个类中使用@Autowired或者@Value注解,咱们会发现失效了。缘由是,spring容器执行接口的方法时,此时尚未去解析@Autowired或者@Value注解。若是咱们要使用获取配置文件属性,能够经过原始方式,直接用IO读取配置文件,而后获得Properties对象,而后再获取配置值。

MapperScannerRegistrar是何方神圣?

这里会调用registerBeanDefinitions方法
ImportBeanDefinitionRegistrar在ConfigurationClassPostProcessor处理Configuration类期间被调用,用来生成该Configuration类所须要的BeanDefinition。而ConfigurationClassPostProcessor正实现了BeanDefinitionRegistryPostProcessor接口(因此支持mapper注册成bean,并注入到spring容器中)。

@Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 
 
  
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) { 
 
  
      registerBeanDefinitions(mapperScanAttrs, registry);
    }
  }

void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) { 
 
  

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
	...
    /** * 这里会将basePackages下的mapper所有扫描成bd并实例化成bean * 注意这里注册的全部bean实际均为MapperFactoryBean[代理] */
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

咱们接着看doScan实现的细节:

@Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) { 
 
  
    // 扫描出全部mybatis的mapper,此时的bd里的class仍是本身
    // 假设UserMapper,那么此时bd中的beanclass = UserMapper.class
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) { 
 
  
	  ...
    } else { 
 
  
      // 对bd进行处理
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { 
 
  
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) { 
 
  
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
	  ...

      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 
      // bean的实际类是MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBean.getClass());
      ...
      if (!explicitFactoryUsed) { 
 
  
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

这里特别要注意最后一行setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);Spring默认状况下的自动装配级别为AUTOWIRE_NO,须要修改成AUTOWIRE_BY_TYPE按类型注入。这样Spring就会经过set方法,而且再根据bean的类型帮咱们注入属性,好比后会说到的MapperFactoryBean中的sqlSessionFactory和sqlSessionTemplate。

Spring中自动装配有四种

// 默认不注入,也是默认级别 = 0
public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;
// 按name = 1
public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
// 按type = 2
public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
// 按构造器 = 3
public static final int AUTOWIRE_CONSTRUCTOR = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;

// 还有一种已经不建议使用了,这里就不赘述自动装配的细节了

为何mybatis不用@AutoWired呢? 主要是解耦吧,能够不依赖Spring进行编译,加了Spring的注解,那么就是强耦合了。

到了这一步,那么此时的mapper,咱们能够认为都变成了bd加入到spring中了,那么下一步就是实例化了,那么咱们就要来看看MapperFactoryBean了

MapperFactoryBean

由于MapperFactoryBean extends SqlSessionDaoSupport extends DaoSupport,DaoSupport implements InitializingBean ,因此在mfb实例化以后会执行afterPropertiesSet

@Override
	public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { 
 
  
		// Let abstract subclasses check their configuration.
		checkDaoConfig();

		// Let concrete implementations initialize themselves.
		try { 
 
  b
			initDao();
		}
		catch (Exception ex) { 
 
  
			throw new BeanInitializationException("Initialization of DAO failed", ex);
		}
	}

afterPropertiesSet其中checkDaoConfig()方法很重要,咱们来看看,在每个mfb实例化的时候,其实方法和sql是在实例化的时候就已经绑定而且放在Map<String, MappedStatement> mappedStatements当中,而不是在你调用的时候才去绑定。

protected void checkDaoConfig() { 
 
  
    super.checkDaoConfig();

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { 
 
  
      try { 
 
  
        // 在这一步,最后会调用一个parse解析,将全部的方法和sql语句绑定
        // 放进一个map当中,因此实际上当你执行一个mapper的方法时,底层实际上是去一个map当中get
        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();
      }
    }
  }

this.mapperInterface这个实际上就是指的当前类,好比UserMapper在实例化的时候,这个mfb里面的mapperInterface就是它本身,由于mfb实现了FactoryBean,因此当你使用注解注入mapper的时候,此时的mapper是由getObject()方法产生的

@Override
  public T getObject() throws Exception { 
 
  
    return getSqlSession().getMapper(this.mapperInterface);
  }

getSqlSession()在和Spring整合中,拿到的一直是SqlSessionTemplate

org.mybatis.spring.support.SqlSessionDaoSupport#getSqlSession
// 这个SqlSession是由Spring管理,它会自动注入
 public SqlSession getSqlSession() { 
 
  
    return this.sqlSessionTemplate;
 }

最后经过SqlSession的getMapper去得到代理类

// 底层调用的是MapperRegistry的getMapper方法
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 
 
  
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) { 
 
  
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try { 
 
  
      // 返回代理类
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) { 
 
  
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

说到这里,咱们大概知道了mybatis的dao接口是怎么变成代理类而且放入Spring容器中的过程了。

代理类执行逻辑

放入到Spring容器后,咱们就能够注入代理对象进行数据库操做了。因为接口方法会被代理逻辑拦截,因此下面咱们把目光聚焦在代理逻辑上面,看看代理逻辑会作哪些事情。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
 
  
    try { 
 
  
        if (Object.class.equals(method.getDeclaringClass())) { 
 
  
            return method.invoke(this, args);
        } else if (isDefaultMethod(method)) { 
 
  
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) { 
 
  
        throw ExceptionUtil.unwrapThrowable(t);
    }
    // 从缓存中获取 MapperMethod 对象,若缓存未命中,则建立 MapperMethod 对象
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 这里的sqlSession在不一样状况下不一样,好比有defaultSqlSession 和 sqlSessionManger
    // 最后仍是调用的sqlSession的方法,可是sqlSessionManger解决自动关闭问题和线程安全问题
    // 调用 execute 方法执行 SQL
    return mapperMethod.execute(sqlSession, args);
}

private MapperMethod cachedMapperMethod(Method method) { 
 
  
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}


/** * 建立映射方法 */
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { 
 
  
    // 建立 SqlCommand 对象,该对象包含一些和 SQL 相关的信息
    this.command = new SqlCommand(config, mapperInterface, method);
    // 建立 MethodSignature 对象,由类名可知,该对象包含了被拦截方法的一些信息
    this.method = new MethodSignature(config, mapperInterface, method);
}
 
/** * 该对象包含一些和 SQL 相关的信息 * 经过它能够找到MappedStatement * 咱们能够看到熟悉的 Invalid bound statement (not found) 异常 */
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { 
 
  
    final String methodName = method.getName();
    final Class<?> declaringClass = method.getDeclaringClass();
    // 解析 MappedStatement
    MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
                                                configuration);
    // 检测当前方法是否有对应的 MappedStatement
    if (ms == null) { 
 
  
        if(method.getAnnotation(Flush.class) != null){ 
 
  
            name = null;
            type = SqlCommandType.FLUSH;
        } else { 
 
  
            // 熟悉的异常
            throw new BindingException("Invalid bound statement (not found): "
                                       + mapperInterface.getName() + "." + methodName);
        }
    } else { 
 
  
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) { 
 
  
            throw new BindingException("Unknown execution method for: " + name);
        }
    }
}

前面已经分析了 MapperMethod 的初始化过程,如今 MapperMethod 建立好了。那么,接下来要作的事情是调用 MapperMethod 的 execute 方法,执行 SQL,后面的就不分析了,看到这,你应该知道Mybatis是怎么实现的和Spring的整合流程了。

这篇文章到这就结束啦,喜欢的话就给个赞 + 收藏 + 关注吧!🤓 有什么想看的欢迎留言!!!

我这边也整理了一份 架构师全套视频教程和关于java的系统化资料,包括java核心知识点、面试专题和20年最新的互联网真题、电子书等都有。有须要的朋友能够点一点下方连接免费领取!

连接:1103806531暗号:CSDN

在这里插入图片描述
在这里插入图片描述