IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 较真儿学源码系列-MyBatis整合Spring核心流程源码分析 -> 正文阅读

[Java知识库]较真儿学源码系列-MyBatis整合Spring核心流程源码分析

????????MyBatis-Spring版本:2.1.0-SNAPSHOT。

????????源码注释:https://github.com/MonkeyOneCool/mybatis-spring-sourcecode-study/tree/master


1 简介

????????在我们使用MyBatis时,大部分情况下都不是单独使用的,都是需要和Spring进行集成后再使用。那么下面就来看下MyBatis是如何与Spring进行集成的(我之前写过对MyBatis源码进行分析的文章《较真儿学源码系列-MyBatis核心流程源码分析》,建议看一下,因为我下面的分析都是基于此之上再进行分析的)。


2 SqlSessionFactoryBean

????????我们在单独使用MyBatis的时候,是通过SqlSessionFactoryBuilder的build方法生成的SqlSessionFactory,而在和Spring集成后,是通过SqlSessionFactoryBean来生成的:

@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource());
    factoryBean.setMapperLocations(new ClassPathResource("org/mybatis/spring/demo/xml/UserMapper.xml"));
    return factoryBean.getObject();
}

? ? ? ? 可以看到,这里将数据源和mapper文件传给SqlSessionFactoryBean了。SqlSessionFactoryBean本身是个FactoryBean,熟悉Spring源码的同学应该知道,FactoryBean的getObject方法的返回值会被做成bean放进IoC容器中(我之前有写过对Spring源码的解析《较真儿学源码系列-Spring IoC核心流程源码分析》,感兴趣的话可以查看)。那么下面就来看下其getObject方法的实现:

/**
 * SqlSessionFactoryBean:
 */
@Override
public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
        afterPropertiesSet();
    }

    return this.sqlSessionFactory;
}

/**
 * 第7行代码处:
 */
@Override
public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
        "Property 'configuration' and 'configLocation' can not specified with together");

    this.sqlSessionFactory = buildSqlSessionFactory();
}

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    //...

    if (xmlConfigBuilder != null) {
        try {
            //之前在分析MyBatis的过程中已经解释过了,通过XMLConfigBuilder的parse方法来解析配置文件的内容,放到Configuration里
            xmlConfigBuilder.parse();
            LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
        } catch (Exception ex) {
            throw new IOException("Failed to parse config resource: " + this.configLocation, ex);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    /*
    这里会把SpringManagedTransactionFactory赋值给transactionFactory,所以最终用到的Transaction会是Spring事务管理器创建的Transaction;
    最终用到的connection会是事务管理器创建好的连接
     */
    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));

    if (this.mapperLocations != null) {
        if (this.mapperLocations.length == 0) {
            LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
        } else {
            for (Resource mapperLocation : this.mapperLocations) {
                if (mapperLocation == null) {
                    continue;
                }
                try {
                    //之前也说过,创建XMLMapperBuilder并调用其parse方法,会对mapper文件进行解析
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                    targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                    xmlMapperBuilder.parse();
                } catch (Exception e) {
                    throw new IOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
                } finally {
                    ErrorContext.instance().reset();
                }
                LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
            }
        }
    } else {
        LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }

    //调用build方法,将Configuration放进SqlSessionFactoryBuilder中
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

????????至此就明白了SqlSessionFactoryBean的作用就是将MyBatis的配置信息放到Configuration对象中。


3 Spring如何去管理Mapper接口

3.1?MapperFactoryBean

?????????如果想把一个类交给Spring容器来管理的话,我们只需要在这个类上加上@Component或@Service或@Controller注解即可。但是在MyBatis中我们却不能在这样做,因为mapper是接口。如果是接口或者抽象类的话,是不能被Spring所实例化的:

