Mybatis入门看这篇就够了

本文意在带你完整体验一次 Mybatis 的使用方式,你可以将它当作一篇值得一读的入门文章。但是你看完可能会发现它不仅仅是入门,因为笔者在多处地方尝试分析了一些底层原理以给读者后续学习 Mybatis 建立一定的基础。完整学习 Mybatis 请详见 从0到1学Mybatis

1.创建Maven工程

由于日常开发中,我们多是基于 Maven 来构建项目,因此在正式开始之前必然少不了 Maven 工程的创建。

如果对 Maven 还不了解,请转至 Maven学习 进行查缺补漏。

2.导入Mybatis依赖

要使用 MyBatis,则需将下面的依赖代码置于 pom.xml 文件中:

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

3.SqlSessionFactory与SqlSession

在 MyBatis 中,org.apache.ibatis.session.SqlSessionFactoryorg.apache.ibatis.session.SqlSession 是两个重要的接口,它们是访问数据库的关键组件。

3.1 SqlSessionFactory接口

SqlSessionFactory 是 Mybatis 中的一个重要接口,它主要负责创建和管理 SqlSession 实例。由于 SqlSessionFactory 实例是线程安全的,可以在多个线程之间共享,因此通常将其设计为单例模式。一般来说,每个数据库对应一个 SqlSessionFactory 实例即可。

这样做的原因如下:

  • 资源利用率:创建 SqlSessionFactory 实例需要一定的时间和资源,如加载配置文件、连接数据库等,将其设计为单例可以避免重复创建,从而提高资源利用率。
  • 配置一致性SqlSessionFactory 会包含 Mybatis 的全局配置信息。作为单例,确保了所有 SqlSession 实例使用相同的配置,保证了配置一致性。

3.2 SqlSession接口

SqlSession 是 Mybatis 中的一个核心接口,它代表与数据库的会话。它提供了执行 SQL 操作的方法,如查询、插入、更新和删除。另外,SqlSession 不是线程安全的,因此每个线程都应该拥有自己的 SqlSession 实例,以避免潜在的并发问题。通常情况下,SqlSession 的生命周期较短,用完即刻关闭。

SqlSession 的设计原则如下:

  • 线程不安全SqlSession 实例本身不是线程安全的,这意味着在多个线程同时访问同一个 SqlSession 实例时可能会产生并发问题。设计成线程不安全的原因是为了避免性能开销,因为线程安全通常需要同步机制(加同步锁),这会增加性能消耗。
  • 每个线程一个实例:由于 SqlSession 不是线程安全的,每个线程都应该拥有自己的 SqlSession 实例。这样可以避免潜在的并发问题,如数据不一致、资源竞争等。同时,每个线程拥有自己的 SqlSession 实例也有助于更好地管理事务,因为每个线程可以独立地提交或回滚事务。

OK,总结一下,SqlSessionFactory 负责创建和管理 SqlSession 实例,而 SqlSession 负责与数据库交互,执行 SQL 操作。在使用 Mybatis 时,首先创建一个 SqlSessionFactory 实例,然后通过 SqlSessionFactory 获取 SqlSession 实例,接着通过 SqlSession 执行 SQL 操作。最后,关闭 SqlSession 以释放资源。

4.构建SqlSessionFactory

Mybatis 官方文档给出了两种构建 SqlSessionFactory 的方式,分别是基于 XML 进行构建和基于纯 Java 代码进行构建。但是在日常开发中,一般建议使用基于 XML 的构建方式,下面分别进行介绍。

4.1 基于 XML 进行构建

4.1.1 准备Mybatis配置文件

通过 XML 配置文件创建 SqlSessionFactory 是最常见的创建方式,需要先准备 Mybatis 的 XML 配置文件。

  • 对于配置文件的命名,官方的建议是 mybatis-config.xml,因此我也会默认使用该名字。
  • 对于配置文件的位置,如果是 Maven 项目直接存放在 src/main/resources 目录下即可。

XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)等。

后面会再探讨 XML 配置文件的详细内容,还有很多可以在 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>
    <!-- 引入外部配置文件 -->
    <properties resource="classpath:db.properties"/>
    <!-- 数据库连接信息 -->
    <environments default="development">
        <environment id="development">
            <!--  使用JDBC进行事务管理  -->
            <transactionManager type="JDBC"/>
            <!--  内置数据源(连接池)  -->
            <dataSource type="POOLED">
                <!--  数据库驱动类 -->
                <property name="driver" value="${driver}"/>
                <!--  数据库URL  -->
                <property name="url" value="${url}"/>
                <!--  数据库用户名 -->
                <property name="username" value="${username}"/>
                <!--  数据库密码  -->
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

    <!-- Mapper XML文件路径 -->
    <mappers>
        <mapper resource="org/mybatis/example/StudentMapper.xml"/>
    </mappers>
</configuration> 

