mybatis与spring的整合以及源码分析


注释: 本篇会从xml配置和注解两个方面分析MyBatis-Spring的应用以及源码

MyBatis-Spring官网

学会看官网,吃喝不用愁!入门

xml配置应用

官网提供的是xml配置,那么就先以xml配置形式来分析。
所需的jar包就不多说了,直接看配置。
要和 Spring 一起使用 MyBatis,你需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类

SqlSessionFactory属性注入

这可以是任意 的 DataSource,配置它就和配置其它 Spring 数据库连接一样

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

数据映射器接口类

要注意,所指定的映射器类必须是一个接口,而不是具体的实现类。在这个示例中注解被用来指定 SQL 语句,但是 MyBatis 的映射器 XML 文件也可以用(这里先不做解释)

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

MapperFactoryBean属性注入

那么可以使用 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>

调用 MyBatis 数据方法

public class FooServiceImpl implements FooService {

private UserMapper userMapper;

public void setUserMapper(UserMapper userMapper) {
  this.userMapper = userMapper;
}

public User doSomeBusinessStuff(String userId) {
  return this.userMapper.getUser(userId);
}

现在问题来了,调用的是接口方法但是有具体的实现,那么这里肯定有动态代理,下面就来分析源码。

源码分析

在上面示例中,Spring注入的userMapper肯定是一个实现UserMapper接口的代理类(从这里可看出一定是jdk态代理)

private UserMapper userMapper;

MapperFactoryBean初始化

mybatis与spring的整合以及源码分析
userMapper对应的bean是MapperFactoryBean,它实现了FactoryBean接口,因此注入userMapper实则是MapperFactoryBeangetObject()方法返回的代理对象

public T getObject() throws Exception {
   // mapperInterface就是注入的数据映射器接口类
    return this.getSqlSession().getMapper(this.mapperInterface);
}

先卖个关子,这里会调用MapperRegistry.getMapper,根据数据映射器接口类去获取MapperProxyFactory(映射代理工厂),如果不存在则抛出异常。那么说明在注入userMapper之后,肯定是注册了一个对应的MapperProxyFactory

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    } else {
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception var5) {
            throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
        }
    }
}

注册SqlSession

看到上面getMapper(Class<T> type, SqlSession sqlSession)会用到SqlSession ,这是什么时候注册的呢?可以看下官网的解释SqlSessionFactoryBean,它实现了FactoryBean接口。
在基本的 MyBatis 中,session 工厂可以使用 SqlSessionFactoryBuilder 来创建。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来替代。
Spring 最终创建的 bean 不是 SqlSessionFactoryBean 本身, 。 而是工厂类的 getObject()返回的方法的结果

SqlSessionFactoryBean.getObject()返回SqlSessionFactory

public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
        this.afterPropertiesSet();
    }
    return this.sqlSessionFactory;
}

这里的sqlSessionFactory其实是调用了SqlSessionFactoryBuilder方法来创建的

return this.sqlSessionFactoryBuilder.build(configuration);

看一下在MapperFactoryBean注入SqlSessionFactoryBean都干了些什么,具体实现在父类SqlSessionDaoSupport

setSqlSessionFactory初始化SqlSession

SqlSessionTemplate 实现了SqlSession接口

public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
        // 初始化sqlSession
        this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
}

注册MapperProxyFactory

调动afterPropertiesSet()方法

看上面的结构图,MapperFactoryBean最终实现了InitializingBean接口,它有一个afterPropertiesSet()方法,就是在属性注入完成后执行的(一般在bean初始化完成后做某些事情,对应有个销毁方法)

DaoSupport重写了afterPropertiesSet()

public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
    this.checkDaoConfig();
    try {
        this.initDao();
    } catch (Exception var2) {
        throw new BeanInitializationException("Initialization of DAO failed", var2);
    }
}

MapperFactoryBean重写了checkDaoConfig()

先判断sqlSession(就是SqlSessionTemplate)是否注入,然后获取Configuration注册MapperProxyFactory

