目录
- MyBatis
- 前言
- 搭建一个简单的MyBatis
- 创建Maven项目
- 引入必要依赖
- 创建数据表结构
- 创建User实体类
- 创建Mapper接口
- Mapper层
- Dao层
- 创建MyBatis的Mapper映射文件
- 编写测试类
- 传统测试类
- JUnit测试
MyBatis
介绍:MyBatis是一款半自动的ORM持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性,但它的数据库无关性较低
前言
我们先来看几个名词:
-
ORM
Object Relation Mapping,对象关系映射。对象指的是Java对象,关系指的是数据库中的关系模型,对象关系映射,指的就是在Java对象和数据库的关系模型之间建立一种对应关系,比如用一个Java的User类,去对应数据库中的一张user表,类中的属性和表中的列一一对应。User类就对应user表,一个User对象就对应user表中的一行数据。
-
持久层
持久层指的是:将业务数据存储到磁盘,也具备长期存储能力,只要磁盘不损坏,如果在断电情况下,重启系统仍然可以读取数据。
持久是相对于瞬时来说的,其实就是可以把数据固化在硬盘或者磁带一类可以保存很长时间的设备上,不像放在内存中一样断电就消失了。 -
为什么是半自动?
MyBatis开发过程中,需要我们手动去编写SQL语句,所以称为半自动框架。而对于全自动框架,例如HIbernate,只需要定义好ORM关系,就可以直接进行CRUD操作(增删改查等等)。
由于MyBatis需要手写SQL语句,所以它有较高的灵活性,可以根据需要,自由地对SQL进行定制,也因为要手写SQL,当要切换数据库时,SQL语句可能就要重写,因为不同的数据库有不同的方言(Dialect),所以MyBatis的数据库无关性低。但是同时MyBatis 封装了 JDBC 的复杂操作,通过 XML 或注解配置 SQL 映射,简化了数据库访问代码,提高了开发效率和可维护性。它支持动态 SQL,提供了缓存机制和事务管理功能,能够更好地处理复杂业务逻辑,适用于大型项目和频繁修改 SQL 语句的场景。
搭建一个简单的MyBatis
这里简单介绍一下我用到的工具:
-
IDE:IntelliJ IDEA 2024.2.3
-
项目构建:Maven3.9.8
-
数据库:MySQL8.4.2
这里提一下不同版本MySQL注意事项:
-
驱动类:驱动类driver-class-name (这个可以在添加依赖之后的依赖包中找到具体位置)
MySQL 5版本使用jdbc5驱动,驱动类使用:com.mysql.jdbc.Driver
MySQL 8版本使用jdbc8驱动,驱动类使用:com.mysql.cj.jdbc.Driver
-
连接地址url
MySQL 5版本的url: jdbc:mysql://localhost:3306/ssm
MySQL 8版本的url: jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
-
创建Maven项目
这里不再过多介绍创建步骤,具体步骤可以看这篇博客:【Maven】一篇带你了解Maven项目管理工具-CSDN博客,打包方式我们直接选择jar即可:
引入必要依赖
引入我们的MyBatis核心、JUnit测试工具、MySQL驱动,可以在中央仓库中搜索MyBatis,选择对应版本并赋值对应代码即可:
如果你用的工具版本和我一样,可以直接复制下面这段代码到你pom.xml文件中:
<!-- 引入依赖 -->
<dependencies>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
然后通过Maven重构你的项目
创建数据表结构
直接在你的NaviCat或者其他数据库管理工具中执行以下查询,或者你自己写一段查询语句创建一个差不多的表格:
CREATE DATABASE myfirstbatis;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(20) DEFAULT NULL COMMENT '用户名',
`password` varchar(20) DEFAULT NULL COMMENT '密码',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`sex` varchar(1) DEFAULT NULL COMMENT '性别',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
# 随便加几条数据进去
insert into `user`(`id`,`username`,`password`,`age`,`sex`,`email`) values (1,'zhangsan','19732846','20','男','xxx@qq.com'),(2,'lisi','123456','23','男','xxx@gmail.com'),(3,'wangwu','123789','55','女','xxx@163.com'),(4,'zhaoliu','123456','30','女','xxx@gmail.com');
创建User实体类
上面我们创建的表名为user,如果我们想使用MyBatis对项目进行操作,需要一个对应的实体类User,这个实体类我将它放在项目的entities路径下:
你的表格中有几列就添加几条属性,属性名和列名对应,别照抄我的,然后添加对应的构造方法和Getter()、Setter()方法以及重写toString()方法:
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
private String sex;
private String email;
/**
* 提供一个带参构造方法,同时提供一个无参构造方法
* 提供Getter()和Setter()
* 提供toString()
*/
public User(Integer id, String username, String password, Integer age, String sex, String email) {
this.id = id;
this.username = username;
this.password = password;
this.age = age;
this.sex = sex;
this.email = email;
}
public User() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", email='" + email + '\'' +
'}';
}
}
创建Mapper接口
Mapper
层和 DAO
(Data Access Object)层是在持久层中用于处理数据访问的两个概念。虽然这两者的目的都是用于访问数据库,但它们之间有一些区别。在Java开发中,这两个概念通常与MyBatis(或其他ORM框架)结合使用。简单说一下这两个的区别:
Mapper层
- 定义:Mapper 是 MyBatis 中的一个术语,指的是用于描述如何映射数据库中的数据到 Java 对象的接口。每一个 Mapper 接口对应一个数据操作接口。
- 职责:
- 定义了数据访问的接口,提供了一组数据库操作的方法。
- 包含了与具体数据访问有关的 SQL 语句和映射规则。
- 使用方法:通常使用 MyBatis 的注解或 XML 文件来描述 SQL 映射。
Dao层
- 定义:DAO(Data Access Object)是一个更通用的设计模式,用于封装与数据存储交互的逻辑。在Java中,通常指的是数据访问对象。
- 职责:
- 提供了一个抽象接口,定义了数据访问的方法。
- 包含了数据访问的具体实现,可以是直接对数据库进行操作,也可以通过调用其他服务、API等方式进行数据访问。
- 使用方法:通常不涉及注解,而是通过实现接口的方式来定义数据访问方法。
// DAO 接口
public interface UserDao {
User getUserById(int id);
void insertUser(User user);
}
// DAO 实现
public class UserDaoImpl implements UserDao {
// 数据库操作的具体实现
}
这里我们既然使用了MyBatis框架,就直接使用Mapper接口来映射数据库中的数据
public interface UserMapper { // Mapper不需要实现类
/**
* 添加用户信息
* @return Integer
*/
Integer insertUser();
}
创建MyBatis的Mapper映射文件
上面我们提到Mapper可以通过注解或者是XML 文件来描述 SQL 映射,所以我们需要这样一个XML文件去实现我们接口中的方法,但是要注意:映射文件在resources文件夹的路径要和Mapper接口的路径一致,比如这里我的Mapper接口在com/qcby/mybatis/mappers
,这个路径下,那么我的XML文件就需要在:resources文件夹的com/qcby/mybatis/mappers
路径下,保持一致
然后Mapper.xml文件里面也要保持两个一致:
- Mapper接口中的全类名和映射文件的命名空间(namespace)保持一致
- Mapper接口中的方法名和映射文件中编写SQL的标签的id属性保持一致
<?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">
<!-- namespace命名空间 和Mapper中的全类名保持一致 -->
<mapper namespace="com.qcby.mybatis.mappers.UserMapper">
<!-- mapper接口中的方法名和映射文件中编写sql的标签的id属性保持一致 -->
<insert id="insertUser">
insert into user values (null, 'Ray', '928322', 23, '男', 'Ray@qq.com')
</insert>
</mapper>
编写测试类
上面的架构搭好之后,我们来进行功能测试:
传统测试类
传统测试功能一般在main()
方法中实现,有以下的步骤:
- 获取核心配置文件
- 获取
SqlSessionFactoryBuilder
对象 - 获取
SqlSessionFactory
对象 - 获取
SqlSession
对象 - 获取代理实现类对象
- 调用方法,实现对应功能
- 关闭流
我们来看看具体实现代码:
public class mybatisTest {
// 传统的测试方法:
public static void main(String[] args) throws IOException {
// 1. 获取核心配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 获取SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 3. 获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
// 4. 获取SqlSession对象(不会自动提交事务)
SqlSession sqlSession = sqlSessionFactory.openSession();
// 5. 获取代理实现类对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 6. 调用方法实现添加功能
Integer num = userMapper.insertUser();
if (num > 0) {
System.out.println("插入成功!!");
} else {
System.out.println("插入失败!!");
}
sqlSession.close();
inputStream.close();
}
}
但是我们执行之后会发现,虽然提示插入成功,但是数据库中其实并没有添加们前面设定好的数据——(null, 'Ray', '928322', 23, '男', 'Ray@qq.com')
,如下图:
数据库中并没有更新
这是因为数据库在获取到添加或者是更新这种命令的时候需要确认事务是否提交,这里我们并没有提交事务,所以数据库自动回滚到插入之前的状态,也就是不作出改变。下面介绍三种事务提交的方式:
-
在执行插入方法之后手动提交事务
sqlSession.commit(); 提交事务
-
自动提交事务
SqlSession sqlSession = sqlSessionFactory.openSession(true);
我们在创建
SqlSession
对象的时候添加参数true,可以实现事务的自动提交 -
传统方法提交事务(最不常用)
我们用命名空间+id的方式来实现接口中的方法,同样需要手动提交事务
int num = sqlSession.insert("com.qcby.mybatis.mappers.UserMapper.insertUser"); sqlSession.commit(); 提交事务
提交事务之后,我们就可以正常的插入数据了,
但是我们发现新插入的数据id并不是连续的,这是因为虽然数据库虽然回滚到插入之前的状态,但是id已经自增了一个,下次插入就会继续自增,导致了不连续。
JUnit测试
我们可以发现传统测试类我们如果想测试一个新方法就需要重新创建各种对象,执行很多重复的步骤来实现测试功能,那么我们使用JUnit测试,来简化这一过程。
首先我们将重复的步骤都写在一个公共类——SqlSessionUtil
中,如:SqlSession对象的创建过程以及执行完测试之后的销毁过程:
public class SqlSessionUtil {
private static InputStream inputStream = null;
public static SqlSession getSqlSession() {
SqlSession sqlSession = null;
try {
// 1. 获取核心配置文件
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 获取SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 3. 获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
// 4. 获取SqlSession对象
sqlSession = sqlSessionFactory.openSession(true);
} catch (IOException e) {
throw new RuntimeException(e);
}
return sqlSession;
}
// 关闭输入流
public static void closeInputStream() {
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
这样我们只需要在测试类中通过这个公共类初始化我们的SqlSession
对象,然后重复使用这个对象就可以了。JUnit为我们提供了这个功能,我们使用注解来实现:初始化、测试、销毁等功能,下面我结合代码简单介绍一下三种常用的JUnit注解:
-
@Before:在
@Before
注解下的代码块会自动在执行其他代码块的时候执行,通常用于初始化操作,比如我们就可以通过此注解实现我们的SqlSession
对象的创建:@Before public void init(){ // 通过公共类获取SqlSession对象 sqlSession = SqlSessionUtil.getSqlSession(); // 5. 获取代理实现类对象 userMapper = sqlSession.getMapper(UserMapper.class); }
-
@After:这个也好理解,此注解下的代码会在其他代码块执行完毕之后自动执行,通常用于销毁操作,比如关闭各种输入输出流:
@After public void destroy(){ sqlSession.close(); SqlSessionUtil.closeInputStream(); }
-
@Test:
@Test
注解算是我们JUnit测试的核心注解,他可以让我们的代码块摆脱main()
方法执行,我们只需要定义方法执行逻辑,然后就可以直接运行,执行测试功能:@Test public void userInsertTest() throws IOException { // 6. 调用方法实现添加功能 Integer num = userMapper.insertUser(); if (num > 0) { System.out.println("插入成功!!"); } else { System.out.println("插入失败!!"); } }
至此我们就搭建好了一个简单的MyBatis框架,用于各种功能的测试