框架丨MyBatis 入门

前言

SSM 框架中的 M 就是 MyBatis 框架的缩写。那 MyBatis 是什么?

摘自官方文档的介绍
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

简介

MyBatis 本是 Apache 的一个开源项目 iBATIS,2010 年这个项目由 Apache Software Foundation 迁移到了 Google Code,并且改名为 MyBatis,现在的官网是 mybatis.org,Github 仓库是 mybatis/mybatis-3,官方中文文档是 MyBatis 文档 - 简体中文。可以看到现在最新的版本是 3.5.9,所以一般称之为 MyBatis-3

持久化

持久化是将程序数据在持久状态和瞬时状态间转换的机制,即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML 数据文件中等等。例如:JDBC 就是一种持久化机制;文件 IO 也是一种持久化机制。
为什么需要持久化服务呢?这是为了解决内存本身的缺陷带来的不便。
因为内存断电后数据会丢失,但有一些对象是无论如何都不能丢失的,比如银行账号等,遗憾的是,人们还无法保证内存永不掉电。内存过于昂贵,与硬盘、光盘等外存相比,内存的价格要高 2~3 个数量级,而且维持成本也高,至少需要一直供电吧。所以即使对象不需要永久保存,也会因为内存的容量限制不能一直呆在内存中,需要持久化来缓存到外存。

为什么使用 MyBatis

在我们传统的 JDBC 操作中,除了需要自己提供 SQL 外,还必须操作 Connection、Statment、ResultSet,不仅如此,为了访问不同的表,不同字段的数据,我们需要些很多雷同模板化的代码,闲的繁琐又枯燥。
而我们在使用了 MyBatis 之后,只需要提供 SQL 语句就好了,其余的诸如:建立连接、操作 Statment、ResultSet,处理 JDBC 相关异常等等都可以交给 MyBatis 去处理,我们的关注点就可以集中在 SQL 语句上,关注在增删改查这些操作层面上,提高了开发效率。

入门程序

环境搭建

如果要使用 MyBatis,只需下载,然后将 mybatis-x.x.x.jar 文件置于 classpath 中即可。
如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>

既然是查询 MySQL 数据库,那还需要加入对应的驱动 JDBC Driver:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>

数据准备

创建一个数据库 mybatis,然后再创建一个名为 user 的表,插入一些数据。

