MyBatis-Spring中MyBatis概要流程

参考1

1.每次执行sql时一直要创建代理对象吗?

不会,MapperScannerConfigurer会创建的代理对象,spring会将代理对象注入到ioc容器中进行复用

2.mapper代理对象怎么在xml中找到与之匹配的sql映射配置信息的?

XMLMapperBuilder

public void parse() {
    if (!this.configuration.isResourceLoaded(this.resource)) {

        //1.这里是解析Mapper文件的核心,它内部把Mapper的select,delete,update,insert这些
        //标签添加到了MapperStatement内,也就是SQL语句
        this.configurationElement(this.parser.evalNode("/mapper"));

        this.configuration.addLoadedResource(this.resource);

        //2.这一步也是非常重要的,它绑定了mapper文件的命名空间
        this.bindMapperForNamespace();
    }

    //解析还没有解析的各个元素
    this.parsePendingResultMaps();
    this.parsePendingCacheRefs();
    this.parsePendingStatements();
}



1. 项目启动时会加载映射配置文件,封装增删改查标签属性为mapperStatememt对象,放入map中,key是mapper接口【全限定名+方法名称】,mapperStatememt记录了sql信息
2. 构建完mapperStatememt后,会继续加载当前mapper中的namespace(名称空间)对应的接口,然后将该接口放入MapperRegistry中的map中,key是映射接口全限定类名,value是MapperProxyFactory(用来创建代理对象的)
3. 代理对象调用目标方法,会调用mapperProxy中的invoke方法前,其中逻辑会先创建mapperMethod对象,mapperMethod记录了当前执行方法的【接口全限定名+方法名称】key1,通过此key1可以获取到相应的MapperStatement对象即sql信息,方法参数中也传递了请求参数,这样就可以找到并执行相应的方法

3.Spring怎么加载的MyBatis的xml资源的?

  1. 主要依赖于SqlSessionFactoryBean,其实现了InitializingBean中的afterPropertiesSet方法,Spring在初始化SqlSessionFactoryBean对象时候会调用afterPropertiesSet方法,该方法会调用buildSqlSessionFactory方法创建SqlSessionFactory,其中里面比较重要的两点逻辑是:
    一、加载指定路径下的mapper配置文件,将其下的所有标签封装为MappedStatement方法,将其放入到Configuration对象中的map中,key是mapper的名称空间+标签的id。
    二、将mapper接口对应的类进行类加载,封装到MapperRegister的map中,key是接口的全限定类名,value是MapperProxyFactory对象。MapperProxyFactory是用来创建当前接口代理对象用的

4.只扫描到了mapper接口,但是没有指定mapper映射xml的文件,如何进行加载的?

其中有个类MapperFactoryBean继承了DaoSupport并实现了checkDaoConfig方法,而DaoSupport实现了InitializingBean的afterPropertiesSet方法,其方法又调用了checkDaoConfig方法。checkDaoConfig会将接口以及对应的MapperProxyFactory放入MapperRegister的map中,并在MapperAnnotationBuilder.loadXmlResource方法中以当前接口的全限定类名和方法名到类路径下加载相应的xml配置文件(类名+方法名+.xml,其中名称要大小写完全匹配才能加载到)。

如果是指定了映射配置文件路径,先按照指定的映射配置文件进行加载,没有指定再加载与当前mapper接口全限定名相应的配置文件,只会加载一次

5.原生加载、spring加载Mybatis、springboot怎么加载启动的?

原生加载是通过SqlSessionFactoryBuilder.build进行加载的。

spring整合是通过SqlSessionFactoryBean实现InitialingBean接口的afterPropertiesSet方法加载的。

springboot是通过MybatisAutoConfiguration自动配置类进行加载的,其中也是通过调用FactoryBean执行getObject方法,该方法又调用afterPropertiesSet方法,其又调用了SqlSessionFactoryBuilder.build创建的

6.SqlSessionFactoryBean还实现了FactoryBean方法?

FactoryBean中的getObject方法可以允许用户自定义 获取bean实例对象

@Component
public class MyFactoryBean implements FactoryBean {

    @Override
    public Object getObject() throws Exception {
        String data1 = buildData1();
        return data1;
    }

    private String buildData1() {
        return "data1";
    }


    @Override
    public Class<?> getObjectType() {
        return null;
    }
}

public static void main(String[] args) {
    ConfigurableApplicationContext applicationContext = SpringApplication.run(LogbackDemoApplication.class, args);
    Object myFactoryBean = applicationContext.getBean("myFactoryBean");
    System.out.println(myFactoryBean);
    Object myFactoryBean1 = applicationContext.getBean("&myFactoryBean");
    System.out.println(myFactoryBean1);
}

输出内容:

data1
com.abucloud.logbackdemo.config.MyFactoryBean@62c3f556

