Spring xml 方式整合MyBatis(含源码剖析)

一、概述

整合主要是配好配置 SqlSessionFactoryBean 和 MapperScannerConfigurer。

二、实验

2.1不整合之前

UserMapper:

package com.itheima.mapper;

import com.itheima.pojo.User;

import java.util.List;

public interface UserMapper {

    List<User> findAll();
}

UserServiceImpl:

package com.itheima.service.impl;

import com.itheima.dao.UserDao;
import com.itheima.mapper.UserMapper;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.context.ServletContextAware;

import javax.servlet.ServletContext;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class UserServiceImpl implements UserService {
    @Override
    public void show() {

    }
}

测试文件

package com.itheima.test;

import com.itheima.mapper.UserMapper;
import com.itheima.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MyBatisTest {
    public static void main(String[] args) throws Exception {
//        先是加载配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
//        再是得到sqlSessionFactroyBuilder
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//        再是得到sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);
//        再是得到sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
//        再是找到mapper
        UserMapper userMapper =sqlSession.getMapper(UserMapper.class);
//        然后调用方法
        List<User> all = userMapper.findAll();
//        遍历输出
        for (User user :all){
            System.out.println(user);
        }

    }
}

2.2整合之后

UserServiceImpl:

package com.itheima.service.impl;

import com.itheima.dao.UserDao;
import com.itheima.mapper.UserMapper;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.context.ServletContextAware;

import javax.servlet.ServletContext;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class UserServiceImpl implements UserService {
//需要UserMapper就注入
    private UserMapper userMapper;

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

    @Override
    public void show() {
        List<User> all = userMapper.findAll();
        for (User user :all){
            System.out.println(user);
        }
    }
}

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="username" value="root"/>
        <property name="password" value="guangxun.kg"/>
    </bean>
<!--    作用是将SqlSessionFactory存储到Spring容器中-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
<!--        最终是要得到由Sqlsession得到Conection,需要数据源属性-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
<!--配给接口扫描,目的是扫描接口,将相应的mapper接口存储到Spring容器-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--        指定要扫描的包-->
        <property name="basePackage" value="com.itheima.mapper"></property>
    </bean>
    <bean class="com.itheima.service.impl.UserServiceImpl" id="userService">
<!--        需要Mapper属性就注入,因为上面的MapperScannerConfigurer已经将mapper扫描且添加到spring容器当中了
             不需要再配置了-->
        <property name="userMapper" ref="userMapper"/>
    </bean>
</beans>

测试:

package com.itheima.test;

import com.alibaba.druid.pool.DruidDataSource;
import com.itheima.beans.OtherBean;
import com.itheima.beans.XxxBean;
import com.itheima.dao.PersonDao;
import com.itheima.dao.UserDao;
import com.itheima.service.UserService;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ApplicationContextTest {

    public static void main(String[] args) throws Exception {

        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = applicationContext.getBean(UserService.class);
//        System.out.println(/userService);
        userService.show();

    }

}

三、原理剖析

3.1SqlSessionFactoryBean

首先在使用上我们是谁要使用SqlSessionFactory就去调用SqlSessionFactory的getObject方法,下面将从阐述理由(温馨提示:看源码时不要什么都看,只看自己需要的即可,不然会很乱):

首先进入SqlSessionFactoryBean的源码:

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent>

从开头我们就知道SqlSessionFactoryBean实现了FactoryBean、InitializingBean,那么就必然有它们的方法,我们先来看InitializingBean,我们知道InitializingBean是用来进行初始化化的,它的方法只有一个:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.beans.factory;

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

这个方法是在bean的实例化结束、postProcessBeforeInitialization结束,postProcessAfterInitialization执行之前执行的,实际上就是进行初始化。

当然从源码中我们也可以得知afterPropertiesSet()是在属性填充之前执行,因为setDataSource方法先执行,注意看行号:

​ 从上面这张图片我们可以得知afterPropertiesSet方法执行了**“this.sqlSessionFactory = this.buildSqlSessionFactory();”**,紧接着我们看buildSqlSessionFactory()方法(主要是看它的返回值)

我们可以清晰地看到buildSqlSessionFactory()的返回值是:”return this.sqlSessionFactoryBuilder.build(targetConfiguration);“,从Mybatis的客户端代码我们可以得知sqlSessionFactoryBuilder.build()方法返回的是一个SqlSessionFactory对象。

然后是再回到**“this.sqlSessionFactory = this.buildSqlSessionFactory();”**,我们就知道afterPropertiesSet()已经将sqlSessionFactory赋好值,完成初始化。

然后是再来看FactoryBean:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.beans.factory;

import org.springframework.lang.Nullable;

public interface FactoryBean<T> {
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }
}

它有一个getObject()方法,SqlSessionFactoryBean实现如下:

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

        return this.sqlSessionFactory;
    }

所以结论得证:谁要使用SqlSessionFactory就去调用SqlSessionFactory的getObject方法。

3.2MapperScannerConfigurer

首先是可以看到MapperScannerConfigurer是实现了BeanDefinitionRegistryPostProcessor, InitializingBean等接口

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware

那么我们就继续分析MapperScannerConfigurer实现的方法,先看实现的InitializingBean的afterPropertiesSet方法:

 public void afterPropertiesSet() throws Exception {
        Assert.notNull(this.basePackage, "Property 'basePackage' is required");
    }

