我正在参加「启航方案」

一、MyBatis 是什么?

MyBatis 是一款优秀的耐久层结构,它支持自界说 SQL、存储进程以及高档映射。MyBatis 去除了简直 一切的 JDBC 代码以及设置参数和获取成果集的工作。MyBatis 能够经过简略的 XML 或注解来装备和 映射原始类型、接口和 Java POJO(Plain Old Java Objects,一般旧式 Java 目标)为数据库中的记 录。

简略来说 MyBatis 是更简略完毕程序和数据库交互的东西,也便是更简略的操作和读取数据库东西。

针对 “MyBatis 是一款优秀的耐久层结构” 进行剖析和弥补:

MyBatis也是一个 ORM (Object Relational Mapping,即目标联络映射)结构。

在面向目标编程语言中,将联络型数据库中的数据与目标树立起映射联络,进而主动的完毕数据与目标的彼此转化:

  1. 将输入数据(即传入目标)+ SQL 映射成原生 SQL。
  2. 将成果集映射为回来目标,即输出目标。\

ORM 把数据库映射为目标:

  • 数据库表(table) –> 类(class)
  • 记录(record,行数据) –> 目标(object)
  • 字段(field) –> 目标的特点(attribute)

一般的 ORM 结构,会将数据库模型的每张表都映射为一个 Java 类。

即,运用 MyBatis 能够像操作目标相同来操作数据库中的表,能够完毕目标和数据库表之间的转化。


即:MyBatis 能够当作是一座 “桥梁”:

将数据库 和 程序,映射起来。

MySQL 和 MyBatis 是不相同的:

MySQL 供给了一个 数据存取(数据办理)的软件。

而 MyBatis 是一个 “中间桥梁”,用于衔接程序和数据库,树立映射联络,进行 数据操作 的中间层(耐久层)。

二、为什么要学习 MyBatis

关于后端开发来说,程序是由以下两个重要的部分组成的:

  1. 后端程序
  2. 数据库

MyBatis 查询数据库入门

而这两个重要的组成部分要通讯,就要依靠数据库衔接东西,那数据库衔接东西有哪些?比方之前咱们 学习的 JDBC,还有今日咱们即将介绍的 MyBatis,那现已有了 JDBC 了,为什么还要学习 MyBatis?

这是因为 JDBC 的操作太繁琐了,咱们回忆一下 JDBC 的操作流程:

  1. 创立数据库衔接池 DataSource
  2. 经过 DataSource获取数据库衔接 Connection
  3. 编写要履行带 ? 占位符的 SQL 句子
  4. 经过 ConnectionSQL 创立操作指令目标 Statement
  5. 替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值
  6. 运用 Statement 履行 SQL 句子
  7. 查询操作:回来成果集 ResultSet,更新操作:回来更新的数量
  8. 处理成果集
  9. 开释资源

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 相关依靠

这儿会涉及两个场景:

  1. 项目创立的时分,引进 MyBatis 相关依靠。
  2. 老项目增加 MyBatis。

1. 新建一个 MyBatis 项目

  1. 创立新项目:

MyBatis 查询数据库入门

MyBatis 查询数据库入门

\

  1. MyBatis 项目是树立在 Spring MVC 项目上的,因而,下面三个结构依靠必不可少:

MyBatis 查询数据库入门

\

  1. 增加 MyBatis 项目:

MyBatis 查询数据库入门

\

  1. 引进可驱动的数据库:

MyBatis 查询数据库入门

  1. 点击 finish即可完毕。

2. 老项目中引进 MyBatis 相关依靠

运用 Edit Starter插件进行增加。

pom.xml文件中的 dependencies标签中,找到一个空行(人工一个也行),鼠标右击,选择 generate

MyBatis 查询数据库入门

MyBatis 查询数据库入门

下面为获取依靠的官方网址:

MyBatis 查询数据库入门

选择要增加的依靠:

MyBatis 查询数据库入门

\

\

1.2 装备数据库衔接字符串