在SpringBoot整合MyBatis时,SqlSessionFactoryBean,这个类实现了InitializingBean、FactoryBean类。

其中的getObject方法调用了当前方法afterPropertiesSet(),这个方法用来构建SqlSessionFactory对象,然后getObject方法创建的SqlSessionFactory对象

7.整体流程

1. 解析封装全局配置信息到Configuration对象中;

2.1
如果配置了mapperLocations指定资源位置,并且匹配的到,那么加载这些资源执行2步骤的操作
2.2
如果没有配置或配置了但匹配不到,那么会检查每个映射器接口,以映射器接口路径改为xml资源路径,去匹配加载相应的xml资源,能够找到就执行2步骤操作

2.先通过mapperLocations指定的资源配置位置加载各个xml配置文件,解析每个xml配置文件到Configuration的mappedStatements中,(mappedStatements的keynamespace,value是增删改查封装的标签);然后再通过各个xml中的指定的namespace进行类加载,得到Class对象后
放到MapperRegister中的knownMappers中,knownMappers(map对象)的key是映射接口的Class对象,value是其对应的MappperProxyFactory(MappperProxyFactory用来创建代理对象)


2. 通过@MapperScan能够扫描各个映射接口,并将其封装为MapperFactoryBean。由于MapperFactoryBean接口实现了FactoryBean的getObject方法,同时拥有来自SqlSessionFactoryFactory创建的sqlSessionTempalte。Spring在通过调用getObject方法将返回值放入容器中(其实就是将各个映射接口创建出代理对象,并放在容器里,实质上是利用2步骤中放在kownMapper找到匹配的映射接口对应的MapperProxyFactory,进而创建的映射接口的代理对象,增强逻辑是在MapperProxy中,其实现了InvocationHandler)

3.真正调用时其实使用的是代理对象调用mapper的目标方法,一旦调用就会执行MapperProxy.invoke方法,会构建MapperMethod方法,MapperMethod找到对应的增删改方法,最终通过接口的全限定名+方法名定位匹配mappedStatement,并将参数传入解析

4. 再调用jdbc方法执行
  1. MapperScannerConfigurer作用
    通过扫描指定的mapper接口,并将各个mapper接口动态封装到MapperFactoryBean对象中。而spring会将MapperFactoryBean的getObject方法的返回值放入到ioc容器中,MapperFactoryBean#getObject方法处理的逻辑就是创建mapper接口的代理对象

SqlSessionTemplate、SqlSessionInterceptor

MyBatis-Spring中MyBatis整体流程

三个重要类

  • SqlSessionFactoryBean
    读取全局配置信息、读取各个xml文件构建mapperStatement、加载xml对应的映射接口并构建器代理工厂
  • MapperScannerConfigurer
    扫描指定包路径下映射接口到封装为BeanDefinition对象,然后偷梁换柱映射接口对应构建的BeanDefinitiont构建为MapperFactoryBean对象,而映射接口作为BeanDefinition的一个属性mapperInterface。
    最后交给spring调用其getObject注入映射接口的代理对象,如果构建映射接口是下面mapperFactoryBean#getObject方法做的事情
  • MapperFactoryBean
    从MapperRegistry找到当前映射接口对应的对应的代理工厂MapperProxyFactory并构建代理对象,但是内部使用的是SqlSessionTemplate作为桥梁,创建真正的DefaultSqlSession是构建SqlSessionInterceptor中处理的。
    
    之所以使用SqlSessionTemplate,是因为其使得Mybatis的SqlSession参与到spring的事务中
    

一、初始化SqlSessionFactory

核心流程

核心使用到了SqlSessionFactoryBean的afterPropertiesSet、getObject方法

afterPropertiesSet:用于初始化DefaultSqlSessionFactory并封装配置数据

getObject:用于注入DefaultSqlSessionFactory对象到容器中

详情逻辑

一、在将SqlSessionFactoryBean放在IOC容器过程中,由于SqlSessionFactoryBean实现了InitializingBean#afterPropertiesSet方法
afterPropertiesSet会做以下几件事
1. 将全局配置信息封装到Configuration对象中
2. 读取并解析各个配置的xml文件并封装到ConfigurationMap<String, MappedStatement> mappedStatements对象中,key是xml的名称空间+各个标签id,value是标签的内容
3. 同时类加载各个配置文件中名称空间对应的映射接口得到Class对象,构建相应的代理工厂对象MapperProxyFactory放到Configuration#MapperRegistry的Map<Class<?>, MapperProxyFactory<?>> knowanMappers中,方便创建代理对象使用
将封装好数据的Configuration对象作为属性放在创建的DefaultSqlSessionFactory对象中,这样就构建了DefaultSqlSessionFactory对象

二、SqlSessionFactoryBean其实现了FactoryBean#getObject方法,getObject方法会将上边的DefaultSqlSessionFactory对象注入到IOC容器中