可以看到它只是实现了一个断言,并没有多大的分析意义。

那么就继续看实现的BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if (this.processPropertyPlaceHolders) {
            this.processPropertyPlaceHolders();
        }

        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        scanner.setAddToConfig(this.addToConfig);
        scanner.setAnnotationClass(this.annotationClass);
        scanner.setMarkerInterface(this.markerInterface);
        scanner.setSqlSessionFactory(this.sqlSessionFactory);
        scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
        scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
        scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
        scanner.setResourceLoader(this.applicationContext);
        scanner.setBeanNameGenerator(this.nameGenerator);
        scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
        if (StringUtils.hasText(this.lazyInitialization)) {
            scanner.setLazyInitialization(Boolean.valueOf(this.lazyInitialization));
        }

        scanner.registerFilters();
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
    }

可以看到在postProcessBeanDefinitionRegistry方法中是创建了ClassPathMapperScanner,且是调用了scan方法,那我们就来分析一下ClassPathMapperScanner这个类:

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

        return beanDefinitions;
    }

我们发现它只有doscan方法并没有scan方法,但是我们发现它有一个父类:

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner

它的父类有scan,那么就是调用它的父类的scan方法了:

public int scan(String... basePackages) {
        int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
        this.doScan(basePackages);
        if (this.includeAnnotationConfig) {
            AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
        }

        return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
    }

在它的父类方法中又去调用doScan方法,由于其子类有了doScan方法,所以是调用它的子类的doScan方法,在它的子类中我们可以看到这样一行代码:

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

子类又去调用它父类的doScan方法:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet();

        for(String basePackage : basePackages) {
            for(BeanDefinition candidate : this.findCandidateComponents(basePackage)) {
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                candidate.setScope(scopeMetadata.getScopeName());
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                if (candidate instanceof AbstractBeanDefinition) {
                    this.postProcessBeanDefinition((AbstractBeanDefinition)candidate, beanName);
                }

                if (candidate instanceof AnnotatedBeanDefinition) {
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition)candidate);
                }

                if (this.checkCandidate(beanName, candidate)) {
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);
                    this.registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }

        return beanDefinitions;
    }

我们主要看这行:

 this.registerBeanDefinition(definitionHolder, this.registry);

可以知道只是将BeanDefinition注册到BeanDefinitionMap中,实际上definitionHolder就是将BeanDefinition再包一层。最后将所有的BeanDefinition注册好后,返回beanDefinitions的集合给子类。

然后子类 ClassPathMapperScanner去调用processBeanDefinitions方法:

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        for(BeanDefinitionHolder holder : beanDefinitions) {
            GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
            String beanClassName = definition.getBeanClassName();
            LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface");
            definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
            definition.setBeanClass(this.mapperFactoryBeanClass);
            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) {
                LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
                definition.setAutowireMode(2);
            }

            definition.setLazyInit(this.lazyInitialization);
        }

    }

可以看到里面执行了**definition.setBeanClass(this.mapperFactoryBeanClass);**这段代码,就是将当前的BeanClass进行覆盖了,因为当前的BeanClass是Mappper接口,不是对象。紧接着看到参数列表的mapperFactoryBeanClass,继续挖:

private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;

发现它又等于MapperFactoryBean.class,那就挖:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> 

我们发现MapperFactoryBean实现了FactoryBean,那就必然有getObject方法:

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

到这里恍然大悟,这逻辑不是和Mybatis的客户端代码一样吗?

紧接着继续分析,ClassPathMapperScanner的processBeanDefinitions方法有这样一段代码definition.setAutowireMode(2);,见名知意就是设置自动注入,其中“2”是代表按照类型自动注入。

四、小结

至此,Spring xml 方式整合MyBatis具体操作、原理剖析就全部完成了,让我们进行一个总结吧:

Spring 整合 MyBatis 的原理剖析:

整合包里提供了一个 SqlSessionFactoryBean 和一个扫描 Mapper 的配置对象,SqlSessionFactoryBean 一旦被实例化,就开始扫描 Mapper 并通过动态代理产生 Mapper 的实现类存储到 Spring 容器中。相关的有如下四个类:

  • SqlSessionFactoryBean:需要进行配置,用于提供 SqlSessionFactory;

  • MapperScannerConfigurer:需要进行配置,用于扫描指定 mapper 注册 BeanDefinition;

  • MapperFactoryBean:Mapper 的 FactoryBean,获得指定 Mapper 时调用 getObject 方法;

  • ClassPathMapperScanner:definition.setAutowireMode (2) 修改了自动注入状态,所以 MapperFactoryBean 中的 setSqlSessionFactory 会自动注入进去。

    拓展:

撒花:

^ <

✿✿✿✿✿✿✿✿✿✿✿✿✿✿✿

✿✿✿✿✿✿✿✿✿✿✿✿✿✿✿

✿✿✿✿✿✿✿✿✿✿✿✿✿✿✿

Logo

昇腾计算产业是基于昇腾系列(HUAWEI Ascend)处理器和基础软件构建的全栈 AI计算基础设施、行业应用及服务,https://devpress.csdn.net/organization/setting/general/146749包括昇腾系列处理器、系列硬件、CANN、AI计算框架、应用使能、开发工具链、管理运维工具、行业应用及服务等全产业链

更多推荐