Part08-Mybatis

YangeIT大约 28 分钟数据库数据库JDBCMyBatisSpringBoot

Part08-Mybatis

前言

在前面我们学习MySQL数据库时,都是利用图形化客户端工具(如:idea、datagrip),来操作数据库的。

在客户端工具中,编写增删改查的SQL语句,发给MySQL数据库管理系统,由数据库管理系统执行SQL语句并返回执行结果。

增删改操作:返回受影响行数

查询操作:返回结果集(查询的结果)

我们做为后端程序开发人员,通常会使用Java程序来完成对数据库的操作。Java程序操作数据库的技术呢,有很多啊。

  • JDBC:(Java DataBase Connectivity),就是使用Java语言操作关系型数据库的一套API。 【是操作数据库最为基础、底层的技术】

那现在在企业项目开发中呢,一般都会使用基于JDBC的封装的高级框架,比如:Mybatis、MybatisPlus、Hibernate、SpringDataJPA。 而这些技术,目前的市场占有份额如下图所示:

image-20231130112408855

从上图中,我们也可以看到,目前最为主流的就是Mybatis,其次是MybatisPlus。

所以,在我们的课程体系中呢,这两种主流的操作数据库的框架我们都要学习。 而我们在学习这两个主流的框架之前,还需要学习一下操作数据库的基础基础 JDBC。 然后接下来,再来学习Mybatis。 而在我们后面的课程中,我们还要学习MybatisPlus框架。 那么今天呢,我们就先来学习 JDBC 和 Mybatis。

今天的课程安排

  1. 能够说出 JDBC 的作用 🍐
  2. 能够使用 JDBC 对数据库进行增删改查操作 ✏️
  3. 能够说出数据库连接池作用 🍐
  4. SpringBoot整合Mybatis入门 ✏️🍐 ❤️
  5. Mybatis操作curd ✏️🍐 ❤️

1. JDBC

1.1 概述和入门 🍐 ✏️

概述和入门

JDBC:(Java DataBase Connectivity),就是使用Java语言操作关系型数据库的一套API。

image-20231130113441567

本质:

  • sun公司官方定义的一套操作所有关系型数据库的规范,即接口。

  • 各个数据库厂商去实现这套接口,提供数据库驱动jar包。

  • 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。

快速入门

需求: 通过JDBC程序,执行update语法,更新用户表中的数据

步骤:

  • 准备工作:
    • 创建项目,引入mysql的驱动、junit依赖
    • 注册驱动
    • 获取连接对象 Connection
    • 获取SQL语句执行对象 Statement
  • 执行SQL语句
  • 释放资源
  1. 准备数据库 db86,及数据库表 user
create table user(
    id int unsigned primary key auto_increment comment 'ID,主键',
    username varchar(20) comment '用户名',
    password varchar(32) comment '密码',
    name varchar(10) comment '姓名',
    age tinyint unsigned comment '年龄'
) comment '用户表';

insert into user(id, username, password, name, age) values (1, 'daqiao', '123456', '大乔', 22),
                                                            (2, 'xiaoqiao', '123456', '小乔', 18),
                                                            (3, 'diaochan', '123456', '貂蝉', 24),
                                                            (4, 'lvbu', '123456', '吕布', 28),
                                                            (5, 'zhaoyun', '12345678', '赵云', 27);
  1. 创建一个普通的maven项目(非springboot项目),名字可为:jdbc_maven_demo,引入mysql的驱动、junit依赖
image
image
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.9.3</version>
    <scope>test</scope>
</dependency>

总结

课堂作业

  1. jdbc操作数据库,本质是什么?🎤
  2. jdbc代码和sql语句混在一起,方便维护吗?🎤
  3. jdbc的结果集,变成对象,变成集合逻辑繁琐吗? 🎤

2. Mybatis基础 ✏️ 🍐 ❤️

2.1 介绍Mybatis ✏️

介绍Mybatis

image-20231130165537310
  • MyBatis是一款优秀的 持久层 框架,用于简化JDBC的开发。

MyBatis本是 Apache的一个开源项目iBatis,2010年这个项目由apache迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。

