Mybatis源码详解系列(一)–持久层框架解决了什么及如何使用Mybatis

本文是 Mybatis 源码解析系列的第一篇,介绍了 Mybatis 作为持久层框架如何解决 JDBC 代码繁琐、SQL 与程序耦合度高的问题。通过对比 JDBC 和 Mybatis 查询员工的例子,展示了 Mybatis 如何通过高级封装与 SQL 解耦来简化开发。此外,文章还涵盖了 Mybatis 的基本使用,包括配置、Mapper XML 文件的编写、SqlSession 的获取、Repository 的编写和测试。后续文章将深入探讨 Mybatis 的高级特性,如条件查询、关联查询和分页查询。
摘要由CSDN通过智能技术生成

简介

Mybatis 是一个持久层框架,它对 JDBC 进行了高级封装,使我们的代码中不会出现任何的 JDBC 代码,另外,它还通过 xml 或注解的方式将 sql 从 DAO/Repository 层中解耦出来,除了这些基本功能外,它还提供了动态 sql、延迟加载、缓存等功能。 相比 Hibernate,Mybatis 更面向数据库,可以灵活地对 sql 语句进行优化。

针对 Mybatis 的分析,我会拆分成使用、配置、源码、生成器等部分,都放在 Mybatis 这个系列里,内容将持续更新。本文是这个系列的第一篇文章,将从以下两个问题展开 :

  1. 持久层框架解决了哪些问题?

  2. 如何使用 Mybatis(这里会从入门到深入)?

项目环境的说明

为了更好地分析 Mybatis 的特性,本项目不会引入任何的依赖注入框架,将使用比较原生态的方式来使用 Mybatis。

工程环境

JDK:1.8.0_231

maven:3.6.1

IDE:Spring Tool Suites4 for Eclipse 4.12 (装有 Mybatipse 插件)

mysql:5.7.28

依赖引入

Mybatis 有自带的连接池,但实际项目中建议还是引入第三方的比较好。

        <!-- Mybatis -->
        <dependency>
            <groupId>org.Mybatis</groupId>
            <artifactId>Mybatis</artifactId>
            <version>3.5.4</version>
        </dependency>
        <!-- mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version>
        </dependency>
        <!-- logback -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
            <type>jar</type>
        </dependency>
        <!-- 连接池 -->
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>2.6.1</version>
        </dependency>
        <!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

数据库脚本

在这个项目里面,我希望尽可能地模拟出实际项目的各种场景,例如,高级条件查询、关联查询(一对一关联、多对多关联和自关联),并研究在对应场景下如何使用 Mybatis 解决问题。本项目的 ER 图如下,涉及到 4 张主表和 2 张中间表,具体的 sql 脚本也提供好了([脚本路径]( https://github.com/ZhangZiSheng001/mybatis-projects /sql)):

持久层框架解决了哪些问题

在分析如何使用 mybatis 之前,我们先来研究一个问题:持久层框架解决了哪些问题?

假设没有持久层框架,首先想到的就是使用 JDBC 来操作数据库。这里我简单地引入一个需求,就是我想通过 id 查询出一个员工对象。下面仅会从repository/DAO 层的角度来考虑如何实现,所以我们不需要去考虑 service 层中事务提交连接关闭的问题,当然,这样会遇到一个问题,就是我们必须保证 service 层的事务和持久层的是同一个,这一点会通过 **Utils 来解决,因为不是本文重点,这里不展开。

用 JDBC 方式查询一个员工

下面用 JDBC 查询员工对象。

有人可能会问,就算你不适用持久层框架,你还可以使用 DBUtils 或者自己封装 JDBC 代码啊?这里需要强调下,这种封装其实是持久层框架应该做的事,我们自己手动封装,其实已经在实现一个持久层框架了。所以,为了暴露纯粹的 JDBC 实现的缺点,这里尽量不去封装。

    @Override
    public Employee get(String id) throws SQLException {
        Employee employee = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        
        // 创建sql
        String sql = "select * from demo_employee where id = ?";
        try {
            // 获得连接(JDBCUtils保证同一线程获得同一个连接对象)
            Connection connection = JDBCUtils.getConnection();
            // 获得Statement对象
            statement = connection.prepareStatement(sql);

            // 设置参数
            statement.setObject(1, id);

            // 执行,获取结果集
            resultSet = statement.executeQuery();

            if(resultSet.next()) {
                // 映射结果集
                employee = convert(resultSet);
            }
            // 返回员工对象
            return employee;

        } finally {
            // 释放资源
            JDBCUtils.release(null, statement, resultSet);
        }
    }

    /**
     * <p>通过结果集构造员工对象</p>
     * @author: zzs
     * @date: 2020年3月28日 下午12:20:02
     * @param resultSet
     * @return: Employee
     * @throws SQLException 
     */
    private Employee convert(ResultSet resultSet) throws SQLException {
        Employee employee = new Employee();
        employee.setId(resultSet.getString("id"));
        employee.setName(resultSet.getString("name"));
        employee.setGender(resultSet.getBoolean("gender"));
        employee.setNo(resultSet.getString("no"));
        employee.setAddress(resultSet.getString("address"));
        employee.setDeleted(resultSet.getBoolean("deleted"));
        employee.setDepartmentId(resultSet.getString("department_id"));
        employee.setPassword(resultSet.getString("password"));
        employee.setPhone(resultSet.getString("phone"));
        employee.setStatus(resultSet.getByte("status"));
        employee.setCreate(resultSet.getDate("gmt_create"));
        employee.setModified(resultSet.getDate("gmt_modified"));
        return employee;
    }

通过上面的代码,我们可以看到两个主要的问题:

  1. 每个 Repository/DAO 方法都会出现繁琐、重复的 JDBC 代码
  2. sql 和 DAO/Repository 的程序代码耦合度太高,不能统一管理。这里的 sql 包括了 sql 的定义、参数设置和结果集映射,强调一点,不是说 sql 不能出现在 java 类中,而是说应该从 DAO/Repository 的程序代码中解耦出来,进行集中管理

说到这里,我们可以总结出来,为了项目的方便和解耦,一个基本的持久层框架需要做到:

  1. 对 JDBC 代码进行高级封装,为我们提供更简单的接口
  2. 将 sql 从 DAO/Repository 中解耦出来

Mybatis 作为一个优秀的持久层框架,针对以上问题提供了解决方案,下面我们再看看使用 Mybatis 如何实现上面的需求。

用 Mybatis 方式查询一个员工

还是通过查询员工的例子来说明,代码如下:

    public Employee get(String id) {
        return MybatisUtils.getMapper(EmployeeMapper.class).selectByPrimaryKey(id);
    }  

上面的代码没有出现任何的 JDBC 代码和 sql 代码,因为 Mybatis 对 JDBC 进行了高级封装,并且采用 Mapper 的注解或 xml 文件来统一管理 sql 的定义、参数设置和结果集映射。下面看下 xml 文件的方式:

    <!-- 基础映射表 -->
    <resultMap id="BaseResultMap" type="cn.zzs.mybatis.entity.Employee">
       <result column="id" property="id" javaType="string" jdbcType="VARCHAR"/>
       <result column="department_id" property="departmentId" javaType="string" jdbcType="VARCHAR"/>
       <result column="gmt_create" property="create" javaType="date" jdbcType="TIMESTAMP"/>
       <result column="gmt_modified" property="modified" javaType="date" jdbcType="TIMESTAMP"/>
    </resultMap>
    <!-- 基础字段 -->
    <sql id="Base_Column_List">
        e.id, 
        e.`name`, 
        e.gender, 
        e.no, 
        e.password, 
        e.phone, 
        e.address, 
        e.status, 
        e.deleted, 
        e.department_id, 
        e.gmt_create, 
        e.gmt_modified  
    </sql>
    <!-- 根据id查询 -->
    <select id="selectByPrimaryKey" 
        parameterType="java.lang.String"
        resultMap="BaseResultMap">
        select
            <include refid="Base_Column_List" />
        from 
            demo_employee e 
        where 
            e.id = #{id}
    </select>

针对 sql 解耦的问题,早期的持久层框架都偏向于将 sql 独立在配置文件中,后来才逐渐引入注解的支持,如下是Mybatis 的注解方式(EmployeeMapper 接口):

    @Select("SELECT e.id, e.`name`, e.gender, e.no, e.password, e.phone, e.address, e.status, e.deleted, e.department_id, e.gmt_create, e.gmt_modified FROM demo_employee e WHERE id = #{id}")
    @resultMap("BaseResultMap")
    Employee selectByPrimaryKey(String id);

我认为,正如前面说到的,sql 在项目中存在形式不是重点,我们的目的是希望 sql 能被统一管理,基于这个目的实现的不同方案,都是合理的。

Mybatis 作为一款优秀的持久层框架,除了解决上面的两个基本问题,还为我们提供了懒加载、缓存、动态语句、插件等功能,下文会讲到。

补充

通过上面的内容,我们已经回答了问题:持久层框架解决了哪些问题?这里需要补充一点:

本文只是指出持久层框架的需要解决的基本问题,并没有强调必须使用 Mybatis 或 Hibernate 等通用框架。出于性能方面的考虑,部分开发者可能会采用更轻量的实现,而不是使用流行的通用框架。当然,这也是自己造轮子和使用通用轮子的区别了。

如何使用 Mybatis

本项目会模拟实际开发的各种场景来研究 Mybatis 的使用方法。在我看来,DAO 层只要有以下几个方法,已经可以满足大部分使用需求。在 DAO 层定义大量的*By*方法是非常低级和不负责任的,然而,我接触过许多人都是这么搞的。

public interface IEmployeeRepository {
    // 查询
    Employee get(String id);//根据id查询
    
    List<Employee> list(EmployeeCondition con);//根据条件查询

    long count(EmployeeCondition con);//根据条件查询数量
    
    // 删除
    int delete(EmployeeCondition con);//根据条件删除

    int delete(String id);//根据id删除

    // 新增
    int save(Employee employee);//新增
    
    int save(List<Employee> list);//批量新增

    // 更新
    int update(Employee employee, EmployeeCo
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郭勤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值