protected void checkDaoConfig() {
    // 执行父类SqlSessionDaoSupport的方法,校验sqlSession是否不为空
    super.checkDaoConfig();
    Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    // configuration在SqlSessionFactoryBean中初始化的
    Configuration configuration = this.getSqlSession().getConfiguration();
    // 有的话就不注册
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
        try {
            // 注册对应的MapperProxyFactory
            configuration.addMapper(this.mapperInterface);
        } catch (Exception var6) {
            this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
            throw new IllegalArgumentException(var6);
        } finally {
            ErrorContext.instance().reset();
        }
    }
}

Configuration.addMapper

public <T> void addMapper(Class<T> type) {
    // mapperRegistry在Configuration构造函数中初始化的
    this.mapperRegistry.addMapper(type);
}

MapperRegistry.addMapper注册并解析

这里根据映射接口注册MapperProxyFactory
然后解析映射接口:获取方法名、获取sql语句等mybatis核心组件之MapperAnnotationBuilder

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        if (this.hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            // 根据映射接口注册MapperProxyFactory
            this.knownMappers.put(type, new MapperProxyFactory(type));
            // 解析映射接口方法以及注解
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                this.knownMappers.remove(type);
            }
        }
    }
}

MapperFactoryBean.getObject()返回代理对象

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

SqlsessionTemplate

public <T> T getMapper(Class<T> type) {
    return this.getConfiguration().getMapper(type, this);
}

Configuration

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

MapperRegistry.getMapper获取MapperProxyFactory

根据传入的映射接口拿到之前注册的MapperProxyFactory,进一步实例化出对象(实现映射接口的代理对象)

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // 拿到映射接口对应MapperProxyFactory
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                // 实例化对象
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

MapperProxyFactory.newInstance构造MapperProxy返回代理对象

这里的sqlSession就是SqlSessionTemplate
这里的mapperInterface就是之前根据映射接口注册MapperProxyFactory时的映射接口
methodCache是一个ConcurrentHashMap,用于缓存方法对应的MapperMethod,当调用同一方法时直接从缓存中拿

public T newInstance(SqlSession sqlSession) {
    MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
    return this.newInstance(mapperProxy);
}

protected T newInstance(MapperProxy<T> mapperProxy) {
    // 返回代理对象(jdk动态代理)
    return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}

Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy)可以看出两点

  1. 因为Proxy.newProxyInstance的第二个参数只能传接口数组,所以mapperInterface是接口,也就是说数据映射器只能是接口
  2. MapperProxy实现了InvocationHandler,具体的代理在它重写的invoke方法中
    JDK动态代理源码分析
    mybatis与spring的整合以及源码分析

代理对象执行方法

MapperProxy.invok

执行userMapper.getUser(userId),执行的是代理对象的getUser(userId),会执行invok
MapperProxy.invok

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 如果方法是Object类的方法,则直接反射执行
    if (Object.class.equals(method.getDeclaringClass())) {
        try {
            return method.invoke(this, args);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    } else {
       // 获取MapperMethod
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        // 执行sql语句
        return mapperMethod.execute(this.sqlSession, args);
    }
}
  1. 先判断执行的方法是不是Object类的方法,比如tostringhashcode等方法,是的话则直接反射执行这些方法
  2. 如果不是,从缓存中获取MapperMethod,如果为空则创建并加入缓存,然后执行sql语句
private MapperMethod cachedMapperMethod(Method method) {
    // 根据方法从缓存中获取
    MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
    if (mapperMethod == null) {
        // 不存在则创建一个
        mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
        // 放入缓存
        this.methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
}

MapperMethod.execute执行具体sql语句

mybatis核心组件之MapperMethod

java配置应用

配置类

只需要这么一个配置类,就完成了MyBatis-Spring的整合,具体看@MapperScan注解

@Configuration
@ComponentScan("cn.yukang.javaConfig")// 扫描包
@PropertySource("classpath:datasource.properties")// 加载数据源配置信息
@MapperScan("cn.yukang.javaConfig.mapper")
public class MyConfig {

	@Value("${jdbc.driverClassName}")
	private String driverclassname;
	
	@Value("${jdbc.url}")
	private String url;
	
	@Value("${jdbc.username}")
	private String userName;
	
	@Value("${jdbc.password}")
	private String passWord; 
	
	@Bean
	public BasicDataSource dataSource(){
		BasicDataSource dataSource = new BasicDataSource();
		dataSource.setDriverClassName(driverclassname);
		dataSource.setUrl(url);
		dataSource.setUsername(userName);
		dataSource.setPassword(passWord);
		return dataSource;
	}
	
	@Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        // 数据库与javaBean映射关系
        //sessionFactory.setTypeAliasesPackage("cn.yukang.javaConfig.bean");
        // 添加映射-使用接口+xml的形式(xml的命名空间为接口)
        //sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper.xml"));
        return sessionFactory;
    }
}

