Mybatis源码笔记&Spring整合Mybatis

String resource = "mybatis-config.xml";
        Reader reader;
        try {
            //将XML配置文件构建为Configuration配置类
            reader = Resources.getResourceAsReader(resource);
            // 通过加载配置文件流构建一个SqlSessionFactory  DefaultSqlSessionFactory
            SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
            // 数据源 执行器  DefaultSqlSession
            SqlSession session = sqlMapper.openSession();
            try {
                // 执行查询 底层执行jdbc
                User user = (User)session.selectOne("com.tuling.mapper.UserMapper.selectById", 1L);

                UserMapper mapper = session.getMapper(UserMapper.class);
                System.out.println(mapper.getClass());
                User user = mapper.selectById(1L);
                session.commit();
                System.out.println(user.getUserName());
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                session.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);

这句话主要就是解析我们的xml配置文件生成Configuration对象,构建SqlSessionFactory
在这里插入图片描述
我们看下xml怎么变成Configuration的

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--SqlSessionFactoryBuilder中配置的配置文件的优先级最高;config.properties配置文件的优先级次之;properties标签中的配置优先级最低 -->
    <properties resource="org/mybatis/example/config.properties">
      <property name="username" value="dev_user"/>
      <property name="password" value="F2Fa3!33TYyg"/>
    </properties>

    <!--一些重要的全局配置-->
    <settings>
    <setting name="cacheEnabled" value="true"/>
    <!--<setting name="lazyLoadingEnabled" value="true"/>-->
    <!--<setting name="multipleResultSetsEnabled" value="true"/>-->
    <!--<setting name="useColumnLabel" value="true"/>-->
    <!--<setting name="useGeneratedKeys" value="false"/>-->
    <!--<setting name="autoMappingBehavior" value="PARTIAL"/>-->
    <!--<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>-->
    <!--<setting name="defaultExecutorType" value="SIMPLE"/>-->
    <!--<setting name="defaultStatementTimeout" value="25"/>-->
    <!--<setting name="defaultFetchSize" value="100"/>-->
    <!--<setting name="safeRowBoundsEnabled" value="false"/>-->
    <!--<setting name="mapUnderscoreToCamelCase" value="false"/>-->
    <!--<setting name="localCacheScope" value="STATEMENT"/>-->
    <!--<setting name="jdbcTypeForNull" value="OTHER"/>-->
    <!--<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>-->
    <!--<setting name="logImpl" value="STDOUT_LOGGING" />-->
    </settings>

    <typeAliases>

    </typeAliases>

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!--默认值为 false,当该参数设置为 true 时,如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果-->
            <!--如果某些查询数据量非常大,不应该允许查出所有数据-->
            <property name="pageSizeZero" value="true"/>
        </plugin>
    </plugins>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://10.59.97.10:3308/windty"/>
                <property name="username" value="windty_opr"/>
                <property name="password" value="windty!234"/>
            </dataSource>
        </environment>
    </environments>

    <databaseIdProvider type="DB_VENDOR">
        <property name="MySQL" value="mysql" />
        <property name="Oracle" value="oracle" />
    </databaseIdProvider>

    <mappers>
        <!--这边可以使用package和resource两种方式加载mapper-->
        <!--<package name="包名"/>-->
        <!--<mapper resource="./mappers/SysUserMapper.xml"/>-->
        <mapper resource="./mappers/CbondissuerMapper.xml"/>
    </mappers>

</configuration>

在这里插入图片描述
构建了XMLConfigBuilder 对象用来解析xml
这里面没啥好说的,就是解析xml然后负值到Configuration属性上,包括日志、别名、插件、数据源、数据库厂商、typeHandlermapper,这样我们Configuration对象就构建好了。

重点看下mapper怎么解析的

在这里插入图片描述
构建了XMLMapperBuilder用来解析namespace、参数、resultMap、缓存等
ResultMap

org.apache.ibatis.builder.xml.XMLMapperBuilder#resultMapElement(org.apache.ibatis.parsing.XNode, java.util.List<org.apache.ibatis.mapping.ResultMapping>, java.lang.Class<?>)

这里会构建ResultMapping集合
在这里插入图片描述
其实也就是我们一些jdbcType、javaType,对应的列,以及resultMap唯一id
在这里插入图片描述

然后放到

  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");

id 就是 唯一id

接下来看怎么解析sql 标签
在这里插入图片描述

org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode

解析一些缓存、节点名称、节点类型SqlCommandType(后续执行sql 会用到,判断是增删改查)、自增id、解析sql、resultMap 最后封装成MappedStatement当道Configuration的mappedStatements中,key 就是namsespace+MappedStatementId
重点看下sql 怎么解析的
在这里插入图片描述
分为动态sql、和静态sql,返回SqlSource 放入我们的MappedStatement
sqlSource 有getBoundSql 方法
在这里插入图片描述
然后创建 MapperProxyFactory 放入到
Configuration的MapperRegistry的knownMappers.put(type, new MapperProxyFactory<>(type));

SqlSession session = sqlMapper.openSession();

在这里插入图片描述
其实就是构建了我们的DefaultSqlSession,DefaultSqlSession对象中包含Executor和Configuration
我们看下Executor
在这里插入图片描述
默认就是SimpleExecutor
如果开启缓存CachingExecutor,然后调用拦截器

UserMapper mapper = session.getMapper(UserMapper.class);

在这里插入图片描述
在这里插入图片描述
可以看到就是从我们的configuration的mapperRegistry 中拿,看下具体怎么拿的
在这里插入图片描述

在这里插入图片描述
从knownMappers拿到MapperProxyFactory,MapperProxyFactory创建代理,所以最后返回的对象就是MapperProxy

User user = mapper.selectById(1L);

也就是我们代理对象 的

org.apache.ibatis.binding.MapperProxy#invoke

在这里插入图片描述

首先 封装 MapperMethod
在这里插入图片描述
在这里插入图片描述
通过MappedStatement解析出name和type

接下来执行
在这里插入图片描述
其实最后还是调用了sqlSession的方法
sqlSession最后还是调用了executor
在这里插入图片描述

在这里插入图片描述
拿出sql
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
会解析我们的sql,还记得上面的动态sql吗
在这里插入图片描述

如果开启了二级缓存,从二级缓存先取
在这里插入图片描述
在这里插入图片描述
可以看到二级缓存是装饰器模式,可以实现线程安全、fifo内存淘汰、命中率统计
二级缓存没有,从一级缓存取,一级缓存就是一个map
一级缓存是sqlSession级别的,insert、update 操作会清空缓存,commit 也会,所以同一个sqllSerssion 多次操作 会命中一级缓存,一级缓存默认开启,二级缓存默认关闭,且可以持久化,二级缓存提交的时候才回去更新进去,防止回滚脏读,之前也是暂存在map里
org.apache.ibatis.cache.decorators.TransactionalCache#flushPendingEntries
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
new RoutingStatementHandler的时候会初始化父类,这个时候会进行
在这里插入图片描述
在这里插入图片描述
ParameterHandler和ResultSetHandler的拦截
StatementHandler 拦截

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过typeHandler 设置我们的参数
在这里插入图片描述
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSet
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValues
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValuesForNestedResultMap
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#applyPropertyMappings
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getPropertyMappingValue

大概思想就是通过
TypeHandler 对应类型的实现类 从resultSet 中 通过列名 拿到对应的java类型的值,反射放到object上

Plugin

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

上面说过 ParameterHandler、ResultSetHandler、StatementHandler、Executor实例化后会调用我们的InterceptorChain.pluginAll方法,其实就是调用每个拦截器的plugin方法
所以我们plugin方法就是返回代理对象,默认Plugin.wrap(target, this);
我们看看他做了什么
在这里插入图片描述
其实也就是判断当前自定义注解上的type和 目前调用plugin是不是一样,一样就能返回代理对象,那代理对象调用的时候如果方法包含目标方法又回调了我们Interceptor的intercept方法,否则调用本身方法
在这里插入图片描述

Spring整合Mybatis

Spring整合Mybatis,其实我们就是把mapper的代理对象交给IOC容器管理,但是创建过程还是由第三方框架来做,也就是mybatis来做
我们回忆一下mybatis有几个重要组件?
1.SqlSessionFactory封装了Configuration
2.SqlSession
3.MapperProxy
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看出来就是mybatis解析xml那一套

在这里插入图片描述

第一点很好实现,配置类就好了,同样SqlSessionFactory的创建也要交给Mybatis来做,所以Spring提供了SqlSessionFactoryBean 实现了FactoryBean,getObject返回我们的SqlSessionFactory,那解析配置文件,实例化Configuration和SqlSessionFactory我们找个生命周期来做就好了,所以SqlSessionFactoryBean 实现了InitializingBean,在afterPropertiesSet实例化

接下来我们就是解析所有的@Mapper 注解,把他交给Spring管理
1.我们知道Mapper 是接口,无法实例化
2.@Mapper没有包含@Compoment,无法被spring扫描
3.Mapper代理的创建过程还是mybatis做,只不过MapperProxy交给IOC

基于以上几点,我们还是想到了FactoryBean,在getObject中SqlSession,getMapper(Mapper.class) 就好了

所以我们要扫描所有的@Mapper注解,偷天换日为MapperFactoryBean class
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在MapperScannerConfigurer的postProcessBeanDefinitionRegistry中添加我们的BeanDefinition
在这里插入图片描述
因为@Mapper不能被spring原生扫描器扫描,所以自定义ClassPathMapperScanner扫描,
因为spring默认不扫描接口,而我们@Mapper就是接口,所以要重写扫描的过滤规则

spring 默认

org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent(org.springframework.beans.factory.annotation.AnnotatedBeanDefinition)

在这里插入图片描述
mybatis 可以扫描接口
在这里插入图片描述

在这里插入图片描述
目前我们扫描出来的还是@Mapper 的bean定义,接口是不能被实例化的,而且我们要把代理创建给mybatis,所以我们要修改bean定义
在这里插入图片描述
构造器实例化MapperFactoryBean的时候传入真正的mapperClass作为ObjectType以及从MapperRegistry的knownMappers获取Mapper代理的key
并且把bean的class修改为MapperFactoryBean
在这里插入图片描述
在这里插入图片描述
还记得Spring自动注入的几种模式吗?
在这里插入图片描述

接下来要根据类型从IOC注入我们获取mapper的必要组件SqlSessionTemplate、SqlSessionFactory

最后说一下 SqlSessionTemplate 实现了 SqlSession,mybatis是DefaultSqlSession
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
所以调用selectOne 都是走的SqlSessionInterceptor,所以加事务可以缓存我们的sqlSession(也就是我们的SqlSessionTemplate对象)

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值