/**
 * ClassPathScanningCandidateComponentProvider:
 */
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    AnnotationMetadata metadata = beanDefinition.getMetadata();
	return (metadata.isIndependent() && (metadata.isConcrete() ||
        (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}

/**
 * ClassMetadata:
 * 第6行代码处:
 */
default boolean isConcrete() {
    return !(isInterface() || isAbstract());
}

? ? ? ? 这个时候就需要MapperFactoryBean登场了,它会对mapper接口进行包装:

@Bean
public MapperFactoryBean userMapper() throws Exception {
    MapperFactoryBean mapperFactoryBean = new MapperFactoryBean(UserMapper.class);
    mapperFactoryBean.setSqlSessionFactory(sqlSessionFactory());
    return mapperFactoryBean;
}

????????同理,因为是个FactoryBean,所以查看其getObject方法实现:

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

/**
 * SqlSessionTemplate:
 */
@Override
public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
}

????????可以看到,最终就是通过调用sqlSession的getMapper方法来创建出代理类,之前在分析MyBatis的文章中已经讲过了。

3.2?并发控制

? ? ? ? 这里需要对SqlSessionTemplate单独做一些分析。我们之前对MyBatis源码进行分析的时候,如果细心的话可以发现DefaultSqlSession乃至整个MyBatis源码中都很少有加锁的控制。那么可以认为DefaultSqlSession是线程不安全的(这点其实没毛病,MyBatis的宗旨就是简单轻量化)。那么现在与Spring集成后,就不得不考虑这点了。SqlSessionTemplate是mybatis-spring项目单独封装的类,下面来看下它的构造器:

/**
 * SqlSessionTemplate:
 */
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
    this(sqlSessionFactory, executorType,
        new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
                          PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    //这里是在动态代理,生成代理类。重点关注下最后一个参数,之后在调用sqlSessionProxy的方法时会调用到SqlSessionInterceptor的invoke方法
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class}, new SqlSessionInterceptor());
}

/**
 * SqlSessionInterceptor:
 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //重点看下是如何获取到sqlSession的
    SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
    try {
        //目标方法的调用
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
            // force commit even on non-dirty sessions because some databases require
            // a commit/rollback before calling close()
            sqlSession.commit(true);
        }
        return result;
    } catch (Throwable t) {
        //...
    } finally {
        if (sqlSession != null) {
            closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
    }
}

/**
 * SqlSessionUtils:
 * 第33行代码处:
 */
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
                                       PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    /*
    获取同一个线程之前有没有创建过sqlSession
    Spring事务相关的获取资源的方法,这里不详细去看了,底层就是在使用ThreadLocal
     */
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    //通过sqlSessionHolder获取sqlSession
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
        //如果之前存在sqlSession的话,就直接返回
        return session;
    }

    LOGGER.debug(() -> "Creating a new SqlSession");
    //之前不存在sqlSession,说明当前线程是第一次获取,此时通过openSession方法获取到sqlSession
    session = sessionFactory.openSession(executorType);

    //并放进ThreadLocal中(如果事务同步激活的话)
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
}

? ? ? ? 可以看到,Mybatis集成Spring后,是通过ThreadLocal来保证的并发安全(我之前写过对ThreadLocal进行源码分析的文章《较真儿学源码系列-ThreadLocal(逐行源码带你分析作者思路)》,感兴趣的话可以查看)。这样的话,每个线程都有自己单独的sqlSession。

? ? ? ? 需要注意的一点是:如果事务没有开启的话,ThreadLocal中是不会放入值的。这样造成的结果就是每次请求(注意这里的措辞,不是同一个线程)都是new出来的新的sqlSession。这样虽然不会有并发问题,但是会使缓存失效。还记得MyBatis的一级缓存是sqlSession级别的吗?所以这也就是为什么没有开启事务,MyBatis一级缓存失效的原因。因为每次请求都会创建一个新的sqlSession,不管在不在同一个线程内。之前sqlSession的缓存都作废了。

? ? ? ? 至于为什么会这么做,为什么事务不开启的时候不往ThreadLocal里存值。猜测是为了Spring事务的概念。如果一个方法里面有多条查询SQL的语句的话,那么可以认为这几条查询语句是相互独立的,而如果在方法上加上@Transactional注解的话,则代表这里面的所有查询SQL是一个整体,所以需要共用同一个sqlSession、共用一级缓存(?我之前有写过对Spring事务的源码解析《较真儿学源码系列-Spring事务管理核心流程源码分析》,感兴趣的话可以查看)。

3.3?@MapperScan

? ? ? ? 前面讲解了通过MapperFactoryBean包装的方式来创建mapper接口的bean,但是我们不可能每次创建出一个mapper接口,就手动用MapperFactoryBean包装一下,再注入到Spring容器中。这个时候我们就会用到@MapperScan注解,以此来提供统一的注册mapper接口的方式。查看其实现:

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

    //...

}

