美文网首页Java
Mybatis源码解读

Mybatis源码解读

作者: TZX_0710 | 来源:发表于2021-07-06 20:00 被阅读0次

Mybatis入门

Mybatis构成

  1. SqlSessionFactoryBuilder(构造器):根据配置信息或者代码生成SqlSessionFactory
  2. SqlSessionFactory:依靠工厂来生成SqlSession会话
  3. SqlSession是一个可以发送sql去执行并返回结果,也可以获取Mapper的接口
  4. SqlMapper:它是一个由Java接口或者XML文件构成的,给出对应的SQL和映射规则,它复杂发送SQL去执行,返回结果,

SqlSession的作用

  1. 获取映射器,让映射器通过命名空间和方法查找对应的sql,发送给数据库并执行返回结果
  2. 直接通过命名信息去执行SQL返回结果,SqlSession通过支持事务,通过commit,rollback方法提交或者回滚事务。

映射器

  1. 应设计是由Java接口和XML共同组成,作用
    1. 定义参数类型
    2. 描述缓存
    3. 描述SQL语句
    4. 定义POJO和查询结果的关系

Mybatis映射原理,使用Java语言的动态代理java.lang.reflect实现。Mybtais上下文中描述接口,Mybatis为这个接口生成代理对象,代理对象根据命名空间+方法名匹配找到对应的XML文完成所需要的任务,返回需要的结果。

生命周期

SqlSessionFactoryBuilder

SqlSessionFactoryBuilder是利用XML或者Java编码获得资源来构建SqlSessionFactory的,通过它可以构建多个SessionFactory。它的作用就是一个构造器,一旦构建了SqlSessionFactory。它的作用也就结束。所以它的作用限于方法的局部,它的作用就是生成SqlSessionFactory对象。

SqlSessionFactory

SqlSessionFactory的作用是创建SqlSession,而SqlSession是一个会话,相当于JDBC的Connection对象。每次程序访问数据库都需要通过SqlSessionFactory创建Sqlsession。所以SqlSessionFactory应该在整个Mybtais的生命周期中。

SqlSession

SqlSession是一个会话,不是一个线程安全的对象,它的生命周期在于请求数据库处理事务的过程中。创建的SqlSession必须及时关闭,长时间存活会使数据库的连接池的活动资源减少。

Mapper

Mapper是一个接口,而没有任何实现类,它的作用是发送SQL,然后返回我们需要的结果。因此它应该在一个SqlSession事务方法之类,是一个方法级别的东西

配置

properties元素

properties是一个配置属性的元素,配置文见当中使用该元素。mybatis提供三种配置方式

  1. property子元素
  2. properties配置文件
  3. 程序参数传递

Mybatis支持的配置方式可能同时出现,所以MyBatis按照顺序去加载

  1. 在properties元素体内指定的属性首先被读取
  2. 首选的方式是使用properties文件
  3. 如果需要对其进行加密或其它的加工满足特殊需求,可以使用配置方式和编码方式结合。

别名

别名(typeAliases)是一个指代的名称。因为我们的类全路径太长,所以采用一个简短的名称去指定。Mybatis中别名是不分大小写。一个typeAliases的实例是在解析配置文件时生成的,然后长期保存在Configuration对象当中。

系统定义别名

Mybatis 当中已经注册了一些自定义的系统别名 如Integer=int,arraylist=ArrayList等一系列自定义注册信息.位于org.apache.ibatis.type.TypeAliasRegistry包当中

自定义别名

系统自定义的别名是肯定不够用的,因为不同的应用有着不同的需要,不同的实体等。所以Mybatis允许自定义别名。

<!-- 定义别名 -->
<typeAliases>
 <typeAlias  alias="user"  type="com.zyzpp.model.User"/>
</typeAliases>
<!-- 扫描形式 -->
<typeAliases>
 <package name="com.zyzpp.model" />
</typeAliases>
//Java注解心事
@Alias("user")
public class User{
 //...
}

typeHandler类型处理器