官网:https://mybatis.org/mybatis-3/zh/index.htmlopen in new window

在上面我们提到了两个词:一个是持久层,另一个是框架。

  • 持久层:指的是就是数据访问层(dao),是用来操作数据库的。
image-20220901114951631
image-20220901114951631
  • 框架:是一个半成品软件,是一套可重用的、通用的、软件基础代码模型。在框架的基础上进行软件开发更加高效、规范、通用、可拓展。

Mybatis对比JDBC:

image-20231130170316723
image-20231130170316723

通过Mybatis就可以大大简化原生的JDBC程序的代码编写,比如 通过 select * from user 查询所有的用户数据,通过JDBC程序操作呢,需要大量的代码实现,而如果通过Mybatis实现相同的功能,只需要简单的三四行就可以搞定。

代码操作

1. 创建springboot工程

创建springboot工程(如springboot_mybatis_quickstart),建议使用maven方式

image
image

2. 项目工程创建完成后,自动在pom.xml文件中,导入Mybatis依赖和MySQL驱动依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!-- 如果安装的是1.8  就改成1.8  -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>


    <groupId>cn.yangeit</groupId>
    <artifactId>springboot_mybatis_quickstart</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


    <dependencies>
        <!--   web的开发的starter依赖,包含tomcat     -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--   单元测试starter依赖     -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--可以免除getset方法的书写-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
        <!--工具类-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.27</version>
        </dependency>

        <!-- Mybatis的起步依赖 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.0</version>

        </dependency>

        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <!--作用: 项目打包时,把需要的各种依赖包都打到jar包中,jar包可以独立运行,使用“java -jar”可以直接运行-->
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.7.4</version>
            </plugin>
        </plugins>
    </build>
</project>



















































 
 
 
 
 
 
 
 
 
 
 
 
 
 
 













3. 数据准备

创建用户表user,并创建对应的实体类User。

  • 用户表(如果已经存在,就不用创建了)
create table user(
    id int unsigned primary key auto_increment comment 'ID,主键',
    username varchar(20) comment '用户名',
    password varchar(32) comment '密码',
    name varchar(10) comment '姓名',
    age tinyint unsigned comment '年龄'
) comment '用户表';

insert into user(id, username, password, name, age) values
    (1, 'daqiao', '123456', '大乔', 22),
    (2, 'xiaoqiao', '123456', '小乔', 18),
    (3, 'diaochan', '123456', '貂蝉', 24),
    (4, 'lvbu', '123456', '吕布', 28),
    (5, 'zhaoyun', '12345678', '赵云', 27);
image
image

4. 实体类:实体类的属性名与表中的字段名一一对应。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id; //ID
    private String username; //用户名
    private String password; //密码
    private String name; //姓名
    private Integer age; //年龄
}

项目创建后的效果图:

image
image

此时不要运行,因为没有配置数据库链接信息,会报错!

总结

课堂作业

  1. 参考上述步骤,完成MyBatis的入门案例!和日志输出🎤

2.2 JDBC VS Mybatis 🍐

JDBC VS Mybatis

JDBC程序的缺点:

  • url、username、password 等相关参数全部硬编码在java代码中。
  • 查询结果的解析、封装比较繁琐。
  • 每一次操作数据库之前,先获取连接,操作完毕之后,关闭连接。 频繁的获取连接、释放连接造成资源浪费。

分析了JDBC的缺点之后,我们再来看一下在mybatis中,是如何解决这些问题的:

  1. 数据库连接四要素(驱动、链接、用户名、密码),都配置在springboot默认的配置文件 application.properties中

  2. 查询结果的解析及封装,由mybatis自动完成映射封装,我们无需关注

  3. 在mybatis中使用了数据库连接池技术,从而避免了频繁的创建连接、销毁连接而带来的资源浪费。

image-20231130184132204

使用SpringBoot+Mybatis的方式操作数据库,能够提升开发效率、降低资源浪费

而对于Mybatis来说,我们在开发持久层程序操作数据库时,需要重点关注以下两个方面:

  1. application.yml 配置文件中的数据源链接信息
