一、理论储备
1、MyBatis 的概念
MyBatis 是比 JDBC 更简单的操作和读取数据库⼯具,它去除了繁琐的代码,通过简单的 XML 或 注解来配置和映射原始类型、接口和 Java POJO 为数据库记录。
2、MyBatis 的作用
对于后端开发来说,程序主要由两部分组成:后端程序 和 数据库。
我们依赖数据库连接工具,使得后端程序能够访问数据库,进行增删改查的操作。我们之前已经学习过一种工具(JDBC),但 JDBC 的操作太过繁琐了,MyBatis 更加简单。
回顾一下 JDBC 的流程:
- 创建数据库连接池 DataSource
- 通过 DataSource 获取数据库连接 Connection
- 编写要执⾏带 ? 占位符的 SQL 语句
- 通过 Connection 及 SQL 创建操作命令对象 Statement
- 替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值
- 使⽤ Statement 执⾏ SQL 语句
- 查询操作:返回结果集 ResultSet,更新操作:返回更新的数量
- 处理结果集
- 释放资源
二、第⼀个MyBatis查询
MyBatis 的组成:
- InterFace(接口):当前类的所有方法的声明
- XML:实现接口
MyBatis 也是⼀个 ORM 框架,ORM(Object Relational Mapping),即对象关系映射。在⾯向对象编程语⾔中,将关系型数据库中的数据与对象建⽴起映射关系,进⽽⾃动的完成数据与对象的互相转换:
- 将输⼊数据(即传⼊对象)+SQL 映射成原⽣ SQL
- 将结果集映射为返回对象,即输出对象
ORM 把数据库映射为对象:
- 数据库表(table)–> 类(class)
- 记录(record,⾏数据)–> 对象(object)
- 字段(field) --> 对象的属性(attribute)
⼀般的 ORM 框架,会将数据库模型的每张表都映射为⼀个 Java 类。
也就是说使⽤ MyBatis 可以像操作对象⼀样来操作数据库中的表,可以实现对象和数据库表之间
的转换
1、创建数据库和表
接下来我们要实现的功能是:使⽤ 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';
-- 添加一个用户信息
INSERT INTO `mycnblog`.`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);
2、添加MyBatis框架支持
添加 MyBatis 框架⽀持分为两种情况:
- 对⾃⼰之前的 Spring 项⽬进⾏升级;
- 创建⼀个全新的 MyBatis 和 Spring Boot 的项⽬。
① 老项目添加MyBatis
Ⅰ. 新增功能
在 pom.xml 文件中,直接增加框架支持:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
Ⅱ. EditStarters插件
在 pom.xml 文件中,鼠标右键 -> Generate -> EditStarters:增加 如下两个框架即可
② 新项目添加MyBatis
在创建时,直接添加框架即可:
3、配置连接字符串和MyBatis
① 配置连接字符串
在 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 路径
MyBatis 的 XML 中保存是查询数据库的具体操作 SQL,配置如下:
# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
configuration: # 配置打印 mybatis 执行的 SQL
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
4、添加业务代码
① 添加实体类
创建一个与表对应的实体类:
代码如下:
package com.example.mybatisdemo.model;
import lombok.Data;
import java.util.Date;
/**
* 用户信息
* 普通的实体类,用于 MyBatis 做数据库表(userinfo)的映射
* 注意事项:标准类属性名称和 userinfo 表的字段完全一致()。
*/
@Data
public class UserInfo {
/**
* 用户id
*/
private Integer id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 电话号码
*/
private String photo;
/**
* 创建时间
*/
private Date createTime;
/**
* 修改时间
*/
private Date updateTime;
/**
* 状态
*/
private Integer state;
}
② 添加 mapper 接口
创建一个与表对应的 mapper 接口:
代码如下:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 使用 @Mapper,让接口变为 mybatis 的接口,不可忽略
*/
@Mapper
public interface UserInfoMapper {
/**
* 查询所有的信息
* @return
*/
public List<UserInfo> getAll();
/**
* 传参查询
* @param id
* @return
*/
public UserInfo getUserById(@Param("id")Integer id);
}
③ 添加 UserMapper.xml
在 resources 目录下的 mapper 包下创建 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.mybatisdemo.mapper.UserInfoMapper">
<select id="getAll" resultType="com.example.mybatisdemo.model.UserInfo">
select * from userinfo
</select>
<select id="getUserById" resultType="com.example.mybatisdemo.model.UserInfo">
select * from userinfo where id=#{id}
</select>
</mapper>
说明:
- < mapper>标签:
- namespace -> 实现的接口名(包名+类名)
- < select>标签:
- id -> 实现的方法名
- resultType -> 返回的数据类型(对应的实体类名)
- 在传参查询的时候,通过 #{id} 的方式来获取参数(此处的 id 与接口中的 @Param 注解所传 id 相同)
④ 测试
Ⅰ. 使用接口测试
a. 添加 Service
package com.example.mybatisdemo.service;
import com.example.mybatisdemo.mapper.UserInfoMapper;
import com.example.mybatisdemo.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserInfoService {
@Autowired
private UserInfoMapper userInfoMapper;
/**
* 查询所有的信息
* @return
*/
public List<UserInfo> getAll(){
return userInfoMapper.getAll();
}
/**
* 传参查询
* @param id
* @return
*/
public UserInfo getUserById(Integer id){
return userInfoMapper.getUserById(id);
}
}
b. 添加 Controller
package com.example.mybatisdemo.controller;
import com.example.mybatisdemo.model.UserInfo;
import com.example.mybatisdemo.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@RestController
@RequestMapping("/userinfo")
public class UserInfoController {
@Autowired
private UserInfoService userInfoService;
/**
* 查询所有的信息
* @return
*/
@RequestMapping("/getAll")
public List<UserInfo> getAll() {
return userInfoService.getAll();
}
/**
* 传参查询
* @param id
* @return
*/
@RequestMapping("/getUserById")
public UserInfo getUserById(@RequestParam(value = "id", required = false) Integer id) {
return userInfoService.getUserById(id);
}
}
c. 使用 postman 测试
测试:http://localhost:8080/userinfo/getall
测试:http://localhost:8080/userinfo/getuserbyid?id=1
Ⅱ. 使用单元测试
在 Spring Boot 中,使用单元测试,教程链接:单元测试
测试类代码:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.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 // 当前测试的上下文环境为 Spring Boot
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void getAll() {
List<UserInfo> list = userInfoMapper.getAll();
for (UserInfo user : list) {
System.out.println(user.toString());
}
}
@Test
void getUserById() {
UserInfo userInfo = userInfoMapper.getUserById(1);
System.out.println(userInfo);
}
}
a. getAll 方法
b. getUserById 方法
三、增、删、改操作
1、准备工作
创建表:
-- 使用数据数据
use mycnblog;
-- 创建文章表
drop table if exists articleinfo;
create table articleinfo(
id int primary key auto_increment,
title varchar(100) not null,
content text not null,
createtime datetime default now(),
updatetime datetime default now(),
uid int not null,
rcount int not null default 1,
`state` int default 1
)default charset 'utf8mb4';
创建实体类:
package com.example.mybatisdemo.model;
import lombok.Data;
import java.util.Date;
/**
* 文章的实体类
*/
@Data
public class ArticleInfo {
/**
* 文章id
*/
private Integer id;
/**
* 标题
*/
private String title;
/**
* 正文
*/
private String content;
/**
* 创建时间
*/
private Date createTime;
/**
* 修改时间
*/
private Date updateTime;
/**
* 发布文章的uid
*/
private Integer uid;
/**
* 访问量
*/
private Integer rcount;
/**
* 状态
*/
private Integer state;
}
2、增加用户操作
① 不使用 @Param
mapper 接口:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ArticleInfoMapper {
/**
* 添加方法
* @return
*/
public Integer add(ArticleInfo articleInfo);
}
Mapper.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.mybatisdemo.mapper.ArticleInfoMapper">
<insert id="add">
insert into articleinfo(title, content, uid)
values (#{title}, #{content}, #{uid})
</insert>
</mapper>
单元测试代码为:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.ArticleInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class ArticleInfoMapperTest {
@Autowired
private ArticleInfoMapper articleInfoMapper;
@Test
void add() {
ArticleInfo articleInfo = new ArticleInfo();
articleInfo.setTitle("MyBatis");
articleInfo.setContent("hello,MyBatis!");
articleInfo.setUid(1);
Integer result = articleInfoMapper.add(articleInfo);
System.out.println("添加结果为:" + result);
}
}
单元测试结果为:
② 使用 @Param
mapper 接口:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface ArticleInfoMapper {
/**
* 添加方法
* @param articleInfo
* @return
*/
public Integer add(@Param("articleInfo") ArticleInfo articleInfo);
}
Mapper.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.mybatisdemo.mapper.ArticleInfoMapper">
<insert id="add">
insert into articleinfo(title, content, uid)
values (#{articleInfo.title}, #{articleInfo.content}, #{articleInfo.uid})
</insert>
</mapper>
单元测试代码为:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.ArticleInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class ArticleInfoMapperTest {
@Autowired
private ArticleInfoMapper articleInfoMapper;
@Test
void add() {
ArticleInfo articleInfo = new ArticleInfo();
articleInfo.setTitle("MyBatis");
articleInfo.setContent("hello,MyBatis!");
articleInfo.setUid(1);
Integer result = articleInfoMapper.add(articleInfo);
System.out.println("添加结果为:" + result);
}
}
单元测试结果为:
③ 返回自增 id
返回自增 id 可以通过 mapper.xml中的 < insert> 标签 的属性来设置:
- useGeneratedKeys:取出由数据库内部⽣成的主键,默认值:false。
- keyColumn:设置⽣成键值在表中的列名,当主键列不是表中的第⼀列的时候,是必须设置的。如果⽣成列不⽌⼀个,可以⽤逗号分隔多个属性名称。
- keyProperty:指定能够唯⼀识别对象的属性,如果⽣成列不⽌⼀个,可以⽤逗号分隔多个属性名称。
mapper 接口:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface ArticleInfoMapper {
/**
* 添加方法(得到自增主键的id)
* @param articleInfo
* @return
*/
public Integer addGetId(@Param("articleInfo") ArticleInfo articleInfo);
}
Mapper.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.mybatisdemo.mapper.ArticleInfoMapper">
<insert id="addGetId" useGeneratedKeys="true" keyProperty="id">
insert into articleinfo(title, content, uid)
values (#{articleInfo.title}, #{articleInfo.content}, #{articleInfo.uid})
</insert>
</mapper>
单元测试代码为:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.ArticleInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class ArticleInfoMapperTest {
@Autowired
private ArticleInfoMapper articleInfoMapper;
@Test
void addGetId() {
ArticleInfo articleInfo = new ArticleInfo();
articleInfo.setTitle("MyBatis自增主键id");
articleInfo.setContent("MyBatis主键id在自增!");
articleInfo.setUid(1);
Integer result = articleInfoMapper.addGetId(articleInfo);
System.out.println("添加结果为:" + result +
" | 自增id:" + articleInfo.getId());
}
}
单元测试结果为:
3、删除用户操作
mapper 接口:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface ArticleInfoMapper {
/**
* 删除单条数据
* @param id
* @return
*/
public int delById(@Param("id") Integer id);
}
Mapper.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.mybatisdemo.mapper.ArticleInfoMapper">
<delete id="delById">
delete from articleinfo where id=#{id}
</delete>
</mapper>
单元测试代码为:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.ArticleInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class ArticleInfoMapperTest {
@Autowired
private ArticleInfoMapper articleInfoMapper;
@Test
void delById() {
Integer result = articleInfoMapper.delById(5);
System.out.println("删除结果为:" + result);
}
}
单元测试结果为:
4、修改用户操作
mapper 接口:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface ArticleInfoMapper {
/**
* 修改标题
*
* @param id
* @param title
* @return
*/
public Integer updateTitle(@Param("id") Integer id, @Param("title") String title);
}
Mapper.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.mybatisdemo.mapper.ArticleInfoMapper">
<update id="updateTitle">
update articleinfo set title=#{title} where id=#{id}
</update>
</mapper>
单元测试代码为:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.ArticleInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class ArticleInfoMapperTest {
@Autowired
private ArticleInfoMapper articleInfoMapper;
@Test
void updateTitle() {
Integer result = articleInfoMapper.updateTitle(1, "hello");
System.out.println("修改结果为:" + result);
}
}
单元测试结果为:
四、查询操作
1、单表查询
① 参数占位符 #{} 和 ${}
案例:
mapper接口:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 使用 @Mapper,让接口变为 mybatis 的接口,不可忽略
*/
@Mapper
public interface UserInfoMapper {
/**
* 根据用户名查询
* @param username
* @return
*/
public UserInfo getUserByName(@Param("username")String username);
}
单元测试类:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.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 // 当前测试的上下文环境为 Spring Boot
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void getUserByName() {
UserInfo userInfo = userInfoMapper.getUserByName("admin");
System.out.println("userInfo -> "+userInfo);
}
}
Ⅰ. #{}
Mapper.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.mybatisdemo.mapper.UserInfoMapper">
<select id="getUserByName" resultType="com.example.mybatisdemo.model.UserInfo">
select * from userinfo where username=#{username}
</select>
</mapper>
单元测试结果为:
Ⅱ. ${}
Mapper.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.mybatisdemo.mapper.UserInfoMapper">
<select id="getUserByName" resultType="com.example.mybatisdemo.model.UserInfo">
select * from userinfo where username=${username}
</select>
</mapper>
单元测试结果为:
Ⅲ. 对比
- #{}:预编译处理。
- ${}:字符直接替换。
MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为?号,使⽤ PreparedStatement 的 set ⽅法来赋值。MyBatis 在处理 ${} 时,会把 ${} 替换成变量的值。
在刚刚的案例中,我们使用分别使用了 #{} 和 ${}:
- #{}:select * from userinfo where username=‘admin’
- ${}:select * from userinfo where username=admin
所以在使用 ${} 的时候就会报错,因为没有一个字段名叫 admin,改正 -> 加上单引号:
Mapper.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.mybatisdemo.mapper.UserInfoMapper">
<select id="getUserByName" resultType="com.example.mybatisdemo.model.UserInfo">
select * from userinfo where username='${username}'
</select>
</mapper>
单元测试结果为:
但这种使用方法其实是不严谨的,对于字符串我们可以加上单引号,那其他类型的数据查询呢?我们再加上单引号,就会存在数据类型转换,当数据库较大时,花费的时间就非常多了,所以这样做也是有风险的!
② ${} 优点
刚刚我们对比了 #{} 和 ${},我们发现使用 ${} 对于字符串来说要加引号,很麻烦,对于其他数据类型又不合理,那 ${} 有什么用呢?
实际上 ${} 可以专递 SQL 关键字。
案例:
mapper接口:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 使用 @Mapper,让接口变为 mybatis 的接口,不可忽略
*/
@Mapper
public interface UserInfoMapper {
/**
* 查询所有的信息(根据排序条件进行排序)
* @param order
* @return
*/
public List<UserInfo> getAllByOrder(@Param("order")String order);
}
Mapper.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.mybatisdemo.mapper.UserInfoMapper">
<select id="getAllByOrder" resultType="com.example.mybatisdemo.model.UserInfo">
select * from userinfo order by id ${order}
</select>
</mapper>
单元测试代码:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.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 // 当前测试的上下文环境为 Spring Boot
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void getAllByOrder() {
List<UserInfo> list = userInfoMapper.getAllByOrder("desc");
for (UserInfo user : list) {
System.out.println(user.toString());
}
}
}
单元测试结果为:
这里如果使用 #{} 查询时,如果传递的值为 String,它就会加上单引号,从而导致 SQL 错误。
③ SQL 注入问题
Ⅰ. 概念
在正常情况下,我们进行登录时,要同时匹配用户名和密码,当有一项错误时,就无法查询到数据:
但是当我们不知道密码时,我们可以采用漏洞来获取数据:
我们通过 1=‘1’ 这个恒成立的条件来获取密码,无论用户名和密码是什么,因为 1=‘1’ ,所以一定能查询到所有用户名对应的密码,这就是 SQL 注入!
Ⅱ. ${} 带来的 SQL 注入问题
mapper接口:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 使用 @Mapper,让接口变为 mybatis 的接口,不可忽略
*/
@Mapper
public interface UserInfoMapper {
/**
* 用户登录
* @param username
* @param password
* @return
*/
public UserInfo login(@Param("username")String username,
@Param("password")String password);
}
单元测试代码:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.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 // 当前测试的上下文环境为 Spring Boot
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void login() {
String username = "abc";
String password = "' or 1='1";
UserInfo userInfo = userInfoMapper.login(username,password);
System.out.println("userInfo -> "+userInfo);
}
}
Ⅰ. #{}
Mapper.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.mybatisdemo.mapper.UserInfoMapper">
<select id="login" resultType="com.example.mybatisdemo.model.UserInfo">
select * from userinfo where username=#{username} and password=#{password}
</select>
</mapper>
单元测试结果为:
我们可以发现,使用 #{} 时,当用户名和密码不对时,无法查询到数据。
Ⅱ. ${}
Mapper.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.mybatisdemo.mapper.UserInfoMapper">
<select id="login" resultType="com.example.mybatisdemo.model.UserInfo">
select * from userinfo where username='${username}' and password='${password}'
</select>
</mapper>
单元测试结果为:
我们可以发现,使用 ${} 时,即使用户名和密码完全不对,但是通过 SQL 注入,我们仍然能够查询出结果。
Ⅲ. 结论
用于查询的字段,尽量使用 #{} 预查询的⽅式。
④ like 查询
我们在使用 like 进行模糊查询的时候,查询的输入项可能在前面也可能在后边,所以我们要在前后都加上 %,使用 #{} 时:
<select id="findUserByName" resultType="com.example.mybatisdemo.model.UserInfo">
select * from userinfo where username like '%#{username}%';
</select>
这样是会报错的,原因:
上面的 sql -> select * from userinfo where username like ‘%‘username’%’;
这里又不能使用 ${},存在安全隐患,我们又没法用穷举的方式验证 ${} 是否安全,那应该怎么办呢?
实际上,我们可以使用 mysql 的内置函数 concat() 来拼接 %,代码如下:
<select id="findUserByName" resultType="com.example.mybatisdemo.model.UserInfo">
select * from userinfo where username like concat('%',#{username},'%');
</select>
这样我们就可以实现模糊查询了。
2、多表查询
① 返回类型:resultType
- 如果是增、删、改返回搜影响的⾏数,那么在 mapper.xml 中是可以不设置返回的类型的;
- 但对于查询来说,即使是最简单查询⽤户的名称也要设置返回的类型。
在使用查询的时候,我们就需要声明其返回类型,使用 resultType 进行返回,直接定义到实体类即可。
<?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.mybatisdemo.mapper.UserInfoMapper">
<select id="getAll" resultType="com.example.mybatisdemo.model.UserInfo">
select * from userinfo
</select>
</mapper>
② 返回字典映射:resultMap
resultMap 使⽤场景:
- 字段名称和程序中的属性名不同的情况,可使⽤ resultMap 配置映射;
- ⼀对⼀和⼀对多关系可以使⽤ resultMap 映射并查询数据。
字段名与属性名不同
如图,在定义实体类时,我们将 username 定义为 name,那么通过查询,就不能正确赋值。
查询结果如下:
这时候就需要用到 resultMap:
Mapper.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.mybatisdemo.mapper.UserInfoMapper">
<resultMap id="BashMap" type="com.example.mybatisdemo.model.UserInfo">
<id column="id" property="id"></id>
<result column="username" property="name"></result>
<result column="password" property="password"></result>
<result column="photo" property="photo"></result>
<result column="createTime" property="createTime"></result>
<result column="updateTime" property="updateTime"></result>
<result column="state" property="state"></result>
</resultMap>
<select id="getAll" resultMap="BashMap">
select * from userinfo
</select>
</mapper>
单元测试结果如下:
③ 多表查询
Ⅰ. 一对一的表映射
案例:查询文章的所有信息已经文章的作者。
a. resultMap 映射
ArticleInfo 类(文章实体类):加入需要查询的字段
package com.example.mybatisdemo.model;
import lombok.Data;
import java.util.Date;
/**
* 文章的实体类
*/
@Data
public class ArticleInfo {
/**
* 文章id
*/
private Integer id;
/**
* 标题
*/
private String title;
/**
* 正文
*/
private String content;
/**
* 创建时间
*/
private Date createTime;
/**
* 修改时间
*/
private Date updateTime;
/**
* 发布文章的uid
*/
private Integer uid;
/**
* 访问量
*/
private Integer rcount;
/**
* 状态
*/
private Integer state;
/**
* 文章作者名
*/
private String name;
}
mapper接口:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface ArticleInfoMapper {
/**
* 查询所有的信息
* @return
*/
public List<ArticleInfo> getAll();
}
Mapper.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.mybatisdemo.mapper.ArticleInfoMapper">
<resultMap id="BaseMap" type="com.example.mybatisdemo.model.ArticleInfo">
<id property="id" column="id"></id>
<result property="title" column="title"></result>
<result property="content" column="content"></result>
<result property="createTime" column="createTime"></result>
<result property="updateTime" column="updateTime"></result>
<result property="uid" column="uid"></result>
<result property="rcount" column="rcount"></result>
<result property="state" column="state"></result>
<result property="username" column="name"></result>
</resultMap>
<select id="getAll" resultMap="BaseMap">
select a.*,u.username from articleinfo a
left join userinfo u
on a.uid=u.id
</select>
</mapper>
单元测试代码:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.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 ArticleInfoMapperTest {
@Autowired
private ArticleInfoMapper articleInfoMapper;
@Test
void getAll() {
List<ArticleInfo> list = articleInfoMapper.getAll();
for (ArticleInfo item : list) {
System.out.println(item);
}
}
}
单元测试结果:
b. 查询时重命名
Mapper.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.mybatisdemo.mapper.ArticleInfoMapper">
<select id="getAll" resultType="com.example.mybatisdemo.model.ArticleInfo">
select a.*,u.username as name from articleinfo a
left join userinfo u
on a.uid=u.id
</select>
</mapper>
单元测试结果:
Ⅱ. 一对多:一个用户多篇文章案例
案例:查询用户的所有文章
UserInfo 类(文章实体类):加入需要查询的字段
package com.example.mybatisdemo.model;
import lombok.Data;
import java.util.Date;
/**
* 用户信息
* 普通的实体类,用于 MyBatis 做数据库表(userinfo)的映射
* 注意事项:标准类属性名称和 userinfo 表的字段完全一致()。
*/
@Data
public class UserInfo {
/**
* 用户id
*/
private Integer id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 电话号码
*/
private String photo;
/**
* 创建时间
*/
private Date createTime;
/**
* 修改时间
*/
private Date updateTime;
/**
* 状态
*/
private Integer state;
/**
* 文章
*/
private String title;
}
mapper接口:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 使用 @Mapper,让接口变为 mybatis 的接口,不可忽略
*/
@Mapper
public interface UserInfoMapper {
/**
* 查询用户的所有文章
* @param id
* @return
*/
public List<UserInfo> getArticleListByUid(@Param("id")Integer id);
}
Mapper.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.mybatisdemo.mapper.UserInfoMapper">
<select id="getArticleListByUid" resultType="com.example.mybatisdemo.model.UserInfo">
select u.*,a.title as title from userinfo u
left join articleinfo a
on u.id=a.uid where u.id=#{id}
</select>
</mapper>
Service 类:
package com.example.mybatisdemo.service;
import com.example.mybatisdemo.mapper.UserInfoMapper;
import com.example.mybatisdemo.model.ArticleInfo;
import com.example.mybatisdemo.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserInfoService {
@Autowired
private UserInfoMapper userInfoMapper;
/**
* 查询用户的所有文章
* @param uid
* @return
*/
public List<UserInfo> getArticleListByUid(Integer uid){
return userInfoMapper.getArticleListByUid(uid);
}
}
Controller 类:
package com.example.mybatisdemo.controller;
import com.example.mybatisdemo.model.ArticleInfo;
import com.example.mybatisdemo.model.UserInfo;
import com.example.mybatisdemo.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/userinfo")
public class UserInfoController {
@Autowired
private UserInfoService userInfoService;
/**
* 查询用户的所有文章
* @param uid
* @return
*/
@RequestMapping("/getArticleListByUid")
public List<UserInfo> getArticleListByUid(@RequestParam("uid")Integer uid){
return userInfoService.getArticleListByUid(uid);
}
}
postman 测试:
五、复杂情况:动态SQL使用
官⽅⽂档:mybatis - 动态 SQL
1、< if >标签
在用户注册时,时长会有一些必传参数,那么我们在注册时,不确定是否传参的时候,该怎么办呢?
① 使用方法
<if test="...... != null">
......
</if>
② 案例
mapper接口:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 使用 @Mapper,让接口变为 mybatis 的接口,不可忽略
*/
@Mapper
public interface UserInfoMapper {
/**
* 增加用户
* @param username
* @param password
* @param photo
* @return
*/
public Integer add(@Param("username")String username,
@Param("password")String password,
@Param("photo")String photo);
}
Mapper.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.mybatisdemo.mapper.UserInfoMapper">
<insert id="add">
insert into userinfo(username,
password,
<if test="photo!=null">
photo
</if>
)
values (#{username},
#{password},
<if test="photo!=null">
#{photo}
</if>)
</insert>
</mapper>
Ⅰ. 传值
单元测试类:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.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 // 当前测试的上下文环境为 Spring Boot
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void add() {
Integer result = userInfoMapper.add("张三", "zhangsan", "111111");
System.out.println("添加用户结果: " + result);
}
}
单元测试结果:
Ⅱ. 不传值
单元测试类:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.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 // 当前测试的上下文环境为 Spring Boot
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void add() {
Integer result = userInfoMapper.add("张三", "zhangsan", null);
System.out.println("添加用户结果: " + result);
}
}
单元测试结果:
这里就会报错,这是因为我们将可选参数作为最后传参导致逗号无法确定,从而导致语法错误。
2、< trim >标签
对于上述案例,我们使用 trim 标签就可以解决:
① 使用方法
< trim>标签中有如下属性:
- prefix:表示整个语句块,以prefix的值作为前缀
- suffix:表示整个语句块,以suffix的值作为后缀
- prefixOverrides:表示整个语句块要去除掉的前缀
- suffixOverrides:表示整个语句块要去除掉的后缀
<trim prefix="" suffix="" prefixOverrides="" suffixOverrides="">
.......
</trim>
② 案例
Mapper.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.mybatisdemo.mapper.UserInfoMapper">
<insert id="add">
insert into userinfo
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username!=null">
username,
</if>
<if test="password!=null">
password,
</if>
<if test="photo!=null">
photo,
</if>
</trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username!=null">
#{username},
</if>
<if test="password!=null">
#{password},
</if>
<if test="photo!=null">
#{photo},
</if>
</trim>
</insert>
</mapper>
单元测试类:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.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 // 当前测试的上下文环境为 Spring Boot
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void add() {
Integer result = userInfoMapper.add("张三", "zhangsan", null);
System.out.println("添加用户结果: " + result);
}
}
单元测试结果:
3、< where >标签
① 使用方法
<where>
.......
</where>
<!-- where可以用trim 替换 -->
<trim prefix="where" prefixOverrides="and">
② 案例
mapper接口:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 使用 @Mapper,让接口变为 mybatis 的接口,不可忽略
*/
@Mapper
public interface UserInfoMapper {
/**
* 根据用户名查询
* @param username
* @return
*/
public UserInfo getUserByName(@Param("username")String username);
}
Mapper.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.mybatisdemo.mapper.UserInfoMapper">
<select id="getUserByName" resultType="com.example.mybatisdemo.model.UserInfo">
select * from userinfo
<where>
<if test="username!=null">
and username = #{username}
</if>
</where>
</select>
</mapper>
Ⅰ. 传值
单元测试类:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.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 // 当前测试的上下文环境为 Spring Boot
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void getUserByName() {
UserInfo userInfo = userInfoMapper.getUserByName("admin");
System.out.println("userInfo -> " + userInfo);
}
}
单元测试结果:
Ⅱ. 不传值
单元测试类:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.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 // 当前测试的上下文环境为 Spring Boot
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void getUserByName() {
UserInfo userInfo = userInfoMapper.getUserByName(null);
System.out.println("userInfo -> " + userInfo);
}
}
单元测试结果:
4、< set >标签
① 使用方法
<set>
.......
</set>
<!-- set可以用trim 替换 -->
<trim prefix="set" suffixOverrides=",">
② 案例
mapper接口:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface ArticleInfoMapper {
/**
* 修改标题
*
* @param id
* @param title
* @return
*/
public Integer updateTitle(@Param("id") Integer id, @Param("title") String title);
}
Mapper.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.mybatisdemo.mapper.ArticleInfoMapper">
<update id="updateTitle">
update articleinfo
<set>
<if test="title!=null">
title=#{title}
</if>
</set>
where id = #{id}
</update>
</mapper>
单元测试类:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.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 ArticleInfoMapperTest {
@Autowired
private ArticleInfoMapper articleInfoMapper;
@Test
void updateTitle() {
Integer result = articleInfoMapper.updateTitle(1, "hello");
System.out.println("修改结果为:" + result);
}
}
单元测试结果为:
5、< foreach >标签
① 使用方法
< foreach>标签有如下属性:
- collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象
- item:遍历时的每⼀个对象
- open:语句块开头的字符串
- close:语句块结束的字符串
- separator:每次遍历之间间隔的字符串
② 案例
mapper接口:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 使用 @Mapper,让接口变为 mybatis 的接口,不可忽略
*/
@Mapper
public interface UserInfoMapper {
/**
* 多条用户的删除
* @param ids
* @return
*/
public Integer delByIds(List<Integer> ids);
}
Mapper.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.mybatisdemo.mapper.UserInfoMapper">
<delete id="delByIds">
delete from userinfo where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
</mapper>
单元测试类:
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.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.ArrayList;
import java.util.List;
@SpringBootTest // 当前测试的上下文环境为 Spring Boot
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void delByIds() {
List<Integer> list = new ArrayList<>();
list.add(7);
list.add(8);
list.add(9);
Integer result = userInfoMapper.delByIds(list);
System.out.println("删除了: " + result);
}
}
单元测试结果为: