我正在参加「启航方案」
一、MyBatis 是什么?
MyBatis 是一款优秀的耐久层结构,它支持自界说 SQL、存储进程以及高档映射。MyBatis 去除了简直 一切的 JDBC 代码以及设置参数和获取成果集的工作。MyBatis 能够经过简略的 XML 或注解来装备和 映射原始类型、接口和 Java POJO(Plain Old Java Objects,一般旧式 Java 目标)为数据库中的记 录。
简略来说 MyBatis 是更简略完毕程序和数据库交互的东西,也便是更简略的操作和读取数据库东西。
针对 “MyBatis 是一款优秀的耐久层结构” 进行剖析和弥补:
MyBatis也是一个 ORM (Object Relational Mapping,即目标联络映射)结构。
在面向目标编程语言中,将联络型数据库中的数据与目标树立起映射联络,进而主动的完毕数据与目标的彼此转化:
- 将输入数据(即传入目标)+ SQL 映射成原生 SQL。
- 将成果集映射为回来目标,即输出目标。\
ORM 把数据库映射为目标:
- 数据库表(table) –> 类(class)
- 记录(record,行数据) –> 目标(object)
- 字段(field) –> 目标的特点(attribute)
一般的 ORM 结构,会将数据库模型的每张表都映射为一个 Java 类。
即,运用 MyBatis 能够像操作目标相同来操作数据库中的表,能够完毕目标和数据库表之间的转化。
即:MyBatis 能够当作是一座 “桥梁”:
将数据库 和 程序,映射起来。
MySQL 和 MyBatis 是不相同的:
MySQL 供给了一个 数据存取(数据办理)的软件。
而 MyBatis 是一个 “中间桥梁”,用于衔接程序和数据库,树立映射联络,进行 数据操作 的中间层(耐久层)。
二、为什么要学习 MyBatis
关于后端开发来说,程序是由以下两个重要的部分组成的:
- 后端程序
- 数据库
而这两个重要的组成部分要通讯,就要依靠数据库衔接东西,那数据库衔接东西有哪些?比方之前咱们 学习的 JDBC,还有今日咱们即将介绍的 MyBatis,那现已有了 JDBC 了,为什么还要学习 MyBatis?
这是因为 JDBC 的操作太繁琐了,咱们回忆一下 JDBC 的操作流程:
- 创立数据库衔接池
DataSource
- 经过
DataSource
获取数据库衔接Connection
- 编写要履行带
?
占位符的SQL
句子 - 经过
Connection
及SQL
创立操作指令目标Statement
- 替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值
- 运用
Statement
履行SQL
句子 - 查询操作:回来成果集
ResultSet
,更新操作:回来更新的数量 - 处理成果集
- 开释资源
JDBC 操作示例回忆
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public class TestJDBC {
public static void main(String[] args) throws SQLException {
//让用户手动输入数据到数据库中
Scanner scanner = new Scanner(System.in);
//1. 创立数据源
DataSource dataSource = new MysqlDataSource();
//设置数据库地点的地址
((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/java?characterEncoding=utf8&useSSL=false");
//设置登录数据库的用户名
((MysqlDataSource)dataSource).setUser("root");
//设置登录数据库的暗码
((MysqlDataSource)dataSource).setPassword("1234");
//2. 让代码和数据库服务器之间树立衔接
Connection connection = dataSource.getConnection();
//3. 操作数据库,以刺进数据为例
// 关键地点:结构一个 SQL 句子
// 在 JDBC 中结构 SQL 句子,不用带上 ;
// ; 只是在指令行中用来差异不同的句子,现在是直接在代码中操作
String sql = "insert into JDBC values(1,'张三')";
// 此处光是一个 String 类型的 sql 还不行,需求把这个 String 包装成一个 “句子目标”
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//用户手动输入数据
System.out.println("请输入 ID:");
int id = scanner.nextInt();
System.out.println("请输入 姓名:");
String name = scanner.next();
//? 相当于告知 java程序,这两个字段的值 还不确定,此刻就运用 ? 先占个方位
// 再运用 PreparedStatement 的 setXXX 办法 进行替换,这儿的setXXX办法许多,需求让这儿的办法和数据库的列的类型匹配
String sql2 = "insert into JDBC values(?,?)";
PreparedStatement statement = connection.prepareStatement(sql2);
//进行替换操作
statement.setInt(1,id); //下标从 1 开始核算,把第一个 ? 替换成 id 这样的值
statement.setString(2,name);
System.out.println("statement: " + statement); //经过这个打印操作,能够看到拼装好之后的 SQL 长啥样
//4. 履行 SQL
// SQL 里边假如是 insert, update, delete ,都运用 executeUpdate 办法,
// SQL 里边假如是 select,则运用 executeQuery 办法
// 回来值就表明这个操作,影响到了几行,就相当于在操控台里输入 sql 之后,得到的数字
int ret = preparedStatement.executeUpdate();
int ret2 = statement.executeUpdate();
System.out.println(ret);
System.out.println(ret2);
//5. 此刻 SQL 现已履行完毕,然后还需求开释资源
preparedStatement.close();
statement.close();
connection.close();
}
}
从上述代码和操作流程能够看出,关于 JDBC 来说,整个操作十分的繁琐,咱们不但要拼接每一个参 数,并且还要按照模板代码的办法,一步步的操作数据库,并且在每次操作完,还要手动封闭衔接等, 而一切的这些操作过程都需求在每个办法中重复书写。于是咱们就想,那有没有一种办法,能够更简略、更方便的操作数据库呢?
答案是必定的,这便是咱们要学习 MyBatis 的真实原因,它能够协助咱们更方便、更快速的操作数据库。
三、怎样学 MyBatis
MyBatis 学习只分为两部分:
- 装备 MyBatis 开发环境。
- 运用 MyBatis 模式和语法操作数据库。
1. 创立 MyBatis 项目
准备工作:创立数据库 和 数据表
-- 创立数据库
drop database if exists Mybatis;
create database Mybatis DEFAULT CHARACTER SET utf8;
-- 使⽤数据数据
use Mybatis;
-- 创立表[⽤户表]
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
);
-- 创立⽂章表
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
);
-- 创立视频表
drop table if exists videoinfo;
create table videoinfo(
vid int primary key,
`title` varchar(250),
`url` varchar(1000),
createtime datetime default now(),
updatetime datetime default now(),
uid int
);
-- 增加⼀个⽤户信息
INSERT INTO `Mybatis`.`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);
将上面的代码仿制到本地的 MySQL。
1.1 增加 MyBatis 相关依靠
这儿会涉及两个场景:
- 项目创立的时分,引进 MyBatis 相关依靠。
- 老项目增加 MyBatis。
1. 新建一个 MyBatis 项目
- 创立新项目:
\
- MyBatis 项目是树立在 Spring MVC 项目上的,因而,下面三个结构依靠必不可少:
\
- 增加 MyBatis 项目:
\
- 引进可驱动的数据库:
- 点击
finish
即可完毕。
2. 老项目中引进 MyBatis 相关依靠
运用 Edit Starter
插件进行增加。
在 pom.xml
文件中的 dependencies
标签中,找到一个空行(人工一个也行),鼠标右击,选择 generate
:
下面为获取依靠的官方网址:
选择要增加的依靠:
\
\
1.2 装备数据库衔接字符串
不要当即发动项目,不然就会报错:
\
- 在
resources
下创立三个.yml
文件
(现在的学习还不会涉及到生产环境)
- 装备开发环境装备文件:
留意:关于 driver-class-name
中所写的驱动称号
- 驱动称号:咱们运用的是 MySQL,因而咱们填写的是 MySQL 的驱动称号。
- 假如运用的 MySQL 是
5.x
之前的版本,则用com.mysql.jdbc.Driver
。
假如版本大于5.x
,则用com.mysql.cj.jdbc.Driver
。
\
- 在主装备文件中激活开发环境
\
此刻发动项目后就不会报错了:
\
1.3 装备 MyBatis 保存的 xml 的目录
MyBatis 有两种操作办法:
- 运用
xml
的形式。 - 注解。(MyBatis 3.1 版本之后供给)
可是,注解的办法并不好用,咱们首要还是重视 xml
形式的操作。
- 一般咱们在
resources
目录下,创立一个子目录,来寄存xml
文件:
\
- 因为此装备是公共的,所以咱们在主装备文件装备 MyBatis 的 xml 保存途径:
\
四、运用 MyBatis 的操作模式操作数据库
MyBatis 的操作模式
MyBatis 的操作模式,包括两个部分:
- Interface(接口,里边是办法的界说)
- xml 文件(对 办法 的完毕,运用 SQL 句子)
在 Interface 接口中会加一个注解,这个注解是MyBatis里的注解 @Mapper
,将一般的接口变为 MyBatis 里边的接口,将接口里边的办法映射进 xml
文件。
\
MyBatis 查询:完毕一个依据用户id来查询用户信息的操作
数据库中现已创立好的表:
\
1. 界说接口
现在根目录底下创立几个包:
- model 与数据库交互
- controller 与前端交互
- server 决定调用哪些映射办法
- mapper 操作数据,完毕映射
\
为了能与数据进行交互,需求创立实体类,来存储在数据库中得到的成果。在 model
目录底下创立实体类,(实体类称号最好与数据库的表名共同,这样更好处理):
在实体类里边的字段,也要和数据库中表里边的字段称号相同:
\
下面就能够在 mapper
包底下写数据了:
留意:一定要在接口上加上 注解@Mapper。
在咱们给接口加上 @Mapper 注解后,它就不是一个单纯的接口了。此刻,UserMapper
接口变成 MyBatis 的一部分了。这个 接口 里边的办法,就能够运用 xml
去完毕了。
可是 Java 的接口是依靠类来完毕的。这的确和传统的开发是不相同的,传统开发直接写业务代码就行了。
现在是要写 SQL 了,SQL 和 业务代码,是两个不同的系统。
一般接口变成 MyBatis 接口,有什么好处呢?
它将接口的生命周期,交给容器来托管,它会完毕 interface 和 xml 之间的相关联络。
\
下面来写 User Mapper
接口里边办法的界说:
package com.example.MyBatisDemo.mapper;
import com.example.MyBatisDemo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper // 变成 mybatis interface
public interface UserMapper {
// 依据用户 id 查询用户
public UserInfo getUserById(@Param("id")Integer id); // @Param 代表参数在xml中姓名为 id
}
\
2. 创立 xml,完毕接口里边界说的办法
xml 文件,不能随意创立。
因为咱们在之前的装备文件中,指定了 xml 文件的保存途径,并且命名规矩也指定了。
按照规矩创立 xml 文件,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">
<!-- namespace 要设置成完毕接口的详细包名加类名 -->
<mapper namespace="">
</mapper>
其中里边的 namespace
设置成完毕接口的方位:包名 + 接口称号:
\
接着,在 xml 文件中,完毕 接口UserMapper
里边的办法了:
查询标签里边的 resultType 是不能省略的,不然拜访网页会报错:
\
-
select
标签里边的内容你就按照 SQL 句子写就行了,此处咱们写的是select * from userinfo where id=#{id}
。
\
- 关于接口
UserMapper
中中的@Param:
假如不写这个注解,默认在 xml 文件中,获取办法参数的称号,便是办法参数原本的称号。
可是,在有一些 jdk 版本中,不写这个注解就会报错。所以为了避免费事,还是加上这个注解比较好。\
-
这儿面要留意一点,
#{id}
里边的 id 是@Param("id")
里的 id 称号。(换句话说,假如@Param("userId")
,你 SQL 里边要变为#{userId}
。\ -
还有一点,SQL 句子末尾能够不要分号。
\
- 关于获取办法参数,运用
${}
和#{}
的差异,后面会详细讲。
运转成果展示
在 service
包底下创立一个 UserService
类。
在 UserService
类里边写一个办法,去调用 接口UserMapper
中的办法。
然后,在 controller
包下,创立 UserContronller
类,去调用 类UserService
中的办法。
UserMapper 中办法
service 层
controller 层
\
发动项目,打开浏览器去拜访:
\
\
敞开 MyBatis 日志打印功用
一般在开发环境中敞开。
目录结构:
# 敞开 Mybatis SQL 打印
logging:
level:
com:
example:
MyBatisDemo: debug
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
敞开后能够在idea的操控台上看见日志:
当咱们敞开 MyBatis SQL 日志打印的时分,咱们能清楚看到终究生成的SQL句子以及履行成果。
Spring Boot 单元测验简略运用
五、增、删、改 操作
接下来完毕以下用户信息的增加、删去和修正操作,对应运用MyBatis 的标签如下:
-
<insert>
标签:刺进句子。 -
<update>
标签:修正句子。 -
<delete>
标签:删去句子。
@Transactional 注解
有一点要留意:在进行增、删、改的时分,假如不想污染数据库,能够在Test中加注解:@Transactional(表明在办法履行完之后回滚业务)
\
MyBatis 修正操作
- interface 里边增加修正办法的声明:
留意:@Param 主张要增加上,不然有些电脑上运转会报错,会提示找不到参数。
它和 xml 里边的参数是匹配的。
- 在 xml 中增加
<update>
标签和详细的履行 sql
称号要对应:
\
MyBatis 删去操作
- 在 interface 里边增加 删去 的代码声明
- 在 xml 中增加
<delete>
标签和删去的 sql 编写
\
MyBatis 增加操作
- 在 interface 增加办法声明
- 在 xml 完毕增加业务
\
留意:
这儿的参数传入的是一个目标啊,咱们怎样才能获取到里边的特点呢?能够直接经过 #{特点名}
去获取就行:
\
假如不想将影响的行数,作为回来值了。想要 刺进成功的用户信息的ID 作为回来值!尽管完毕的过程没有发生变化,可是想要达到预期的作用,还需求增加些东西:
在 第二个过程中(办法称号设为 addGetId):想要回来用户信息的ID,还需求增加两个特点:useGeneratedKeys
和 keyProperty
- useGeneratedKeys: 是否主动生成主键(true 为是,false 为否)。这会令 MyBatis 运用 JDBC 的 getGeneratedKeys 办法来取出由数据 库内部生成的主键(比方:像 MySQL 和 SQL Server 这样的联络型数据库办理系统的主动递增字段),默认值:false
- keyProperty: 回来的主键赋值到那个特点上,这个时分能够指定 id 特点了。指定能够唯一识别目标的特点,MyBatis 会运用 getGeneratedKeys 的回来值 或 insert 句子的 selectKey 子元素设置它的值,默认值:未设置(unset)。假如生成列不止一个,能够用逗号分隔多个特点称号。
留意:最好加一个特点 keyColumn
: 设置生成键值在表中的列名,在某些数据库(如 PostgreSQL)中,当主键列不是表中第一列的时分,是有必要设置的。假如生成列不止一个,就能够用逗号分隔多个特点称号。
即,参加 keyColumn 的原因便是为了保证获取对应字段。
\
\
六、查询操作
单表查询
单表查询即为上面所介绍的:
\
参数占位符 #{} 和 ${}
1. 功用不同
-
#{}
预编译处理。 -
${}
字符直接替换。
预编译处理 : MyBatis 在处理 #{}
时,会将 SQL 中的 #{}
替换为 ?
。运用 PreparedStatement 的 set 办法来赋值
直接替换 : MyBatis 在处理 ${}
时,把 ${}
替换成变量的值。
能够在之前写过的查询代码中,演示下 #{}
和 ${}
的差异:
测验类
运用 #{}
占位符:
测验类运转成果:
运用 ${}
占位符:
测验类运转成果:
\
${} 的问题
当参数为数值类型时(在不考虑安全问题的前提下),${}
和 #{} 的履行作用都是相同的,可是当参数类型为 字符时,再运用 ${}
就有问题了,如下代码所示:
<select id="getUserById" resultType="com.example.MyBatisDemo.model.UserInfo">
select * from userinfo where username=${name}
</select>
以上程序履行时,生成的 SQL 句子如下:
这会导致程序报错,因为传递的参数是字符类型的,而在 SQL 语法中,假如是字符类型需求给值增加单引号,不然就会报错,而 ${}
是直接替换的,不会主动增加单引号,所以履行就会报错。而运用 #{}
采用的是 占位符 预履行的,所以不存在任何问题,它的完毕代码如下:
<select id="getUserById" resultType="com.example.MyBatisDemo.model.UserInfo">
select * from userinfo where username=#{name}
</select>
以上程序的终究生成的履行 SQL 如下:
2. 运用场景不同
尽管运用 #{} 的办法能够处理恣意类型的参数,可是当传递的参数是一个 SQL 指令或 SQL 关键字时 #{} 就会出问题了。比方,当咱们要依据价格从高到低(倒序)、或从低到高(正序)查询时,如下图所示:
此刻咱们要传递的排序的关键字,desc 倒序(价格从高到低)或者是 asc 正序(价格从低到高),此刻咱们运用${}
的完毕代码如下:
<select id="getOrderList" resultType="com.example.MyBatisDemo.model.Goods">
select * from goods order by price ${order}
</select>
以上代码生成的履行 SQL 和 运转成果如下:
可是,假如将代码中的 ${}
改为 #{}
,那么程序就会报错,#{}
的完毕代码如下:
<select id="getOrderList" resultType="com.example.MyBatisDemo.model.Goods">
select * from goods order by price #{order}
</select>
以上代码生成的履行 SQL 和 运转成果如下:
从上述的履行成果能够看出,当传递的是一般参数时,需求运用 #{}
的办法,而当传递的是 SQL 指令或 SQL 关键字时,需求运用 ${}
来对 SQL 中的参数进行直接替换并履行。
3. 安全性不同
${}
和 #{}
最首要的差异体现在安全方面,当运用 ${}
会呈现安全问题,也便是 SQL 注入的问题,而运用 #{}
因为是预处理,所以不会呈现安全问题, 下面经过登录功用来调查二者的差异:
3.1 运用 ${} 完毕用户登录
信息:
UserMapper.xml 中的完毕代码如下:
<!-- 用户登录 -->
<select id="login" resultType="com.example.MyBatisDemo.model.UserInfo">
select * from userinfo where username='${username}' and password='${password}'
</select>
单元测验代码如下:
@Test
void login() {
UserInfo userInfo = userMapper.login("admin","admin");
System.out.println(userInfo);
}
以上代码生成的履行 SQL 和运转成果如下:
从成果能够看出,当咱们传入了正确的用户名和暗码时,能成功查询数据。可是,在咱们运用 ${}
时,当咱们在不知道正确暗码的状况下,运用 SQL 注入句子也能得到用户的私家信息,SQL 注入的完毕代码如下:
@Test
void login() {
List<UserInfo> userInfo = userMapper.login("admin","' or 1='1");
System.out.println("用户信息:"+userInfo);
}
以上代码生成的履行 SQL 和 运转成果如下:
从上述成果能够看出,当运用 ${} 时,在不知道正确暗码的状况下也能得到用户的私家数据,这就像一个小偷在没有你们家钥匙的状况下,也能轻松的打开你们家大门相同,这是何其恐惧的工作。那运用 #{} 有没有安全问题呢?接下来咱们来测验一下。
3.2 运用 #{} 完毕用户登录
首先将 UserMapper.xml 中的代码改成以下内容:
<select id="login" resultType="com.example.MyBatisDemo.model.UserInfo">
select * from userinfo where username=#{username} and password=#{password}
</select>
接着咱们运用上面的 SQL 注入来测验登录功用:
@Test
void login() {
List<UserInfo> userInfo = userMapper.login("admin","' or 1='1");
System.out.println("用户信息:"+userInfo);
}
终究生成的 SQL 和 履行成果如下:
从上述代码能够看出,运用 SQL 注入是无法攻破 #{} 的“大门”的,所以能够放心运用。
总结:
${}
和 #{}
都是 MyBatis 中用来替换参数的,它们二者的差异首要体现在:
- 功用不同:
${}
是直接替换,而#{}
是预处理。 - 运用场景不同:一般参数运用
#{}
,假如传递的是 SQL 指令或 SQL 关键字,需求运用${}
,但在运用前一定要做好安全验证。 - 安全性不同:运用
${}
存在安全问题,而#{}
则不存在安全问题。
like 查询 – 特殊状况
在前面的问题中,因为关键字就那么几个,能够直接穷举,所以很简单在 Controller 层里边判别数据的正确性。
可是!含糊匹配的成果是不能穷举的!假如数据有几百万个,咱们验证数据的正确性得累死!
下面演示:
like 运用 #{}
报错:
<select id="getUserByName2" resultType="com.example.MyBatisDemo.model.UserInfo">
select * from userinfo where username like '%#{username}%'
</select>
报错信息如下:
相当于:select * from userinfo where username like '%'username'%'
。
所以这儿就不能运用 #{}
了,能够运用 ${}
进行直接的替换,就不会呈现单双引号了:
<select id="getUserByName2" resultType="com.example.MyBatisDemo.model.UserInfo">
select * from userinfo where username like '%${username}%'
</select>
测验类运转成果如下:
可是!运用了 ${}
,就需求在 Controller 里进行验证数据,这一点很重要!!
可是含糊匹配的成果,是海量的!咱们不或许悉数穷举出来。
\
此刻就能够考虑 concat
拼接办法:
能够考虑运用 mysql 的内置函数 concat()
来处理,此函数的作用为:
完毕代码如下:
<select id="getUserByName2" resultType="com.example.MyBatisDemo.model.UserInfo">
select * from userinfo where username like concat('%',#{username},'%')
</select>
成果如下:
\
多表查询
前置知识
回来类型:resultType
这个咱们很熟悉,这是用于指定 SQL 查询成果映射的目标类型。
可是这儿有个细节!!
尽管咱们只是运用了 id 特点,可是有一个问题:当咱们指定了回来的类型之后,这个实体类(UserInfo)里边的特点有必要和数据库中的字段是共同的!哪怕这个特点在 SQL 句子中没有被运用,也是相同的,有必要相同!
在姓名相同的状况下,运转测验类,看看状况:
\
假如咱们将 UserInfo 中的 username 特点 改为 name:
此刻再来运转测验类:
尽管测验经过了,可是 name 特点是为 null 的,而不是 admin。
呈现这个问题的原因便是:数据库表中的 字段称号 和 实体类特点称号,不相同!!
因而无法完毕匹配赋值!
除了修正 实体类特点称号,还有一个办法,运用下面介绍的 resultMap
:
回来字典映射:resultMap
resultMap 运用场景:
- 字段称号和程序中特点名不同的状况,能够运用 resultMap 装备映射:
下面的写法只合适 单表查询
<resultMap id="BaseMap" type="com.example.MyBatisDemo.model.UserInfo">
<!-- 主键映射 -->
<id column="id" property="id"></id>
<!-- 表明一般特点映射 -->
<result column="username" property="name"></result>
</resultMap>
- resultMap里,为此 resultMap 命名为
id
,type
为你要映射的类的地址 - 一般特点映射里边,
column
代表的是数据库中的字段名,property
代表的是程序中特点名。即 column 映射到 property。
此刻就可把 resultType 删去,改用 resultMap了,其值为咱们给 resultMap 起的别号 BaseMap:
<select id="getUserById" resultMap="BaseMap">
select * from userinfo where id=${id}
</select>
此刻再运转测验单元,得到成果:
\
- 1对1和多对多联络能够运用 resultMap 映射并查询数据。
多表查询:1对1联络
下面来模拟完毕1对1的联络。文章和作者便是1对1的联络,因为一篇文章只能对应一个作者。
咱们先创立文章表的实体类:因为 文章表 和 作者表 是1对1联络,所以能够在文章表的实体类中参加 UserInfo
的特点:
package com.example.MyBatisDemo.model;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class ArticleInfo {
private Integer id;
private String title;
private String content;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private Integer uid;
private Integer rcount;
private Integer state;
private UserInfo userInfo;
}
先来调查下 articleInfo 表中的信息,方便后面写代码:
\
在 mapper 包中创立 MyBatis 接口,完毕依据文章 id 查询到文章的详细信息:
@Mapper
public interface ArticleMapper {
public List<ArticleInfo> getAll(@Param("id")Integer id);
}
创立一个新的 xml 用来编写 SQL:
<mapper namespace="com.example.MyBatisDemo.mapper.ArticleMapper">
<select id="getAll" resultType="com.example.MyBatisDemo.model.ArticleInfo">
select * from articleinfo where id=#{id}
</select>
</mapper>
编写测验类:
@SpringBootTest
@Slf4j
class ArticleMapperTest {
@Resource
private ArticleMapper articleMapper;
@Test
void getAll() {
List<ArticleInfo> articleInfo = articleMapper.getAll(1);
log.info("作者信息: " + articleInfo);
}
}
测验类履行成果:
咋一看上面的成果没啥问题,可是调查 特点 UserInfo 时却发现是空的:
为什么会呈现上面的状况呢?
原因便是,实体类 ArticleInfo
中有 UserInfo
特点,可是 文章表中却没有这个字段:
此刻咱们就需求 resultMap
了。
设置 resultMap
:
<mapper namespace="com.example.MyBatisDemo.mapper.ArticleMapper">
<resultMap id="BaseMap" type="com.example.MyBatisDemo.model.ArticleInfo">
</resultMap>
<select id="getAll" resultMap="BaseMap">
select * from articleinfo where id=#{id}
</select>
</mapper>
-
BaseMap
为此标签的别号,与其他 xml 中的 resultMap 的别号重复的话,不会有影响。
在 resultMap 中设置映射联络:将文章表中一切字段都映射出来:
<mapper namespace="com.example.MyBatisDemo.mapper.ArticleMapper">
<resultMap id="BaseMap" type="com.example.MyBatisDemo.model.ArticleInfo">
<id column="id" property="id"></id>
<result column="title" property="title"></result>
<result column="content" property="content"></result>
<result column="createtime" property="createtime"></result>
<result column="updatetime" property="updatetime"></result>
<result column="uid" property="uid"></result>
<result column="rcount" property="rcount"></result>
<result column="state" property="state"></result>
</resultMap>
<select id="getAll" resultMap="BaseMap">
select * from articleinfo where id=#{id}
</select>
</mapper>
此刻咱们发现,resultMap 中还没有映射目标,这时分就需求用到标签 association
:
<mapper namespace="com.example.MyBatisDemo.mapper.ArticleMapper">
<resultMap id="BaseMap" type="com.example.MyBatisDemo.model.ArticleInfo">
<id column="id" property="id"></id>
<result column="title" property="title"></result>
<result column="content" property="content"></result>
<result column="createtime" property="createtime"></result>
<result column="updatetime" property="updatetime"></result>
<result column="uid" property="uid"></result>
<result column="rcount" property="rcount"></result>
<result column="state" property="state"></result>
<association property="userInfo"
resultMap="com.example.MyBatisDemo.mapper.UserMapper.BaseMap">
</association>
</resultMap>
<select id="getAll" resultMap="BaseMap">
select * from articleinfo where id=#{id}
</select>
</mapper>
association
标签的意思为:经过 resultMap 将目前的 resultMap 与另一个 resultMap 进行相关!便是经过 UserMapper 的 BaseMap 装备的字段信息,打包赋值给 userinfo。
将 SQL 句子改为多表的查询:
<mapper namespace="com.example.MyBatisDemo.mapper.ArticleMapper">
<select id="getAll" resultMap="BaseMap">
select a.*,u.* from articleinfo a left join userinfo u on a.uid=u.id where a.id=#{id}
</select>
</mapper>
可是假如 UserMapper 的 BaseMap 中只装备了部分字段,就会呈现一些问题:
<mapper namespace="com.example.MyBatisDemo.mapper.UserMapper">
<resultMap id="BaseMap" type="com.example.MyBatisDemo.model.UserInfo">
<!-- 主键映射 -->
<id column="id" property="id"></id>
<!-- 表明一般特点映射 -->
<result column="username" property="name"></result>
</resultMap>
</mapper>
履行 ArticleMapperTest 测验单元,成果如下:
能够看到 UserInfo 里边只显示了在 UserMapper.xml 中映射的字段,其他没有映射的字段都是为空(status 默以为 0)
这儿能够得出一个小定论:
- 在自身 xml 文件中,是能够不用映射一切特点的信息,因为它是自己调用自己,所以不需求 resultMap 将特点悉数映射,都能主动完毕一切特点的映射。
- 而想要在一个 resultMap 中调用另一个 resultMap 中的信息,只能是它映射了的信息,不然无法获取。
这时分咱们将 UserMapper.xml 中的特点映射补全:
<mapper namespace="com.example.MyBatisDemo.mapper.UserMapper">
<resultMap id="BaseMap" 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>
</mapper>
测验类 ArticleMapperTest 运转成果:
能够看到UserInfo的一切字段都出来了。
可是上面取得的部分信息却是有误的:
可是有误的信息却和 ArticleInfo 中的信息相符合:
\
得出一个定论:
当一个字段在两张表中都存在时,默认读取的是 association 标签的 property 参数地点的数据表的字段值。
当前 property 是在 ArticleInfo 表中的:
下面来看下重名的字段:
下面来处理重名的问题。
处理 不同数据表中字段重名的状况
处理办法很简略,增加一个前缀,让 SQL 中的字段都带有 “身份符号”,这样能够避免重名的状况。
这种增加前缀的办法,是经过在 association
中增加一个特点 columnPrefix
,翻译成中文便是 特点前缀 的意思。
<association property="userInfo"
resultMap="com.example.MyBatisDemo.mapper.UserMapper.BaseMap"
columnPrefix="u_">
</association>
设置好前缀后,修正 SQL 句子:把那些字段称号重复的,前面加 u_
前缀,并且咱们获取 userinfo 中悉数的字段信息:
<select id="getAll" resultMap="BaseMap">
select a.*,u.id u_id,u.updatetime u_updatetime,u.createtime u_createtime,u.state u_state,
u.username u_username,u.password u_password,u.photo u_photo
from articleinfo a left join userinfo u on a.uid=u.id where a.id=#{id}
</select>
测验类 ArticleMapperTest 运转成果:
\
这才真实是完毕了 1对1 联络的多表查询!!
多表查询:一对多联络
一对多联络,比方,一个用户能够是多篇文章的作者。
一对多联络,需求运用 <collection>
标签,用法和 是相同的。
下面来演示:
首先将 用户表 的实体类进行处理:
@Data
public class UserInfo {
private Integer id;
private String name;
private String password;
private String photo;
private String createtime;
private String updatetime;
private int state;
private List<ArticleInfo> articleInfoList;
}
运用线性表的特点,来接收 多篇文章的信息。
在 UserMapper 中界说办法:
@Mapper
public interface UserMapper {
// 依据用户 id 查询用户信息,和 所对应的文章信息
public UserInfo getUserAndArticleById(@Param("id")Integer id);
}
\
在 UserMapper,xml 中映射特点:
<mapper namespace="com.example.MyBatisDemo.mapper.UserMapper">
<resultMap id="BaseMap" 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>
</mapper>
映射
中 articleList 目标,运用 collection
标签:
<collection property="articleInfoList"
resultMap="com.example.MyBatisDemo.mapper.ArticleMapper.BaseMap"
columnPrefix="a_">
</collection>
-
property
:需求映射的特点。 -
resultMap
:映射目标。 -
columnPrefix
:将文章表中 带有a_
前缀的字段值,打包映射到 articleList 目标中。
修正 SQL 句子:
<!-- 依据用户 id 查询用户信息,和 所对应的文章信息 -->
<select id="getUserAndArticleById" resultMap="BaseMap">
select u.*,a.id a_id,a.title a_title,a.content a_content,a.createtime a_createtime,a.updatetime a_updatetime,a.uid a_uid,a.rcount a_rcount,a.state a_state
from userinfo u left join articleinfo a on u.id=a.uid where u.id=#{id}
</select>
\
生成并编写测验单元:
@SpringBootTest
@Slf4j
class UserMapperTest {
@Resource
private UserMapper userMapper;
@Test
void getUserAndArticleById() {
UserInfo userInfo = userMapper.getUserAndArticleById(1);
log.info("用户信息:" + userInfo);
}
}
运转成果:
\
至此一对多查询完毕!
七、复杂状况:动态 SQL 运用
动态 sql 是Mybatis的强壮特性之一,能够完毕不同条件下不同的 sql 拼接。
能够参考官方文档:mybatis – MyBatis 3 | 动态 SQL
1. if 标签
在注册用户的时分,或许会有这样一个问题,如下图所示:
注册分为两种字段:必填字段和非必填字段,那假如在增加用户的时分有不确定的字段传入,程序应该 怎么完毕呢?
这个时分就需求运用动态标签 来判别了。
if 标签判别一个参数是否有值,假如没值,那么就会躲藏 if 中的 sql。
可是 有必要有一个值是必传的。
语法:
<if test="username!=null">
username=#{username}
</if>
- 在 test 中,判别表达式是否为空。
- 假如test 的成果为 true,那么拼接里边的 SQL 句子,加上
username=#{username}
。 - 假如test 的成果为 false,那么 if 标签中的 sql 就会被忽略。
- 这两个 username 是对应的。姓名是 实体类中的特点名。
演示:
咱们先给 userinfo 表中的 photo 设置一个默认值,默以为 default.png
\
在 UserMapper 中增加办法 add2:
@Mapper // 变成 mybatis interface
public interface UserMapper {
// 增加用户,photo 对错必传参数
public int add2(UserInfo userInfo);
}
\
在 UserMapper.xml 中编写 SQL:
<!-- 增加用户,photo 对错必传参数 -->
<insert id="add2">
insert into userinfo(username,password
<if test="photo!=null">
,photo
</if>
) value(#{name},#{password}
<if test="photo!=null">
,#{photo}
</if>
)
</insert>
test 里边的 photo 为目标的特点名。
编写测验类,当传入 photo 特点时:
@SpringBootTest
@Slf4j
class UserMapperTest {
@Resource
private UserMapper userMapper;
@Test
void add2() {
UserInfo userInfo = new UserInfo();
userInfo.setName("张三");
userInfo.setPassword("123");
userInfo.setPhoto("123.png");
int result = userMapper.add2(userInfo);
log.info("用户信息:"+userInfo);
}
}
运转成果:
当不传入 photo 时:
2. trim 标签
最首要的作用:去除 SQL 句子前后多余的某个字符。
标签中有如下特点:
- prefix:表明整个句子块,以prefix的值作为前缀
- suffix:表明整个句子块,以suffix的值作为后缀
- prefixOverrides:表明整个句子块要去除掉的前缀
- suffixOverrides:表明整个句子块要去除掉的后缀
\
语法:
<trim prefix="(" suffix")" prefixOverrides="," suffixOverrides=",">
<if test="xxx">
...
</if>
...
</trim>
- 根据
prefix
装备,开始部分加上(
- 根据
suffix
装备,完毕部分加上)
- 多个 组织的句子都以
,
结束,在最终拼接好的字符串还会以,
结束,会根据suffixOverrides
装备去掉最终一个,
演示:
在 UserMapper 增加办法 add3.
编写 SQL 句子:
<mapper namespace="com.example.MyBatisDemo.mapper.UserMapper">
<insert id="add3">
insert into userinfo
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name!=null">
username,
</if>
<if test="password!=null">
password,
</if>
<if test="photo!=null">
photo,
</if>
</trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name!=null">
#{name},
</if>
<if test="password!=null">
#{password},
</if>
<if test="photo!=null">
#{photo},
</if>
</trim>
</insert>
</mapper>
测验类:不传入 photo 时
@SpringBootTest
@Slf4j
class UserMapperTest {
@Resource
private UserMapper userMapper;
@Test
void add3() {
UserInfo userInfo = new UserInfo();
userInfo.setName("王五");
userInfo.setPassword("123");
//userInfo.setPhoto("123.png");
int result = userMapper.add3(userInfo);
log.info("用户信息:"+userInfo);
}
}
运转成果:
当传入 photo 时:
\
3. where 标签
首要作用:完毕查询中的 where 替换。它能够完毕假如没有任何的查询条件,那么它能够躲藏查询中的 where sql,但假如存在查询条件,那么会生成 where 的 sql 查询,并且运用 where 标签能够主动的去除最前面一个 and 字符。
演示:
SQL 句子编写:
<!-- 依据 id 查询用户 -->
<select id="getUserById" resultMap="BaseMap">
select * from userinfo
<where>
<if test="id!=null">
id=#{id}
</if>
</where>
</select>
当传入id 时:
成果:
当不传入 id 时:
成果:
假如 里边的都为空了,则不会增加 where 句子。
能够去除最前一个 and:
可是无法去除最终一个 and:
以上标签也能够运用 <trim prefix="where" prefixOverrides="and">
替换。
4. set 标签
依据传入的用户目标特点来更新用户数据,能够运用标签来指定动态内容。
能够去除最终一个,
语法:
update table_name
<set>
<if test="xxx">
...
</if>
...
</set>
演示:
<update id="updateById">
update userinfo
<set>
<if test="name!=null">
username=#{name},
</if>
<if test="password!=null">
password=#{password},
</if>
<if test="photo!=null">
photo=#{photo}
</if>
</set>
where id=#{id}
</update>
测验:
@Test
void updateById() {
UserInfo userInfo = new UserInfo();
userInfo.setId(5);
userInfo.setName("拉普拉斯");
int result = userMapper.updateById(userInfo);
}
成果:
以上标签也能够运用 <trim prefix="set" suffixOverrides=",">
替换。
5. foreach 标签
对集合进行遍历时能够运用该标签。标签有如下特点:
- collection:绑定办法参数中的集合,如 List,Set,Map或数组目标
- item:遍历时的每一个目标
- open:句子块最初的字符串
- close:句子块完毕的字符串
- separator:每次遍历之间距离的字符串
\
演示:
在 UserMapper 中增加办法 delIds
@Mapper // 变成 mybatis interface
public interface UserMapper {
public int delIds(List<Integer> ids);
}
编写 sql 句子:
<mapper namespace="com.example.MyBatisDemo.mapper.UserMapper">
<delete id="delIds">
delete from userinfo where id in
<foreach collection="ids" open="(" close=")" separator="," item="id">
#{id}
</foreach>
</delete>
</mapper>
\
编写测验类:
@SpringBootTest
@Slf4j
class UserMapperTest {
@Resource
private UserMapper userMapper;
@Test
void delIds() {
List<Integer> list = new ArrayList<>();
list.add(5);
list.add(6);
list.add(7);
list.add(8);
list.add(9);
int result = userMapper.delIds(list);
}
}
履行成果:
删去前:
删去后:
\