不要当即发动项目,不然就会报错:

MyBatis 查询数据库入门

\

  1. resources下创立三个.yml文件

MyBatis 查询数据库入门
(现在的学习还不会涉及到生产环境)

  1. 装备开发环境装备文件:

MyBatis 查询数据库入门

留意:关于 driver-class-name中所写的驱动称号

  • 驱动称号:咱们运用的是 MySQL,因而咱们填写的是 MySQL 的驱动称号。
  • 假如运用的 MySQL 是 5.x之前的版本,则用 com.mysql.jdbc.Driver
    假如版本大于 5.x,则用 com.mysql.cj.jdbc.Driver

\

  1. 在主装备文件中激活开发环境

MyBatis 查询数据库入门

\

此刻发动项目后就不会报错了:

MyBatis 查询数据库入门

\

1.3 装备 MyBatis 保存的 xml 的目录

MyBatis 有两种操作办法:

  1. 运用 xml的形式。
  2. 注解。(MyBatis 3.1 版本之后供给)

可是,注解的办法并不好用,咱们首要还是重视 xml形式的操作。

  1. 一般咱们在 resources目录下,创立一个子目录,来寄存 xml文件:

MyBatis 查询数据库入门

\

  1. 因为此装备是公共的,所以咱们在主装备文件装备 MyBatis 的 xml 保存途径:

MyBatis 查询数据库入门

\

四、运用 MyBatis 的操作模式操作数据库

MyBatis 的操作模式

MyBatis 查询数据库入门

MyBatis 的操作模式,包括两个部分:

  1. Interface(接口,里边是办法的界说)
  2. xml 文件(对 办法 的完毕,运用 SQL 句子)

在 Interface 接口中会加一个注解,这个注解是MyBatis里的注解 @Mapper,将一般的接口变为 MyBatis 里边的接口,将接口里边的办法映射进 xml文件。

\

MyBatis 查询:完毕一个依据用户id来查询用户信息的操作

数据库中现已创立好的表:

MyBatis 查询数据库入门

\

1. 界说接口

现在根目录底下创立几个包:

  • model 与数据库交互
  • controller 与前端交互
  • server 决定调用哪些映射办法
  • mapper 操作数据,完毕映射

MyBatis 查询数据库入门

\

为了能与数据进行交互,需求创立实体类,来存储在数据库中得到的成果。在 model目录底下创立实体类,(实体类称号最好与数据库的表名共同,这样更好处理):

MyBatis 查询数据库入门

在实体类里边的字段,也要和数据库中表里边的字段称号相同:

MyBatis 查询数据库入门

\

下面就能够在 mapper包底下写数据了:

MyBatis 查询数据库入门

留意:一定要在接口上加上 注解@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 文件的保存途径,并且命名规矩也指定了。

MyBatis 查询数据库入门

按照规矩创立 xml 文件,xml 文件的称号主张和 接口 称号相同,比较好联络:

MyBatis 查询数据库入门

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设置成完毕接口的方位:包名 + 接口称号:

MyBatis 查询数据库入门

\

接着,在 xml 文件中,完毕 接口UserMapper里边的办法了:

查询标签里边的 resultType 是不能省略的,不然拜访网页会报错:

MyBatis 查询数据库入门

\

MyBatis 查询数据库入门

  • 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 中办法

MyBatis 查询数据库入门

service 层

MyBatis 查询数据库入门

controller 层

MyBatis 查询数据库入门

\

发动项目,打开浏览器去拜访:

MyBatis 查询数据库入门

MyBatis 查询数据库入门

\

\

敞开 MyBatis 日志打印功用

一般在开发环境中敞开。

目录结构:

MyBatis 查询数据库入门

# 敞开 Mybatis SQL 打印
logging:
	level:
		com:
			example:
				MyBatisDemo: debug
mybatis:
	configuration:
		log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

敞开后能够在idea的操控台上看见日志:

MyBatis 查询数据库入门

