MyBatis 源码解析 - MyBatis 入门

种一棵树最好的时间是十年前,其次是现在;

一个Java开发的渣渣,工作两年回头才发现自己什么都是会一点;

总结总结不做,说话说话不会。从现在开始从基础开始扎实学习。学习之后一定要求自己总结复盘,这样才有成长。


ORM 简介

面向对象程序设计(OOP - Object Oriented Programming)是企业级开发常用的设计方式。

我们常用的语言,如Java、.Net、C++ 等,是面向对象的编程语言。

数据库产品,如MySql、Oracle等,则都是关系型数据库。

在系统开发的过程中,则需要使用面向对象的思维实现业务逻辑,但设计数据库表或操作数据库记录时,则需要通过关系型的思维方式考虑问题。所以在应用程序与关系型数据库之间进行交互时,数据在对象和关系结构中的表、列、字段等之间进行转换。

JDBC(Java Database Connectivity) 是 Java 与数据库交互的统一 API,实际上分为两组 API,一组是面向应用程序开发人员的API,是一个标准的Java API 独立于各个厂家的数据库实现。另一组就是面向数据库驱动程序开发人员的API,是用于编写数据库驱动为应用程序开发提供支持。

在实际开发Java 系统时,我们可以通过JDBC 完成多种数据库操作。这里以传统 JDBC 编程中的查询操作为例进行说明,其主要步骤如下:

(1)注册数据库驱动类,明确指定数据库URL 地址、数据库用户名、密码等连接信息。

(2)通过 DriverManager 打开数据库连接。

(3)通过数据库连接创建 Statement 对象。

(4)通过 Statement 对象执行SQL 语句,得到ResultSet 对象。

(5)通过 ResultSet 读取数据,并将数据转换成JavaBean 对象。

(6)关闭 ResultSet 、Statement 对象以及数据库连接,释放相关资源。

public class JdbcHelloWord {

    public static void main(String[] args) throws Exception {
        //定义一个MYSQL链接对象
        Connection con = null;
        // MYSQL驱动
        Class.forName("com.mysql.jdbc.Driver").newInstance();
        // 注册数据库驱动类,明确指定数据库URL 地址、数据库用户名、密码等连接信息。
        // 通过 DriverManager 打开数据库连接。
        con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test_self?useUnicode=true", "root", "1234");

        // 更新一条数据
        String updateSql = "UPDATE user SET age = '25' WHERE user_id = ?";
        // 通过数据库连接创建 Statement 对象。
        PreparedStatement pstmt = con.prepareStatement(updateSql);
        pstmt.setString(1, "1");
        // 通过 Statement 对象执行SQL 语句,得到ResultSet 对象。
        long updateRes = pstmt.executeUpdate();
        System.out.print("UPDATE:" + updateRes);

        // 查询数据并输出
        String sql = "select user_id, username, age from user where user_id = ?";
        PreparedStatement pstmt2 = con.prepareStatement(sql);
        pstmt2.setString(1, "1");
        // 通过 Statement 对象执行SQL 语句,得到ResultSet 对象。
        ResultSet rs = pstmt2.executeQuery();
        // 通过 ResultSet 读取数据,并将数据转换成JavaBean 对象。
        while (rs.next()) { //循环输出结果集
            String userId = rs.getString("userId");
            String username = rs.getString("username");
            Integer age = rs.getInt("age");
            System.out.print("\r\n\r\n");
            System.out.print("lfPartyId:" + lfPartyId + "partyName:" + partyName + "age: " + age);
        }
    }
}

其中 步骤1~步骤4 与 步骤 6 在查询语句中都会出现,在增改删语句中也会出现类似的代码,对此可以封装成一个工具类。

步骤 5 是完成关系模型到对象模型的转换,如果使用通用的方式封装是比较困难的。

为了解决该问题,ORM(Object Relational Mapping,对象-关系映射)框架应运而生。如图所示,ORM框架的主要功能就是根据映射配置文件,完成在对象模型与关系模型之间的映射(步骤5),同时也屏蔽了上述重复的代码(步骤1~步骤4 与 步骤 6 ),只暴露简单的 API 供开发人员使用。

ORM框架
ORM 框架

实际生产环境中对系统的性能是有一定要求的,数据库作为系统中比较珍贵的资源,极易成为整个系统的性能瓶颈,所以我们不能像上述JDBC 操作那样简单粗暴地直接访问数据库、直接关闭数据库连接。应用程序一般需要通过集成缓存、数据源、数据库连接池等组件进行优化,如果没有ORM 框架的存在,就要求开发人员熟悉相关组件的API 井手动编写集成相关的代码,这就提高了开发难度并延长了开发周期。

 