上面的配置内容大致如下:

  • <properties>:此元素用于定义一组属性,这些属性可以在整个配置文件中使用。我们可以通过 resourceurl 属性引用外部属性文件。从而使这些属性可以用来替换配置文件中的占位符,以实现参数化配置(例如下面的数据库连接信息)。
  • <environment>:此元素定义了一个环境配置,它包含了事务管理器 <transactionManager> 和数据源 <dataSource>。Mybatis 支持多个环境配置,但每次运行只能选择一个作为活动环境。你可以在 <environments> 元素的 default 属性中指定默认环境。
  • <transactionManager>:此元素定义了事务管理器的类型。Mybatis 支持两种类型的事务管理器:JDBCMANAGEDJDBC 类型使用 JDBC 的 commitrollback 来管理事务;MANAGED 类型将事务管理委托给容器,如 Java EE 容器。
  • <dataSource>:此元素定义了数据源的类型和配置。Mybatis 支持三种类型的数据源:UNPOOLED(非池化数据源)、POOLED(池化数据源)和 JNDI(Java 命名和目录接口数据源)。数据源的具体配置依赖于所选类型,通常包括数据库驱动、URL、用户名和密码等信息。
  • <mapper>:此元素用于指定 Mybatis 的映射文件或映射器接口。映射文件(如StudentMapper.xml)定义了 SQL 语句、参数映射和结果映射等信息;

4.1.2 准备外部属性文件

上面我们通过 properties 元素引入了外部属性文件来指定了连接数据库的四个必要信息,从而可以使用 ${key} 的方式来引用对应的值。我们需要在 src/main/resources 目录下创建对应的属性文件(如 db.properties),文件内容对应如下:

# 数据库驱动(8.0以下为com.mysql.jdbc.Driver)
jdbc.className=com.mysql.cj.jdbc.Driver
# 数据库URL(8.0以上需加上时区参数)
jdbc.url=jdbc:mysql://hostname:port/database?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
# 数据库用户名
jdbc.username=username
# 数据库密码
jdbc.password=yourpassword

请根据实际请款进行下列修改:

  1. 请将 jdbc.url 中的 hostname 部分对应修改为你的数据库对应的 IP 地址,如果是本地数据库则使用 localhost 即可。同时将 port 部分修改为你的数据库对应的端口号,mysql 默认端口号为 3306。最后,请将 databases 部分修改为你想要连接的具体数据库名即可。

上面默认使用的是 mysql 8.0.x 及以上版本,并且在 URL 部分加上了该版本需要的一些参数信息,如时区(serverTimezone=UTC)。如果你使用的 mysql 版本低于 8.0,请务必将 jdbc.className 的值改为 com.mysql.jdbc.Driver

  1. 请将 jdbc.username 值修改为你实际的数据库用户名,如果是本地数据库则可直接修改为 root

  2. 请将 jdbc.password 值修改为你实际的数据库登陆密码。

下面是一个简单范例:

# 数据库驱动(8.0以下为com.mysql.jdbc.Driver)
jdbc.className=com.mysql.cj.jdbc.Driver
# 数据库URL(8.0以上需加上时区参数)
jdbc.url=jdbc:mysql://localhost:3306/db_mybatis?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
# 数据库用户名
jdbc.username=root
# 数据库密码
jdbc.password=123456

4.1.3 准备相关依赖

既然要链接数据库,那么我们固然要在 pom.xml 配置文件中加入相关依赖:

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

为了后面方便进行单元测试,也需要加上相关的 Junit4 依赖:

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

4.1.4 获取SqlSessionFactory对象

在 Mybatis 中,SqlSessionFactoryBuilder 是一个用于创建 SqlSessionFactory 实例的构建器类。它的主要职责是解析上面的 Mybatis 配置文件,根据配置信息创建并返回 SqlSessionFactory 实例。

示例代码如下:

@Test
public void testSqlSessionFactoryByXML() throws IOException {
    
    
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}

上述代码中,使用 Mybatis 提供的 Resources 类获取 mybatis-config.xml 配置文件的输入流,然后将输入流传递给 SqlSessionFactoryBuilderbuild 方法创建 SqlSessionFactory 对象。

接下来我们详细分析一下 SqlSessionFactoryBuilder 的工作原理。

  1. 解析配置文件

    SqlSessionFactoryBuilder 通过解析 XML 配置文件(通常是 mybatis-config.xml)或直接使用 Java 代码配置的方式来构建 SqlSessionFactory。在解析 XML 配置文件的过程中,SqlSessionFactoryBuilder 会使用 XML 解析器(例如,XPath解析器)来解析各种元素(如 propertiesenvironmenttransactionManagerdataSource等)并将这些配置信息存储在 Configuration 对象中。这个 Configuration 对象将作为 SqlSessionFactory 实例的一部分。

  2. 创建SqlSessionFactory实例

    SqlSessionFactoryBuilder 在解析配置文件并生成 Configuration 对象后,会创建一个 DefaultSqlSessionFactory 实例,并将 Configuration 对象传递给它。此时, SqlSessionFactory 实例已经具备了所有必要的配置信息,可以用于创建 SqlSession 实例。

4.1.5 获取SqlSession对象

既然有了 SqlSessionFactory,顾名思义,我们就可以从中获得 SqlSession 的实例。