MyBatis在预处理语句当中设置一个参数,或者从结果集当中取出一个值,都会使用到typeHandler的处理。用于数据库的字段映射到PoJo当中一个类型转换。

自定义typeHandler

  1. 配置XML文件当中需要处理什么类型参数
<typeHandlers>
    //处理JDBC 为varchar类型的数据 javaType=String类型的参数 用该Handler执行
   <typeHandler handler="com.example.demo.util.StringHandler" jdbcType="VARCHAR" javaType="String"/>
</typeHandlers>
  1. 自定义配置项去完成转换
//映射JDBC类型为varchar
@MappedJdbcTypes({JdbcType.VARCHAR})
//映射类型为String
@MappedTypes({String.class})
public class StringHandler extends BaseTypeHandler<String> {

    //处理该参数 塞到数据库当中运用到
    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException {
        System.out.println("setNonNullParameter");
        preparedStatement.setString(i,s);
    }

    //根据roleName获取值
    @Override
    public String getNullableResult(ResultSet resultSet, String s) throws SQLException {
        System.out.println("getNullableResult   roleName");
        return resultSet.getString(s);
    }

    //下标获取值
    @Override
    public String getNullableResult(ResultSet resultSet, int i) throws SQLException {
        System.out.println("getNullableResult   index");
        return resultSet.getString(i);
    }

    //存储过程当中获取值
    @Override
    public String getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        System.out.println("getNullableResult   callableStatement");
        return callableStatement.getString(i);
    }
}

//自定义typeHandler里用注解配置JdbcType和JavaType。JdbcType需要满足ibatis.JdbcType 的枚举类型。

  1. 标识哪些参数或者结果类型去用typehandler进行转换,在没用任何标识情况下,Mybatis是不会启用你定义的typeHandler进行转换结果的。有以下三种方法

    • 在结果集里面直接定义了jdbcType和 javaType和 handler一致
    <resultMap id="sysDict" type="com.example.demo.entity.SysDictType">
            <result column="dict_name" jdbcType="VARCHAR" javaType="String"  typeHandler="com.example.demo.util.StringHandler"/>
    </resultMap>
    <select id="findAll" resultMap="sysDict">
            select *
            from sys_dict_type
    </select>
    
    • 在mybatis-config配置 扫描 handler,设置handler别名

      <typeHandlers>
              <package name="com.example.demo.util"/>
      </typeHandlers>
      
    • 在springboot的yml文件当中配置扫描设置别名

    mybatis:
      type-handlers-package: com.example.demo
    

枚举类型typeHandler

MyBatis内部提供了两个转化枚举类型的typeHandler给我们使用。

  1. EnumTypeHandler
  2. EnumOrdinalTypeHandler

其中EnumTypeHandler是使用枚举字符串名称作为参数传递的,EnumOrdinalTypeHandler是使用整数下标作为参数传递的。

objectFactory

当Mybatis再构建一个结果返回的时候,都会使用ObjectFactory去构建POJO,在Mybatis中可以定制自己的对象工厂。在大部分的场景夏我们都不用修改,如果要特定的工厂则需要配置。

自定义实现 ObjectFactory的要求是实现ObjectFactory,但是DefaultObject已经实现了ObjectFactory,所以我们可以采用继承DefaultObjectFactory的方式去实现自定义的。

缓存cache

  1. 一级缓存

Mybatis默认情况下,只开启一级缓存,一级缓存只是相对于同一个SqlSession而言

一级缓存
  1. 二级缓存

二级缓存开启需要配置,实现二级缓存,POJO必须是可序列化的,也就是实现Serializable 接口。然后再映射xml文件配置开启。

二级缓存默认是不开启的在config配置文件当中开启
<settings>
    <setting name = "cacheEnabled" value = "true" />
</settings>

还需要在Mapper文件当中使用<cache/>来标识对应的mapper文件开启二级缓存。

<cache eviction="LRU"
 flushInterval="60000"
 readOnly="true"
 size="1024"
/>

