5.3 Spring Boot 集成 MyBatis

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。

MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

官方参考文档,有中文版,描述非常详细,在这里,能解决你的所有疑问。

由于 MyBatis 在系统复杂性、便捷性和可控性方面找到了一个较好平衡。能够满足 DBA 对数据库的架构设计,也能满足开发人员对复杂 SQL 的编写,同时又能满足技术经理/架构师对 SQL 调优的需求。所以,最近几年来,MyBatis 使用得越来越广泛。

要想掌握 MyBatis,必须熟悉其几个重要的概念:

  1. mapper: 映射器,里面存放了增删改查等映射语句和其对应的 Java 接口(在运行时使用 动态代理),通常情况下,我们指一个 mapper 是在说这个 mapper 的 XML 文件和其对应的接口 Java 文件。MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器(mapper)的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。
  2. namespace: 命名空间。在之前版本的 MyBatis 中,命名空间的作用并不大,是可选的。 但现在,随着命名空间越发重要,你必须指定命名空间。命名空间的作用有两个。
    • 利用更长的完全限定名来将不同的语句隔离开来,同时将 mapper 的 XML 文件和 Java 接口绑定。
    • 将命名空间置于合适的 Java 包命名空间之中,代码会变得更加整洁,也有利于你更方便地使用 MyBatis。
  3. resultType: 返回结果类型。例如如一个按照 id 查询的 select 语句返回表中一条记录,MyBatis 通过 mapper,在 Java 中将其映射成一个对象返回。

MyBatis 中的概念较多,对应使用到的文件也较多,为了更加形象的理解 MyBatis 的配置对应关系,我们通过如下 Spring Boot 项目的 MyBatis 配置关系示意图,来学习如何正确地将 MyBatis 的各种元素有机的联系起来。

如上图所示:

  1. 红色 mapper 2.1 指定了项目中 mapper 映射 XML 文件(例如上图中的 2.1* UserMapper.xml 文件,其中存放了 select 等语句)存放的位置(大多数项目都习惯于将其存放于 resources/mapping 文件夹下);
  2. 红色 namespace 命名空间 2.2 将本 mapper 映射 XML 文件和对应的 Java 接口文件(2.2*)连接起来;
  3. 蓝色1.1的type-aliases-package指定了1.2resultType的简写方法(上图中的 1.2User会被MyBatis解释成com.example.mybatis.entity.User);
  4. 蓝色返回类型 1.2User(实际上是com.example.mybatis.entity.User)对应的是 1.4 包下的 entity 实体类 1.3User;
  5. 绿色 3.1selectUser 是一个 select 查询,其对应的 Java 接口文件中的方法为 3.1**selectUser 方法,返回类型为蓝色 1.2User;
  6. 绿色 3.1selectUser 这个 select 查询,使用了一个 3.2id 参数,对应 Java 接口文件 3.1** selectUser 方法参数 3.2**id;
  7. 紫色 4.1@MapperScan("com.example.mybatis.mapper") 注解指定了 mapper 的 Java 接口文件扫描的包位置 4.1*,上图中,红色 2.2UserMapper 接口文件就存放在其中。
  • application.yml 配置文件中(红色2.1)设定了 mapper 的 XML 文件存放位置;
  • Spring Boot 配置类(本例中将其配置到了 Spring Boot 的启动类上了)中,设定了 mapper 的 Java 接口文件扫描位置。在项目启动时,MyBatis 将扫描(扫描@Repository注解)到的 mapper 的 Java 接口文件和 mapper 的 XML 动态代理成我们真正用到的 Dao 类,完成数据库的访问,并返回映射后的 Java 对象,完成 ORM(对象关系映射)过程。

Spring Boot 通过 starter 使用 MyBatis 是非常方便的。

在 pom.xml 文件中添加mybatis-spring-boot-starter启动器,和 MySQL 数据的 JDBC 驱动,就可以使用 MyBatis 了。

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