-- 创建数据库
CREATE DATABASE mybatis DEFAULT CHARACTER SET = 'utf8mb4';
-- 使用
use mybatis;
-- 创建数据表
CREATE Table `user` (
    `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'ID',
    `name` VARCHAR(20) DEFAULT NULL COMMENT '用户名',
    `pwd` VARCHAR(30) DEFAULT NULL COMMENT '密码'
)engine=INNODB DEFAULT charset=utf8mb4;
-- 新增数据
insert into user(name, pwd) VALUES ('张三', '123456');

配置文件

在项目的 resources 目录下新建一个用于存放配置文件的目录 config,再在 config 目录下创建 mysql.properties 文件,其主要作用是提供连接数据库用的驱动,数据名称,编码方式,账号密码等。

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username=root
password=123456

然后创建 MyBaits 的主配置文件 mybatis-config.xml,在其中使用 <properties> 标签引入 mysql.properties 文件,这些属性将会替换对应的值。

<?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">
<!-- 配置 mybatis -->
<configuration>
    <!-- 导入外部配置文件 -->
    <properties resource="config/mysql.properties">
        <!-- 可以设置同名的子属性,但外部配置文件的优先级更高  -->
        <property name="username" value="dev_user"/>
        <property name="password" value="F2Fa3!33TYyg"/>
    </properties>

    <!-- 配置 settings -->
    <settings>
        <!-- 指定日志的具体实现:STDOUT_LOGGING 标准日志输出 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <!-- 配置别名 -->
    <typeAliases>
        <!-- 扫描整个包下,默认使用首字母小写的类名作为别名 -->
        <package name="top.yyt.entity"/>
    </typeAliases>

    <!-- 配置环境 -->
    <environments default="development">
        <!-- 开发环境 -->
        <environment id="development">
            <!-- 事务管理器 -->
            <transactionManager type="JDBC"/>
            <!-- 属性将会由 mysql.properties 文件中对应的值来替换 -->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!-- mappers:映射器,告诉 MyBatis 到哪里去找映射文件 -->
    <mappers>
        <package name="top.yyt.mapper"/>
    </mappers>
</configuration>

编写程序

在项目的 src/main/java 目录下新建对应层次的目录,新建几个包:用于存放实体类的包 entity(也称为 POJO),存放与数据库进行交互的接口的包 mapper(也称为 DAO),存放工具类的包 utils
实体类 User 的属性和数据表 user 的字段一一对应,然后加上有参构造、无参构造方法、属性的 set、get 方法。

public class User {
    private int id;
    private String name;
    private String pwd;

    public User() {
    }

    public User(int id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }
    // 省略 setter 和 getter
    ... 
}

mapper 包中新建一个操作 User 的接口 UserMapper 以及同名的 XML 映射文件 UserMapper.xml

import top.yyt.entity.User;

import java.util.List;
import java.util.Map;

/**
 * @author yyt
 * @date 2021年6月14日 16:23
 */
public interface UserMapper {

    /**
     * 获取所有的 User
     */
    List<User> getAllUsers();

    /**
     * 通过用户名模糊查询用户
     */
    List<User> queryUsersByFuzzyName(String fuzzyName);

    /**
     * 根据用户 id 获取对应的 User
     */
    User getUserById(int id);

    /**
     * 新增一个 User
     */
    int addUser(User user);

    /**
     * 新增一个 User
     */
    int addUserByMap(Map<String, Object> map);

    /**
     * 修改 User 信息
     */
    int updateUser(User user);

    /**
     * 根据用户 id 删除一个 User
     */
    int deleteUserById(int id);
}

对于 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 接口 -->
<mapper namespace="top.yyt.mapper.UserMapper">
    <!-- 结果映射 -->
    <resultMap id="userMap" type="user">
        <!-- property 是实体类的属性名,column 是数据表中的字段名 -->
        <id property="id" column="id"/>
        <!-- 如果都一致,就没必要写了,只需要映射不一致的 -->
        <result property="name" column="name"/>
        <result property="pwd" column="pwd"/>
    </resultMap>

    <!-- 新增方法 -->
    <insert id="addUser" parameterType="user">
        insert into user(name, pwd)
        VALUES (#{name}, #{pwd});
    </insert>

    <!-- 注意传入的 map 中需要有对应的参数 -->
    <insert id="addUserByMap" parameterType="map">
        insert into user(name, pwd)
        VALUES (#{userName}, #{password});
    </insert>

    <!-- 修改方法 -->
    <update id="updateUser" parameterType="user">
        update user
        set pwd = #{pwd}
        where id = #{id};
    </update>

    <!-- 删除方法 -->
    <delete id="deleteUserById" parameterType="int">
        delete
        from user
        where id = #{id};
    </delete>

    <!-- 查询方法 -->
    <select id="getAllUsers" resultType="user">
        select *
        from user
    </select>

    <select id="getUserById" resultType="user" parameterType="int">
        select *
        from user
        where id = #{id}
    </select>

    <select id="queryUsersByFuzzyName" resultType="user" parameterType="java.lang.String">
        select *
        from user
        where name like #{fuzzyName}
    </select>
</mapper>

工具包 utils 中新建一个工具类 MyBatisUtil,用于构建 SqlSessionFactory,从 SqlSessionFactory 中获取 SqlSession。

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;

public class MyBatisUtil {

    private static SqlSessionFactory sqlSessionFactory;

    // 获取 SqlSessionFactory 的实例
    static {
        try {
            String resource = "config/mybatis-config.xml";
            // //用输入流读取 XML 核心配置文件
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 从 SqlSessionFactory 中获取 SqlSession
     */
    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}

测试运行

编写功能接口后,紧接着编写测试啊。JUnit 是流行的 Java 单元测试框架之一,现在最新版本是 5,但是 JUnit 5 和 JUnit 4 的区别比较大,为了方便,引入 JUnit 4 依赖:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>

在项目的 test 目录下创建对应 java 目录的包,在其中新建一个测试类 UserDaoTest

import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import top.yyt.entity.User;
import top.yyt.mapper.UserMapper;
import top.yyt.utils.MyBatisUtil;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class UserDaoTest {

    @Test
    public void test() {
        // try-with-resources 确保每次都能执行 SqlSession 关闭操作
        try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            User newUser = new User();
            newUser.setName("靓仔");
            newUser.setPwd("20220102");
            int result = mapper.addUser(newUser);
            System.out.println("插入结果:" + result);
            // 增删改,凡是对数据有修改的,都需要提交事务
            sqlSession.commit();
            List<User> allUsers = mapper.getAllUsers();
            for (User user : allUsers) {
                System.out.println(user);
            }
        }
    }
}

排错分析

错误1

如果出现以下错误:

org.apache.ibatis.binding.BindingException: Type interface top.yyt.dao.UserDao is not known to the MapperRegistry

这是没有告诉 MyBatis 到哪里去找 XML 映射文件。解决方案是:在 mybatis-config.xml 中加入 <mappers> 标签,然后使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等把映射文件加载进来。

<!-- 配置映射器 -->
<mappers>
    <!-- 使用相对于类路径的资源引用 -->
    <mapper resource="top/yyt/mapper/UserMapper.xml"/>
</mappers>

错误2

如果出现以下错误:

Caused by: java.io.IOException: Could not find resource top/yyt/mapper/UserMapper.xml

那就是加载不了对应的 XML 映射文件。你可以检查在生成的文件夹 target 中根本没有对应的 mapper 文件夹。解决方案是:在 Maven 的配置文件 pom.xml 中加入对资源的过滤处理。

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

错误3

如果出现以下错误:

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): top.yyt.dao.UserDao.getAllUsers

这是 DAO 接口与 mapper.xml 配置文件在做映射绑定的时候出现问题,简单说,就是接口与 XML 文件要么是找不到,要么是找到了却匹配不到。解决方案是:确保接口名与 Mybatis 的映射文件 mapper.xml 的文件名完全一致(使用扫描包的方式也需要);其次,两个文件必须在同一个包中,也就是路径完全一致

参考

W3Cschool - MyBatis教程
知乎专栏
打赏
评论区
头像
文章目录