持久层框架

  • Hibernate
  • JPA(Java Persistence API)
  • Spring JDBC
  • MyBatis

MyBatis

与其他持久层框架一样

1、可以屏蔽底层重复性的原生 JDBC 代码。

2、通过配置文件或注解将ResultSet 映射为 Java对象,其映射规则可以嵌套其他映射规则以及子查询,从而实现复杂的映射逻辑,可以实现一对一,一对多,多对多映射与双线映射。

优点:

相较于Hibernate, MyBatis 更加轻量级,可控性也更高,在使用MyBatis 时我们直接在映射配置文件中编写待执行的原生SQL 语句,这就给了我们直接优化SQL 语句的机会,让SQL语句选择合适的索引,能更好地提高系统的性能,比较适合大数据量、高并发等场景。在编写SQL 语句时,我们也可以比较方便地指定查询返回的列,而不是查询所有列并映射对象后返回,这在列比较多的时候也能起到一定的优化效果。

对同一数据集的查询条件可能是动态变化的,如果读者有使用 JDBC 或其他类似框架的经历就能体会到,根据不同条件拼接SQL 语句是一件非常麻烦的事情,尤其是拼接过程中要确保在合适的位置添加“ where ”、“ and ”、“ in ”等SQL 语句的关键字以及空格、逗号、等号等分隔符,而且这个拼接过程非常枯燥、没有技术含量,可能经过反复调试才能得到一个可执行的SQL 语句。MyBatis 提供了强大的动态SQL 功能来帮助开发人员摆脱这种窘境,开发人员只需要在映射配置文件中编写好动态SQL 语句, MyBatis 就可以根据执行时传入的实际参数值拼凑出完整的、可执行的SQL 语句。

比较:

从性能角度来看:Hibernate 生成的SQL 语句难以优化, Spring JDBC 和 MyBatis 直接使用原生SQL 语句,优化空间比较大,MyBatis 和 Hibernate 有设计良好的缓存机制, 三者都可以与第三方数据源配合使用:

从可移植性角度来看:Hibernate 帮助开发人员屏蔽了底层数据库方言,而Spring JDBC 和MyBatis 在该方面没有做很好的支持,但实践中很少有项目会来回切换底层使用的数据库产品,所以这点并不是特别重要;

从开发效率的角度来看:Hibernate 和 MyBatis 都提供了XML 映射配置文件和注解两种方式实现映射, Spring JDBC 则是通过ORM 化的Callback 的方式进行映射。读者在进行技术选型时,可以从更多角度进行比较,权衡性能、可扩展性、开发人员技术枝等多个方面选择合适的框架。

 

MyBatis 整体架构

MyBatis 的整体架构分为三层, 分别是基础支持层、核心处理层和接口层,如图:

mybatis整体架构图

基础支持层

  • 反射模块

Java 中的反射虽然功能强大,但对大多数开发人员来说,写出高质量的反射代码还是有一定难度的。MyBatis 中专门提供了反射模块,该模块对Java 原生的反射进行了良好的封装,提供了更加简洁易用的API ,方便上层使调用,并且对反射操作进行了一系列优化,例如缓存了类的元数据,提高了反射操作的性能。

  • 类型转换模块

MyBatis 为简化配置文件提供了别名机制, 该机制是类型转换模块的主要功能之一。类型转换模块的另一个功能是实现JDBC 类型与Java 类型之间的转换,该功能在为SQL 语句绑定实参以及映射查询结果集时都会涉及。在为SQL 语句绑定实参时, 会将数据由Java 类型转换成JDBC 类型;而在映射结果集时,会将数据由JDBC 类型转换成 Java 类型。

  • 日志模块

无论在开发测试环境中,还是在线上生产环境中,日志在整个系统中的地位都是非常重要的。良好的日志功能可以帮助开发人员和测试人员快速定位Bug 代码,也可以帮助运维人员快速定位性能瓶颈、等问题。目前的Java 世界中存在很多优秀的日志框架,例如Log4j 、Log4j2, slf4j 等。MyBatis 作为一个设计优良的框架,除了提供详细的日志输出信息,还要能够集成多种日志框架,其日志模块的一个主要功能就是集成第三方日志框架。

  • 资源加载模块

资源加载模块主要是对类加载器进行封装,确定类加载器的使用顺序,并提供了加载类文件以及其他资源文件的功能。

  • 解析器模块