当咱们敞开 MyBatis SQL 日志打印的时分,咱们能清楚看到终究生成的SQL句子以及履行成果。

Spring Boot 单元测验简略运用

五、增、删、改 操作

接下来完毕以下用户信息的增加、删去和修正操作,对应运用MyBatis 的标签如下:

  • <insert>标签:刺进句子。
  • <update>标签:修正句子。
  • <delete>标签:删去句子。

@Transactional 注解

有一点要留意:在进行增、删、改的时分,假如不想污染数据库,能够在Test中加注解:@Transactional(表明在办法履行完之后回滚业务)

MyBatis 查询数据库入门

\

MyBatis 修正操作

  1. interface 里边增加修正办法的声明:

MyBatis 查询数据库入门

留意:@Param 主张要增加上,不然有些电脑上运转会报错,会提示找不到参数。

MyBatis 查询数据库入门
它和 xml 里边的参数是匹配的。

  1. 在 xml 中增加<update>标签和详细的履行 sql

MyBatis 查询数据库入门

称号要对应:

MyBatis 查询数据库入门

\

MyBatis 删去操作

  1. 在 interface 里边增加 删去 的代码声明

MyBatis 查询数据库入门

  1. 在 xml 中增加 <delete>标签和删去的 sql 编写

MyBatis 查询数据库入门

\

MyBatis 增加操作

  1. 在 interface 增加办法声明

MyBatis 查询数据库入门

  1. 在 xml 完毕增加业务

MyBatis 查询数据库入门

\

留意:

MyBatis 查询数据库入门
这儿的参数传入的是一个目标啊,咱们怎样才能获取到里边的特点呢?能够直接经过 #{特点名}去获取就行:

MyBatis 查询数据库入门

\

假如不想将影响的行数,作为回来值了。想要 刺进成功的用户信息的ID 作为回来值!尽管完毕的过程没有发生变化,可是想要达到预期的作用,还需求增加些东西:

在 第二个过程中(办法称号设为 addGetId):想要回来用户信息的ID,还需求增加两个特点:useGeneratedKeyskeyProperty

MyBatis 查询数据库入门

  • useGeneratedKeys: 是否主动生成主键(true 为是,false 为否)。这会令 MyBatis 运用 JDBC 的 getGeneratedKeys 办法来取出由数据 库内部生成的主键(比方:像 MySQL 和 SQL Server 这样的联络型数据库办理系统的主动递增字段),默认值:false
  • keyProperty: 回来的主键赋值到那个特点上,这个时分能够指定 id 特点了。指定能够唯一识别目标的特点,MyBatis 会运用 getGeneratedKeys 的回来值 或 insert 句子的 selectKey 子元素设置它的值,默认值:未设置(unset)。假如生成列不止一个,能够用逗号分隔多个特点称号。

留意:最好加一个特点 keyColumn 设置生成键值在表中的列名,在某些数据库(如 PostgreSQL)中,当主键列不是表中第一列的时分,是有必要设置的。假如生成列不止一个,就能够用逗号分隔多个特点称号。

即,参加 keyColumn 的原因便是为了保证获取对应字段。

\

\

六、查询操作

单表查询

单表查询即为上面所介绍的:

MyBatis 查询数据库入门

\

参数占位符 #{} 和 ${}

1. 功用不同

  • #{} 预编译处理。
  • ${} 字符直接替换。

预编译处理 MyBatis 在处理 #{}时,会将 SQL 中的 #{}替换为 ?。运用 PreparedStatement 的 set 办法来赋值

直接替换 MyBatis 在处理 ${}时,把 ${}替换成变量的值。

能够在之前写过的查询代码中,演示下 #{}${}的差异:

测验类

MyBatis 查询数据库入门

运用 #{}占位符:

MyBatis 查询数据库入门

测验类运转成果:

MyBatis 查询数据库入门

运用 ${}占位符:

MyBatis 查询数据库入门

测验类运转成果:

MyBatis 查询数据库入门

\

${} 的问题

