MyBatis查询数据库

1.MyBatis 是什么?

MyBatis 是⼀款优秀的持久层框架,它⽀持⾃定义 SQL、存储过程以及⾼级映射。MyBatis 去除了几乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。MyBatis 可以通过简单的 XML 或注解来配置 和映射原始类型、接⼝和 Java POJO(Plain Old Java Objects,普通⽼式 Java 对象)为数据库中的记录。

简单来说 MyBatis 是更简单完成程序和数据库交互的⼯具,也就是更简单的操作和读取数据库⼯具。

Mybatis官⽹

2.为什么要学习 MyBatis?

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

1. 后端程序

2. 数据库

MyBatis查询数据库_第1张图片

⽽这两个重要的组成部分要通讯,就要依靠数据库连接⼯具,那数据库连接⼯具有哪些?⽐如之前的 JDBC,还有今天我们将要介绍的 MyBatis,那已经有了 JDBC 了,为什么还要学习 MyBatis? 这是因为 JDBC 的操作太繁琐了,我们回顾⼀下 JDBC 的操作流程:

1. 创建数据库连接池 DataSource

2. 通过 DataSource 获取数据库连接 Connection

3. 编写要执⾏带 ? 占位符的 SQL 语句

4. 通过 Connection 及 SQL 创建操作命令对象 Statement

5. 替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值

6. 使⽤ Statement 执⾏ SQL 语句

7. 查询操作:返回结果集 ResultSet,更新操作:返回更新的数量

8. 处理结果集

9. 释放资源

对于 JDBC 来说,整个操作⾮常的繁琐,我们不但要拼接每⼀个参 数,⽽且还要按照模板代码的⽅式,⼀步步的操作数据库,并且在每次操作完,还要⼿动关闭连接等, ⽽所有的这些操作步骤都需要在每个⽅法中重复书写。于是我们就想,那有没有⼀种⽅法,可以更简 单、更⽅便的操作数据库呢?

答案是肯定的,学习 MyBatis 的真正原因,就是它可以帮助我们更⽅便、更快速的操作数据 库。

3.怎么学MyBatis?

MyBatis 学习只分为两部分:

  • 配置 MyBatis 开发环境;
  • 使⽤ MyBatis 模式和语法操作数据库

4.第⼀个MyBatis查询

开始搭建 MyBatis 之前,我们先来看⼀下 MyBatis 在整个框架中的定位,框架交互流程图:

MyBatis查询数据库_第2张图片

MyBatis 也是⼀个 ORM 框架,ORM(Object Relational Mapping),即对象关系映射。在⾯向 对象编程语⾔中,将关系型数据库中的数据与对象建⽴起映射关系,进⽽⾃动的完成数据与对象 的互相转换:

1. 将输⼊数据(即传⼊对象)+SQL 映射成原⽣ SQL

2. 将结果集映射为返回对象,即输出对象

ORM 把数据库映射为对象:

数据库表(table)--> 类(class)

记录(record,⾏数据)--> 对象(object)

字段(field) --> 对象的属性(attribute)

⼀般的 ORM 框架,会将数据库模型的每张表都映射为⼀个 Java 类。 也就是说使⽤ MyBatis 可以像操作对象⼀样来操作数据库中的表,可以实现对象和数据库表之间 的转换,接下来我们来看 MyBatis 的使⽤吧。

4.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';

 4.2 添加MyBatis框架⽀持

添加 MyBatis 框架⽀持分为两种情况:⼀种情况是对⾃⼰之前的 Spring 项⽬进⾏升级,另⼀种情况是 创建⼀个全新的 MyBatis 和 Spring Boot 的项⽬,下⾯我们分别来演示这两种情况的具体实现。

4.2.1 ⽼项⽬添加MyBatis

如果是在⽼项⽬中新增功能,添加框架⽀持:



 org.mybatis.spring.boot
 mybatis-spring-boot-starter
 2.1.4



 mysql
 mysql-connector-java
 5.1.38
 runtime

添加了 MyBatis 之后,为什么还需要添加 MySQL 驱动呢? MyBatis 就像⼀个平台(类似京东),⽽数据库相当于商家有很多种,不⽌有 MySQL,还有 SQL Server、DB2 等等.....因此这两个都是需要添加的。 

在⽼项⽬中快速添加框架,更简单的操作⽅式是使⽤EditStarters插件 

MyBatis查询数据库_第3张图片

EditStarters 插件的使⽤⽅法: 

MyBatis查询数据库_第4张图片

搜索“MyBatis”添加即可:

4.2.2 新项⽬添加MyBatis

MyBatis查询数据库_第5张图片

MyBatis查询数据库_第6张图片

 4.3 配置连接字符串和MyBatis

 此步骤需要进⾏两项设置,数据库连接字符串设置和 MyBatis 的 XML ⽂件配置。

4.3.1 配置连接字符串

如果是 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 ” 。

4.3.2 配置 MyBatis 中的 XML 路径