解析器模块的主要提供了两个功能: 一个功能是对XPath 进行封装,为MyBatis 初始化时解析mybatis-config.xml 配置文件以及映射配置文件提供支持;另一个功能是为处理动态SQL 语句中的占位符提供支持。

  • 数据源模块

数据源是实际开发中常用的组件之一。现在开源的数据源都提供了比较丰富的功能,例如,连接池功能、检测连接状态等,选择性能优秀的数据源组件对于提升ORM 框架乃至整个应用的性能都是非常重要的。MyBatis 自身提供了相应的数据源实现,当然MyBatis 也提供了与第三方数据源集成的接口,这些功能都位于数据源模块之中。

  • 事务管理

MyBatis 对数据库中的事务进行了抽象,其自身提供了相应的事务接口和简单实现。在很多场景中, MyBatis 会与Spring 框架集成,并由Spring 框架管理事务;

  • 缓存模块

在优化系统性能时,优化数据库性能是非常重要的一个环节,而添加缓存则是优化数据库时最有效的手段之一。正确、合理地使用缓存可以将一部分数据库请求拦截在缓存这一层,这就能够减少相当一部分数据库的压力。

MyBatis 中提供了一级缓存和二级缓存,而这两级缓存都是依赖于基础支持层中的缓存模块实现的。这里需要读者注意的是, MyBatis 中自带的这两级缓存与MyBatis 以及整个应用是运行在同一个JVM中的,共享同一块堆内存。如果这两级缓存中的数据量较大, 则可能影响系统中其他功能的运行,所以当需要缓存大量数据时,优先考虑使用Redis 、Memcache 等缓存产品。

  • Binding 模块

通过前面的示例我们知道,在调用SqI Session 相应方法执行数据库操作时,需要指定映射文件中定义的SQL 节点,如果出现拼写错误,我们只能在运行时才能发现相应的异常。为了尽早发现这种错误, MyBatis 通过Binding 模块将用户自定义的Mapper 接口与映射配置文件关联起来,系统可以通过调用自定义Mapper 接口中的方法执行相应的SQL 语句完成数据库操作,从而避免上述问题。

核心处理层

在核心处理层中实现了MyBatis 的核心处理流程,其中包括 MyBatis 的初始化以及完成一次数据库操作的涉及的全部流程。

  • 配置解析

在MyBatis 初始化过程中,会加载 mybatis-config.xml 配置文件、映射配置文件以及 Mapper 接口中的注解信息,解析后的配置信息会形成相应的对象并保存到 Configuration 对象中。例如,示例中定义的<resultMap>节点(即ResultSet 的映射规则)会被解析成 ResultMap 对象:示例中定义的<result> 节点(即属性映射)会被解析成 ResultMapping 对象。之后利用该Configuration 对象创建SqlSessionFactory 对象。待My Batis 初始化之后,开发人员可以通过初始化得到SqlSessionFactory 创建 SqlSession 对象并完成数据库操作。

  • SQL解析与scripting 模块

拼凑 SQL 语句是一件烦琐且易出错的过程,为了将开发人员从这项枯燥无趣的工作中解脱出来, MyBatis 实现动态SQL 语句的功能,提供了多种动态SQL 语句对应的节点,例如,<where>节点、<if>节点、<foreach>节点等。通过这些节点的组合使用, 开发人员可以写出几乎满足所有需求的动态SQL 语句。

MyBatis 中的 scripting 模块会根据用户传入的实参,解析映射文件中定义的动态SQL节点,并形成数据库可执行的SQL 语句。之后会处理SQL 语句中的占位符,绑定用户传入的实参。

  • SQL 执行

SQL 语句的执行涉及多个组件,其中比较重要的是 Executor 、StatementHandler 、ParameterHandler 和 ResultSetHandler 。

Executor 主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,它会将数据库相关操作委托给StatementHandler 完成。StatementHandler 首先通过 ParameterHandler 完成SQL语句的实参绑定,然后通过 java.sql.Statement 对象执行SQL 语句并得到结果集,最后通过ResultSetHandler 完成结果集的映射,得到结果对象并返回。

  • 插件

插件是MyBatis 提供的扩展机制之一,用户可以通过添加自定义插件在SQL 语句执行过程中的某一点进行拦截。

Mybatis 自身的功能虽然强大,但是并不能完美切合所有的应用场景,因此MyBatis提供了插件接口,我们可以通过添加用户自定义插件的方式对 MyBatis 进行扩展。用户自定义插件也可以改变Mybatis 的默认行为,例如,我们可以拦截SQL 语句并对其进行重写。由于用户自定义插件会影响My Batis 的核心行为,在使用自定义插件之前,开发人员需要了解 MyBatis 内部的原理,这样才能编写出安全、高效的插件。

 

