目录
[JDBC 操作示例回顾](#JDBC 操作示例回顾)
[1.什么是 MyBatis?](#1.什么是 MyBatis?)
[2.MyBatis 入门](#2.MyBatis 入门)
[2.1 准备工作](#2.1 准备工作)
[2.1.1 创建工程](#2.1.1 创建工程)
[2.1.2 数据准备](#2.1.2 数据准备)
[2.2 配置数据库连接字符串](#2.2 配置数据库连接字符串)
[2.3 写持久层代码](#2.3 写持久层代码)
[2.4 单元测试](#2.4 单元测试)
[使用 Idea 自动生成测试类](#使用 Idea 自动生成测试类)
本节目标
使用MyBatis完成简单的增删改查操作, 参数传递.
掌握MyBatis的两种写法: 注解 和 XML方式
掌握MyBatis 相关的日志配置
前言
在应用分层学习时, 我们了解到web应用程序一般分为三层,即:Controller、Service、Dao .
之前的案例中,请求流程如下: 浏览器发起请求, 先请求Controller, Controller接收到请求之后, 调用 Service进行业务逻辑处理, Service再调用Dao, 但是Dao层的数据是Mock的, 真实的数据应该从数据库中读取.
我们学习MySQL数据库时,已经学习了JDBC来操作数据库, 但是JDBC操作太复杂了.
JDBC 操作示例回顾
我们先来回顾一下 JDBC 的操作流程:
- 创建数据库连接池 DataSource
这一步都是一样的
通过 DataSource 获取数据库连接 Connection
编写要执行带 ? 占位符的 SQL 语句
通过 Connection 及 SQL 创建操作命令对象 Statement
替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值
使用 Statement 执行 SQL 语句
查询操作:返回结果集 ResultSet,更新操作:返回更新的数量
处理结果集
释放资源
下面的一个完整案例,展示了通过 JDBC 的 API 向数据库中添加一条记录,修改一条记录,查询一条记录的操作。
java-- 创建数据库 create database if not exists library default character set utf8mb4; -- 使用数据库 use library; -- 创建表 create table if not exists soft_bookrack ( book_name varchar(32) NOT NULL, book_author varchar(32) NOT NULL, book_isbn varchar(32) NOT NULL primary key );以下是 JDBC 操作的具体实现代码:
javapackage com.example.demo.mapper; import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class SimpleJdbcOperation { private final DataSource dataSource; public SimpleJdbcOperation(DataSource dataSource) { this.dataSource = dataSource; } /** * 添加一本书 */ public void addBook() { Connection connection = null; PreparedStatement stmt = null; try { //获取数据库连接 connection = dataSource.getConnection(); //创建语句 stmt = connection.prepareStatement( "insert into soft_bookrack (book_name, book_author, book_isbn) values (?,?,?);" ); //参数绑定 stmt.setString(1, "Spring in Action"); stmt.setString(2, "Craig Walls"); stmt.setString(3, "9787115417305"); //执行语句 stmt.execute(); } catch (SQLException e) { //处理异常信息 } finally { //清理资源 try { if (stmt != null) { stmt.close(); } if (connection != null) { connection.close(); } } catch (SQLException e) { // } } } /** * 更新一本书 */ public void updateBook() { Connection connection = null; PreparedStatement stmt = null; try { //获取数据库连接 connection = dataSource.getConnection(); //创建语句 stmt = connection.prepareStatement( "update soft_bookrack set book_author=? where book_isbn=?;" ); //参数绑定 stmt.setString(1, "张卫滨"); stmt.setString(2, "9787115417305"); //执行语句 stmt.execute(); } catch (SQLException e) { //处理异常信息 } finally { //清理资源 try { if (stmt != null) { stmt.close(); } if (connection != null) { connection.close(); } } catch (SQLException e) { // } } } /** * 查询一本书 */ public void queryBook() { Connection connection = null; PreparedStatement stmt = null; ResultSet rs = null; Book book = null; try { //获取数据库连接 connection = dataSource.getConnection(); //创建语句 stmt = connection.prepareStatement( "select book_name, book_author, book_isbn from soft_bookrack where book_isbn =?" ); //参数绑定 stmt.setString(1, "9787115417305"); //执行语句 rs = stmt.executeQuery(); if (rs.next()) { book = new Book(); book.setName(rs.getString("book_name")); book.setAuthor(rs.getString("book_author")); book.setIsbn(rs.getString("book_isbn")); } System.out.println(book); } catch (SQLException e) { //处理异常信息 } finally { //清理资源 try { if (rs != null) { rs.close(); } if (stmt != null) { stmt.close(); } if (connection != null) { connection.close(); } } catch (SQLException e) { // } } } public static class Book { private String name; private String author; private String isbn; //省略 setter getter 方法 } }分析上述 CURD 操作,可以看出很多相同点👇
- 创建数据库连接池 DataSource
这一步都是一样的
- 通过 DataSource 获取数据库连接 Connection
这一步都是一样的
- 编写要执行带 ? 占位符的 SQL 语句
这个与业务有关
- 通过 Connection 及 SQL 创建操作命令对象 Statement
这一步都是一样的
- 替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值
这个与 SQL 有关,也就是第三步
- 使用 Statement 执行 SQL 语句
这一步都是一样的
- 查询操作:返回结果集 ResultSet,更新操作:返回更新的数量
就是字段和 Java 属性的映射
- 处理结果集
这一步都是一样的
- 释放资源
这一步都是一样的
所以一整个流程下来,需要我们自己写的代码也就是一个 SQL 语句和参数,多说就是再加上一个处理查询结果集
框架就可以帮我们把共同的东西封装起来,个性化的东西我们采用配置或者给我们提供入口即可~~
从上述代码和操作流程可以看出,对于 JDBC 来说,整个操作非常的繁琐,我们不但要拼接每一个参数,而且还要按照模板代码的方式,一步步的操作数据库,并且在每次操作完,还要手动关闭连接等,而所有的这些操作步骤都需要在每个方法中重复书写. 那有没有一种方法,可以更简单、更方便的操作数据库呢?
答案是肯定的,这就是我们要学习 MyBatis 的真正原因,它可以帮助我们更方便、更快速的操作数据库,MyBatis 可以说就是把能封装的都封装起来了~~
1.什么是 MyBatis?
闲聊:大白话就是访问数据库的,JDBC 写起来太麻烦了,我们对 JDBC 进行封装,然后封装出来一个 MyBatis,直接替代 JDBC 了,因为要想存起来不丢就得存到外存,也就是数据库,这也就持久了~~
• MyBatis是一款优秀的 持久层 框架,用于简化 JDBC 的开发。
闲聊:MyBatis 和 Spring 没有关系~~
我们知道 Spring 非常开放,如果你做的框架比较好,Spring Boot 就会给它集成进来,所以 Spring Boot 把 MyBatis 集成进来了,我们依旧是通过 Spring Boot 来去学习 MyBatis
Spring Boot 集成了 MyBatis,就像 Spring Boot 集成了 Tomcat 一样,但是它们还是两家人,只不过我们觉得这东西很好,就像我们的 12306 集成了外卖,集成了保险一样,我觉得这个保险做的挺好,就把它集成到我的框架里面了,但是外卖、保险和 12306 不是一家人~~
答疑:Servlet 集成了吗?
我们的 Spring 是基于 Servlet 进行封装的,它本身就是站在巨人的肩膀上,站在 Servlet 的基础上封装成的 Spring
• MyBatis本是 Apache的一个开源项目 iBatis,2010年这个项目由 apache 迁移到了 google code,并且改名为 MyBatis 。2013年11月迁移到 Github.
• 官网:MyBatis中文网
在上面我们提到一个词:持久层
• 持久层:指的就是持久化操作的层, 通常指数据访问层(dao), 是用来操作数据库的.
简单来说 MyBatis 是更简单完成程序和数据库交互的框架,也就是更简单的操作和读取数据库工具
接下来,我们就通过一个入门程序,让大家感受一下通过 Mybatis 如何来操作数据库
2.MyBatis 入门
Mybatis操作数据库的步骤:
准备工作(创建 springboot 工程、数据库表准备、实体类)
引入 Mybatis 的相关依赖,配置 Mybatis (数据库连接信息)
编写SQL语句(注解/XML)
测试
2.1 准备工作
2.1.1 创建工程
创建springboot工程,并导入 mybatis 的起步依赖、mysql的驱动包
Mybatis 是一个持久层框架, 具体的数据存储和数据操作还是在 MySQL 中操作的, 所以需要添加 MySQL 驱动
然后我们还是把没用的东西删掉👇
还是只保留这两个即可👇
当然,要是社区版的话需要在 pom.xml 文件中删掉一些没用的东西,删成这样👇
项目工程创建完成后,自动在 pom.xml 文件中,导入 Mybatis 依赖和 MySQL 驱动依赖
版本会随着 SpringBoot 版本发生变化
SpringBoot 3.X对用 MyBatis 版本为 3.X
对应关系参考:Introduction -- mybatis-spring-boot-autoconfigure
XML<!--Mybatis 依赖包--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency> <!--mysql驱动包--> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency>这个依赖加上了 spring-boot,所以这个依赖只能在 SpringBoot 下使用的,而下面这个依赖是在所有的 Java 框架中都可以使用的👇
XML<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>x.x.x</version> </dependency>之前我们学习 JDBC 的时候的调用关系:JDBC→MySQL驱动→MySQL
现在我们学习 MyBatis 的调用关系:MyBatis→MySQL驱动→MySQL
所以我们需要添加 MySQL 驱动,当然,如果你数据库是别的,那就添加别的驱动
如果你是老项目的话,可以使用我们之前安装的插件,进行添加👇
2.1.2 数据准备
创建用户表,并创建对应的实体类 User
sql-- 创建数据库 DROP DATABASE IF EXISTS mybatis_test; CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4; -- 使用数据数据 USE mybatis_test; -- 创建表[用户表] DROP TABLE IF EXISTS user_info; CREATE TABLE `user_info` ( `id` INT ( 11 ) NOT NULL AUTO_INCREMENT, `username` VARCHAR ( 127 ) NOT NULL, `password` VARCHAR ( 127 ) NOT NULL, `age` TINYINT ( 4 ) NOT NULL, `gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-女 0-默认', `phone` VARCHAR ( 15 ) DEFAULT NULL, `delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除', `create_time` DATETIME DEFAULT now(), `update_time` DATETIME DEFAULT now() ON UPDATE now(), PRIMARY KEY ( `id` ) ) ENGINE = INNODB DEFAULT CHARSET = utf8mb4; -- 添加用户信息 INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone ) VALUES ( 'admin', 'admin', 18, 1, '18612340001' ); INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone ) VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' ); INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone ) VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' ); INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone ) VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );将上述内容复制,然后点击新建查询,粘贴到可视化工具上运行👇
此时这个表就建立好了,还有着一些初始化的数据👇
然后注意上述 SQL 中在更新位置用到了 ON UPDATE now(),这一句指的就是在我更新数据的时候,我可以不用去设置它的值,它会自动更新,可以理解为一个触发器吧,当我触发更新这个操作的时候,它会给它自动设置,比如现在的时间是👇
接下来把值改为 1 ,再点击下面的 √ 保存,我们会发现时间自动更新了👇
这个也是我们在创建表的时候经常去用的
创建对应的实体类 UserInfo
实体类的属性名与表中的字段名一一对应
javaimport lombok.Data; import java.util.Date; @Data public class UserInfo { private Integer id; private String username; private String password; private Integer age; private Integer gender; private String phone; private Integer deleteFlag; private Date createTime; private Date updateTime; }闲聊:规范~~
数据库字段:全部小写,单词之间使用 下划线( _ )分割
Java 规范:属性使用小驼峰来表示
通常把数据库字段和 Java 属性一一对应起来~~
2.2 配置数据库连接字符串
Mybatis中要连接数据库,需要数据库相关参数配置
• MySQL驱动类
• 登录名
• 密码
• 数据库连接字符串
如果是application.yml文件, 配置内容如下:
java# 数据库连接配置 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver注意事项:
如果使用 MySQL 是 5.x 之前的使用的是"com.mysql.jdbc.Driver",如果是大于 5.x 使用的
是"com.mysql.cj.jdbc.Driver".
如果是application.properties文件, 配置内容如下:
java#驱动类名称 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #数据库连接的url spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false #连接数据库的用户名 spring.datasource.username=root #连接数据库的密码 spring.datasource.password=root闲聊:这些东西不需要背,以后工作中用的时候可以直接从博客这里来拿,或者你自己存到码云里,工作不考你背代码,考的是你解决问题的能力,在这里你只需要复制下来就可以了~~
MySQL 驱动新版本默认不允许公钥获取,所以当出现报错的时候用下面的配置,表示允许👇(用户名和密码要和本地的对应上~~)
spring: application: name: mybatis-demo # 数据库连接配置 datasource: # url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true username: root # password: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver
2.3 写持久层代码
在项目中, 创建持久层接口UserInfoMapper
我们来实现这样的 SQL 语句👇
javapackage com.bit.mybatis.mapper; import com.bit.mybatis.model.UserInfo; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-11-04 * Time: 21:26 */ @Mapper//交给Spring管理,否则执行不了,这也就是文件夹和接口名都带Mapper的原因 public interface UserInfoMapper { @Select("select * from user_info") List<UserInfo> selectAll(); }从此处也能看到 MyBatis 的前身:ibatis👇
Mybatis 的持久层接口规范一般都叫 XxxMapper
@Mapper注解:表示是MyBatis中的Mapper接口
• 程序运行时, 框架会自动生成接口的实现类对象(代理对象),并给交Spring的IOC容器管理
• @Select注解:代表的就是select查询,也就是注解对应方法的具体实现内容.
2.4 单元测试
在创建出来的 SpringBoot 工程中,在 src 下的 test 目录下,已经自动帮我们创建好了测试类 ,我们可以直接使用这个测试类来进行测试.
接口的实现有两种方式
1.注解的方式
2.XML的方式(后面讲)
下面演示的是注解的方式👇
javaimport com.example.demo.mapper.UserInfoMapper; import com.example.demo.model.UserInfo; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest class DemoApplicationTests { @Autowired private UserInfoMapper userInfoMapper; @Test void contextLoads() { List<UserInfo> userInfoList = userInfoMapper.queryAllUser(); System.out.println(userInfoList); } }测试类上添加了注解 @SpringBootTest,该测试类在运行时,就会自动加载Spring的运行环境.
我们通过@Autowired这个注解, 注入我们要测试的类, 就可以开始进行测试了
运行结果如下:
返回结果中, 可以看到, 只有SQL语句中查询的列对应的属性才有赋值
使用 Idea 自动生成测试类
除此之外, 也可以使用Idea自动生成测试类
- 在需要测试的Mapper接口中, 右键 -> Generate -> Test
- 选择要测试的方法, 点击 OK
此时就在这里生成测试代码了👇
- 书写测试代码
javapackage com.bit.mybatis.mapper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.*; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-11-04 * Time: 21:40 */ @SpringBootTest//加上这个就可以运行测试了 class UserInfoMapperTest { //将刚刚交给Spring管理的UserInfoMapper注入进来 @Autowired private UserInfoMapper userInfoMapper; @Test void selectAll() { System.out.println(userInfoMapper.selectAll()); } }记得加 @SpringBootTest 注解, 加载Spring运行环境
运行结果:
接口测试
除了上面的测试方法,还可以用接口来进行测试👇Controller 调用 Service,Service 调用 Mapper,Mapper 从数据库中获取数据,然后返回,返回给 Service,Service 再返回给 Controller,Controller 再把数据统一返回~~
Controller👇
java@RequestMapping("/user") @RestController public class UserInfoController { //注入进来 @Autowired private UserService userService; @RequestMapping("/getAllUser") public List<UserInfo> getAllUser(){ return userService.getAllUser(); } }Service👇
java@Service public class UserService { @Autowired//注入进来 private UserInfoMapper userInfoMapper; public List<UserInfo> getAllUser() { return userInfoMapper.selectAll(); } }启动服务后测试👇此时我们就把数据库中的值打印出来了👇
每一个都这么写确实麻烦,所以上述讲了单元测试~~
测试对我们还是比较重要的,像之前我们写测试的时候都是直接在 main 方法里写测试(像前面 IoC 那样去写)👇
javapackage com.bit.mybatis; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; @SpringBootApplication public class MybatisDemoApplication { public static void main(String[] args) { ApplicationContext context=SpringApplication.run(MybatisDemoApplication.class, args); } }这其实是不规范的,因为我们的测试代码是不写在 main 里面的,我们拿的时候直接注入就可以了👇
javapackage com.bit.mybatis; import com.bit.mybatis.mapper.UserInfoMapper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; @SpringBootTest class MybatisDemoApplicationTests { @Autowired private ApplicationContext context; @Test void contextLoads() { //在这里就可以直接用了 UserInfoMapper bean=context.getBean(UserInfoMapper.class); bean.selectAll().stream().forEach(x->System.out.println(x)); } }运行之后也是可以正常获取的👇
因为我们已经提前交给 Spring 管理了👇
当然,规范归规范,有些人比较懒,也就在 main 直接写然后测试了,所以只能说不规范,但是大家也有这么用的~~
答疑:
Q1:接口不能实例化,那么这里将接口交给 Spring 管理,Spring 管理的是啥呀??
接口是要实例化的~~,我们下面这一行就已经给它实例化了👇
之前学习 Java 的时候可能还需要去创建一个 class 去实现 UserInfo👇
java@Component public class UserInfoMapperImpl implements UserInfoMapper{ @Override public List<UserInfo> selectAll() { return null; } }确实,在十多年前,ibatis 确实是这么用的,我们实现确实不在里面,而是在外面再创建一个类,但是现在不是了,现在在接口上直接就给它实现了,就不用再写这个实现类了~~
注意:前面学的五大注解是 Spring 的,现在学的 @Mapper 是 MyBatis 的,这两者之间没有关系,不要弄混了~~
Q2:为啥加上 @Mapper 也是交给 Spring 进行管理了??
因为我们的依赖是二者结合的依赖👇(MyBatis 告诉 Spring 让它管理起来)
Q3:什么是 Stream 流??
这是 Java8 新增的一种处理集合数据的方式,下面这行代码👇
javabean.selectAll().stream().forEach(x->System.out.println(x));等价于👇
javaList<UserInfo> userInfos=bean.selectAll(); for(UserInfo userInfo:userInfos){ System.out.println(userInfo); }相当于我们把 这个 List 转换成了一个 流,然后对这个流进行处理,.stream() 就是把它变成 流 了,我们知道 流 肯定要一滴一滴下来,我们就是对每一滴水进行处理,也就是 userInfo 了,处理的内容就是 x->System.out.println(x),这个 x 就是 流 里(userInfo)的元素,然后去打印
当然,除了打印,还可以用 filter 进行过滤操作,比如下面代码就是把 List 里面的 gender=1 的数据过滤出来,然后收集成一个 List👇
javauserInfos.stream().filter(x->x.getGender()==1).collect(Collectors.toList());要是想了解更多,可以自行去网上搜索了解~~
@SpringBootTest 表示:加载 Spring 的运行环境
当你需要使用 Spring 的运行环境,比如注入某个 bean,需要加 @SpringBootTest,不需要则不加
@Test 表示:这是一个测试方法,我们就可以直接运行了,也可以同时创建多个,都可以运行👇,不然还得去写 main 方法,很麻烦~~
在测试方法前面的箭头表示只执行当前测试方法,最上面类上的箭头表示执行所有测试方法👇
创建测试用例(对上面的补充)
上述测试用例并不是我们手写的,而是生成的~~
点击确定👇
java@SpringBootTest//加上这个就可以运行测试了 class UserInfoMapperTest { //将刚刚交给Spring管理的UserInfoMapper注入进来 @Autowired private UserInfoMapper userInfoMapper; @Test void selectAll() { System.out.println(userInfoMapper.selectAll()); } @BeforeEach//在每一个测试方法之前执行 void setUp() { System.out.println("before..."); } @AfterEach//在每一个测试方法之后执行 void tearDown() { System.out.println("after..."); } }运行时我们就能发现,是先打印 before,再打印 after,中间是程序运行的内容👇
这两个是每个测试方法都会执行一次,而不是执行一次就完事儿了,下面我们用两个测试方法来体会一下👇
java@SpringBootTest//加上这个就可以运行测试了 class UserInfoMapperTest { //将刚刚交给Spring管理的UserInfoMapper注入进来 @Autowired private UserInfoMapper userInfoMapper; @Test void selectAll() { System.out.println(userInfoMapper.selectAll()); } @Test void selectAll2() { System.out.println(userInfoMapper.selectAll()); } @BeforeEach//在每一个测试方法之前执行 void setUp() { System.out.println("before..."); } @AfterEach//在每一个测试方法之后执行 void tearDown() { System.out.println("after..."); } }我们发现都执行了👇










