spring:
  datasource:
    # 驱动
    driver-class-name: com.mysql.jdbc.Driver
    # 数据库链接以及参数
    url: jdbc:mysql://localhost:3306/db86?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&useSSL=false
    # 用户名
    username: root
    # 数据库密码
    password: 1234
  1. Mapper接口(编写SQL语句)
@Mapper
public interface UserMapper {
    @Select("select * from user")
    public List<User> list();
}

总结

课堂作业

  1. JDBC的缺点有哪些?🎤

2.3 数据库连接池 🍐

在前面我们所讲解的mybatis中,使用了数据库连接池技术,避免频繁的创建连接、销毁连接而带来的资源浪费。下面我们就具体的了解下数据库连接池。

数据库连接池

image-20221210160341852
image-20221210160341852

没有使用数据库连接池:

客户端执行SQL语句:要先创建一个新的连接对象,然后执行SQL语句,SQL语句执行后又需要关闭连接对象从而释放资源,每次执行SQL时都需要创建连接、销毁链接,这种频繁的重复创建销毁的过程是比较耗费计算机的性能 。数据库连接池是个容器,负责分配、管理数据库连接(Connection)

image-20221210161016314
  • 程序在启动时,会在数据库连接池(容器)中,创建一定数量的Connection对象,允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个

  • 客户端在执行SQL时,先从连接池中获取一个Connection对象,然后在执行SQL语句,SQL语句执行完之后,释放Connection时就会把Connection对象归还给连接池(Connection对象可以复用),释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏

  • 客户端获取到Connection对象了,但是Connection对象并没有去访问数据库(处于空闲),数据库连接池发现Connection对象的空闲时间 > 连接池中预设的最大空闲时间,此时数据库连接池就会自动释放掉这个连接对象

数据库连接池的好处: 👍

  1. 资源重用
  2. 提升系统响应速度
  3. 避免数据库连接遗漏

代码操作

1). Hikari(追光者) [默认的连接池]

image-20231130184846969
image-20231130184846969

总结

课堂作业

  1. :microphon数据库连接池有什么好处?如果不配置,会使用连接池吗?

2.4 参数占位符

参数占位符

在Mybatis中提供的参数占位符有两种:${...}#{...}

  • #{...} 安全,常用

    • 执行SQL时,会将#{…}替换为?,生成预编译SQL,会自动设置参数值
    • 使用时机:参数传递,都使用#{…}
  • ${...} 有危险,慎用

    • 拼接SQL。直接将参数拼接在简单实用SQL语句中,存在SQL注入问题
    • 使用时机:如果对表名、列表进行动态设置时使用

注意事项:在项目开发中,建议使用#{...},生成预编译SQL,防止SQL注入安全。

代码操作

需求1:查询id为1的 用户信息

UserMapper接口

   /*
    mybatis的传值:
    参数占位符:#{参数}
    查询id的用户
     */
    @Select("select * from user where id = #{id}")
    public User getUserById(Integer id);

需求2:查询年龄在[19,20)之间的用户信息


/*
查询年龄在开始年龄 到结束年龄的用户信息
    */
@Select("select * from user where age between #{startage} and #{endAge}")
public List<User> findAllByAge(String startage,String endAge);

测试类:

@Test
public void testGetUserById(){

    User userById = userMapper.getUserById(3);
    System.out.println(userById);
    //User(id=3, username=diaochan, password=123456, name=貂蝉, age=24)
    User user2 = userMapper.getUserById(222222);
    System.out.println(user2);
    //null
}


@Test
public void testFindAllByAge(){
    List<User> allByAge = userMapper.findAllByAge("19", "27");
    System.out.println(allByAge);
}

运行结果:

image
image

注意:

1). 如果我们在定义SQL语句的时候,使用的是 #{...}

image-20231130203428766

我们看到,最终生成的SQL语句是预编译的SQL语句。

2). 如果我们在定义SQL语句的时候,使用的是 ${...}

image-20231130203742136

总结

课堂作业

  1. ${...}#{...}的区别是什么?🎤

在mybatis的mapper接口中,我们定义SQL语句,参数占位符可以使用 #{...}${...},那具体什么区别呢?

