写给新手看的 MyBatis 入门

时间:2022-06-01 14:07:08

最近新接触了 MyBatis,这里主要参考网上各个博客以及官网的文档以及个人的一些理解,讲一下对 MyBatis 实现简单增删改查的方法。

MyBatis 使用前的准备

什么是 MyBatis

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

使用Maven 进行 MyBatis 开发环境搭建

要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于 classpath 中即可。

如果使用 Maven 来构建项目,则需将下面的 dependency 代码置于 pom.xml 文件中:

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

一般来说,在我们开发过程中会使用 IDEA 来开发,我们要搭建一个 MyBatis 和 MySql 的项目,只要简单地 NewProject -> Maven 选择新建一个空的 Maven 项目,然后在 pom.xml 中将上面的 dependcy 丢进去就好,允许右下角 maven 的 enable auto-import ,IDEA 就会帮我们自动下载相关的 jar 包了。作为新手,如果不明白如何使用 Maven,那么这篇文章可能对你作用有限,作为一个广泛使用的工具,强烈建议没接触过的小伙伴去学习一下如何使用。

这里给出一个示例的 pom.xml :

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.studymybatis</groupId>
    <artifactId>MyBatisStudy</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--基本的maven配置就完成了 现在添加使用Mybatis的基本配置-->
    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.3.0</version>
        </dependency>

        <!--其他的基本配置,日志、单元测试、jdbc——jar包-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

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

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.12</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.12</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
            <version>4.1.5</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

</project>

MyBatis 入门

项目整体结构一览

这里我们先通过在 Java 代码中使用 MyBatis 的 demo 来讲解 MyBatis 入门所需的知识。

首先看看整体的项目结构

写给新手看的 MyBatis 入门

使用到的数据库表结构

写给新手看的 MyBatis 入门

下面是一个新建的 MyBatisDemo 类,main 方法中使用了构建好的 MyBatis 来访问数据库:

import entity.User;
import mapper.UserMapper;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;

/**
 * mybatis demo
 *
 * @author Byight
 */
public class MyBatisDemo {
    public static void main(String[] args) {
        /**
         * 第一步:要先获取 SqlSessionFactory, MyBatis 的每一个操作都以 SqlSessionFactory 为核心
         *
         * SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。
         * 而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。
         */
        // 比较推荐的是我们代码中使用的第一种方法;即使用 xml 来构建,如下文使用的代码
        // 设置 mybatis 的 xml 配置文件路径
        String xmlConfigResource = "conf.xml";
        // 使用MyBatis 的 Resources 的工具类,从指定 classpath 或其他位置加载资源文件更加容易
        // 在这里使用 Resources 从指定 classpath 加载了 xml 配置文件
        InputStream resourceAsStream = MyBatisDemo.class.getClassLoader().getResourceAsStream(xmlConfigResource);
        // 构建sqlSession的工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        /**
         * 有了 session 工厂后就可以创建 session 实例,然后使用 session 实例来使用映射器(map)执行 sql 语句了
         * 同样有两种方式,但我们强烈推荐后面的方法
         * 使用正确描述每个语句的参数和返回值的接口(比如 BlogMapper.class)
         * 不仅可以执行更清晰和类型安全的代码,而且还不用担心易错的字符串字面值以及强制类型转换。
         */
        // 不推荐的方法
        /*
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 映射sql的标识字符串
            String statement = "mapper.UserMapper.queryUserById";
            // 执行查询返回一个唯一user对象的sql
            User user = session.selectOne(statement, 2);
            System.out.println(user);
        }
        */
        // 第二种方法有很多优势
        // 首先它不依赖于字符串字面值,会更安全一点;
        // 其次,如果你的 IDE 有代码补全功能,那么代码补全可以帮你快速选择已映射的 SQL 语句。
        try (SqlSession session = sqlSessionFactory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            User user = mapper.queryUserById(2);
            System.out.println(user);
        }
    }
}

上面的代码有很详细的注释。我们以上面的代码为基础,将整个生命周期分为:1.获取 SqlSessionFactory 对象和2.使用 SqlSession 示例及映射器 mapper 执行 Sql 语句这两部分来讲解。