? ? ? ? @MapperScan注解上会引用到MapperScannerRegistrar类,因其实现了ImportBeanDefinitionRegistrar接口,所以我们只需要看其registerBeanDefinitions方法的实现就行了:

/**
 * MapperScannerRegistrar:
 */
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    //获取@MapperScan注解上的配置信息
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
        registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
            generateBaseBeanName(importingClassMetadata, 0));
    }
}

/**
 * 第10行代码处:
 */
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
                             BeanDefinitionRegistry registry, String beanName) {

    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    //...

    // for spring-native
    builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

    //这里是将MapperScannerConfigurer注册为一个bean定义
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

}

? ? ? ? 可以看到,最后是注册了MapperScannerConfigurer。MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,那么接下来就来看下其postProcessBeanDefinitionRegistry接口的实现:

/**
 * MapperScannerConfigurer:
 */
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
        processPropertyPlaceHolders();
    }

    //创建扫描器,可以对mapper进行通用的扫描
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    //...
    scanner.registerFilters();
    //进行扫描
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

/**
 * ClassPathMapperScanner:
 * 第13行代码处:
 */
public void registerFilters() {
    boolean acceptAllInterfaces = true;

    //...

    if (acceptAllInterfaces) {
        // default include filter that accepts all classes
        /*
        这里的作用是添加一个includeFilter,使其可以跳过ClassPathScanningCandidateComponentProvider的isCandidateComponent(MetadataReader)方法
        的校验,之后能跳到ClassPathMapperScanner改写的isCandidateComponent(AnnotatedBeanDefinition)方法里
         */
        addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
    }

    // exclude package-info.java
    addExcludeFilter((metadataReader, metadataReaderFactory) -> {
        String className = metadataReader.getClassMetadata().getClassName();
        return className.endsWith("package-info");
    });
}

/**
 * ClassPathBeanDefinitionScanner:
 * 第15行代码处:
 */
public int scan(String... basePackages) {
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

	doScan(basePackages);

	// Register annotation config processors, if necessary.
	if (this.includeAnnotationConfig) {
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}

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

/**
  * ClassPathMapperScanner:
  * 第51行代码处:
  */
 @Override
 public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //调用原有的扫描逻辑(在其中会调用到isCandidateComponent方法,也就是会跳到ClassPathMapperScanner改写的isCandidateComponent(AnnotatedBeanDefinition)方法里)
    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 {
        //这里是ClassPathMapperScanner自己改写的内容
        processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

/**
 * 第68行代码处:
 */
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    //这里就可以看到:ClassPathMapperScanner改写了Spring默认的实现,使mapper接口可以被注册到Spring容器中
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}

/**
 * 第75行代码处:
 */
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    //遍历所有的mapper
    for (BeanDefinitionHolder holder : beanDefinitions) {
        definition = (AbstractBeanDefinition) holder.getBeanDefinition();
        boolean scopedProxy = false;
        //...
        String beanClassName = definition.getBeanClassName();
        LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
            + "' mapperInterface");

        // the mapper interface is the original class of the bean
        // but, the actual class of the bean is MapperFactoryBean
        //给MapperFactoryBean的构造器传参,参数为mapper接口的Class对象
        definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
        try {
            // for spring-native
            definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));
        } catch (ClassNotFoundException ignore) {
            // ignore
        }

        //bean定义的类型需要从原始mapper的类型改写为MapperFactoryBean(本行代码和上面给构造器传参的代码的顺序不能换)
        definition.setBeanClass(this.mapperFactoryBeanClass);

        //...

        if (!explicitFactoryUsed) {
            LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }

        definition.setLazyInit(lazyInitialization);

        if (scopedProxy) {
            continue;
        }

        //...

    }
}

? ? ? ? 上面最终会把类型为MapperFactoryBean的bean定义注册进去,之后的操作就是调用MapperFactoryBean的getObject方法了,之前在分析MyBatis的过程中已经讲过了。


原创不易,未得准许,请勿转载,翻版必究

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-21 18:48:02  更:2022-05-21 18:48:10 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/31 14:00:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码