当参数为数值类型时(在不考虑安全问题的前提下),${}和 #{} 的履行作用都是相同的,可是当参数类型为 字符时,再运用 ${}就有问题了,如下代码所示:

<select id="getUserById" resultType="com.example.MyBatisDemo.model.UserInfo">
	select * from userinfo where username=${name}
</select>

以上程序履行时,生成的 SQL 句子如下:

MyBatis 查询数据库入门

这会导致程序报错,因为传递的参数是字符类型的,而在 SQL 语法中,假如是字符类型需求给值增加单引号,不然就会报错,而 ${}是直接替换的,不会主动增加单引号,所以履行就会报错。而运用 #{}采用的是 占位符 预履行的,所以不存在任何问题,它的完毕代码如下:

<select id="getUserById" resultType="com.example.MyBatisDemo.model.UserInfo">
	select * from userinfo where username=#{name}
</select>

以上程序的终究生成的履行 SQL 如下:

MyBatis 查询数据库入门

2. 运用场景不同

尽管运用 #{} 的办法能够处理恣意类型的参数,可是当传递的参数是一个 SQL 指令或 SQL 关键字时 #{} 就会出问题了。比方,当咱们要依据价格从高到低(倒序)、或从低到高(正序)查询时,如下图所示:

MyBatis 查询数据库入门

此刻咱们要传递的排序的关键字,desc 倒序(价格从高到低)或者是 asc 正序(价格从低到高),此刻咱们运用${} 的完毕代码如下:

<select id="getOrderList" resultType="com.example.MyBatisDemo.model.Goods">
    select * from goods order by price ${order}
</select>

以上代码生成的履行 SQL 和 运转成果如下:

MyBatis 查询数据库入门

可是,假如将代码中的 ${}改为 #{},那么程序就会报错,#{}的完毕代码如下:

<select id="getOrderList" resultType="com.example.MyBatisDemo.model.Goods">
    select * from goods order by price #{order}
</select>

以上代码生成的履行 SQL 和 运转成果如下:

MyBatis 查询数据库入门

从上述的履行成果能够看出,当传递的是一般参数时,需求运用 #{} 的办法,而当传递的是 SQL 指令或 SQL 关键字时,需求运用 ${} 来对 SQL 中的参数进行直接替换并履行。


3. 安全性不同

${}#{}最首要的差异体现在安全方面,当运用 ${} 会呈现安全问题,也便是 SQL 注入的问题,而运用 #{} 因为是预处理,所以不会呈现安全问题, 下面经过登录功用来调查二者的差异:

3.1 运用 ${} 完毕用户登录

信息:

MyBatis 查询数据库入门

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 和运转成果如下:

MyBatis 查询数据库入门

从成果能够看出,当咱们传入了正确的用户名和暗码时,能成功查询数据。可是,在咱们运用 ${}时,当咱们在不知道正确暗码的状况下,运用 SQL 注入句子也能得到用户的私家信息,SQL 注入的完毕代码如下:

@Test
void login() {
    List<UserInfo> userInfo = userMapper.login("admin","' or 1='1");
    System.out.println("用户信息:"+userInfo);
}

以上代码生成的履行 SQL 和 运转成果如下:

MyBatis 查询数据库入门
从上述成果能够看出,当运用 ${} 时,在不知道正确暗码的状况下也能得到用户的私家数据,这就像一个小偷在没有你们家钥匙的状况下,也能轻松的打开你们家大门相同,这是何其恐惧的工作。那运用 #{} 有没有安全问题呢?接下来咱们来测验一下。

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 和 履行成果如下:

MyBatis 查询数据库入门

从上述代码能够看出,运用 SQL 注入是无法攻破 #{} 的“大门”的,所以能够放心运用。


总结:

${}#{} 都是 MyBatis 中用来替换参数的,它们二者的差异首要体现在:

  1. 功用不同:${} 是直接替换,而 #{} 是预处理。
  2. 运用场景不同:一般参数运用 #{},假如传递的是 SQL 指令或 SQL 关键字,需求运用 ${},但在运用前一定要做好安全验证。
  3. 安全性不同:运用 ${} 存在安全问题,而 #{} 则不存在安全问题。