SqlSession 的作用域通常是方法级别的,每个请求或者方法调用都应该有一个独立的 SqlSession 对象,执行完毕后关闭。它不是线程安全的,因此在多线程环境下需要进行适当的同步处理。

SqlSessionFactory 中拿到 SqlSession 也很简单,示例代码如下:

SqlSession sqlSession = sqlSessionFactory.openSession(true);

上述代码中我们通过调用 SqlSessionFactoryopenSession(true) 方法取得 SqlSession 对象,并传入 true 参数代表获取的是一个自动提交事务的对象。

4.1.6 聊聊SqlSessionFactory的线程安全性

SqlSessionFactory 实例的线程安全如何保证的呢?

SqlSessionFactoryBuilder 本身不保证 SqlSessionFactory 的线程安全。实际上,SqlSessionFactory 的线程安全性主要取决于 DefaultSqlSessionFactory 类的实现。因为 SqlSessionFactory 主要用于创建 SqlSession 实例,而创建 SqlSession 的过程是无状态的,所以在多线程环境下,多个线程可以共享一个 SqlSessionFactory 实例。

所谓“创建 SqlSession 的过程是无状态的”意味着创建 SqlSession 的过程不依赖于任何实例变量或全局变量,这个过程仅仅根据输入参数和配置来生成一个全新的 SqlSession 实例。

在 Mybatis 中,SqlSessionFactory 的实现类(如 DefaultSqlSessionFactory)负责创建 SqlSession 实例。这些实例是独立的,不共享任何状态。每个 SqlSession 实例都有自己独立的事务、执行器和缓存等资源。因此,在多线程环境下,可以确保每个线程获取到的 SqlSession 实例都是独立的。

在创建 SqlSession 实例时,DefaultSqlSessionFactory 不会修改或访问任何实例变量。它仅根据配置信息(存储在 Configuration 对象中)和传递给 openSession 方法的参数来创建一个全新的 SqlSession 实例。这个过程不涉及任何共享状态,因此可以认为是“无状态的”。

这种无状态的特性使得 SqlSessionFactory 可以在多线程环境中安全地创建 SqlSession 实例。因为每个线程获取到的 SqlSession 实例都是独立的,它们不会相互干扰,也不会引发线程安全问题。

需要注意的是,SqlSessionFactoryBuilder 在创建 SqlSessionFactory 实例之后,就不再参与线程之间的共享。通常,开发者会在应用启动时使用 SqlSessionFactoryBuilder 创建一个 SqlSessionFactory 实例,并将此实例在整个应用中共享。因此,SqlSessionFactoryBuilder 不需要考虑线程安全问题。

OK,有了上面的理论部分铺垫,我们来简单看一看 DefaultSqlSessionFactory 类中创建 SqlSession 实例的关键部分源码,以理解其线程安全性。

在 Mybatis 中,DefaultSqlSessionFactory 类的 openSessionFromDataSource() 方法负责创建新的 SqlSession 实例。以下是部分源码:

public class DefaultSqlSessionFactory implements SqlSessionFactory {
    
    
    // Mybatis 配置对象,存储了解析的配置文件信息
    private final Configuration configuration;
	 // 构造函数,接收 Mybatis 配置对象
    public DefaultSqlSessionFactory(Configuration configuration) {
    
    
        this.configuration = configuration;
    }
	 
    // 创建并返回一个默认配置的 SqlSession 对象(不自动提交事务)
    public SqlSession openSession() {
    
    
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }
	
    // 创建并返回一个指定是否自动提交事务的 SqlSession 对象
    public SqlSession openSession(boolean autoCommit) {
    
    
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit);
    }

    // 从数据源中创建并返回一个 SqlSession 对象,其中 execType 为执行器类型,level 为事务隔离级别,autoCommit 为是否自动提交
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    
    
        // 事务对象
        Transaction tx = null;
        // 默认的 SqlSession 对象
        DefaultSqlSession var8;
        // 解析配置文件部分
        try {
    
    
            // 获取 Mybatis 配置对象中的环境对象
            Environment environment = this.configuration.getEnvironment();
            // 根据环境对象获取事务工厂对象
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            // 根据事务工厂对象创建一个事务对象
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            // 根据 Mybatis 配置对象、事务对象、执行器类型创建一个执行器对象
            Executor executor = this.configuration.newExecutor(tx, execType);
            // 创建并返回一个默认的 SqlSession 对象
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
    
    
            // 出现异常时关闭事务
            this.closeTransaction(tx);
            // 抛出异常并包装为 Mybatis 异常
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
        } finally {
    
    
            // 重置错误上下文对象
            ErrorContext.instance().reset();
        }

        // 返回 SqlSession 对象
        return var8;
    }
    
    // ......
}

从源码中可以看出,每次调用 openSessionFromDataSource() 时,都会创建一个新的 TransactionExecutorDefaultSqlSession 实例。由于这些实例都是在方法内部创建的局部变量,它们不会在多个线程间共享。这样,每次调用 openSessionFromDataSource() 时,都会返回一个全新的 SqlSession 实例,而不会共享任何状态。这就确保了 DefaultSqlSessionFactory 在创建 SqlSession 实例时的线程安全性。

