一: MyBatis
1.1 引入 MyBatis
我们学习 MySQL 数据库时,已经学习了 JDBC 来操作数据库, 但是 JDBC 操作太复杂了.
我们先来回顾⼀下 JDBC 的操作流程:
- 创建数据库连接池 DataSource
- 通过 DataSource 获取数据库连接 Connection
- 编写要执行带 ? 占位符的 SQL 语句
- 通过 Connection 及 SQL 创建操作命令对象 Statement
- 替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值
- 使用 Statement 执行 SQL 语句
- 查询操作:返回结果集 ResultSet,更新操作:返回更新的数量
- 处理结果集
- 释放资源
对于 JDBC 来说,整个操作非常的繁琐,我们不但要拼接每⼀个参数,而且还要按照模板代码方式,⼀步步的操作数据库,并且在每次操作完,还要手动关闭连接等,而所有的这些操作步骤都需要在每个方法中重复书写. 那有没有⼀种方法,可以更单、更方便的操作数据库呢?
这就是我们要学习 MyBatis 的真正原因,它可以帮助我们更方便、更快速的操作数据
库.
1.2 什么是 MyBatis
MyBatis 是⼀款优秀的持久层框架,⽤于简化 JDBC 的开发。
持久层:指的就是持久化操作的层, 通常指数据访问层(dao), 一般用来操作数据库.
二: MyBatis 入门
Mybatis操作数据库的步骤:
- 准备工作 (创建 springboot 工程、数据库表准备、实体类)
- 引入 Mybatis 的相关依赖,配置 Mybatis (数据库连接信息)
- 编写 SQL 语句 (注解 / XML)
- 测试
2.1 创建工程
- 创建 springboot 工程,并导入 mybatis 的起步依赖、mysql 的驱动包
Mybatis 是⼀个持久层框架, 具体的数据存储和数据操作还是在 MySQL 中操作的, 所以需要添加 MySQL 驱动
- 项目工程创建完成后自动在 pom.xml ⽂件中,导入 Mybatis 依赖和 MySQL 驱动依赖
<!--Mybatis 依赖包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<!--mysql驱动包-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
2.2 数据准备
- 创建用户表, 并创建对应的实体类 User
-- 创建数据库
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');
- 创建对应的实体类 UserInfo
import 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;
}
2.3 配置数据库连接字符串
Mybatis 中要连接数据库,需要数据库相关参数配置:
- MySQL驱动类
- 登录名
- 密码
- 数据库连接字符串
- application.yml 文件
# 数据库连接配置
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
- application.properties 文件
#驱动类名称
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
2.4 写持久层代码
在项目中, 创建持久层接口 UserInfoMapper
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserInfoMapper {
//查询所有⽤⼾
@Select("select username, `password`, age, gender, phone from userinfo")
public List<UserInfo> queryAllUser();
}
@Mapper
注解:这个注解标识了这个接口是一个 MyBatis 的 Mapper 接口,它告诉MyBatis框架要为这个接口创建一个实现类,并且这个实现类会自映射SQL语句的方法。@Select
注解:这个注解标识了 queryAllUser() 方法使用的查询语句,当需要执行数据库操作时,应用程序会调用 UserInfoMapper 接口中的 queryAllUser() 方法。
注意:Mybatis 的持久层接口规范⼀般都叫 XxxMapper
2.5 单元测试
2.5.1 使用 test 测试类
在创建出来的 SpringBoot 工程中,在 src 下的 test 目录下,已经自动帮我们创建好了测试类 ,我们可以直接使用这个测试类来进行测试.
import 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 Boot 应用程序的集成测试类。这个注解告诉测试运行器去加载整个 Spring 应用程序上下文,包括所有的 bean,并为测试提供一个实例化的应程序环境。
- @Test 注解用于标识一个测试方法。当运行测试类时,它会搜索类中带有 @Test 注解的方法,并执行这些方法。
运行结果如下:
返回结果中, 可以看到, 只有 SQL 语句中查询的列对应的属性才有赋值
2.5.2 使用 Idea 自动生成测试类
除此之外, 也可以使用 Idea 自动生成测试类
- 在需要测试的Mapper接口中, 右键 -> Generate -> Test
2. 选择要测试的方法, 点击 OK
- 书写测试代码
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 UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void queryAllUser() {
List<UserInfo> userInfoList = userInfoMapper.queryAllUser();
System.out.println(userInfoList);
}
}
记得加 @SpringBootTest 注解, 加载 Spring 行环境
运行结果:
三: MyBatis 的基础操作
上面我们学习了 Mybatis 的查询操作, 接下来我们学习 MyBatis 的增, 删, 改操作,在学习这些操作之前, 我们先来学习 MyBatis 日志打印
3.1 打印日志
在 Mybatis 当中我们可以借助日志, 查看到 sql 语句的执行语句、执行传递参数以及执行结果
- yml 配置
mybatis:
configuration: # 配置打印 MyBatis⽇志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
- properties 配置
#指定mybatis输出⽇志的位置, 输出控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
重新运行程序, 可以看到 SQL 执行内容, 以及传递参数和执行结果
- ①: 查询语句
- ②: 传递参数及类型
- ③: SQL执行结果
3.2 参数传递
@Select("select username, `password`, age, gender, phone from userinfo where id= 4 ")
UserInfo queryById();
这条语句只能查找 id=4 的数据,有时候我们需要 id 的数值是动态的,我们该怎么做呢
解决方案:
- 在 queryById 方法中添加⼀个参数 (id),将方法中的参数,传给 SQL 语句
- 然后使用 #{} 的方式获取方法中的参数
@Select("select username, `password`, age, gender, phone from userinfo where id= #{id} ")
UserInfo queryById(Integer id);
如果 mapper 接口方法形参只有⼀个普通类型的参数,#{…} 里面属性名可以随便写,但如果有多个参数的话,建议和参数名保持⼀致
- 测试用例
@Test
void queryById() {
UserInfo userInfo = userInfoMapper.queryById(4);
System.out.println(userInfo);
}
- 运行结果
我们也可以通过 @Param , 设置参数的别名, 如果使用 @Param 设置别名, #{…} 里面的属性名必须和 @Param 设置的⼀样
@Select("select username, `password`, age, gender, phone from userinfo where id= #{userid} ")
UserInfo queryById(@Param("userid") Integer id);
3.3 增 (Insert)
- SQL 语句:
insert into userinfo (username, `password`, age, gender, phone) values ("zhaoliu","zhaoliu",19,1,"18700001234")
- 把 SQL 中的常量替换为动态的参数,用 Mapper 接口:
@Insert("insert into userinfo (username, `password`, age, gender, phone) values (#{username},#{password},#{age},#{gender},#{phone})")
Integer insert(UserInfo userInfo);
- 测试代码:
@Test
void insert() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("zhaoliu");
userInfo.setPassword("zhaoliu");
userInfo.setGender(2);
userInfo.setAge(21);
userInfo.setPhone("18612340005");
userInfoMapper.insert(userInfo);
}
- 如果设置了 @Param 属性, #{…} 需要使用参数.属性来获取
@Insert("insert into userinfo (username, `password`, age, gender, phone) values (#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gender},#{userinfo.phone})")
Integer insert(@Param("userinfo") UserInfo userInfo);
3.3.1 返回主键
Insert 语句默认返回的是受影响的行数,但有些情况下我们需要获取到新插入数据的 id ,如果想要拿到自增 id, 需要在 Mapper 接口的方法上添加⼀个 Options 的注解
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into userinfo (username, age, gender, phone) values (#{userinfo.username},#{userinfo.age},#{userinfo.gender},#{userinfo.phone})")
Integer insert(@Param("userinfo") UserInfo userInfo);
- @Options: 这是 Myatis 中的一个注解,用于配置一些选项以影响 SQL 语句的执行方式。
- useGeneratedKeys = true 这个选项告诉 MyBatis 使用数据库自动生成的主键。
- Property = “id”: 这个选项指定了用来接收生成的主键值的属性名。这个例子中,自动生成的主键值将会被赋给 UserInfo 对象中名为 id 的属性。
测试数据:
@Test
void insert() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("zhaoliu");
userInfo.setPassword("zhaoliu");
userInfo.setGender(2);
userInfo.setAge(21);
userInfo.setPhone("18612340005");
Integer count = userInfoMapper.insert(userInfo);
System.out.println("添加数据条数:" +count +", 数据ID:" + userInfo.getId());
}
运行结果:
注意: 设置 useGeneratedKeys=true 之后, 方法返回值依然是受影响的行数, 自增 id 会设置在上述 keyProperty 指定的属性中.
3.4 删(Delete)
SQL 语句:
delete from userinfo where id=6
把 SQL 中的常量替换为动态的参数:
@Delete("delete from userinfo where id = #{id}")
void delete(Integer id);
3.5 改(Update)
SQL 语句:
update userinfo set username="zhaoliu" where id=5
把 SQL 中的常量替换为动态的参数:
@Update("update userinfo set username=#{username} where id=#{id}")
void update(UserInfo userInfo);
3.6 查(Select)
我们在上面查询时发现, 有几个字段是没有赋值的, 只有Java对象属性和数据库字段⼀模⼀样时, 才会进行赋值,接下来我们多查询⼀些数据
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from userinfo")
List<UserInfo> queryAllUser();
- 查询结果:
从运行结果上可以看到, 我们 SQL 语句中, 查询了 delete_flag, create_time, update_time, 但是这几个属性却没有赋值.
- 原因分析:
当自动映射查询结果时,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略大小写)。 这意味着如果发现了 ID 列和 id 属性,MyBatis 会将列 ID 的值赋给 id 属性,但如果名字不一样就无法进行赋值了。
解决办法:
- 起别名
- 结果映射
- 开启驼峰命名
注意:MyBatis 会根据方法的返回结果进行赋值.
- 方法用对象 UserInfo 接收返回结果, MySQL 查询出来数据为⼀条, 就会自动赋值给对象.
- 方法用 List< UserInfo > 接收返回结果, MySQL 查询出来数据为⼀条或多条时, 也会自动赋值给 List.
- 但如果 MySQL 查询返回多条, 但是方法使用 UserInfo 接收, MyBatis 执行就会报错.
3.6.1 起别名
在 SQL 语句中,给列名起别名,保持别名和实体类属性名⼀样
@Select("select id, username, `password`, age, gender, phone, delete_flag as deleteFlag, " + "create_time as createTime, update_time as updateTime from userinfo")
public List<UserInfo> queryAllUser();
SQL 语句太长时, 使用加号 + 进行字符串拼接
3.6.2 结果映射
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from userinfo")
@Results({
@Result(column = "delete_flag",property = "deleteFlag"),
@Result(column = "create_time",property = "createTime"),
@Result(column = "update_time",property = "updateTime")
})
List<UserInfo> queryAllUser();
如果其他 SQL, 也希望可以复用这个映射关系, 可以给这个 Results 定义⼀个名称
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from userinfo")
@Results(id = "resultMap",value = {
@Result(column = "delete_flag",property = "deleteFlag"),
@Result(column = "create_time",property = "createTime"),
@Result(column = "update_time",property = "updateTime")
})
List<UserInfo> queryAllUser();
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time " + "from userinfo where id= #{userid} ")
@ResultMap(value = "resultMap")
UserInfo queryById(@Param("userid") Integer id);
使用 id 属性给该 Results 定义别名, 使用 @ResultMap 注解来复用其他定义的 ResultMap
3.6.3 开启驼峰命名 (推荐)
通常数据库列使用蛇形命名法进行命名,而 Java 属性⼀般遵循驼峰命名法约定,为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase 设置为 true。
mybatis:
configuration:
map-underscore-to-camel-case: true #配置驼峰⾃动转换
Java 代码不做任何处理
@Select("select id, username, `password`, age, gender, phone, delete_flag as deleteFlag, " + "create_time as createTime, update_time as updateTime from userinfo")
public List<UserInfo> queryAllUser();
添加上述配置, 运行代码:
字段全部进行正确赋值.