符号说明场景优缺点
#{…}执行时,会将#{…}替换为?,生成预编译SQL,并自动设置参数值参数值传递安全、性能高 (推荐)
${…}拼接SQL。直接将参数拼接在SQL语句中,存在SQL注入问题表名、字段名动态设置时使用不安全、性能低

2.5 Mybatis的XML配置文件 ✏️ 🍐 ❤️

Mybatis的开发有两种方式:

  1. 注解
  2. XML

Mybatis的XML配置文件

1. XML配置文件规范

使用Mybatis的注解方式,主要是来完成一些简单的增删改查功能。

如果需要实现复杂的SQL功能,建议使用XML来配置映射语句,也就是将SQL语句写在XML配置文件中。

在Mybatis中使用XML映射文件方式开发,需要符合一定的规范:非常重要

  1. XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名)

  2. XML映射文件的namespace属性为Mapper接口全限定名一致

  3. XML映射文件中sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致。

image-20231130185307759

<select>标签:就是用于编写select查询语句的。

resultType属性,指的是查询返回的单条记录所封装的类型。

代码操作

第1步:创建XML映射文件

image
image
image
image

在resources下创建同名目录,注意不是使用. 而是使用 /

MybatisX的使用在后续学习中会继续分享

学习了Mybatis中XML配置文件的开发方式了,大家可能会存在一个疑问:到底是使用注解方式开发还是使用XML方式开发?

官方说明:https://mybatis.net.cn/getting-started.htmlopen in new window

image-20220901173948645
image-20220901173948645

结论: 使用Mybatis的注解,主要是来完成一些简单的增删改查功能。如果需要实现复杂的SQL功能,建议使用XML来配置映射语句。

总结

课堂作业

  1. 实际开发中使用注解还是xml文件映射,为什么?🎤
  2. 在Mybatis中使用XML映射文件方式开发,需要符合一定的规范,是哪些规范?

3. 管理系统升级 ✏️

3.1 部门列表查询

部门列表查询

需求:查询数据库表中的所有部门数据,展示在页面上。

image-20231130190642478
image-20231130190642478

代码操作

  1. 准备数据库表 dept 及 实体类 Dept

    -- 部门管理
    create table dept(
        id int unsigned primary key auto_increment comment '主键ID',
        name varchar(10) not null unique comment '部门名称',
        create_time datetime  comment '创建时间',
        update_time datetime  comment '修改时间'
    ) comment '部门表';
    
    INSERT INTO `dept` VALUES (1,'学工部','2023-09-25 09:47:40','2023-09-25 09:47:40'),
    						(2,'教研部','2023-09-25 09:47:40','2023-09-25 09:47:40'),
    						(3,'咨询部','2023-09-25 09:47:40','2023-09-25 09:47:40'),
    						(4,'就业部','2023-09-25 09:47:40','2023-09-25 09:47:40'),
    						(5,'人事部','2023-09-25 09:47:40','2023-09-25 09:47:40'),
    						(6,'行政部','2023-09-27 14:00:00','2023-09-27 14:00:00'),
    						(7,'综合部','2023-09-25 14:44:19','2023-09-25 14:44:19');
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import java.time.LocalDateTime;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Dept {
        private Integer id;
        private String name;
        private LocalDateTime createTime;
        private LocalDateTime updateTime;
    }
    
    
  2. 在项目中引入Mybatis的起步依赖,mysql的驱动包

<!-- Mybatis的起步依赖 -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.0</version>

</dependency>

<!-- MySQL驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
    <scope>runtime</scope>
</dependency>
  1. 在项目的application.yml 中引入Mybatis的配置信息 (数据库连接、日志输出)
spring:
  datasource:
    # 驱动
    driver-class-name: com.mysql.jdbc.Driver
    # 数据库链接以及参数
    url: jdbc:mysql://localhost:3306/db86?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&useSSL=false
    # 用户名
    username: root
    # 数据库密码
    password: 1234
    type: com.alibaba.druid.pool.DruidDataSource

# mybatis的配置
mybatis:
  configuration:
    # 标准日志输出
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3.2 数据封装

数据封装

我们看到查询返回的结果中大部分字段是有值的,但是createTime,updateTime这两个字段是没有值的,而数据库中是有对应的字段值的,这是为什么呢?

image-20231130192556070
image-20231130192556070

原因如下:

  • 实体类属性名和数据库表查询返回的字段名一致,mybatis会自动封装。
  • 如果实体类属性名和数据库表查询返回的字段名不一致,不能自动封装。

解决方案:

  1. 起别名
  2. 结果映射
  3. 开启驼峰命名

代码操作

1. 起别名:在SQL语句中,对不一样的列名起别名,别名和实体类属性名一样

@Select("select id, name, create_time createTime, update_time updateTime from dept")
public List<Dept> findAll();

2. 开启驼峰命名(推荐):如果字段名与属性名符合驼峰命名规则,mybatis会自动通过驼峰命名规则映射

驼峰命名规则: abc_xyz => abcXyz

  • 表中字段名:abc_xyz
  • 类中属性名:abcXyz
# mybatis的配置
mybatis:
  configuration:
    # 标准日志输出
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 开启驼峰命名
    map-underscore-to-camel-case: true






 

要使用驼峰命名前提是 实体类的属性 与 数据库表中的字段名严格遵守驼峰命名。 image

3.3 删除部门

删除部门

删除部门数据。在点击 "删除" 按钮,会根据ID删除部门数据。

image-20231130200858803

了解了需求之后,我们再看看接口文档中,关于删除部门的接口的描述,然后根据接口文档进行服务端接口的开发。

image-20231130201306202

思路分析

明确了删除部门的需求之后,再来梳理一下实现该功能时,三层架构每一层的职责:

image-20231130201422073

代码操作

简单参数接收

我们看到,在controller中,需要接收前端传递的请求参数。 那接下来,我们就先来看看在服务器端的Controller程序中,如何获取这类简单参数。

具体的方案有如下三种:

方案一:通过原始的 HttpServletRequest 对象获取请求参数

/**
* 根据ID删除部门 - 简单参数接收: 方式一 (HttpServletRequest)
*/
@DeleteMapping("/depts")
public Result delete(HttpServletRequest request){
    String idStr = request.getParameter("id");
    int id = Integer.parseInt(idStr);

    System.out.println("根据ID删除部门: " + id);
    return Result.success();
}

这种方案实现较为繁琐,而且还需要进行手动类型转换。

方案二:通过Spring提供的 @RequestParam 注解,将请求参数绑定给方法形参

@DeleteMapping("/depts")
public Result delete(@RequestParam("id") Integer deptId){
    System.out.println("根据ID删除部门: " + deptId);
    return Result.success();
}

@RequestParam 注解的value属性,需要与前端传递的参数名保持一致 。

@RequestParam注解required属性默认为true,代表该参数必须传递,如果不传递将报错。 如果参数可选,可以将属性设置为false。

方案三:如果请求参数名与形参变量名相同,直接定义方法形参即可接收。(省略@RequestParam)

@DeleteMapping("/depts")
public Result delete(Integer id){
    System.out.println("根据ID删除部门: " + deptId);
    return Result.success();
}

对于以上的这三种方案呢,我们推荐第三种方案。

总结

课堂作业

1. @RequestParam注解有什么作用?什么时候可以省略?🎤

3.4 新增部门

新增部门

点击 "新增部门" 的按钮之后,弹出新增部门表单,填写部门名称之后,点击确定之后,保存部门数据。

image-20231130204018116

了解了需求之后,我们再看看接口文档中,关于新增部门的接口的描述,然后根据接口文档进行服务端接口的开发 。

image-20231130204242647

思路说明

明确了新增部门的需求之后,再来梳理一下实现该功能时,三层架构每一层的职责:

image-20231130204542046

代码操作

json参数接收

我们看到,在controller中,需要接收前端传递的请求参数。 那接下来,我们就先来看看在服务器端的Controller程序中,如何获取json格式的参数。

  • JSON格式的参数,通常会使用一个实体对象进行接收 。

  • 规则:JSON数据的键名与方法形参对象的属性名相同,并需要使用@RequestBody注解标识。

image-20231130205040475

总结

课堂作业

  1. @RequestBody@RequestParam 注解区别?🎤