MyBatis 的 XML 中保存是查询数据库的具体操作 SQL,配置如下:

# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis:
 mapper-locations: classpath:mapper/**Mapper.xml

4.4 添加业务代码 

下⾯按照后端开发的⼯程思路,也就是下⾯的流程来实现 MyBatis 查询所有⽤户的功能:

MyBatis查询数据库_第7张图片

4.4.1 添加实体类

先添加⽤户的实体类:

package com.example.springmybatisdemo.model;

import lombok.Data;

import java.util.Date;
@Data
public class User {
    private Integer id;
    private String name;
    private String password;
    private String photo;
    private Date createtime;
    private Date updatetime;
}

 4.4.2 添加 mapper 接⼝

数据持久层的接⼝定义:

package com.example.springmybatisdemo.mapper;

import com.example.springmybatisdemo.model.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface UserMapper {
    List queryAll();
}

4.4.3 添加 UserMapper.xml 

数据持久成的实现,mybatis 的固定 xml 格式:




    

UserMapper.xml 查询所有⽤户的具体实现 SQL:




    

 以下是对以上标签的说明:

  • 标签:需要指定 namespace 属性,表示命名空间,值为 mapper 接⼝的全限定 名,包括全包名.类名。
  • select * from userinfo insert into userinfo (username,password,photo)values (#{username},#{password},#{photo}) update userinfo set username = #{username},password = #{password},photo = #{photo} where id = #{id} delete from userinfo where id = #{id}

    特殊的添加:返回⾃增 id 

    默认情况下返回的是受影响的⾏号,如果想要返回⾃增 id,具体实现如下

    MyBatis查询数据库_第8张图片

    • useGeneratedKeys:这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据 库内部⽣成的主键(⽐如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的⾃动 递增字段),默认值:false。 
    • keyColumn:设置⽣成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列 不是表中的第⼀列的时候,是必须设置的。如果⽣成列不⽌⼀个,可以⽤逗号分隔多个属性 名称。
    •  keyProperty:指定能够唯⼀识别对象的属性,MyBatis 会使⽤ getGeneratedKeys 的返回 值或 insert 语句的 selectKey ⼦元素设置它的值,默认值:未设置(unset)。如果⽣成列 不⽌⼀个,可以⽤逗号分隔多个属性名称。

    6.查询操作

    6.1 单表查询

    下⾯我们来实现⼀下根据⽤户 id 查询⽤户信息的功能。 Controller 实现代码如下:

    @RequestMapping("/getuser")
    public User getUserById(Integer id) {
     return userService.getUserById(id);
    }
    

    Mapper.xml 实现代码如下:

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

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

    预编译处理是指:MyBatis 在处理#{}时,会将 SQL 中的 #{} 替换为?号,使⽤ PreparedStatement 的 set ⽅法来赋值。直接替换:是MyBatis 在处理 ${} 时,就是把 ${} 替换成变量的值。

    MyBatis查询数据库_第9张图片

    6.1.2 ${} 优点

    使⽤ ${sort} 可以实现排序查询,⽽使⽤ #{sort} 就不能实现排序查询了,因为当使⽤ #{sort} 查询时, 如果传递的值为 String 则会加单引号,就会导致 sql 错误。

    6.1.3 SQL 注⼊问题

    sql 注⼊代码:“' or 1='1”

    使用#号

     测试:

    @Test
        void queryByNameAndPassWord() {
            String username = "admin";
            String password = "' or 1='1";
            User user = userMapper.queryByNameAndPassWord(username,password);
            log.info(user == null ? null:user.toString());
        }

    结果:数据没有查询出来,响应正常

    MyBatis查询数据库_第10张图片 使用$符号

     

    测试:

     @Test
        void queryByNameAndPassWord() {
            String username = "admin";
    //        String password = "123";
            String password = "' or 1='1";
            User user = userMapper.queryByNameAndPassWord(username,password);
            log.info(user == null ? null:user.toString());
        }

     结果:数据不该被查出来,结果被查出来

    MyBatis查询数据库_第11张图片

     什么时候使用$MyBatis查询数据库_第12张图片

     排序时,只能使用$。

    MyBatis查询数据库_第13张图片

    MyBatis查询数据库_第14张图片

    6.1.4 like 查询

    使用#号

    MyBatis查询数据库_第15张图片

    使用$符号

    MyBatis查询数据库_第16张图片程序可以正常运行,但是$存在SQL注入的问题

    需要使用MySQL的内置函数

    MyBatis查询数据库_第17张图片 $和#区别

    MyBatis查询数据库_第18张图片

    6.2 多表查询

    如果是增、删、改返回搜影响的⾏数,那么在 mapper.xml 中是可以不设置返回的类型的:

    
            insert into userinfo (username,password,photo)values (#{username},#{password},#{photo})
        
        
            update userinfo set username = #{username},password = #{password},photo = #{photo} where id = #{id}
        
        
            delete from userinfo where id = #{id}
        

    对于 select * from userinfo

    它的优点是使⽤⽅便,直接定义到某个实体类即可。 

    6.2.2 返回字典映射:resultMap

    resultMap 使⽤场景:

    • 字段名称和程序中的属性名不同的情况,可使⽤ resultMap 配置映射;
    • ⼀对⼀和⼀对多关系可以使⽤ resultMap 映射并查询数据。

    字段名和属性名不同的情况 

    数据库中

    MyBatis查询数据库_第19张图片 

    程序中

    public class User {
        private Integer id;
        private String name;
        private String pwd;
        private String photo;
        private Date createtime;
        private Date updatetime;
    }
    

    mapper.xml 

    
            
            
            
        
        

    MyBatis查询数据库_第20张图片

    MyBatis查询数据库_第21张图片

    MyBatis查询数据库_第22张图片

     

    6.2.3 多表查询

    在多表查询时,如果使⽤ resultType 标签,在⼀个类中包含了另⼀个对象是查询不出来被包含的对象 的,⽐如以下实体类:

    @Data
    public class ArticleInfo {
     private Integer id;
     private String title;
     private String content;
     private LocalDateTime createtime;
     private LocalDateTime updatetime;
     private Integer rcount;
     // 包含了 userinfo 对象
     private UserInfo user;
    }

    程序的执⾏结果如下图所示:

     此时我们就需要使⽤特殊的⼿段来实现联表查询了。 

    6.2.3.1 ⼀对⼀的表映射

    ⼀对⼀映射要使⽤ 标签,具体实现如下(⼀篇⽂章只对应⼀个作者):

    
            
            
            
            
            
            
    
            
        
        

    以上使⽤ 标签,表示⼀对⼀的结果映射:

    • property 属性:指定 Article 中对应的属性,即⽤户。
    • resultMap 属性:指定关联的结果集映射,将基于该映射配置来组织⽤户数据。
    • columnPrefix 属性:绑定⼀对⼀对象时,是通过 columnPrefix+association.resultMap.column 来映射结果集字段。 association.resultMap.column是指 标签中 resultMap属性,对应的结果集映 射中,column字段。  

    注意:columnPrefix 属性不能省略,如果省略当联表中如果有相同的字段,那么就会导致查询出错。 ⽐如两篇⽂章都是⼀个⼈写的,如果没有 columnPrefix 就会导致查询的⽤户 id(因为和⽂章表 id 相 同)查询出错。 

    articleinfo:

    public class ArticleInfo {
        private Integer id;
        private String title;
        private String content;
        private Date createtime;
        private Date updatetime;
        private Integer rcount;
        private User user;
    }

     Mapper:

    @Mapper
    public interface ArticleMapper  {
        List queryArticle();
    }
    

    6.2.3.2 ⼀对多:⼀个用户多篇⽂章案例

    ⼀对多需要使⽤ 标签,⽤法和 相同

    7.复杂情况:动态SQL使用

    动态 sql 是Mybatis的强⼤特性之⼀,能够完成不同条件下不同的 sql 拼接。

    动态SQL:根据输入参数不同,动态的拼接SQL

    可以参考官⽅⽂档:mybatis – MyBatis 3 | 动态 SQL

    7.1 标签

    在注册⽤户的时候,可能会有这样⼀个问题,如下图所示:

    MyBatis查询数据库_第23张图片 

    注册分为两种字段:必填字段和⾮必填字段,那如果在添加⽤户的时候有不确定的字段传⼊,程序应该 如何实现呢? 

    这个时候就需要使⽤动态标签 来判断了,⽐如添加的时候性别 state为⾮必填字段,具体实现如下:

    
            insert into articleinfo(title,content,uid
            
                ,state
            
            )
            values
            (#{title},#{content},#{userId}
            
                ,#{state}
            
    
            )
        

    注意 test 中的 state,是传⼊对象中的属性,不是数据库字段。 

    7.2 标签

    之前的插⼊⽤户功能,只是有⼀个 state字段可能是选填项,如果所有字段都是⾮必填项,就考虑使⽤ 标签结合标签,对多个字段都采取动态⽣成的⽅式。

    标签中有如下属性:

    • prefix:表示整个语句块,以prefix的值作为前缀
    • suffix:表示整个语句块,以suffix的值作为后缀
    • prefixOverrides:表示整个语句块要去除掉的前缀
    • suffixOverrides:表示整个语句块要去除掉的后缀

    调整 UserMapper.xml 的插⼊语句为: 

       
            insert into articleinfo(
            
                
                    title,
                
                
                    content,
                
                
                    uid,
                
                
                    state
                
                )
            
            
                
                    #{title},
                
                
                    #{content},
                
                
                    #{userId},
                
                
                    #{state}
                
            
        

    7.3 标签

    传⼊的⽤户对象,根据属性做 where 条件查询,⽤户对象中属性不为 null 的,都为查询条件。