eviction 表示回收策略:

  • LRU:最近最少使用的:移除最长时间不被使用的对象
  • FIFO:先进先出:按对象进入缓存的顺序来移除它们
  • SOFT:软引用:移除基于垃圾回收器状态和软引用规则的对象
  • WEAK:弱引用 更积极地移除基于垃圾收集器状态和弱引用规则的对象。

flushInterval:刷新间隔,单位为毫秒。

size:引用数目。可以被设置为任意正整数,设置过大容易导致内存移除默认1024.

readonly:true 只读得缓存会给所有调用者返回缓存对象得相同示例。因此这些数据不能被修改。可读写得缓存会返回缓存对象得拷贝。这会慢,但是安全。


二级缓存执行流程

开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,会先CachingExecutor进行二级缓存的查询。二级缓存开启后,同一个namespace下面的所有操作预计,都影响着同一个Cache。即二级缓存被多个sqlsession共享,是一个全局变量

  1. 自定义缓存

使用第三方缓存(redis),需要实现Mybatis为我们提供的Cache.代码如下:

public interface Cache {
  //获取缓存编号
  String getId();
  //获取缓存对象大小
  int getSize();
  //保存key值缓存对象
  void putObject(Object key, Object value);
  //通过key获取缓存对象
  Object getObject(Object key);
  //通过key删除缓存对象
  Object removeObject(Object key);
  //清空缓存
  void clear();
}

MyBatis的解析和运行原理

Mapper的接口实现方式?

采用动态代理方式实现。jdk的proxy.

构建SqlSessionFactory过程
  1. 通过XMLConfigBuilder解析配置的XML文件,读出配置参数,并将读取的数据存入这个Configuration累中。
  2. 使用Configuration对象去创建SqlSessionFactory。
构建Configuration
  • 读入配置文件,包括基础配置的XML文件和映射器的XML文件
  • 初始化配置基础,比如MyBatis的别名等,一些重要的类对象。
  • 提工单例,为后续创建SessionFactory服务并提供配置的参数
  • 执行一些重要的对象方法,初始化配置信息
映射器的内部组成

一般情况下,映射器是由三个部分组成:

  • MappedStatemnt,保存映射器的一个节点.SQL\SQLID\等配置内容
  • SqlSource 提供BoundSql对象的地方。它是MappedStatement的一个属性
  • BoundSql,它是建立SQL和参数的地方。有三个对应的属性:SQL、parameterObject、parameterMappings。(parameterObject为参数本身,如果使用@Param注解,那么Mybatis会把parameterObject也会变成一个Map<String,Object>对象)

SqlSession运行过程

映射器的动态代理

Mybatis通过动态代理技术+命令模式+SqlSession接口的方法使它执行查询。

SqlSession下的四大对象
  1. Executor代表执行器,用于调度StatementHandler,ParameterHandler,ResultHandler来执行SQL
  2. StatementHandler的作用是使用数据库的Statement执行操作。它是四大对象核心,起到呈上启下的作用。
  3. ParameterHandler 用于SQL对参数的处理
  4. ResultHandler是进行最后数据集的封装返回处理的。


    SqlSession执行流程
  • 执行器
    真正执行Java和数据库交互的东西。在Mybatis中存在三种执行器
  • Simple就是普通的执行器。
  • REUSE执行器会重用预处理语句
  • BATCH执行器将重用语句并执行批量更新。
  • 数据库会话器
  • 数据库会话器就是专门处理数据库会话的。
  • Executor会先调用StatementHandler的prepare()方法预编译sql语句,同事设置一些基本运行参数。然后用parameterize()方法启用parameterHandler设置参数,完成预编译,而update()也是这样的,最后如果需要查询,就采用ResultSetHandler封装结果返回给调用者
  • 参数处理器
  • Mybatis是通过ParameterHandler对预编译语句进行参数设置。它的作用是完成对预编译参数的设置
  • 结果处理器
  • ResultSetHandler是结果处理器的接口定义。


    image.png

Sqlsession是通过Executor创建StatementHandler来运行的,而StatementHandler要经过下面三步。

  • prepared:预编译SQL
  • parameterize: 设置参数
  • query/update:执行sql