源码分析

@MapperScan

通过@Import导入了MapperScannerRegistrar

@Import({MapperScannerRegistrar.class})
public @interface MapperScan

MapperScannerRegistrar.registerBeanDefinitions

mybatis与spring的整合以及源码分析
MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,重写registerBeanDefinitions方法,可以详看另一篇文章处理
带有ImportBeanDefinitionRegistrar的类

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    if (this.resourceLoader != null) {
        scanner.setResourceLoader(this.resourceLoader);
    }
	// 处理@MapperScan注解中的属性
    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));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
        scanner.setMapperFactoryBean((MapperFactoryBean)BeanUtils.instantiateClass(mapperFactoryBeanClass));
    }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
    List<String> basePackages = new ArrayList();
    String[] var10 = annoAttrs.getStringArray("value");
    int var11 = var10.length;

    int var12;
    String pkg;
    for(var12 = 0; var12 < var11; ++var12) {
        pkg = var10[var12];
        if (StringUtils.hasText(pkg)) {
            basePackages.add(pkg);
        }
    }

    var10 = annoAttrs.getStringArray("basePackages");
    var11 = var10.length;

    for(var12 = 0; var12 < var11; ++var12) {
        pkg = var10[var12];
        if (StringUtils.hasText(pkg)) {
            basePackages.add(pkg);
        }
    }

    Class[] var14 = annoAttrs.getClassArray("basePackageClasses");
    var11 = var14.length;

    for(var12 = 0; var12 < var11; ++var12) {
        Class<?> clazz = var14[var12];
        basePackages.add(ClassUtils.getPackageName(clazz));
    }

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

上面方法内容就是获取@MapperScan注解中的属性值, 比如@MapperScan("cn.yukang.javaConfig.mapper")就是获取value属性,放入集合basePackages中,然后调用scanner.doScan(StringUtils.toStringArray(basePackages));开始扫描注册

ClassPathMapperScanner.doScan

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // 调用父类ClassPathBeanDefinitionScanner.doScan
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    if (beanDefinitions.isEmpty()) {
        this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
        // 处理注册后的bd
        this.processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

父类ClassPathBeanDefinitionScanner.doScan扫描注册bd

这里先调用父类ClassPathBeanDefinitionScanner.doScan方法扫描包路径并注册bd。可详看另一篇文章spring加载流程之ClassPathBeanDefinitionScanner

processBeanDefinitions处理注册后的bd

循环处理bd

1.设置bd的构造函数参数值为当前bd的类名,也就是映射接口的类名
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
2.改变bd的beanClassMapperFactoryBean.class
definition.setBeanClass(this.mapperFactoryBean.getClass());
3.添加属性addToConfigtrue
definition.getPropertyValues().add("addToConfig", this.addToConfig);
4.设置属性按类型自动注入

如果没有显示的指定SqlSessionFactoryBean,则设置当前bd的属性注入方式为按类型注入,与在属性上加了@Autowired效果一致

if (!explicitFactoryUsed) {
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
    }
    // 设置属性按类型注入
    definition.setAutowireMode(2);
}

因为没有手动注入SqlSessionFactoryBeanMapperFactoryBean,所以在spring实例化bd注入属性的时候,会按类型注入的方式找到类型为SqlSessionFactoryBean的进行注入

后面的注册MapperProxyFactory就跟上面xml配置的源码一致了