like 查询 – 特殊状况

在前面的问题中,因为关键字就那么几个,能够直接穷举,所以很简单在 Controller 层里边判别数据的正确性。

可是!含糊匹配的成果是不能穷举的!假如数据有几百万个,咱们验证数据的正确性得累死!

下面演示:

like 运用 #{}报错:

<select id="getUserByName2" resultType="com.example.MyBatisDemo.model.UserInfo">
  select * from userinfo where username like '%#{username}%'
</select>

报错信息如下:

MyBatis 查询数据库入门

相当于:select * from userinfo where username like '%'username'%'

所以这儿就不能运用 #{}了,能够运用 ${}进行直接的替换,就不会呈现单双引号了:

<select id="getUserByName2" resultType="com.example.MyBatisDemo.model.UserInfo">
  select * from userinfo where username like '%${username}%'
</select>

测验类运转成果如下:

MyBatis 查询数据库入门

可是!运用了 ${},就需求在 Controller 里进行验证数据,这一点很重要!!

可是含糊匹配的成果,是海量的!咱们不或许悉数穷举出来。

\

此刻就能够考虑 concat拼接办法:

能够考虑运用 mysql 的内置函数 concat()来处理,此函数的作用为:

MyBatis 查询数据库入门

完毕代码如下:

<select id="getUserByName2" resultType="com.example.MyBatisDemo.model.UserInfo">
    select * from userinfo where username like concat('%',#{username},'%')
</select>

成果如下:

MyBatis 查询数据库入门

\

多表查询

前置知识

回来类型:resultType

这个咱们很熟悉,这是用于指定 SQL 查询成果映射的目标类型。

可是这儿有个细节!!

MyBatis 查询数据库入门

尽管咱们只是运用了 id 特点,可是有一个问题:当咱们指定了回来的类型之后,这个实体类(UserInfo)里边的特点有必要和数据库中的字段是共同的!哪怕这个特点在 SQL 句子中没有被运用,也是相同的,有必要相同!

在姓名相同的状况下,运转测验类,看看状况:

MyBatis 查询数据库入门

\

假如咱们将 UserInfo 中的 username 特点 改为 name:

MyBatis 查询数据库入门

此刻再来运转测验类:

MyBatis 查询数据库入门

尽管测验经过了,可是 name 特点是为 null 的,而不是 admin。

呈现这个问题的原因便是:数据库表中的 字段称号 和 实体类特点称号,不相同!!

因而无法完毕匹配赋值!

除了修正 实体类特点称号,还有一个办法,运用下面介绍的 resultMap

回来字典映射:resultMap

resultMap 运用场景:

  1. 字段称号和程序中特点名不同的状况,能够运用 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 命名为 idtype 为你要映射的类的地址
  • 一般特点映射里边,column 代表的是数据库中的字段名,property 代表的是程序中特点名。即 column 映射到 property。

此刻就可把 resultType 删去,改用 resultMap了,其值为咱们给 resultMap 起的别号 BaseMap:

<select id="getUserById" resultMap="BaseMap">
    select * from userinfo where id=${id}
</select>

此刻再运转测验单元,得到成果:

MyBatis 查询数据库入门

\

  1. 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;
}

MyBatis 查询数据库入门

先来调查下 articleInfo 表中的信息,方便后面写代码:

MyBatis 查询数据库入门

\

在 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);
    }
}

测验类履行成果:

MyBatis 查询数据库入门

咋一看上面的成果没啥问题,可是调查 特点 UserInfo 时却发现是空的:

MyBatis 查询数据库入门

为什么会呈现上面的状况呢?

原因便是,实体类 ArticleInfo中有 UserInfo特点,可是 文章表中却没有这个字段:

MyBatis 查询数据库入门

此刻咱们就需求 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 中设置映射联络:将文章表中一切字段都映射出来:

MyBatis 查询数据库入门

<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 测验单元,成果如下:

MyBatis 查询数据库入门

MyBatis 查询数据库入门

能够看到 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 运转成果:

MyBatis 查询数据库入门

MyBatis 查询数据库入门

能够看到UserInfo的一切字段都出来了。

可是上面取得的部分信息却是有误的:

MyBatis 查询数据库入门

可是有误的信息却和 ArticleInfo 中的信息相符合:

MyBatis 查询数据库入门

\

得出一个定论:

当一个字段在两张表中都存在时,默认读取的是 association 标签的 property 参数地点的数据表的字段值。

当前 property 是在 ArticleInfo 表中的:

MyBatis 查询数据库入门

下面来看下重名的字段:

MyBatis 查询数据库入门

下面来处理重名的问题。

处理 不同数据表中字段重名的状况

处理办法很简略,增加一个前缀,让 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 运转成果:

MyBatis 查询数据库入门

MyBatis 查询数据库入门

\

这才真实是完毕了 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>

映射

MyBatis 查询数据库入门
中 articleList 目标,运用 collection标签:

<collection property="articleInfoList"
            resultMap="com.example.MyBatisDemo.mapper.ArticleMapper.BaseMap"
            columnPrefix="a_">
</collection>
  • property:需求映射的特点。
  • resultMap:映射目标。
  • columnPrefix:将文章表中 带有 a_前缀的字段值,打包映射到 articleList 目标中。

修正 SQL 句子:

MyBatis 查询数据库入门

<!-- 依据用户 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);
    }
}

运转成果:

MyBatis 查询数据库入门

\

至此一对多查询完毕!

七、复杂状况:动态 SQL 运用

动态 sql 是Mybatis的强壮特性之一,能够完毕不同条件下不同的 sql 拼接。

能够参考官方文档:mybatis – MyBatis 3 | 动态 SQL

1. if 标签

在注册用户的时分,或许会有这样一个问题,如下图所示:

MyBatis 查询数据库入门

注册分为两种字段:必填字段和非必填字段,那假如在增加用户的时分有不确定的字段传入,程序应该 怎么完毕呢?

这个时分就需求运用动态标签 来判别了。

if 标签判别一个参数是否有值,假如没值,那么就会躲藏 if 中的 sql。

可是 有必要有一个值是必传的。

语法:

<if test="username!=null">
	username=#{username}
</if>
  • 在 test 中,判别表达式是否为空。
  • 假如test 的成果为 true,那么拼接里边的 SQL 句子,加上 username=#{username}
  • 假如test 的成果为 false,那么 if 标签中的 sql 就会被忽略。
  • MyBatis 查询数据库入门
    这两个 username 是对应的。姓名是 实体类中的特点名。

演示:

咱们先给 userinfo 表中的 photo 设置一个默认值,默以为 default.png

MyBatis 查询数据库入门

\

在 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);
    }
}

运转成果:

MyBatis 查询数据库入门

当不传入 photo 时:

MyBatis 查询数据库入门

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);
    }
}

运转成果:

MyBatis 查询数据库入门

MyBatis 查询数据库入门

当传入 photo 时:

MyBatis 查询数据库入门

MyBatis 查询数据库入门

\

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 时:

MyBatis 查询数据库入门

成果:

MyBatis 查询数据库入门

当不传入 id 时:

MyBatis 查询数据库入门

成果:

MyBatis 查询数据库入门

假如 里边的都为空了,则不会增加 where 句子。

能够去除最前一个 and:

MyBatis 查询数据库入门

可是无法去除最终一个 and:

MyBatis 查询数据库入门

以上标签也能够运用 <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);
}

成果:

MyBatis 查询数据库入门

MyBatis 查询数据库入门

以上标签也能够运用 <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);
    }
}

履行成果:

MyBatis 查询数据库入门

删去前:

MyBatis 查询数据库入门

删去后:

MyBatis 查询数据库入门

\

MyBatis 查询数据库入门