其中parameterize 是调用parameterHandler的方法去设置,而参数是根据类型处理器typeHandler去处理的。query/update方法是通过resultHandler进行处理结果的封装,如果是update语句,它就返回证书,否则它就通过typeHandler处理结果类型,然后用ObjectFactory提供的规则组装对象,返回给调用者。

插件

插件接口

在Mybatis中使用插件,我们就必须实现inteceptor,让我们先看看它的定义和各个方法的含义。

  • intecept方法:它将直接覆盖你所拦截对象原有的方法,因此它是插件的和新方法。intecept里面的Invocation对象,通过它可以反射调度原来对象的方法。
  • plugin方法:target是被拦截的对象,它的作用是返回一个代理对象。
  • setProperties:允许plugin元素中配置所需参数,方法在插件初始化的时候调用一次,然后把对象插入到配置中,再取出。template模式
插件的初始化

插件的初始化是在Mybatis初始化的时候完成的。Mybatis上下文初始化过程中,就可以读入插件节点和我们配置的参数,同时使用反射技术生成对应的插件实例,。所有的插件都是在实例对象的时候就被初始化的。

插件的代理和反射设计

插件用的是责任链模式。责任链就是一个对象处于Mybatis四大对象当中的一个,在做个角色传递。处在传递链上的任何角色都有处理它的机会。

MyBatis的责任链是由inteceptorChain去定义的。

MyBatis中提供了一个常用的工具类,用来生成代理对象,它便是plugin类。Plugin实现了InvocationHandler接口,采用的是JDK动态代理。

MetaObject

MetaObject这个工具类可以用于有效读取或者修改一些重要对象的属性,在MyBatis中,四大对象给我们提供的public级别设置参数的方法很少。常用得三个方法

  • MetaObject forObject方法用于包装对象。这个方法已经不再使用,Mybatis为我们提供得SystemMetaObject.for(Object)
  • Object getValue(String name)用于获取对象属性值,支持OGNL
  • void setValue(String name,Object value)用于修改对象得属性值,支持OGNL.

在Mybatis对象中大量使用了这个类进行包装,包括四大对象,使得我们可以通过它来给四大对象的某些属性赋值满足我们的需要。

插件开发

正如mybatis插件可以拦截四大对象任意一个一样。从Plugin源码中我们可以看到需要注册签名才可以运行。签名需要一些要素

  • 确定需要拦截的对象
    • Executor是执行sql的全过程,包括组装参数,组装结果返回和执行sql执行过程。
    • StatementHandler是执行SQL过程,我们可以重写执行SQL过程。
    • ParameterHandler 组要拦截执行SQL的参数组装。
    • ResultHandler用于拦截执行结果的组装。

我们清楚需要拦截得是StatementHandler对象,应该在预编译SQL之前,修改SQL使得结果返回数量被限制。

@Intercepts(
        {
                @Signature(type = StatementHandler.class,//确定要拦截的对象
                        method = "prepare",//确定要拦截的方法
                        args = {Connection.class}//拦截方法的参数
                        )
        }
)
public class QueryLimitPlugin implements Interceptor{}

该插件拦截StatementHandler类当中的method方法,在mybatis-config,xml配置对应的插件地址

<plugins>
    <plugin interceptor="QueryLimitPlugin">
    </plugin>
</plugins>  
总结:
  1. 能不用插件尽量不用插件,因为会涉及到底层修改
  2. 插件生成的是层层代理对象的责任链模式,通过反射方法运行,效率不搞。
  3. 编写插件需要拦截mybatis的运行原理,了解四大对象及其方法的作用,准确判断需要拦截什么对象
  4. 在插件中否需要读取和修改mybatis映射器的属性,需要熟练掌握mybatis映射器内部组成的知识
  5. 插件代码要考虑全面特别是多个插件层层代理的时候,要保证逻辑正确
  6. 尽量少改动底层,避免发生错误

相关文章

网友评论

    本文标题:Mybatis源码解读

    本文链接:https://www.haomeiwen.com/subject/hdriultx.html