SpringBoot整合Mybatis实现的原理

SpringBoot整合MyBatis

上一篇文章简单分析了mybatis的原理。实际开发过程中spring帮我们简化了Mybatis的配置。Springboot更是简化了xml配置,更多地使用注解来配置spring容器。这篇文章分析一下springBoot在整合mybatis的时候做了哪些事情。

在项目中,SpringBoot启动类中加入注解MapperScan扫描包路径下定义的mapper接口。

@MapperScan("com.xxx.mapper")
public class ServiceApplication {
    public static void main(String[] args) {
         new SpringApplicationBuilder(ServiceApplication.class).web(true).run(args);
    }
}

这个注解引入了MapperScannerRegistrar

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

IOC容器启动的时候会调用它的registerBeanDefinitions方法。

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);
    }

    Class 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 generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
        scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass));
    }

    Class 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 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));
}

这行代码不做详细的解释,注意看最后调用的MapperFactoryBean的doScan方法。MapperFactoryBean实现了FactoryBean接口

public Set doScan(String... basePackages) {
    Set 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 {
        this.processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

processBeanDefinitions方法关键代码

definition.setBeanClass(this.mapperFactoryBean.getClass());

为什么要将接口的Bean设置为MapperFactoryBean呢,就是因为当IOC容器中Bean实现了FactoryBean后,通过getBean获取到的Bean对象并不是FactoryBean的实现类对象,而是实现类getObject方法返回的对象。MapperFactoryBean类的getObject方法如下:

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

这跟上一篇文章getMapper的方法是一致的。不过addmapper是在checkDaoConfig方法里执行的。

protected void checkDaoConfig() {
    super.checkDaoConfig();
    Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    Configuration configuration = this.getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
        try {
            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();
        }
    }
}

MapperFactoryBean继承了SqlSessionDaoSupport类,在processBeanDefinitions时会设置SqlSessionFactory

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

SqlSessionTemplate这个类就是Spring整合Mybatis的核心

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

seqlSessionProxy生成代理对象,调用SqlSession的方法。创建代理时用到了SqlSessionInterceptor这个内部拦截类。调用的时候会执行它的invoke方法。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

        Object unwrapped;
        try {
            Object result = method.invoke(sqlSession, args);
            if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                sqlSession.commit(true);
            }

            unwrapped = result;
        } catch (Throwable var11) {
            unwrapped = ExceptionUtil.unwrapThrowable(var11);
            if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                sqlSession = null;
                Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
                if (translated != null) {
                    unwrapped = translated;
                }
            }

            throw (Throwable)unwrapped;
        } finally {
            if (sqlSession != null) {
                SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }

        }

        return unwrapped;
    }

首先调用SqlSessionUtils的静态方法getSqlSession,然后调用相应方法,检测事物是否有spring管理。并根据此结果决定是否提交事物,最后清除Session。

SpringBoot在启动时MybatisAutoConfiguration自动配置Mybatis的参数。通过SqlSessionFactoryBean.getObject获取SqlSessionFactory对象。

总结一下Mapper的创建过程。

1、IOC通过注解扫描指定包,在初始化的时候调用@MapperScan注解,执行doScan方法,将所有的Mapper接口的Bean定义为MapperFactoryBean,并将SqlSessionTemplate添加到该类中。

2.SpringIOC在实例化该Bean的时候,需要传入接口类型,并将SqlSessionFactory和SqlSessionTemplate注入到Bean中,并调用addmapper方法,解析配置文件。

3、当调用MapperFactoryBean的getObject方法的时候,事实上是调用SqlSession的getMapper方法。这个方法返回一个动态代理对象,所有这个代理对象的方法调用都是底层的SqlSession的方法。

你可能感兴趣的:(SpringBoot整合Mybatis实现的原理)