MyBatis 的简单生命周期

1.获取 SqlSessionFactory

对于 MyBatis 的使用,我们首先要知道,MyBatis 的每一个操作都以 SqlSession 为核心:对于 MyBatis ,可以认为它是通过 SqlSession 来执行 Sql 的相关语句。

在 MyBatis 中,我们需要使用 SqlSessionFactory 来获取 SqlSession 的实例,相当于 SqlSession 这种对象的制造工厂,能够通过其方法返回给使用者 SqlSession 的实例。当使用时我们要先构建 一个 SqlSeesionFactory 来获取 SqlSession 对象。

SqlSessionFactory 的实例需要通过 SqlSessionFactoryBuilder 获得。SqlSessionFactoryBuilder 需要给定它 MyBatis 的 xml 配置文件进行解析(或者是一个已经设定好的 Configuration 的实例),这样它能够为我们返回一个按照我们配置要求的 SqlSessionFactory 的实例。

我们有两种方法使用,我们推荐使用 xml 构建:

        // 比较推荐的是我们代码中使用的第一种方法;即使用 xml 来构建,如下文使用的代码
        // 设置 mybatis 的 xml 配置文件路径
        String xmlConfigResource = "conf.xml";
        // 使用MyBatis 的 Resources 的工具类,从指定 classpath 或其他位置加载资源文件更加容易
        // 在这里使用 Resources 从指定 classpath 加载了 xml 配置文件
        InputStream resourceAsStream = MyBatisDemo.class.getClassLoader().getResourceAsStream(xmlConfigResource);

        // 构建sqlSession的工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

1.1 Resource 加载指定的配置文件