接口层

接口层相对简单,其核心是SqlSession 接口,该接口中定义了MyBatis 暴露给应用程序调用的API ,也就是上层应用与MyBatis 交互的桥梁。接口层在接收到调用请求时,会调用核心处理层的相应模块来完成具体的数据库操作。

 

MyBatis 实例

使用 Mybatis 查询数据库如下:

(1)配置 MyBatis 初始化配置文件(Configuration.xml)与对应的 mapper(UserMapper.xml)文件;

(2)Mapper 对应的 IUserMapper.Java,提供给Java应用调用绑定Mapper.xml 的接口;

(3)使用;

Configuration.xml

<?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>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>
    <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://127.0.0.1:3306/test_self?useUnicode=true"/>
                <property name="username" value="root"/>
                <property name="password" value="1234"/>
            </dataSource>
        </environment>
    </environments>
    <!--使用package自动搜索的模式,这样指定package下所有接口都会被注册为mapper-->
    <!--<mappers>
        <package name="org.mybatis.internal.example"/>
    </mappers>-->
    <mappers>
        <mapper resource="org/mybatis/internal/example/UserMapper.xml"/>
        <!--<mapper url="file:///org/mybatis/internal/example/UserMapper.xml"/>
        <mapper class="org.mybatis.internal.example.mapper.UserMapper"/>-->
    </mappers>
</configuration>

IUserMapper.java

public interface UserMapper {
    User getUser(Integer userId);
    Map findName();
}

 UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.internal.example.mapper.UserMapper">
    <select id="getUser" parameterType="int" resultType="org.mybatis.internal.example.pojo.User">
        select user_id userId, username username, age age from self_user where user_id = #{id}
    </select>
    <select id="findName" statementType="CALLABLE" resultType="java.util.Map">
      {call findName(
        #{name, mode=IN},
        #{email_address, mode=OUT, jdbcType=VARCHAR}
    )}
    </select>
</mapper>

 MybatisHelloWorld.java

public class MybatisHelloWorld {
    public static void main(String[] args) {
        String resource = "org/mybatis/internal/example/Configuration.xml";
        Reader reader;
        try {
            // mybatis 初始化
            reader = Resources.getResourceAsReader(resource);
            SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);

            // 执行 sql 语句
            // 首先要拿到代表JDBC底层连接的一个对象,这在mybatis中的实现就是SqlSession
            // mybatis的事务管理模式分为两种,自动提交和手工提交,DefaultSqlSessionFactory的openSession中重载中,
            // 提供了一个参数用于控制是否自动提交事务, 该参数最终被传递给 java.sql.Connection.setAutoCommit()方法
            // 用于控制是否自动提交事务(默认情况下,连接是自动提交的)
            try (SqlSession session = sqlMapper.openSession(true)) {

                /*
                 * 简单来说,SqlSession就是jdbc连接的代表,openSession()就是获取jdbc连接(当然其背后可能是从jdbc连接池获取);
                 * session中的各种selectXXX方法或者调用mapper的具体方法就是集合了JDBC调用的第3、4、5、6步。
                 *
                 * 根据sql语句使用xml进行维护或者在注解上配置,sql语句执行的入口分为两种:
                 * 第一种,调用 org.apache.ibatis.session.SqlSession 的 crud 方法比如 selectList/selectOne 传递完整的语句id直接执行;
                 * 第二种,先调用SqlSession的getMapper()方法得到mapper接口的一个实现,然后调用具体的方法。
                 * 除非早期,现在实际开发中,我们一般采用这种方式。
                 */
                User user = session.selectOne("org.mybatis.internal.example.mapper.UserMapper.getUser", 1);
                /*
                 * 通过SqlSession.getMapper执行CRUD语句
                 */
                // UserMapper mapper = session.getMapper(UserMapper.class);
                // // 这样当我们应用层执行List users = mapper.getUser(1);的时候,JVM会首先调用 MapperProxy.invoke,如下:
                // User user = mapper.getUser(1);
                System.out.println(user.getUserId() + "," + user.getUsername() + "," + user.getAge());
                // session.commit();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

使用 MyBatis 操作数据就可以使用了。


参考:《MyBatis 技术内幕》

以上是学习MyBatis 技术内幕时的总结,有关于Mybatis的相关总结会继续下去(立flag)如果有误欢迎留言指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值