MyBatis 是⼀款优秀的持久层框架,它⽀持⾃定义 SQL、存储过程以及⾼级映射。MyBatis 去除了几乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。MyBatis 可以通过简单的 XML 或注解来配置 和映射原始类型、接⼝和 Java POJO(Plain Old Java Objects,普通⽼式 Java 对象)为数据库中的记录。
简单来说 MyBatis 是更简单完成程序和数据库交互的⼯具,也就是更简单的操作和读取数据库⼯具。
Mybatis官⽹
对于后端开发来说,程序是由以下两个重要的部分组成的:
1. 后端程序
2. 数据库
⽽这两个重要的组成部分要通讯,就要依靠数据库连接⼯具,那数据库连接⼯具有哪些?⽐如之前的 JDBC,还有今天我们将要介绍的 MyBatis,那已经有了 JDBC 了,为什么还要学习 MyBatis? 这是因为 JDBC 的操作太繁琐了,我们回顾⼀下 JDBC 的操作流程:
1. 创建数据库连接池 DataSource
2. 通过 DataSource 获取数据库连接 Connection
3. 编写要执⾏带 ? 占位符的 SQL 语句
4. 通过 Connection 及 SQL 创建操作命令对象 Statement
5. 替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值
6. 使⽤ Statement 执⾏ SQL 语句
7. 查询操作:返回结果集 ResultSet,更新操作:返回更新的数量
8. 处理结果集
9. 释放资源
对于 JDBC 来说,整个操作⾮常的繁琐,我们不但要拼接每⼀个参 数,⽽且还要按照模板代码的⽅式,⼀步步的操作数据库,并且在每次操作完,还要⼿动关闭连接等, ⽽所有的这些操作步骤都需要在每个⽅法中重复书写。于是我们就想,那有没有⼀种⽅法,可以更简 单、更⽅便的操作数据库呢?
答案是肯定的,学习 MyBatis 的真正原因,就是它可以帮助我们更⽅便、更快速的操作数据 库。
MyBatis 学习只分为两部分:
开始搭建 MyBatis 之前,我们先来看⼀下 MyBatis 在整个框架中的定位,框架交互流程图:
MyBatis 也是⼀个 ORM 框架,ORM(Object Relational Mapping),即对象关系映射。在⾯向 对象编程语⾔中,将关系型数据库中的数据与对象建⽴起映射关系,进⽽⾃动的完成数据与对象 的互相转换:
1. 将输⼊数据(即传⼊对象)+SQL 映射成原⽣ SQL
2. 将结果集映射为返回对象,即输出对象
ORM 把数据库映射为对象:
数据库表(table)--> 类(class)
记录(record,⾏数据)--> 对象(object)
字段(field) --> 对象的属性(attribute)
⼀般的 ORM 框架,会将数据库模型的每张表都映射为⼀个 Java 类。 也就是说使⽤ MyBatis 可以像操作对象⼀样来操作数据库中的表,可以实现对象和数据库表之间 的转换,接下来我们来看 MyBatis 的使⽤吧。
接下来我们要实现的功能是:使⽤ MyBatis 的⽅式来读取⽤户表中的所有⽤户,我们使⽤个⼈博 客的数据库和数据包,具体 SQL 如下。
-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;
-- 使⽤数据数据
use mycnblog;
-- 创建表[⽤户表]
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 datetime default now(),
updatetime datetime default now(),
`state` int default 1
) default charset 'utf8mb4';
添加 MyBatis 框架⽀持分为两种情况:⼀种情况是对⾃⼰之前的 Spring 项⽬进⾏升级,另⼀种情况是 创建⼀个全新的 MyBatis 和 Spring Boot 的项⽬,下⾯我们分别来演示这两种情况的具体实现。
如果是在⽼项⽬中新增功能,添加框架⽀持:
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.4
mysql
mysql-connector-java
5.1.38
runtime
添加了 MyBatis 之后,为什么还需要添加 MySQL 驱动呢? MyBatis 就像⼀个平台(类似京东),⽽数据库相当于商家有很多种,不⽌有 MySQL,还有 SQL Server、DB2 等等.....因此这两个都是需要添加的。
在⽼项⽬中快速添加框架,更简单的操作⽅式是使⽤EditStarters插件
EditStarters 插件的使⽤⽅法:
搜索“MyBatis”添加即可:
此步骤需要进⾏两项设置,数据库连接字符串设置和 MyBatis 的 XML ⽂件配置。
如果是 application.yml 添加如下内容:
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/mycnblog?characterEncoding=utf8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
注意事项:
如果使⽤ mysql-connector-java 是 5.x 之前的使⽤的是“ com.mysql.jdbc.Driver ” ,如果是⼤于 5.x 使⽤的是“ com.mysql.cj.jdbc.Driver ” 。
MyBatis 的 XML 中保存是查询数据库的具体操作 SQL,配置如下:
# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
下⾯按照后端开发的⼯程思路,也就是下⾯的流程来实现 MyBatis 查询所有⽤户的功能:
先添加⽤户的实体类:
package com.example.springmybatisdemo.model;
import lombok.Data;
import java.util.Date;
@Data
public class User {
private Integer id;
private String name;
private String password;
private String photo;
private Date createtime;
private Date updatetime;
}
数据持久层的接⼝定义:
package com.example.springmybatisdemo.mapper;
import com.example.springmybatisdemo.model.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserMapper {
List queryAll();
}
数据持久成的实现,mybatis 的固定 xml 格式:
UserMapper.xml 查询所有⽤户的具体实现 SQL:
以下是对以上标签的说明:
服务层实现代码如下:
控制器层的实现代码如下:
接下来,我们来实现⼀下⽤户的增加、删除和修改的操作,对应使⽤ MyBatis 的标签如下:
package com.example.springmybatisdemo.mapper;
import com.example.springmybatisdemo.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface UserMapper {
/**
* 查询所有数据
* @return
*/
List queryAll();
/**
* 根据id来查询
*/
User queryById(@Param("uid") Integer id);
/**
* 插入
*/
Integer insert(User user);
/**
* 修改数据
*/
void update(User user);
/**
* 删除数据
*/
void delete(Integer id);
}
insert into userinfo (username,password,photo)values (#{username},#{password},#{photo})
update userinfo set username = #{username},password = #{password},photo = #{photo} where id = #{id}
delete from userinfo where id = #{id}
默认情况下返回的是受影响的⾏号,如果想要返回⾃增 id,具体实现如下
下⾯我们来实现⼀下根据⽤户 id 查询⽤户信息的功能。 Controller 实现代码如下:
@RequestMapping("/getuser")
public User getUserById(Integer id) {
return userService.getUserById(id);
}
Mapper.xml 实现代码如下:
预编译处理是指:MyBatis 在处理#{}时,会将 SQL 中的 #{} 替换为?号,使⽤ PreparedStatement 的 set ⽅法来赋值。直接替换:是MyBatis 在处理 ${} 时,就是把 ${} 替换成变量的值。
使⽤ ${sort} 可以实现排序查询,⽽使⽤ #{sort} 就不能实现排序查询了,因为当使⽤ #{sort} 查询时, 如果传递的值为 String 则会加单引号,就会导致 sql 错误。
sql 注⼊代码:“' or 1='1”
测试:
@Test
void queryByNameAndPassWord() {
String username = "admin";
String password = "' or 1='1";
User user = userMapper.queryByNameAndPassWord(username,password);
log.info(user == null ? null:user.toString());
}
结果:数据没有查询出来,响应正常
测试:
@Test
void queryByNameAndPassWord() {
String username = "admin";
// String password = "123";
String password = "' or 1='1";
User user = userMapper.queryByNameAndPassWord(username,password);
log.info(user == null ? null:user.toString());
}
结果:数据不该被查出来,结果被查出来
排序时,只能使用$。
使用#号
使用$符号
需要使用MySQL的内置函数
如果是增、删、改返回搜影响的⾏数,那么在 mapper.xml 中是可以不设置返回的类型的:
insert into userinfo (username,password,photo)values (#{username},#{password},#{photo})
update userinfo set username = #{username},password = #{password},photo = #{photo} where id = #{id}
delete from userinfo where id = #{id}
对于
id 属性:⽤于标识实现接⼝中的那个⽅法;
结果映射属性:结果映射有两种实现标签:
绝⼤数查询场景可以使⽤ resultType 进⾏返回,如下代码所示:
它的优点是使⽤⽅便,直接定义到某个实体类即可。
resultMap 使⽤场景:
字段名和属性名不同的情况
数据库中
程序中
public class User {
private Integer id;
private String name;
private String pwd;
private String photo;
private Date createtime;
private Date updatetime;
}
mapper.xml
在多表查询时,如果使⽤ resultType 标签,在⼀个类中包含了另⼀个对象是查询不出来被包含的对象 的,⽐如以下实体类:
@Data
public class ArticleInfo {
private Integer id;
private String title;
private String content;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private Integer rcount;
// 包含了 userinfo 对象
private UserInfo user;
}
程序的执⾏结果如下图所示:
⼀对⼀映射要使⽤
以上使⽤ 标签,
注意:columnPrefix 属性不能省略,如果省略当联表中如果有相同的字段,那么就会导致查询出错。 ⽐如两篇⽂章都是⼀个⼈写的,如果没有 columnPrefix 就会导致查询的⽤户 id(因为和⽂章表 id 相 同)查询出错。
articleinfo:
public class ArticleInfo {
private Integer id;
private String title;
private String content;
private Date createtime;
private Date updatetime;
private Integer rcount;
private User user;
}
Mapper:
@Mapper
public interface ArticleMapper {
List queryArticle();
}
⼀对多需要使⽤
动态 sql 是Mybatis的强⼤特性之⼀,能够完成不同条件下不同的 sql 拼接。
动态SQL:根据输入参数不同,动态的拼接SQL
可以参考官⽅⽂档:mybatis – MyBatis 3 | 动态 SQL
在注册⽤户的时候,可能会有这样⼀个问题,如下图所示:
注册分为两种字段:必填字段和⾮必填字段,那如果在添加⽤户的时候有不确定的字段传⼊,程序应该 如何实现呢?
这个时候就需要使⽤动态标签
insert into articleinfo(title,content,uid
,state
)
values
(#{title},#{content},#{userId}
,#{state}
)
注意 test 中的 state,是传⼊对象中的属性,不是数据库字段。
之前的插⼊⽤户功能,只是有⼀个 state字段可能是选填项,如果所有字段都是⾮必填项,就考虑使⽤
调整 UserMapper.xml 的插⼊语句为:
insert into articleinfo(
title,
content,
uid,
state
)
#{title},
#{content},
#{userId},
#{state}
传⼊的⽤户对象,根据属性做 where 条件查询,⽤户对象中属性不为 null 的,都为查询条件。
测试:
@Test
void queryByCondition() {
List articleInfos = articleMapper.queryByCondition(null,null);
log.info(articleInfos.toString());
}
根据传⼊的⽤户对象属性来更新⽤户数据,可以使⽤标签来指定动态内容。
update articleinfo
uid=#{uid},
state=#{state}
对集合进⾏遍历时可以使⽤该标签。
delete from articleinfo where id in
#{id}
Mybatis的实现,有两种方式
1.xml方式
2.注解的方式
例如:
package com.example.springmybatisdemo.mapper;
import com.example.springmybatisdemo.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserMapper2 {
@Select("select * from userinfo")
List queryAll();
@Select("select * from userinfo where id = #{id}")
User queryById(Integer aaa);
}
测试:
package com.example.springmybatisdemo.mapper;
import com.example.springmybatisdemo.model.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Select;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@Slf4j
@SpringBootTest
class UserMapper2Test {
@Autowired
private UserMapper2 userMapper2;
@Test
void queryAll() {
List userList = userMapper2.queryAll();
log.info(userList.toString());
}
@Test
void queryById() {
User user = userMapper2.queryById(1);
log.info(user.toString());
}
}
但是动态SQL写出来比较复杂