目录
️️️
1 为什么要使用 MyBatis 呢?
2 MyBatis 学习之旅
2.1 配置 MyBatis 开发环境
2.1.1 添加 MyBatis 依赖
2.1.2 设置 MyBatis 的配置
① 数据库连接字符串设置
② MyBatis 的 XML ⽂件配置
2.2 使⽤ MyBatis 操作数据库
2.2.1 查询操作
① 查询全部用户信息
② 根据 id 查询用户信息
️ ③ ${} 与 #{}
④ ${} 的适用场景
2.2.2 MyBatisX
2.2.3 删除操作
2.2.4 修改操作
️2.2.5 添加操作
2.2.6 类中的属性和数据库表中的字段名不一致,查询结果为 null时,该怎么办?
2.2.7 like 查询
2.2.8 多表查询
MyBatis 是一款优秀的持久层框架,它⽀持⾃定义 SQL、存储过程以及⾼级映射。
MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的过程,可以通过简单的 XML 或注解来配置 和映射原始类型、接⼝和 Java POJO(Plain Old Java Objects,普通⽼式 Java 对象)为数据库中的记录。
MyBatis 是更简单完成程序和数据库交互的⼯具,也就是更简单的操作和读取数据库⼯具。
对于后端开发来说,有两个重要的组成部分:后端程序以及数据库。那么这两个独立部分就得通过一些额外的工具来进行数据的相互传递。比如 JDBC,比如这节课要学习的 MyBatis。
为什么学了 JDBC 还得学习 MyBatis 呢?原因在于,JDBC 的语句十分地繁琐,而 MyBatis 可以帮助我们更⽅便、更快速的操作数据库。
JDBC 的操作流程:
创建新的 Spring Boot 项目时:
选择 MyBatis 框架以及数据库的类型。
创建项目之后,如果直接运行,程序会报错:
那是因为没有告诉程序,指定连接哪一个数据库,哪一个 MySQL。如果电脑里面有多个版本的 MySQL,那么项目应该连接哪一个呢?就像你已经选择了打车去车站了,但是得告诉司机,去哪一个车站才行。
即设置要连接的数据库的地址。
# 1. 设置数据库的相关连接信息
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 2. 设置 MyBatis XML 存放路径和命名格式
mybatis.mapper-locations=classpath:mybatis/*Mapper.xml
# 配置 MyBatis 执行时打印 SQL(可选配置)
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
logging.level.com.example.demo=debug
MyBatis 的 XML 中保存的是查询数据库的具体操作 SQL。
常规的写法包含两个文件:
1. 接口:方法的声明(给其他层 Service 调用)
2. xml:具体实现接口里的方法
例子:mycnblog 这个数据库中的用户名查询
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;
}
1. 定义接口
package com.example.demo.Dao;
import com.example.demo.Model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
// 不是普通的接口,而是 MyBatis 中的接口
// 需要添加以下注解
@Mapper // 该注解是数据持久层的标志
public interface UserMapper {
List getAll();
}
2. 使用 XML 实现接口
namespace 使用包名类名来关联接口,标明当前 xml 实现的那个接口,不是使用 xml 文件名和接口进行匹配的。
xml 中有很多标签,可以来实现特定操作。
id 是要实现接口中的方法名。resultType 是返回的数据类型,包名+类名。
查询操作要用 select 标签。select 标签里面是具体的 SQL 语句。
package com.example.demo.Dao;
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;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest // 这个注解不能省略,表明当前测试是在 Spring Boot 环境下运行的
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void getAll() {
List list = userMapper.getAll();
System.out.println(list);
}
}
传递参数的方式,只能使用包装类,因为包装类可以接受 null ,即没有传参,不能使用基础数据类型,因为它们没有 null 类型,为了防止前端没有传参的情况下后端程序不报错。
/**
* 根据 id 查询用户信息
* @param id
* @return
*/
UserInfo getUserById(@Param("id") Integer uid);
SQL 语句中的 id 是数据库里面的变量名。后面读参有两种方式,一是 ${ },二是 #{ }。花括号里面的参数,是方法中 @Param注解里的参数,而不是形参。所以一般为了方便,可以将两者写成一样的参数。
@Test
void getUserById() {
UserInfo userInfo = userMapper.getUserById(1);
System.out.println(userInfo.toString());
}
那么换成另一种写法呢?
常见面试题:${} 与 #{} 的区别
使用 ${ } 是直接替换,可以传递 SQL 语句,及时执行,执行是不安全,存在 SQL 注入。
使用 #{ } 是占位符,只能传递值,而不能是 SQL 语句,预执行,它的执行是安全的,不存在 SQL 注入。
SQL 注入:
使用一些特殊的 SQL 语句来完成一些非法的操作。
/**
* 探讨 ${} 与 #{} 的区别
*/
UserInfo login(@Param("username") String username, @Param("password")String password);
@Test
void login() {
String username = "admin";
String password = "admin";
UserInfo userInfo = userMapper.login(username,password);
System.out.println(userInfo);
}
MySQL 中,非数值的变量是会加单引号的,而 ${ } 还会存在这样一个问题,如果里面放的的非数值类型,直接替换就会出问题。而如果是 #{ },则不会有问题,因为是预执行,所以会根据读取参数的类型,自动转换成 MySQL 中的语法,在这个案例中,就是会自动加上单引号。
换成 # 就完全没问题了。
如果硬要用 ${} 呢?写成下面这样行不行呢?
执行结果看起来挺对的:
在上述写法的情况下,将密码修改成非“admin”,会得到什么?
会得到 null。
但如果写成下面这样呢:
@Test
void login() {
String username = "admin";
String password = "'or 1='1";
UserInfo userInfo = userMapper.login(username,password);
System.out.println(userInfo);
}
在密码不正确的情况下,也查询到相应的结果:
false or true 为 true。
这就叫做 SQL 注入。
再次换成 #:
从上面的示例可以看出,所有 ${} 能实现的功能,#{} 也能实现。在 ${} 存在 SQL 注入这种问题的情况下,为什么 ${} 还存在?
是因为 ${} 能使用一些 #{} 不能使用的场景——${} 能传递 SQL 语句!!
/**
* ${} 的适用场景:传递 SQL 语句!
* @param myOrder
* @return
*/
List getAllByOrder(@Param("myOrder")String myOrder);
@Test
void getAllByOrder() {
List userInfos = userMapper.getAllByOrder("asc");
System.out.println(userInfos);
}
此时如果是 #{} 的话,它会认为是字符串,会自动加单引号,那这就不是 SQL 语句了。
总结:
${} 的适用场景:传递 SQL 语句的时候,只能使用 ${},而不能使用 #{},但需要注意的是,由于传递的是 SQL 命令,所有要保证传递的参数是能够被穷举,为了防止 SQL 注入,也就是要么是 asc,要么是 desc,这两种选项。
从 Interface 接口的某一个方法跳转到关联的 xml 具体实现位置,可以使用插件 MyBatisX 来辅助。
删除不需要返回类型。能使用 #{},就不用 ${}。
/**
* 删除操作
*/
int delById(@Param("id") Integer id);
delete from userinfo where id=#{id};
// 如果只是想进行测试,但又不想真的影响数据库里的数据,可以在测试这里,使用以下注解
@Transactional
@Test
void delById() {
int id = 2;
int result = userMapper.delById(id);
System.out.println("受影响行数:"+result);
}
修改操作中,传递的是对象,但 SQL 语句读取的时候,MyBatis 框架已经为程序员设计好了,只需要直接输入属性名即可。
update userinfo set username=#{username} where id=#{id};
/**
* 修改操作
* @param userInfo
* @return
*/
int update(UserInfo userInfo);
@Test
void update() {
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setUsername("超级无敌巨无霸");
int result = userMapper.update(userInfo);
System.out.println("受影响的行数:"+result);
}
/**
* 添加操作
* @param userInfo
* @return
*/
int add(UserInfo userInfo);
insert into userinfo(username, password, photo)
values(#{username}, #{password}, #{photo})
@Test
void add() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("白骨精");
userInfo.setPassword("656569");
userInfo.setPhoto("/image/default.png");
int result = userMapper.add(userInfo);
System.out.println("受影响行数:"+result);
}
如果想要拿到自增 id ,该怎么办呢?
接口是一样的,只是实现的方式略有不同。
/**
* 返回自增 id
* @param userInfo
* @return
*/
int insert(UserInfo userInfo);
insert into userinfo(username, password, photo)
values(#{username}, #{password}, #{photo})
useGeneratedKeys="true" ————是否开启自增 idkeyProperty ———— 将数据库自增的 id 赋值给此属性keyColumn ———— 数据库自增主键字段名
@Test
void insert() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("女儿国国王");
userInfo.setPassword("999");
userInfo.setPhoto("/image/XiXi.png");
int result = userMapper.add(userInfo);
System.out.println("受影响行数:"+result+" ID:"+userInfo.getId());
}
① 将类中的属性名更改,使其与数据库表中的字段名保持一致
② 使用 SQL 语句中的 as 进行列名(字段名)重命名,让字段名等于属性名
此处的属性名为 name,而字段名为 username。
③ 定义一个 resultMap,将属性名和字段名进行手动映射。一旦要手动映射,就必须全部都映射,不能只映射不同的那个。
resultMap 是在 xml 里定义的,需要设定一个 id,大驼峰,方便后面查询。type 是映射的类是啥,需要包名+类名。
主键使用的是 id,column 是字段名。property 属性名。普通字段使用 result 标签。
/**
* like 查询
* @param username
* @return
*/
List getLikeList(@Param("username")String username);
@Test
void getLikeList() {
String name = "n";
List list = userMapper.getLikeList(name);
System.out.println(list);
}
根据前面学过的知识就可以知道,#{} 会自动添加单引号,造成了 SQL 语句语法错误。这里也不可以使用 ${},,因为结果不可以穷举,也就有了 SQL 注入的问题。 可以采用下列方法修改:
@Test
void getLikeList() {
String name = "%n%";
List list = userMapper.getLikeList(name);
System.out.println(list);
}
这样略显变扭。可以使用 MySQL 内置的函数 concat 进行拼接:
在这里直接在接口文件写方法的具体实现。
package com.example.demo.Dao;
import com.example.demo.Model.Articleinfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface ArticleMapper {
@Select("select a.*,u.username from articleinfo a left join userinfo u on a.uid=u.id")
List getAll();
}
package com.example.demo.Dao;
import com.example.demo.Model.Articleinfo;
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
class ArticleMapperTest {
@Autowired
private ArticleMapper articleMapper;
@Test
void getAll() {
List list = articleMapper.getAll();
System.out.println(list);
}
}