需要注意的是:在我们的练习环境中使用的是 MySQL 5.7 数据库,Spring Boot 使用当前的 JDBC驱动(8.0.18)application.yml 配置需要注意如下两点:

  • url 需要设置时区参数,例如jdbc:mysql://localhost:3306/spring_boot_course?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
  • JDBC 驱动,需要使用com.mysql.cj.jdbc.Driver

5.2.1 创建项目

首先在 STS 中创建一个 Spring Boot 项目,选中的 starter 有:

  • Spring Web;
  • MySQL Driver;
  • MyBatis Framework.

其 pom.xml 文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>mybatis</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>MyBatis</name>
	<description>MyBatis Example.</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.1</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

5.2.2 修改配置文件

在 application.yml 文件中配置数据库连接信息:

# mysql数据源配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/spring_boot_course?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
# mybatis 配置信息
mybatis:
  mapper-locations: classpath:mapping/*Mapper.xml
  type-aliases-package: com.example.mybatis.entity

5.2.3 启动类

启动类,也是一个配置类(@SpringBootApplication是个复合注解),可在上配置 MyBatis 的扫描注解。

package com.example.mybatis;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.example.mybatis.mapper")
public class MyBatisApplication {
	public static void main(String[] args) {
		SpringApplication.run(MyBatisApplication.class, args);
	}
}

其中第 8 行是添加的 MyBatis 的 mapper 扫描包位置。

项目中通常会包含多个业务功能模块,mapper 会放置在各自功能模块下的 mapper 包中,请参考如下代码配置多个扫描包位置:

@MapperScan({"com.example.mybatis.admin.mapper","com.example.mybatis.report.mapper"})

5.2.4 实体类

实体类,一般对应了数据库中的表。

package com.example.mybatis.entity;
public class User {
	private Integer id;
	private String userName;
	private String passWord;
	private String realName;
(省略getter、setter和toString方法)

对应表 user 的 MySQL DDL 如下:

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(32) NOT NULL AUTO_INCREMENT,
  `userName` varchar(32) NOT NULL,
  `passWord` varchar(50) NOT NULL,
  `realName` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

插入 1 条数据,供测试:

INSERT INTO `user` VALUES ('1', 'Kevin', '123456', '长的帅');

5.2.5 映射器(mapper)

mapper 的 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="com.example.mybatis.mapper.UserMapper">
    <select id="selectUser" resultType="User">
        select * from user where id = #{id}
    </select>
 
</mapper>

mapper 的 Java 接口文件:

package com.example.mybatis.mapper;
import org.springframework.stereotype.Repository;
import com.example.mybatis.entity.User;
@Repository
public interface UserMapper {
	User selectUser(int id);
}

注意第 7 行的注解,标注了这是个受 MyBatis(Spring) 管理的 DAO 类(接口)。

5.2.6 服务类

服务类完成业务功能。一般企业应用,都会在服务类中注入 DAO 来操作数据库。

package com.example.mybatis.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.mybatis.entity.User;
import com.example.mybatis.mapper.UserMapper;
@Service
public class UserService {
    @Autowired
    UserMapper userMapper;
    public User selectUser(int id){
        return userMapper.selectUser(id);
    }
}

其中第 11-12 行,就注入了UserMapper这个 DAO。

5.2.7 控制器类

控制器类与前端交互,并调用服务类完成业务操作。

package com.example.mybatis.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.mybatis.service.UserService;
@RestController
@RequestMapping("/user")
public class UserController {
 
    @Autowired
    private UserService userService;
 
    @RequestMapping("get/{id}")
    public String getUser(@PathVariable int id){
        return userService.selectUser(id).toString();
    }
}

其中第 14-15 行,就注入了服务类UserService

5.2.8 运行验证

运行该项目的启动类MyBatisApplication,在浏览器中访问 http://localhost:8080/user/get/1,验证是否可以正确的查询到数据库中的记录。

以上,就是 Spring Boot 如何集成使用 MyBatis 的方法。至于进一步的 MyBatis 进阶使用,会包括在后续的分页插件、MyBatis Plus 章节介绍。

更加详细的 MyBatis 中的 SQL 映射应该怎么写,请参考官方文档

下一节:一旦谈及 ORM 持久化,就一定会涉及其查询结果集的分页问题。