相应配置

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <!--映射配置文件位置-->
    <property name="mapperLocations" value="classpath:/mapper/*.xml"/>

    <!--其他配置-->
    <property name="configuration">
        <bean class="org.apache.ibatis.session.Configuration">
            <!--开启列名转驼峰映射配置-->
            <property name="mapUnderscoreToCamelCase" value="true"/>
            <!--日志输出到控制台-->
            <property name="logImpl" value="org.apache.ibatis.logging.stdout.StdOutImpl"/>
        </bean>
    </property>
</bean>

二、创建接口的代理对象并放在IOC容器中

核心流程

MapperScannerConfigurer#postProcessBeanDefinitionRegistry

  1. 扫描指定包路径下的映射器mapper接口封装为BeanDefinition
  2. 然后偷梁换柱将mapper接口变成MapperFactoryBean对象
  3. 最后交给spring,将MapperFactoryBean中getObject方法返回值(即mapper接口的代理对象)注入到容器中

补充:

MapperFactoryBean有两个重要作用,一是将映射接口封装都MapperFactoryBean中,通过getObject方法将代理对象注入到容器中,二是当没有指定mapperLocations配置文件时,会自动识别以接口的全限定名去与接口相同的类路径下找匹配的xml文件

三:代理对象执行目标方法

核心流程

当代理对象调用目标方法时,会通过MapperProxy#invoke作为入口,SqlSesionTemplate中间桥梁委托给sqlSessionProxy执行,再通过SqlSessionInterceptor#invoke方法创建真正的DefaultSqlSession对象执行crud

详情逻辑

1. 代理对象执行目标方法,就会执行MapperProxy#invoke方法,会构建MapperMethod,解析拿到映射器接口及相应的mapperStatement对象
2. 通过mapperMethod找到执行哪个操作(crud),接着继续委托给sqlSessionTemplate(是在创建MapperProxy对象塞进去的)执行
3. sqlSessionTemplate会继续委托给sqlSessionProxy对象执行,其是代理对象,执行操作会触发增强逻辑SqlSessionInterceptor执行
4. SqlSessionInterceptor内部会创建DefaultSqlSession对象,反射执行内部相应的方法
5. DefaultSqlSession会继续委托给相应的Exexutor(比如是simpleExecutor)
6. Exexutor会从数据源中拿数据库连接对象,通过ParameterHandler进行参数解析(Java数据->sql数据)
7. 最终通过PreparedStatementHandler执行jdbc操作,拿到返回结果集通过ResultSetHandler处理解析结果集(sql数据->Java数据)

其他

在同一个spring事务中,自始至终使用的都是同一个DefalutSqlSession对象,原因是创建DefalutSqlSession时会将其绑定到线程的局部变量中,key是DefalutSqlSessionFactory,value是DefalutSqlSession对象

真正关闭sqlSession对象在SqlSessionUtils#beforeCompletion/afterCompletion,而这两个方法beforeCompletion/afterCompletion触发的时机是spring事务TransationAspectSupport执行结束后会触发

获取sqlsession对象并注册到spring事务的流程

  public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

	// 从线程中获取sqlsessionHolder对象
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    // 使用了spring事务则返回实例;否则返回null
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }
	// 创建一个sqlSession对象
    session = sessionFactory.openSession(executorType);
    // 如果使用了spring事务,则进行注册到当前spring事务中,并绑定到threadlocal对象中;否则什么也不处理
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

怎么判定数据库连接、sqlsession与事务是同步关系呢?

SqlSessionHolder与ConnectionHolder都继承与ResourceHolderSupport的synchronizedWithTransaction属性
当使用事务时都会设置synchronizedWithTransaction为true。通过此标识进行判断是否开启了事务

spring事务数据库连接是什么时候创建的呢?

在spring事务开启是就已经获取数据库连接对象了,并且放在了线程局部变量的map

MapperScanConfigurar

1. 扫描指定包路径下的映射器mapper接口封装为BeanDefinition
2. 然后偷梁换柱将mapper接口变成MapperFactoryBean对象
3. 最后交给spring,将MapperFactoryBean中getObject方法返回值(即mapper接口的代理对象)注入到容器中

FactoryBean接口的特点

1. 会将实现类注入到ioc容器中
2. 也会将getObject方法构建复杂bean注入容器中

Spring的ConnectionHolder与mybatis的SqlSessionHodlder创建先后顺序?

ConnectionHolder是spring事务创建时就初始化了,在spring事务执行完进行关闭的
而SqlSessionHolder则是被spring事务包裹着,创建与关闭是在其间执行的

有事务与无事务时connection对象与sqlSession对象创建顺序?

有事务:
spring事务创建时就会将connection创建出来,会放在线程的上下文中,然后会创建数据库连接对象放在线程的上下文中

无事务:
先创建数据库连接对象放在线程的上下文中,再创建connection对象并使用
posted @   永无八哥  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示