需要注意的是,DefaultSqlSessionFactory 类不仅仅包含 openSessionFromDataSource() 方法,还包含一系列重载的 openSession() 方法,上面仅给出了常用的两个,这些方法最终都会调用 openSessionFromDataSource()。因此,通过 DefaultSqlSessionFactory 创建的 SqlSession 实例始终是线程安全的。

简而言之,DefaultSqlSessionFactory 类的线程安全性主要是因为它在创建 SqlSession 实例时没有共享状态。每次创建新的 SqlSession 实例时,都会创建新的 TransactionExecutorDefaultSqlSession 实例,这些实例作为局部变量在方法内部,因此在多个线程之间不会共享。这就确保了在多线程环境下,DefaultSqlSessionFactory 创建 SqlSession 实例的线程安全性。

4.1.7 通过SqlSession实例执行SQL语句

你可以通过拿到的 SqlSession 实例来直接执行已映射的 SQL 语句,该接口提供了一些常用的 CRUD 方法如下:

方法名 描述
selectOne(String statement) 执行查询操作,返回单个结果。参数 statement 是 Mapper XML 文件中定义的语句 ID,返回结果类型可以是任何 Java 类型。
selectList(String statement) 执行查询操作,返回多个结果。参数 statement 是 Mapper XML 文件中定义的语句 ID,返回结果类型可以是任何 Java 类型。
insert(String statement) 执行插入操作,返回插入的记录数。参数 statement 是 Mapper XML 文件中定义的语句 ID。
update(String statement) 执行更新操作,返回更新的记录数。参数 statement 是 Mapper XML 文件中定义的语句 ID。
delete(String statement) 执行删除操作,返回删除的记录数。参数 statement 是 Mapper XML 文件中定义的语句 ID。
select(String statement, Object parameter) 执行查询操作,返回多个结果。参数 statement 是 Mapper XML 文件中定义的语句 ID,parameter 是传递给 SQL 语句的参数对象。
insert(String statement, Object parameter) 执行插入操作,返回插入的记录数。参数 statement 是 Mapper XML 文件中定义的语句 ID,parameter 是传递给 SQL 语句的参数对象。
update(String statement, Object parameter) 执行更新操作,返回更新的记录数。参数 statement 是 Mapper XML 文件中定义的语句 ID,parameter 是传递给 SQL 语句的参数对象。
delete(String statement, Object parameter) 执行删除操作,返回删除的记录数。参数 statement 是 Mapper XML 文件中定义的语句 ID,parameter 是传递给 SQL 语句的参数对象。
commit() 提交事务,将所有未提交的 SQL 语句一起提交到数据库中。
rollback() 回滚事务,撤销所有未提交的 SQL 语句。
close() 关闭 SqlSession 对象,释放相关的资源。

上述方法了解即可,在实际应用中多是使用 Spring 等框架集成 Mybatis 框架,其使用方式与上述方法调用截然不同,此部分仅是为了简单了解 Mybatis 原生用法从而在 Spring 集成 Mybatis 后就更能体会出集成之后的简便性。

大概知道了有哪些常用方法后,你可就以通过 SqlSession 实例来直接执行已映射的 SQL 语句了。例如:

/*
 * @author: JavGo
 * @Time: 2023/3/17  22:54
 */
public class MybatisTest {
    
    
    private SqlSessionFactory sqlSessionFactory;

    @Test
    public void testSqlSession() throws IOException {
    
    
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
        try(SqlSession sqlSession = factory.openSession(true);){
    
    
            Student student = (Student) sqlSession.selectOne("cn.javgo.mapper.StudentMapper.selectById",1);
        }
    }
}

相关的 Mapper 接口与 Mapper XML 映射文件我们还有没有定义,此处先了解即可,稍后即进行补充完善。

诚然,这种方式能够正常工作,对使用旧版本 MyBatis 的用户来说也比较熟悉。但现在有了一种更简洁的方式——使用和指定语句的参数和返回值相匹配的接口(比如 StudentMapper.class),现在你的代码不仅更清晰,更加类型安全,还不用担心可能出错的字符串字面值以及强制类型转换。

例如:

public class MybatisTest {
    
    
    private SqlSessionFactory sqlSessionFactory;

    @Test
    public void testSqlSession() throws IOException {
    
    
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
        try(SqlSession sqlSession = factory.openSession(true);){
    
    
            Student student = (Student) sqlSession.selectOne("cn.javgo.mapper.StudentMapper.selectById",1);
        }
    }
}

OK,我们下面再简单介绍一下如何基于 Java 代码构建 SqlSessionFactory 之后再开始探究一下上面这段代码究竟做了些什么。

4.2 基于Java代码进行构建

除了通过 XML 配置文件创建 SqlSessionFactory 外,还可以通过 Java 代码进行创建。这种方式主要用于动态配置 SqlSessionFactory

一个与上一节的 XML 配置文件等效的 Java 示例代码如下:

@Test
public void testSqlSessionFactoryByJava(){
    
    
    // 1.创建数据源(池化数据源)
    PooledDataSource dataSource = new PooledDataSource();
    dataSource.setDriver("com.mysql.cj.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://hostname:port/database?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8");
    dataSource.setUsername("root");
    dataSource.setPassword("password");

    // 2.创建事务工厂(JDBC管理事务)
    JdbcTransactionFactory transactionFactory = new JdbcTransactionFactory();

    // 3.创建环境(使用数据源、事务工厂作为参数)
    Environment environment = new Environment("development", transactionFactory, dataSource);

    // 4.创建Configuration对象
    Configuration configuration = new Configuration(environment);

    // 5.添加Mapper映射接口(类似于之前的"mapper/StudentMapper.xml")
    configuration.addMapper(StudentMapper.class);
    
    // 6.创建SqlSessionFactory
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(configuration);
}

在上述示例中,首先创建了一个 PooledDataSource 对象,并设置了连接数据库所需要的参数,例如驱动程序、数据库连接URL、用户名和密码等。然后创建了一个 JdbcTransactionFactory 对象作为事务工厂,接着创建了一个 Environment 对象,并将数据源和事务工厂设置到其中。最后,创建了一个 Configuration 对象,并添加了 StudentMapper 类的映射关系。最终通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 对象。

注意该例中,Configuration 添加了一个映射器类(mapper class),也就是上面的 configuration.addMapper(StudentMapper.class)。映射器类是一个 Java 类(准确来说是一个接口),它们包含 SQL 映射注解从而避免依赖 Mapper XML 文件。

由于 Java 注解的一些限制以及某些 MyBatis 映射的复杂性,要使用大多数高级映射(比如:嵌套联合映射),仍然需要使用 XML 配置。有鉴于此,如果存在一个与 Mapper 接口同名的 XML 配置文件,MyBatis 会自动查找并加载它(在这个例子中,基于类路径和 StudentMapper.class 的类名,会加载 StdentMapper.xml 文件)。具体细节稍后讨论。

对此我们现在需要准备对应的 Mapper 接口如下:

/*
 * @author: JavGo
 * @Time: 2023/3/17  23:41
 */
public interface StudentMapper {
    
    
    @Select("select stu_id,stu_name,stu_age from student where stu_id=#{stuId}")
    Student selectById(int stuId);
}

上面我们准备了一个简单的查找方法,通过 Mybatis 提供的 @Select 注解,并写入对应的 SQL 语句。

原生的通过 Mybatis 执行已经映射的 SQL 语句的方法也非常简单,与上面的 XML 方式大同小异。下面给出简单的示例代码:

try(SqlSession sqlSession = sessionFactory.openSession(true)){
    
    
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    Student student = studentMapper.selectById(1);
}

5.探究已映射的 SQL 语句

现在你可能很想知道 SqlSession 和 Mapper 到底具体执行了些什么操作,但 SQL 语句映射是个相当广泛的话题,可能会占去文档的大部分篇幅。 但为了让你能够了解个大概,这里会给出几个例子。

5.1 Mapper 组件介绍

在 MyBatis 中,Mapper 是一个核心组件,它负责将 SQL 语句与 Java 方法进行映射。

MyBatis 提供了多种方式来进行 Mapper 映射的配置。其中,最常用的方式是使用 Mapper XML 文件和注解两种方式。

  • 方式一:使用 Mapper 接口定义CRUD抽象方法 + XML文件定义 SQL 语句。
  • 方式二:使用 Mapper 接口定义CRUD抽象方法 + Mybatis 提供的注解定义 SQL 语句。

由于注解方式不太常用,这里我们主要介绍第一种方式。Mapper 接口通常定义在 Java 中,而 SQL 语句则定义在与 Mapper 接口同名的 XML 文件中,这些 XML 文件通常被称为 Mapper XML 文件。Mapper 接口和 Mapper XML 文件一起工作,将 Java 方法调用转换为 SQL 语句,并将结果转换为 Java 对象或基本类型。

5.2 准备 Mapper 接口

现在,我们先来定义一个 Mapper 接口(以上面提到的 StudentMapper 为例),用于对数据库中的数据表(如 stdent )进行封装 CRUD 方法。为了更加显示地表明我们使用了 Mybatis,我们在这里建议将之前持久层的包名 dao 修改为 mapper,然后在下面创建 Mapper 接口。示例如下:

在 Mapper 接口中定义操作数据库的 CRUD 方法:

/*
 * @author: JavGo
 * @Time: 2023/3/17  23:41
 */
public interface StudentMapper {
    
    
    Student selectById(int stuId);
}

上面定义了一个 selectById 抽象方法,用于通过数据表主键查询对应记录,并期望 Mybatis 将返回的记录自动映射为 Student 类型。

对此,我们应该准备对应的数据库表:

对应的 SQL 语句如下:

CREATE TABLE `student` (
  `stu_id` int NOT NULL AUTO_INCREMENT,
  `stu_name` varchar(100) NOT NULL,
  `stu_age` int NOT NULL,
  PRIMARY KEY (`stu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

还有用于映射的 Student 类:

/*
 * @author: JavGo
 * @Time: 2023/3/18  9:53
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Student {
    
    
    private int stuId;
    private String stuName;
    private int stuAge;
}

上面使用 lombok 简化代码编写,详见 IDEA插件-Lombok

类中定义了用于数据表映射的一些必要属性,这些属性将用于 Mybatis 自动执行对象关系映射(ORM)。

5.3 准备 Mapper XML 映射文件

5.3.1 聊聊 Mapper XML 文件的位置

最后就是准备对应于 Mapper 接口的 Mapper XML 文件,对于该文件的位置在老项目中比较常见的就是直接与持久层接口放在同一目录下,文件名与 Mapper 接口相同。如下:

但是,这样就就会不得不面临一个比较重要的问题。试着编译一下项目,然后来看看编译后的 target/classes 目录下是否有 Mapper XML 文件。

编译后的项目 class 文件信息如下:

可以看到,这里并没有将我们准备的 Mapper XML 文件一同编译进来,那么当你运行项目的时候报错是必然的,因为压根找不到对应的 XML 文件进行映射。这是因为默认情况下,Maven 只会将源代码目录下的 Java 文件编译成 class 文件,而不会处理其他类型的文件,如 XML 文件。

要解决这个问题,通常可以采用以下两种方式之一:

  1. 手动将 Mapper XML 文件复制到 classes 目录下:可以使用构建工具的插件或手动操作的方式,将 Mapper XML 文件复制到编译后的 classes 目录下。(但这样做显然不太聪明的样子)

  2. 在 Maven 的 pom.xml 文件中设置资源不过滤:可以通过在 pom.xml 文件中设置资源不过滤来保留 Mapper XML 文件的原位置,使得项目编译后 Mapper XML 也一同出现在 classes 目录下。

    在 pom.xml 文件中,我们需要使用 <resources> 元素来指定不需要过滤的资源文件,例如:

    <build>
      <resources>
        <resource>
          <!-- 指定不需要过滤的资源文件的目录 -->  
          <directory>src/main/java</directory>
          <!-- 不需要过滤的资源文件的文件类型 -->   
          <includes>
            <include>**/*.xml</include>
          </includes>
          <!-- 避免对该文件进行过滤 -->   
          <filtering>false</filtering>
        </resource>
      </resources>
    </build>
    

    通过这样的配置,Maven 将不会对这些 Mapper XML 文件进行过滤,并将它们保留在原来的位置。

    需要注意的是,这种方式可能会导致编译出来的应用程序体积变大,因为 Mapper XML 文件被包含在了应用程序的 JAR 文件中。如果项目中包含大量的 Mapper XML 文件,这可能会增加应用程序的加载时间和内存占用。

所以,日常开发我们一般将 Mapper XML 资源文件存放在项目的 src/main/resources 资源目录下,如下:

这里补充一下,创建好 Mapper XML 文件后记得对应更新一下 Mybatis 的配置文件,将 Mapper XML 文件路径正确配置进去:

5.3.2 编写 Mapper XML 文件内容

下面,我们正式开始 Mapper XML 文件内容的编写。在 Mybatis 中,Mapper XML 文件用于定义 SQL 语句和映射关系,是 Mybatis 实现数据访问的核心组件之一。我们可以在其中定义 SQL 语句、定义参数映射信息、定义结果集映射关系和模块化 SQL 语句,这些将在后续的内容中逐一展开。

这里给出一个基于 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="cn.javgo.mapper.StudentMapper">

    <!--  定义 SQL 语句  -->
    <select id="selectById" resultMap="studentResultMap">
        select stu_id, stu_name, stu_age
        from student
        where stu_id = #{stuId}
    </select>
    <resultMap id="studentResultMap" type="cn.javgo.pojo.Student">
        <id property="stuId" column="stu_id"/>
        <result property="stuName" column="stu_name"/>
        <result property="stuAge" column="stu_age"/>
    </resultMap>
</mapper>

这便是一个简单的 Mybatis 的 Mapper XML 映射文件。为了这个简单的例子,我们似乎写了不少配置,但其实并不多。在一个 XML 映射文件中,可以定义无数个映射语句(虽然我们这里只定义了一个用于查询的语句),这样一来,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">

以上部分声明了 XML 文档的版本和编码,以及 MyBatis Mapper DTD 的位置,用于验证 XML 文件的结构。

<mapper namespace="cn.javgo.mapper.StudentMapper">
    
</mapper>   

<mapper> 元素定义了一个 MyBatis Mapper 文件,namespace 属性指定了该映射文件所对应的 Java 接口的全限定名(即 Mapper 接口)。这个属性值将用于将 Mapper XML 文件中定义的 SQL 语句与 Java 接口中的方法进行关联。

<!--  定义 SQL 语句  -->
<select id="selectById" resultMap="studentResultMap">
    select stu_id, stu_name, stu_age
    from student
    where stu_id = #{stuId}
</select>

<select> 元素定义了一个查询操作。id 属性指定了该查询操作的唯一标识符,它将与 Java 接口中的方法名匹配。resultMap 属性指定了用于将查询结果映射到 Java 对象的 resultMap 的标识符。在 <select> 元素内部,直接书写了一个 SQL 查询语句。

这里使用了 MyBatis 的占位符语法(#{stuId}),表示将在运行时自动将这个位置替换为方法参数中的值。(后面会详细讨论)

<resultMap id="studentResultMap" type="cn.javgo.pojo.Student">
    <id property="stuId" column="stu_id"/>
    <result property="stuName" column="stu_name"/>
    <result property="stuAge" column="stu_age"/>
</resultMap>

<resultMap> 元素定义了如何将查询结果映射到 Java 对象。id 属性指定了 resultMap 的唯一标识符,type 属性指定了目标 Java 类的全限定名。<resultMap> 元素中的 <id><result> 子元素分别定义了主键字段和普通字段的映射关系。property 属性指定了 Java 类中的属性名,column 属性指定了查询结果集中对应的列名(即数据表中的列名)。

OK,这就是一个简单的 MyBatis Mapper XML 映射文件的基本结构。在这个例子中,我们定义了一个查询操作 selectById,并指定了如何将查询结果映射到 cn.javgo.pojo.Student 类的实例。

5.4 测试程序

相信经过前面的介绍你对 Mybatis 的工作流程已经有了基本的了解,现在我们将进行实际的测试以检测程序的正确性。

5.4.1 准备日志框架

Mybatis 虽然自带了的控制台日志打印功能,但是信息并不全面,我们希望能有一个日志框架能够让我们清楚的了解到程序执行的大致细节,以便我们能够进行简单分析。

日志框架提供了一种记录和管理应用程序运行时信息的方法,它可以帮助开发人员追踪错误、调试代码以及监控程序性能。Log4j 是 Apache 提供的一个流行的 Java 日志框架,它具有灵活、可扩展和高性能的特点。

要在 MyBatis Maven 项目中使用 Log4j 日志框架,需要先在项目的 pom.xml 文件中添加 Log4j 的 Maven 依赖:

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

然后在 MyBatis 的配置文件(如 mybatis-config.xml)中添加如下设置,指定使用 Log4j 作为日志实现:

<!-- 配置 MyBatis 使用 Log4j -->
<settings>
    <setting name="logImpl" value="LOG4J" />
</settings>

请确保它置于正确的位置,否则就会报错,标准位置如下:

对于 Mybatis 配置文件的元素位置顺序,可以直接参考 Mybatis 官方文档的顺序,如果使用过程中提示错误就看看与下面建议的顺序有无差别进行对应调整即可。

接着在项目的 src/main/resources 目录下创建一个名为 log4j.properties 的文件,添加以下配置:

# 设置日志级别
log4j.rootLogger=DEBUG, stdout

# 配置控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

上述配置将 Log4j 的日志级别设置为 DEBUG,并将日志输出到控制台。您可以根据需要修改此配置以满足您的需求。

在大部分教学文章或者视频中可能会建议你使用 log4j.xml 的形式配置日志文件,但是很多时候并不稳定,会出现一些小错误需要进行对应的调整,个人感觉也不是很直观。因此,本文中建议使用 log4j.properties 进行配置。

OK,至此我们就已经在 MyBatis Maven 项目中成功配置并使用了 Log4j 日志框架。

5.4.2 简单了解单元测试

在程序的一开始我们引入了一个 JUnit 单元测试依赖,并未介绍其用法,现在进行简单介绍。

单元测试是一种软件测试方法,用于验证程序中各个独立模块(函数、方法、类等)的功能正确性。通过编写针对各个模块的测试用例,可以确保代码在修改或重构后仍然按预期工作。而 JUnit 是 Java 开发者广泛使用的一个单元测试框架,它提供了一套丰富的注解和断言方法,使得编写和执行单元测试变得更加简单。

要在 Maven 项目中使用 JUnit 进行单元测试,首先得在 pom.xml 文件中添加 JUnit 的 Maven 依赖:

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

注意 scope 属性设置为 test,表示这个依赖仅在测试阶段使用。

然后在项目的 src/test/java 目录下创建一个与要测试的类相对应的测试类。测试类的命名通常遵循 ClassNameTest 的模式。在测试类中编写针对目标类的各个方法的测试用例。

例如,假设你有一个名为 StudentMapper 的类,那么可以创建一个名为 StudentMapperTest 的测试类:

在测试类中,使用 @Test 注解标记要执行的测试方法,每个被标记的方法都可以独立运行。还可以使用 org.junit.Assert 类中提供的断言方法(如 assertEquals)验证方法的输出是否符合预期。

/*
 * @author: JavGo
 * @Time: 2023/3/19  22:53
 */
public class StudentMapperTest {
    
    
    @Test
    public void test01(){
    
    
        System.out.println("test01");
    }
    
    @Test
    public void test02(){
    
    
        System.out.println("test02");
    }
}

编写完测试代码后,我们可以争对性的执行单个方法:

也可以使用 Maven 命令运行全部的测试:

mvn test

执行测试后,Maven 会生成一个测试报告,显示测试的结果、失败的测试用例等信息。

从测试结果可以看到,我们的项目中共运行了 2 个测试用例,其中没有失败(Failures: 0)、错误(Errors: 0)或被跳过的测试(Skipped: 0)。

通过这些步骤,你已经在 Maven 项目中成功配置并使用了 JUnit 进行单元测试。

上面涉及到了 Maven 相关知识,不了解的请详见 Maven学习

5.4.3 编写完整测试程序

现在我们正式开始编写测试程序,看看是否能够顺利拿到数据表的查询信息。

完整测试代码如下:

/*
 * @author: JavGo
 * @Time: 2023/3/19  22:53
 */
public class StudentMapperTest {
    
    
    @Test
    public void testSelectById() throws IOException {
    
    
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try(SqlSession session = sqlSessionFactory.openSession(true)){
    
    
            StudentMapper studentMapper = session.getMapper(StudentMapper.class);
            Student student = studentMapper.selectById(1);
            System.out.println(student);
        }
    }
}

上面的代码出现过多次了,这里就不再重复解释了,很简单,都能看明白。

执行结果如下:

我们通过 log4j 的日志输出可以获取到一下信息:

  1. 23:24:05,999 DEBUG LogFactory:109 - 日志记录初始化,使用的是 log4j 适配器。
  2. 23:24:06,016 DEBUG PooledDataSource:379 - 这四条日志显示 PooledDataSource(连接池)强制关闭/删除了所有连接。
  3. 23:24:06,075 DEBUG JdbcTransaction:143 - 开始打开一个 JDBC 连接。
  4. 23:24:06,197 DEBUG PooledDataSource:454 - 连接池创建了一个新连接,连接的标识为 172678484。
  5. 23:24:06,200 DEBUG selectById:135 - 准备执行 SQL 查询语句:select stu_id, stu_name, stu_age from student where stu_id = ?
  6. 23:24:06,225 DEBUG selectById:135 - SQL 查询的参数设置为:1(Integer 类型)。
  7. 23:24:06,245 DEBUG selectById:135 - SQL 查询结果总数为 1。
  8. 日志输出了查询结果对象:Student(stuId=1, stuName=张三, stuAge=18)
  9. 23:24:06,247 DEBUG JdbcTransaction:97 - 关闭 JDBC 连接(连接对象为:com.mysql.cj.jdbc.ConnectionImpl@a4add54)。
  10. 23:24:06,247 DEBUG PooledDataSource:409 - 将连接 172678484 返回到连接池,而不是正真关闭了数据库连接对象。

5.4.4 总结 Mapper 主要特点

OK,最后我们来通过总结 Mapper 的主要特点结束本文,同时也是对后文内容的简单概述。下面提到的一些内容的具体细节我们将在后面的文章中一一进行详细解释。

Mapper 的主要特点如下:

  1. 接口与映射文件:Mapper 是一个 Java 接口,通常与一个 XML 映射文件(也可以是注解方式)相关联。XML 映射文件定义了 SQL 语句、结果集映射和参数类型等信息。Java 接口中的方法与 XML 映射文件中的 SQL 语句一一对应。
  2. 命名空间:XML 映射文件的根元素 <mapper> 需要有一个 namespace 属性,其值应与对应的 Java 接口的全限定名相匹配。这样,MyBatis 就能够正确地关联接口方法和 XML 中的 SQL 语句。
  3. SQL 语句映射:在 XML 映射文件中,可以定义各种 SQL 语句,如 <select><insert><update><delete>。这些 SQL 语句的 id 属性应与 Java 接口中的方法名相匹配。
  4. 参数映射:MyBatis 支持将 Java 方法的参数传递给 SQL 语句。你可以使用 #{paramName} 的形式在 SQL 语句中引用参数。对于简单类型的单个参数(如 int、String 等),可以直接使用 #{value}。对于多个参数或复杂类型,可以使用 @Param 注解在 Java 方法参数前为参数指定名称。
  5. 结果集映射:MyBatis 支持将 SQL 查询结果映射到 Java 对象。在 XML 映射文件中,可以使用 <resultMap> 元素定义结果集映射规则。你也可以使用 MyBatis 提供的自动映射功能,只需确保查询结果的列名与 Java 对象的属性名相匹配。
  6. 动态 SQL:MyBatis 支持动态生成 SQL 语句。通过在 XML 映射文件中使用 <if><choose><when><otherwise><trim><foreach> 等标签,可以实现条件判断、循环等逻辑,从而生成动态的 SQL 语句。
  7. 集成 MyBatis:为了在应用程序中使用 Mapper,需要将 MyBatis 配置文件(mybatis-config.xml)中的 <mappers> 标签配置为相应的 Mapper 接口或 XML 映射文件。

总之,Mapper 是 MyBatis 中的一个关键组件,它通过 Java 接口与 XML 映射文件(或注解)将 SQL 语句与 Java 方法关联起来。这种做法简化了数据库操作,使得开发人员可以专注于业务逻辑,而不必关心底层的数据库实现细节。

OK,本文到此结束,希望对你有所帮助,从而建立起对 Mybatis 的大体认知。

猜你喜欢

转载自blog.csdn.net/ly1347889755/article/details/129661530