为什么要学习MyBatis
前面我们肯定多多少少学过 sql 语言,sql 语言是一种操作数据库的一类语言,数据库是保证数据能够持久化存储的一种集合。在众多 sql 语言中,MySQL就是其中一种,并且是人们使用较多的一种 sql 语言,而就是因为 MySQL 使用较简单,使用的人较多,所以就出现了 JDBC 编程,也就是 Java 的一个 API,可以让我们通过 Java 代码来操作我们的数据库,但是呢?JDBC 编程的操作太复杂了,为什么会说 JDBC 操作复杂呢?看下面这段代码。
package com.example.mybatis20231226.Dao;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class UserDao {
DataSource dataSource = null;
public UserDao(DataSource dataSource) {
this.dataSource = dataSource;
}
public void addUser() throws SQLException {
Connection connection = null;
PreparedStatement statement = null;
try {
connection = dataSource.getConnection();
String sql = "insert into user values (?,?,?);";
statement = connection.prepareStatement(sql);
statement.setString(2, "小明");
statement.setInt(3, 0);
statement.execute();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
}
}
}
使用 JDBC 操作,需要创建出 DataSource 数据源对象、Connection 对象、PrepareStatement 对象,甚至是 ResultSet 对象,并且在使用完这些资源之后还不能忘记释放掉这些资源,这些 JDBC 很多的操作都是重复的,所以就出现了能够简化 JDBC 操作的框架——MyBatis。
什么是MyBatis
MyBatis的发展历程可以追溯到2001年,当时Clinton Begin发起了一个名为iBATIS的开源项目。iBATIS最初是一个专注于密码软件开发的开源项目,但后来逐渐发展成为一个基于Java的持久层框架。
在2004年,Clinton将iBATIS的名字和源码捐赠给了Apache软件基金会,接下来的6年中,开源软件世界发生了巨大的变化,一切开发实践、基础设施、许可,甚至数据库技术都彻底改变了。
2010年,核心开发团队决定离开Apache软件基金会,并且将iBATIS改名为MyBatis。之后,MyBatis迁移到了Google Code,并在2013年11月再次迁移到了GitHub。
在功能上,MyBatis是一款优秀的支持自定义SQL查询、存储过程和高级映射的持久层框架。它消除了几乎所有的JDBC代码和参数的手动设置以及结果集的检索。MyBatis可以使用简单的XML或注解进行配置和原始映射,将接口和Java的POJOs(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。使得数据持久层的设计更为灵活和高效。
这里是 MyBatis 的中文官方网站https://mybatis.net.cn/
在 Spring 中,三层架构分别是Controller(控制层)、Service(业务逻辑层)和Dao(数据访问层),我们的 MyBatis 就处于三层架构的 Dao 层。
简单来说,MyBatis 就是更简单完成程序和数据库交互的框架,也就是更简单的操作和读取数据库的工具。
MyBatis 入门
首先我们先通过一个使用了 MyBatis 框架的程序来看看 MyBatis 有多么的方便。
创建带有MyBatis框架的SpringBoot项目
在创建项目的时候勾选上 MyBatis Framework
和 MySQL Driver
MyBatis 不是只能用于 Java 的 Spring 框架,它可以独立存在,只是因为 MyBatis 的实用性的方便,所以 Idea 才将 MyBatis 给集成进来了。那么既然选择了 MyBatis,为什么还要选择 MySQL Driver 呢?前面我们说了,MyBatis 是一种框架,他操作的是数据库,只是简化了 JDBC 的操作,所以底层还是 JDBC。
当勾选了 MyBatis 框架了之后,在 SpringBoot 项目的 pom.xml
文件中可以发现已经自动导入了 MyBatis 和 MySQL 的依赖。
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>3.0.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
如果我们已经创建完成了SpringBoot项目了之后,想要在当前项目添加进去 MyBatis 依赖的话,可以使用前面的 Edit Starters 插件来继续添加进去 MyBatis 依赖。
当然我们也可以去 maven 中央仓库通过添加 MyBatis 依赖的坐标到 pom.xml 文件中来加入依赖,其实上一个在创建 SpringBoot 项目的时候勾选 MyBatis 选项也是将 MyBatis 的坐标添加进去 pom.xml 文件中,只不过这个是 Idea 帮我们自动完成了。
注意:手动添加 Mybatis 依赖的时候,需要注意 Spring 版本和 MyBatis 版本的对应关系。
数据准备
我们先在本地数据库中存储一些数据,作为后面 MyBatis 操作的数据。
-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
-- 使⽤数据数据
USE mybatis_test;
-- 创建表[⽤⼾表]
DROP TABLE IF EXISTS userinfo;
CREATE TABLE `userinfo` (
`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(),
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
-- 添加⽤⼾信息
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );
在 Java 中创建出 UserInfo 类与数据库中一行的数据对应。这里为什么要与数据库中的列对应以及可不可以不对应,我们后面再说。
package com.example.mybatis20231226.Model;
import lombok.Data;
import java.util.Date;
@Data
public class UserInfo {
private int id;
private String username;
private String password;
private int age;
private int gender;
private String phone;
private int deleteFlag;
private Date createTime;
private Date updateTime;
}
在配置文件中配置数据库相关信息
我这里选择的是 YAML 配置文件配置,properties 也类似。
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: root
password: xxxxxx
driver-class-name: com.mysql.cj.jdbc.Driver
如果MySQL使用的是5.x之前版本的话,driver-class-name选项的值要使用 com.mysql.jdbc.Driver
,大于5.x版本就使用 com.mysql.jdbc.cj.Driver
实现持久层代码
MyBatis 持久层接口规范一般都叫XxxMapper。
package com.example.mybatis20231226.Mapper;
import com.example.mybatis20231226.Model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserInfoMapper {
@Select("select * from userinfo")
public List<UserInfo> selectAll();
}
注意这里的 @Mapper
注解要选择 org.apache.ibatis.annotations
包下的。
在MyBatis中,@Mapper注解主要用于标识接口,它表示该接口是一个MyBatis的映射器接口。这个注解可以帮助简化代码和提高代码的可读性。
当你使用@Mapper注解标记一个接口时,MyBatis会自动为该接口生成实现类,该实现类包含了该接口中所有方法对应的SQL语句和执行逻辑。而在Spring中使用@Mapper注解,Spring会扫描到这个接口,并将其实例化为一个Bean,自动注入到MyBatis的SqlSession中。这样,你就可以通过直接调用接口方法的方式来执行相应的SQL语句,而不需要手动编写实现代码。
@Select("select * from userinfo")
public List<UserInfo> selectAll();
public List<UserInfo> selectAll()
是方法的声明,而这个 @Select("select * from userinfo")
则是这个方法的实现。Select 说明这个方法是一个查询方法。
那么有人会问了,这里类为什么会选择使用 interface
接口,而不是 class
呢?如果你是 class 的话,那么方法的具体实现就是需要我们写出来的,而上面说了这个方法的实现是通过 @Select("select * from userinfo")
注解实现的,如果我们再在这个方法中写上实现的话,就会导致冲突出现问题,而 interface 接口中的所有方法都是抽象方法,是不需要写出方法的实现的,正好对应 MyBatis 注解来实现,所以 interface 接口是最好的选择。
单元测试
当我们写完上面的代码之后,是否需要再创建一个测试类来测试这个方法呢?可以这样,但是这样比较麻烦,我们可以通过单元测试的方法快速的测试我们的代码功能。
单元测试是一种对软件中的最小可测试单元进行检查和验证的测试活动。在软件开发过程中,单元测试是在最低级别进行的测试活动,通常针对软件的独立单元进行,这些单元可能是函数、类、模块或组件。单元测试的目标是确保每个单元都能按照预期的方式工作,并且能够与其他单元协调工作。
对于单元测试中单元的含义,要根据实际情况去判定其具体含义。例如,在C语言中,单元通常指的是一个函数;在Java中,单元通常指的是一个类;在图形化的软件中,单元可能指的是一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。
我们在需要单元测试的类中右键选择generate。
选择 Test。
当点击ok之后,就会生成一个单元测试代码:
package com.example.mybatis20231226.Mapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@Slf4j
class UserInfoMapperTest {
@BeforeEach
void setUp() {
}
@AfterEach
void tearDown() {
}
@Test
void selectAll() {
}
}
然后我们只需要完成单元测试中函数的实现的可以了。
package com.example.mybatis20231226.Mapper;
import com.example.mybatis20231226.Model.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@Slf4j
class UserInfoMapperTest {
//通过@Autowired注解拿到Bean
@Autowired
private UserInfoMapper userInfoMapper;
@BeforeEach
void setUp() {
log.info("selectAll 执行之前");
}
@AfterEach
void tearDown() {
log.info("selectAll 执行之后");
}
@Test
void selectAll() {
List<UserInfo> list = userInfoMapper.selectAll();
log.info(list.toString());
}
}
当我们运行会发现,运行出现了问题:
为什么会出现这种错误呢?出现这种错误就是因为 Spring 环境没有正确启动。所以我们需要在类上加上类注解 @SpringBootTest
来为这个类加上 Spring 上下文管理。
@SpringBootTest
@Slf4j
class UserInfoMapperTest {
}
package com.example.mybatis20231226.Mapper;
import com.example.mybatis20231226.Model.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Slf4j
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@BeforeEach
void setUp() {
log.info("selectAll 执行之前");
}
@AfterEach
void tearDown() {
log.info("selectAll 执行之后");
}
@Test
void selectAll() {
List<UserInfo> list = userInfoMapper.selectAll();
log.info(list.toString());
}
}
通过这种单元测试就达到了测试代码功能的作用,那么 MyBatis 的详细基础操作我就放在下一篇文章了。