MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射;MyBatis 去除了几乎所有的 JDBC 代码和动设置参数以及获取结果集的工作;MyBatis可以使用简单的 XML 或注解来配置和映射原始类型,将接口和 Java 的 POJO(Plain Old Java Objects,普通老式的 Java 对象)映射成数据库中的记录。
简单来说 MyBatis 其实就是对JDBC进行了封装,它是更简单的完成程序和数据库交互的⼯具,也就是更简单的操作和读取数据库工具。
所以我们使用 MyBatis 的时候需要创建 Mapper(Dao)层接口以及对应的xml
文件,也可以使用注解代替xml
文件进行写 SQL 的方式,但是 SQL 语句复杂的时候使用注解这种方式不太好实现且不太美观
MyBatis 在整个框架中的定位交互如下图:
MyBatis 也是⼀个 ORM 框架,ORM(Object Relational Mapping),即对象关系映射;在面向
对象编程语⾔中,将关系型数据库中的数据与对象建立起映射关系,进而自动的完成数据与对象
的互相转换:
ORM 把数据库映射为对象:
⼀般的 ORM 框架,会将数据库模型的每张表都映射为⼀个 Java 类,也就是说使⽤ MyBatis 可以像操作对象⼀样来操作数据库中的表,可以实现对象和数据库表之间的转换。
这里创建以下数据库和数据表。
-- 创建数据库
drop database if exists mycnblog2023;
create database mycnblog2023 DEFAULT CHARACTER SET utf8mb4;
-- 使用数据数据
use mycnblog2023;
-- 创建表[用户表]
drop table if exists userinfo;
create table userinfo(
id int primary key auto_increment,
username varchar(100) not null,
password varchar(32) not null,
photo varchar(500) default '',
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
`state` int default 1
) default charset 'utf8mb4';
-- 创建文章表
drop table if exists articleinfo;
create table articleinfo(
id int primary key auto_increment,
title varchar(100) not null,
content text not null,
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
uid int not null,
rcount int not null default 1,
`state` int default 1
)default charset 'utf8mb4';
-- 创建视频表
drop table if exists videoinfo;
create table videoinfo(
vid int primary key,
`title` varchar(250),
`url` varchar(1000),
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
uid int
)default charset 'utf8mb4';
-- 添加一个用户信息
INSERT INTO `mycnblog2023`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);
-- 文章添加测试数据
insert into articleinfo(title,content,uid)
values('Java','Java正文',1);
-- 添加视频
insert into videoinfo(vid,title,url,uid) values(1,'java title','http://www.baidu.com',1);
下面的内容主要是对userinfo
表进行操作演示,目前执行上述 sql 后,表内的数据如下:
所以我们针对我们要操作的数据表,还应该有对应的映射类(userinfo 表对应一个 UserInfo 类)。
package com.example.demo.model;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class UserInfo {
private int id;
private String username;
private String password;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private int state;
}
2️⃣第二步,勾选上 MyBatis 相关依赖,包括Mybatis Framework
(一定不能少)和MySQL Driver
(看你使用的是哪个数据库就添加哪个)。
3️⃣第三步,项目创建好后此时如果我们直接启动项目是会报错的,我们还需要在application
配置文件中配置数据库数据源和 MyBatis xml
文件保存路径,其中这个xml
文件是用来编写sql
语句用的。
先配置数据库链接信息,需要配置数据库的 url,账号与密码以及驱动。
.properties
文件格式:
# 设置数据库的相关连接信息
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mycnblog2023?characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=111111
# 版本8之前版本的数据库使用 com.mysql.jdbc.Driver
# 版本8以及之后版本的数据库使用 com.mysql.cj.jdbc.Driver
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
.yml
文件格式:
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8
username: root
password: 111111
driver-class-name: com.mysql.cj.jdbc.Driver
接下来设置 MyBatis xml 文件的路径:
.properties
文件格式:
# 设置 MyBatis XML 存放路径和命名格式
mybatis.mapper-locations=classpath:mybatis/*Mapper.xml
.yml
文件格式:
mybatis:
mapper-locations: classpath:mybatis/*Mapper.xml
其中*Mapper.xml
表示只认Mapper.xml
结尾的文件,存放在资源文件(resources)的mybatis
路径下。
如果需要更方便观察有关 sql 的运行情况,还可以配置一些日志文件,来显示 sql 相关的日志(debug级别,默认是不显示的):
.properties
文件格式:
# 配置 MyBatis 执行时打印 SQL(可选配置)
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
logging.level.com.example.demo=debug
.yml
文件格式:
logging:
level:
com:
example:
demo: debug
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
这样我们的项目就创建好了,下面的内容介绍 MyBatis 的基本使用。
MyBatis 的基本使用流程:
@Mapper
修饰的接口(用来给 Service 层调用),该注解来自 MyBatis,作用是与我们配置的Mapper.xml
结尾的文件做连接。1️⃣第一步,我们定义接口,该接口在软件分层中属于数据持久层,所以我们定义在自建的dao
包下(也可定义为 mapper),为了提高代码的可读性和规范性,接口名称建议Mapper
结尾。
@Mapper
public interface UserMapper {
//方法声明
}
2️⃣第二步,我们在该接口下声明方法,由于该方法的目的就是为了被xml
文件“实现”,然后去操作数据库的,比如我们需要所有查询User
对象的结果,我们声明一个getAll
方法。
// 查询全部用户信息
List<UserInfo> getAll();
3️⃣第三步,在配置文件所规定的目录下创建UserMapper.xml
文件,并加上下面的代码,一般一个 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.demo.mapper.UserMapper">
</mapper>
当我们要编写 sql 时,需要在
标签里面写对应的标签来实现对数据库的操作,比如查询使用select
标签,插入使用insert
标签,更新使用pudate
标签,删除使用delete
标签等。
而在mapper
标签中,我们需要设置该mapper
标签对应的接口在哪里,就是在mapper
标签头中设置namespace
属性,值为加包名的Mapper
接口,如:
<mapper namespace="com.example.demo.mapper.UserMapper">
</mapper>
比如要查询全部用户信息,其中标签头的id
属性与要实现的方法名对应,resultType
表示需要映射的类是哪一个,需要写出完整的包名,这样就会将查询的结果存入到该类的对象中对对象数组中,有一点是需要注意的,那就是使用resultType
映射对象一定得保证数据库中的字段名与对象中的属性名一模一样,否则不能匹配赋值。
<select id="getAll" resultType="com.example.demo.model.UserInfo">
select * from userinfo
</select>
完成上面的工作后我们可以使用单元测试验证我们写的代码是否符合预期。
1️⃣第一步,我们在类中代码右键,选择Generate
选项,比如我们在 Mapper 接口中生成单元测试的类,我们就在右键选择Generate
选项。
然后会跳出一个框,选择 Test。
3️⃣第三步,第二步勾选上需要测试的方法后,会在 test 目录下生成一个同级的目录,会自动生成测试类和测试方法,当然我们也能够手动创建,创建的测试类就在原来类名最后加上一个Test
即可。
再完成以下步骤:
@SpringBootTest
注解,这个一定不要忘记,它的作用是告诉当前的测试程序,当前的项目是运行在 SpringBoot 容器中。@Test
注解上面已经创建好了 Mapper 接口和对应的 xml 文件了,此时我们就可以开始写数据库 sql 的语句了,先介绍基础使用,完成对单表的 CRUD 操作。
1️⃣第一步,在 Mapper 接口中声明一个方法,比如通过用户id
查询用户信息,就可以声明一个方法getUserById
。
UserInfo getUserById(@Param(value = "id") Integer id);
@Param
注解中的value
参数值表示对变量重命名,此时传入到 xml 文件中的变量名字就是设置的这个值,建议该名字与数据库中的字段相对应并同名。
2️⃣第二步,设置 xml 文件所映射的 Mapper 接口,假设我们映射了一个如下图dao
包中的UserMapper
接口,该接口一定要使用@Mapper
修饰,否则就不是属于 MyBatis 中的“接口”,不能与 xml 产生映射。
我们将mapper
标签中的namespace
设置为对应接口在项目目录下的带包全称。
<mapper namespace="com.example.demo.mapper.UserMapper">
3️⃣第三步,设置查询标签以及配置与方法的映射,查询操作使用select
标签,查询标签至少设置两个属性,第一个是id
表示与哪一个方法相对应,就是设置成相关联的方法的方法名,比如我们在 Mapper 接口中的查询方法是getUserById
,则设置id="getUserById"
;
然后设置resultType
属性的值,我们查询需要返回查询结果UserInfo
对象,如果可能有多个结果,对应的方法返回值就写List
,否则写UserInfo
即可,resultType
属性中只需要设置成带包名的类名即可。
<select id="getUserById" resultType="com.example.demo.model.UserInfo">
4️⃣第四步,在 mapper 标签中编写查询 sql,查询 sql 对应的标签就是select
标签,如我们要查询id
为1
的用户,那么标签里写的内容如下:
<select id="getUserById" resultType="com.example.demo.model.UserInfo">
select * from userinfo where id=1
</select>
但是这样写太 sql 就写死了,代码没有通用性,我们可以使用预处理符#{}
或者替换符${}
来将程序传入的参数替换到 sql 语句中:
<select id="getUserById" resultType="com.example.demo.model.UserInfo">
select * from userinfo where id=${id}
//or select * from userinfo where id=#{id}
</select>
其中id=${id}
相当于id=传入的id变量的值
,比如id=1
,则${id}
会被替换为1
,使用该方法存在 SQL 注入的问题,并且传入字符串等其他非数值类型可能会出现问题,而id=#{id}
相当于id=?
,相当于替换成一个占位符,然后会将传入的id
通过占位符的形式插入到 sql 语句中,可以防止 SQL 注入问题,并且适用于所有类型的变量,关于这两者更多的介绍会在下一篇多表查询中介绍。
经过以上步骤我们的 sql 就写好了,由于我们的id
是唯一的,所以查询到的结果也是唯一的,我们以id=1
进行查询演示,目前数据库的数据如下:
然后我们再写单元测试代码进行验证:
@Test
void getUserById() {
UserInfo userinfo = userMapper.getUserById(1);
System.out.println(userinfo.toString());
}
结果如下:
我们在 userinfo 表中再插入一条记录,此时表中数据如下:
现在我们演示如何使用 MyBatis 根据id
修改对应用户名username
,其实和查询操作也差不多,默认返回受影响的行数,是基础类型,此时我们是不用设置resultType
的。
1️⃣第一步,声明方法:
// 根据 id 修改用户名
Integer updateName(@Param(value = "id") Integer id, @Param(value = "username") String username);
2️⃣第二步,在 xml 中写 sql,修改操作使用update
标签。
<update id="updateName" >
update userinfo set username=#{username} where id=#{id}
</update>
我们将id=2
的用户名修改为张三
,生成单元测试:
@Test
void updateName() {
int result = userMapper.updateName(2, "张三");
System.out.println("受影响行数: " + result);
}
再来验证一下数据库中的数据是否完成了修改,
结果是符合我们的预期的,实际上单元测试是不应该去影响数据库中的数据的,我们可以在单元测试方法上使用@Transactional
注解,这样就可以防止污染数据库,实现原理是利用了数据库事务的回滚,比如:
执行单元测试:
此时数据库是没有发生修改的,原因是@Transactional
注解进行了事务的回滚,但是自增主键不会进行回滚,比如你进行了id
为3
用户插入操作,事务回滚了,下一次插入自增主键id
的值为4
:
这也是使用单元测试不会污染数据库的原因所在。
删除其实和查询,修改也是一样的,我们来演示将id
为2
的数据删除。
1️⃣第一步,声明方法:
// 根据id删除用户
public Integer delById(@Param(value = "id") Integer id);
2️⃣第二步,在 xml 文件编写 sql,删除操作使用delete
标签:
<delete id="delById">
delete from userinfo where id=#{id}
</delete>
3️⃣第三步,编写单元测试验证:
@Test
void delById() {
int result = userMapper.delById(2);
System.out.println("受影响行数: " + result);
}
该操作相比于之前的查询,修改和删除操作要复杂一点,但其实也差不多,为了提高代码的通用性,我们声明方法中的形参传入一个UserInfo
对象。
首先,我们来演示插入一条数据,返回一个受影响的行数。
1️⃣第一步,声明方法:
// 添加用户,返回影响行数
Integer add(UserInfo userInfo);
2️⃣第二步,编写 sql 语句,插入操作使用insert
标签。
<insert id="add">
insert into userinfo(username, password, photo) values (#{username}, #{password},#{photo});
</insert>
3️⃣第三步,编写并执行单元测试,验证数据库。
@Test
void add() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("老六");
userInfo.setPassword("666");
int result = userMapper.add(userInfo);
System.out.println("受影响行数: " + result);
}
其实,Mybatis 也可以返回多个参数,比如返回受影响的行数和自增id
的值,这个时候我们就需要对xml
的insert
标签进行配置。
前面声明方法还是需要的:
Integer addGetId(UserInfo userInfo);
xml 的insert
标签中还需要设置useGeneratedKeys="true"
表示为是否自增主键,keyProperty="id"
表示自增主键是哪一个字段。
<insert id="addGetId" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
insert into userinfo(username, password, photo) values (#{username}, #{password},#{photo});
</insert>
单元测试方法:
@Test
void addGetId() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("老八");
userInfo.setPassword("888");
userInfo.setPhoto("default.png");
System.out.println("插入之前的id=" + userInfo.getId());
int result = userMapper.addGetId(userInfo);
System.out.println("插入之后的id=" + userInfo.getId());
System.out.println("受影响行数: " + result);
}
单元测试结果:
这个其实很简单,使用配置文件我们需要在指定路径创建配置文件,实现具体接口的执行 sql;而使用注解实现 sql,直接在对应接口上面加上对应操作的注解,括号里面的属性填好 sql 属性即可。
对应操作注解也就对应 SQL 的增删查改,分别为:
我们来简单针对数据表演示一个查询操作,
1️⃣第一步,声明一个方法getAllUser()
:
// 基于注解查询全部用户
List<UserInfo> getAllUser();
2️⃣第二步,使用@Select
实现具体的 sql 操作。
单元测试代码:
@Test
void updateName() {
int result = userMapper.updateName(2, "张三");
System.out.println("受影响行数: " + result);
}
结果如下:
其他注解使用起来也是一样的,就不在这里赘述了。