在执行前面代码中的MyBatisDemo.class.getClassLoader().getResourceAsStream(xmlConfigResource)`时,Resource的工具类会寻找对应的 xml 配置文件进行加载

这里我们是指定的配置文件是 conf.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//DAO.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
   <!-- 如果在后面的制定mysql属性时直接进行了配置,则用不到这个语句 -->
   <!-- 设置 mysql 配置文件的资源路径 -->
   <properties resource="mysql.properties"/>

   <typeAliases>
       <!-- 别名方式1,一个一个的配置 type中放置的是类的全路径,alias中放置的是类别名
       <typeAliase alias="UserBean" type="com.DAO.demo.Entity.User"/> -->
       <!-- 别名方式2,自动扫描,将JAVA类的首字母小写类名作为类的类别名 -->
       <!--<package name="com.DAO.demo.Entity"/>-->
       <typeAlias alias="User" type="entity.User"/>
   </typeAliases>

   <!-- 配置mybatis运行环境 -->
   <environments default="development">
       <environment id="development">
           <!-- type="JDBC" 代表使用JDBC的提交和回滚来管理事务 -->
           <transactionManager type="JDBC"/>

           <!-- mybatis提供了3种数据源类型,分别是:POOLED,UNPOOLED,JNDI -->
           <!-- POOLED 表示支持JDBC数据源连接池 -->
           <!-- UNPOOLED 表示不支持数据源连接池 -->
           <!-- JNDI 表示支持外部数据源连接池 -->
           <dataSource type="POOLED">
               <!-- 如果这里直接进行配置,那么上面就不需要properties标签了 -->
               <property name="driver" value="${jdbc.driver}"/>
               <property name="url" value="${jdbc.databaseurl}"/>
               <property name="username" value="${jdbc.username}"/>
               <property name="password" value="${jdbc.password}"/>
           </dataSource>
       </environment>
   </environments>

   <mappers>
       <!-- 映射文件方式1,一个一个的配置-->
       <mapper resource="mapper/userMapper.xml"/>
       <!-- 映射文件方式2,自动扫描包内的Mapper接口与配置文件 -->
       <!--<package name="com.DAO.demo"/>-->
   </mappers>

</configuration>

如果对应路径找不到相应的 xml 配置文件的话,getResourceAsStream 方法便会报错,成功的话则会继续执行代码,通过 SqlSessionFactionBuilder 来对配置文件进行解析。

1.2 SqlSessionFactionBuilder 解析 xml 配置文件

SqlSessionFactionBuilder 会自上而下地对 xml 配置文件进行可行性的校验,除了 xml 文件开头固定的两行外,主要是看<configuration>标签中的配置。

自上而下地查看 configuration 的标签体信息,我们会首先看到<properties />标签,它与后面的 enviroments 标签体中的datasource文件相结合来对数据库连接相关属性进行配置。

对于最先看到的mysql.properties,这里也给出了一个示例,大家使用的时候记得替换成自己的信息,这里等号 = 左边的属性名即是和上面 xml 文件的 datasource 的 property 标签中的 value 值:

jdbc.databaseurl=jdbc:mysql://服务器配置地址:默认为3306的数据库端口号/数据库名称?serverTimezone=GMT%2B8
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.username=数据库账号
jdbc.password=数据库密码

Alias 标签体是在后面会提到的 mapper 映射中产生作用,这里不理解也没有关系,知道它是写在 xml 配置文件就好了。

第一次使用 MyBatis 的同学可能会对mappers这个标签产生疑惑,它是指 MyBatis 中一种映射,在入门时我们可以简单理解为是用于执行 Sql 语句的特殊语法,接下去我们对它进行一定的说明。

1.3 SqlSessionFactionBuilder 校验 xml 配置文件中的 mapper 映射文件是否存在

在前面代码的<mappers>标签中,我们可以看到里面有不同的映射文件路径:

    <mappers>
        <!-- 映射文件方式1,一个一个的配置-->
        <mapper resource="mapper/userMapper.xml"/>
        <!-- 映射文件方式2,自动扫描包内的Mapper接口与配置文件 -->
        <!--<package name="com.DAO.demo"/>-->
    </mappers>

xml 配置文件会以项目根目录为基础路径(注意并不是 xml 配置文件的相对路径)来寻找制定的 mapper 映射,其中项目根目录即是 java 和 resources 目录下的文件。

假设这里我们的文件树结构是这样的(这里为了示例将 conf.xml 放在了 mpper 包里,实际上不符合代码规范):

写给新手看的 MyBatis 入门

这里虽然 conf.xml 是在 mapper 目录下而不是根路径,与需要的 userMapper.xml 在同一个包中,但在指定其路径我们仍然需要指定其路径为mapper/userMapper.xml

1.4 校验 mapper 映射文件配置(命名空间)的正确性

我们看一下前面指定的 mapper 映射文件,这里我直接贴出项目中的 userMapper.xml 文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper .//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- 这里是指定 mapper 映射的命名空间,对于入门来说,我们只需要给出 java 文件路径下 mapper 类的包名+类名即可 -->
<mapper namespace="mapper.UserMapper">

    <!-- 在select标签中编写查询的SQL语句 -->
    <!-- 设置 select 标签的id属性为queryUserById,id属性值必须是唯一的,不能重复 -->
    <!-- 使用 parameterType 属性指明查询时使用的参数类型,resultType属性指明查询返回的结果集类型 -->
    <!-- resultType="User"就表示将查询结果封装成一个User类的对象返回,但当前路径中找不到User,
        所以在加载时,会去往加载的 conf 文件中查找对应的 alias 对应的资源路径(type) -->
    <!-- User类就是users表所对应的实体类 -->

    <!--根据id查询得到一个user对象-->
    <select id="queryUserById" parameterType="int" resultType="User">
        SELECT * FROM users WHERE id=#{id}
    </select>

</mapper>

这里我们可以先不用关注<select>标签,前两行的配置是固定的,我们直接粘贴即可,仅需关心 mapper 的 namespace,它会为映射类指定一个命名空间,我们需要保持它的唯一性,这样程序才能总是找到对应的映射接口。对于入门来说,我们只需要给出 java 文件路径下 mapper 类的包名+类名即可。

好了,到这里,如果你这些配置都按照要求的话,那么 SqlSessionFactoryBuilder 就可以正确的解析 xml 配置文件,然后我们就可以通过它的 build 方法获取到 SqlSessionFactory 对象了。


关于 SqlSessionFactoryBuilder 和 SqlSessionFactory 的使用细节

SqlSessionFactoryBuilder

这个类在创建完 SqlSessionFactory 后就不再被需要,因此我们最好仅将其作用域限定在一个方法中(也就是声明为局部变量),这可以让程序的 xml 解析资源不被持续占用。

SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏味道(bad smell)”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式

2.通过 SqlSession 实例和映射器 mapper 执行 sql 语句

我们先贴出代码的第二部分方便进行对照讲解:

有了 session 工厂后就可以创建 session 实例,然后使用 session 实例来使用映射器(map)执行 sql 语句了,和获取 SqlSessionFactory 一样,创建 session 实例也有两种方式;但我们强烈推荐后面的方法:使用正确描述每个语句的参数和返回值的接口(比如 BlogMapper.class), 不仅可以执行更清晰和类型安全的代码,而且还不用担心易错的字符串字面值以及强制类型转换。

        // 1.不推荐的方法
        /*
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 映射sql的标识字符串
            String statement = "mapper.UserMapper.queryUserById";
            // 执行查询返回一个唯一user对象的sql
            User user = session.selectOne(statement, 2);
            System.out.println(user);
        }
        */
        // 第二种方法有很多优势
        // 首先它不依赖于字符串字面值,会更安全一点;
        // 其次,如果你的 IDE 有代码补全功能,那么代码补全可以帮你快速选择已映射的 SQL 语句。
        try (SqlSession session = sqlSessionFactory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            User user = mapper.queryUserById(2);
            System.out.println(user);
        }

如果是初学者的话,可能对上面语句看得不是很明白。

  • 为什么使用 try 语句?

    每个 Sql 操作都需要有它自己的 SqlSession 实例。

    SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的作用域中。 换句话说,每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。 这个关闭操作是很重要的,你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。 上面示例的语句可以确保 SqlSession 在使用后被关闭。

  • try 语句中出现的变量是什么?

    try 语句中便是使用 SqlSession 执行 sql 语句的方法。我们这里先简单的讲一下第一种方法,然后在仔细的讨论一下第二种方法。

2.1 Sql Session 执行映射器 mapper 中定义的 Sql 语句

我们要再看一下之前定义的 Resource 中的映射器 mapper,现在我们便需要关心select标签中出现的东西了,这就是我们自己定义的 Sql 语句了(这里是 Select 也就是查询语句),里面具体的写法放在最后在讲,这里我们先弄明白 MyBatis 语句的执行顺序,以后自己写起来就方便了。

userMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper .//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- 这里是指定 mapper 映射的命名空间,对于入门来说,我们只需要给出 java 文件路径下 mapper 类的包名+类名即可 -->
<mapper namespace="mapper.UserMapper">

    <!-- 在select标签中编写查询的SQL语句 -->
    <!-- 设置 select 标签的id属性为queryUserById,id属性值必须是唯一的,不能重复 -->
    <!-- 使用 parameterType 属性指明查询时使用的参数类型,resultType属性指明查询返回的结果集类型 -->
    <!-- resultType="User"就表示将查询结果封装成一个User类的对象返回,但当前路径中找不到User,
        所以在加载时,会去往加载的 conf 文件中查找对应的 alias 对应的资源路径(type) -->
    <!-- User类就是users表所对应的实体类 -->

    <!--根据id查询得到一个user对象-->
    <select id="queryUserById" parameterType="int" resultType="User">
        SELECT * FROM users WHERE id=#{id}
    </select>

</mapper>

第一种方法中,我们声明了一个 statement 字符串,字符串的内容其实是一个类似于调用方式的指令:

mapper.UserMapper.queryUserById,它会在后面 session 执行 selectOne 方法时(因为 Sql 语句实际上是返回一个记录的 SELECT 语句,其它方法这里不再举例)。实际上就是通过mapper.Usermapper这个字段(注意这个字段和 userMapper.xml 的命名空间相同),告诉 session 在配置文件 conf.xml 的 mapper 标签体中遍历映射器,找到其中命名空间与这个字段相同的映射器 mapper(所以在这里它会找到 userMapper.xml),然后去执行后面的字段queryUserById,也就是寻找到了我们写的 select 标签(因为它的标签 id 是这个),执行其中的 Sql 语句。它只要求前面列出的命名空间是唯一且能找到的,所以上面的第一种方法去掉注释后也可以运行。

接下去我们讲讲第二种方法,这是 MyBatis 官方推荐的写法:

        try (SqlSession session = sqlSessionFactory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            User user = mapper.queryUserById(2);
            System.out.println(user);
        }

同时为了方便理解,这里再次贴出项目的结构图:

写给新手看的 MyBatis 入门

很明显,在 try 语句中首先定义了一个 UserMapper 接口,前面文章有认真看的小伙伴可以发现,前面 userMapper 的命名空间定义就是与此有关,没注意的可以稍微往上看看 userMapper.xml 的代码注释。其实就是相当于将 userMapper.xml 这个映射器和 UserMapper 接口相绑定了,当我们这样使用时,MyBatis 就知道代码应该找到 userMapper.xml 这个映射器来执行 Sql 语句。

既然相互绑定,那么 UserMapper 这个接口要怎么写呢?其实很简单,只要和映射器 userMapper.xml 中的 sql 语句 id 相对应就可以了:

package mapper;

import entity.User;

/**
 * 与 userMapper.xml 相绑定
 * 
 * @author Bylight
 */
public interface UserMapper {
    User queryUserById(int id);
}

可以看到,我们只要保证其中方法和 userMapper 相对应。但我们怎么确定它的返回值和参数,又怎么保证传入参数被正确的放在 Sql 语句中呢?我们接下来简单的介绍一下 Sql 语句相关的知识。

2.2 在映射器 mapper 中写 Sql 语句

在这里我们吧 userMapper.xml 中的 select 标签单独拎出来看看:

    <select id="queryUserById" parameterType="int" resultType="User">
        SELECT * FROM users WHERE id=#{id}
    </select>

我们需要关注以下几个东西:

  • 标签名:这里是 select,也就是表示这个 Sql 语句是一个查询语句。其他的还有 updata、insert 和 delete,各自对应数据库的增删改查操作。

  • id:设置 select 标签的id属性为 queryUserById,id属性值必须是唯一的,不能重复。这里的 id 就是对应的接口中代表这个语句的方法名,也是使用第一种方法时调用这个 Sql 语句的句柄。

  • parameterType:指明传入的参数类型,这里我们需要传入的是 id,属于 int 类型。

  • resultType:指明 Sql 语句返回的结果集类型,表示将查询结果封装成一个User类的对象返回。

    这里我们是定义了一个和表结构相符合的 User 实体类,它仅具备 getter 和 setter 以及 toString 方法,具体代码会在后面贴出。

    但当前路径中找不到User,所以在加载时,会去往加载的配置文件中查找对应的 alias 对应的资源路径(type),这里就知道前面我们在 conf.xml 中的 alias 标签的作用了:

        <typeAliases>
            <!-- 别名方式1,一个一个的配置 type中放置的是类的全路径,alias中放置的是类别名
            <typeAliase alias="UserBean" type="com.mybatis.demo.Entity.User"/> -->
            <!-- 别名方式2,自动扫描,将JAVA类的首字母小写类名作为类的类别名 -->
            <!--<package name="com.mybatis.demo.Entity"/>-->
            <typeAlias alias="User" type="entity.User"/>
        </typeAliases>

    这样我们就可以在 resultType 中直接使用 User 了,如果没有使用别名,那么我们每次都要使用 entity.User。

  • #{id}:这表示传入的int参数是放在这的,我们在 UserMapper 中也要传入相同名称的参数。

    这里关于 # 占位符,有必要特别说明一下 # 和 $ 的区别:

    一般能用#{}的就别用${}

    #{}会将sql语句编译好并使用占位符?然后再进行取值(值转换成字符串)很大的程度防止sql注入

    ${}先取值(不做任何处理直接引用)然后再去编译sql语句,无法防止sql注入,一般用于传入数据库对象,例如传入表名。

好了,到这里一个基础的 MyBatis 实现增删改查就可以实现了。更多的参数和 Sql 语句的其它操作可以看官网的说明,这里就不多说了。