2. Web后端开发-学习笔记-程序员宅基地

技术标签: 学习  笔记  前端  

该笔记包含了Maven,Spring Boot,MySQL和MyBatis的相关内容,为笔者学习黑马程序员Web开发时做的笔记,便于知识遗漏时能及时查看查漏补缺。如果能帮助到您,那便更好了。

2.1 Maven

管理和构建Java项目的开源工具

2.1.1 Maven的作用

image-20231030184048844

2.1.2 Maven的基本思想
  1. 通过Maven自带的各种插件完成项目的标准化构建与各种文件的生成和管理
  2. 项目对象模型POM(Porject Object Model),Maven的核心思想。通过pom.xml中配置的信息来描述Java工程
  3. pom.xml中可添加依赖信息来进行依赖管理(pom.xml中配置的依赖会先在本地仓库中寻找,本地仓库中无就从中央仓库或者远程/私有仓库拉取)

image-20231030190852597

2.1.3 maven依赖管理
1. 依赖配置
 <!-- 所有的依赖都在pom.xml中配置 -->
 <dependencies>
   
   <dependency>
     <groupId>ch.qos.logback</groupId> 项目依赖的组织的唯一标识符
     <artifactId>logback-classic</artifactId> 项目依赖的模块或库的名称
     <version>1.4.5</version> 依赖的具体版本
   </dependency>
   
 </dependencies>

不知道groupId和version可在https://mvnrepository.com/中查看(version可选择Usage最高的版本)

2. 依赖传递

image-20231030212503144

3. 依赖范围

image-20231030213337581

4. 生命周期

image-20231030214045879

2.2 Web入门

从Spring Boot开始,并贯穿始终

2.2.1 Spring生态

image-20231031171312927

2.2.2 SpringBoot Web快速入门

创建好一个SpringBoot项目后,会自动生成一个启动类。要想启动SpringBoot项目就直接运行该启动类

image-20231031194805217

 package com.zzy.springtest01;
 ​
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 ​
 // 启动类——用于启动SpringBoot工程
 @SpringBootApplication
 public class SpringTest01Application {
     public static void main(String[] args) {
         SpringApplication.run(SpringTest01Application.class, args);
     }
 }

接下来我们在/com.zzy.springtest01目录下连包带类创建controller.HelloController,用于处理浏览器请求。

当浏览器url输入localhost:8080/response(SpringBoot项目默认占用8080端口)后,HelloController能返回给浏览器一段信息。

controller层的核心功能就是接收浏览器请求,并响应

 package com.zzy.springtest01.controller;
 ​
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 ​
 /**
  * 请求处理类
  */
 @RestController 
 public class HelloController {
   
     @RequestMapping("/response")
     public String response() {
         String res = "进行请求处理...";
         System.out.println(res);
         return res;
     }
 }

@RestController:指定该类为Spring的请求处理类/控制器类,用于处理HTTP请求,并返回数据而非视图

@RequestMapping(/response):表示当浏览器访问/response时,就会调用该函数

2.2.3 HTTP协议
1. 概述

image-20231031201332511

2. 请求协议(浏览器 -> 服务器)

image-20231031202700372

image-20231031202941967

3. 响应协议(服务器 -> 浏览器)

image-20231031204008960

image-20231031204334412

4. 协议解析

程序员直接解析请求的内容,从字符串中提取出请求行、请求头、请求体,以及在输出流中输出响应行、响应头和响应体是非常麻烦的。

很多公司开发了专门的Web服务器来进行HTTP请求/响应解析,如Apache Tomcat

2.2.4 Apache Tomcat

Tomcat是一个Web服务器,也叫Servlet容器

Web服务器是一个软件,对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作

我们只需在自己的服务器中安装一个Web服务器(如Tomcat),将开发好的应用直接部署在Tomcat上

1. 将自己的程序部署到Tomcat中

将自己的程序资源(比如myApp文件夹)拷贝到Tomcat下的webapps文件夹下就可以了。浏览器访问localhost:8080/myApp(Tomcat默认占用8080端口,Java有时也占用8080端口,易冲突)

2. Spring内嵌Tomcat

SpringBoot中开发web项目都要引入spring-boot-starter-web依赖,这一依赖内嵌了Tomcat服务器,并占用8080端口(就不用自己本地下载安装Tomcat了)(注:spring-boot-starter称为起步依赖,是每个特定项目都要引入的依赖。pom.xml中不会注明版本,因为这些依赖都是交由父工程由Spring团队统一管理)

     <!--  该项目的父工程  -->
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
         <version>3.1.5</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
     <!--  每个版本的父工程都指定了各起步依赖的版本信息,无需自己操心  -->
2.2.5 请求响应

浏览器GET和POST请求,以及对请求的响应

1. 概述

image-20231101163045136

Tomcat也被称为Servlet容器

SpringBoot提供了一项非常核心的程序:DispatcherServlet类,也被称为核心控制器或者前端控制器

它将浏览器的HTTP请求封装到HttpServletRequest类中,将返回给浏览器的数据封装到HttpServletResponse类中,简化了HTTP协议的使用

2. 请求-Postman接口测试工具

接口测试工具。缺少前端时,后端开发人员每开发完一个接口,就可以用Postman进行测试

Postman实质上模拟Chrome,提出GET或者POST请求,并接受返回数据

!!!注意:浏览器地址栏的请求都是GET请求

3. 请求-简单参数

原始方式:通过HttpServletRequest对象获取请求参数

package com.zzy.springtest01.controller;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GetRequestParams {

    @RequestMapping("/params")
    public String getRequestParams(HttpServletRequest httpServletRequest){ // 声明一个HttpServletRequest对象来接受浏览器请求报文
        String name = httpServletRequest.getParameter("name"); // 调用HttpServletRequest对象的getParameter()返回请求的参数(String型)
        String age = httpServletRequest.getParameter("age");

        String str = "获取到数据 name="+name+" age="+age;
        System.out.println(str);
        return str;
    }
}

// Postman GET请求:http://localhost:8080/params?name=Tom&age=21

基于SpringBoot的接收方式:要求形参中的参数名称(name, age)要和请求的参数名称(name, age)一致,SpringBoot才会一一对应

  // 基于SpringBoot来获取请求参数
     @RequestMapping("/params")
     public String getRequestParams(String name, Integer age){ // 直接在方法形参中指定各参数,SpringBoot会自动进行类型转换
         String str = "name = "+name+" age = "+age;
         System.out.println(str);
         return str;
     }
 // Postman GET请求:http://localhost:8080/params?name=Tom&age=21
4. 请求-实体参数

当使用基于SpringBoot的接收方式接收简单参数时,参数数量较多不方便,则可用实体类来接收参数

实体类: Java中不包含业务逻辑的单纯用来存储数据的 java类,定义了一系列存储数据的属性以及简单的set(), get(),toString()方法

 com.zzy.springtest01
   |
   |
   controller
   |   |
   |   RequestHandler.java
   |
   pojo
       |
       User.java
       
 // User.java
 // pojo包下存放实体类
 public class User {
     private String name; // !!!注意,实体类的属性取名要和请求的参数的名称一样,Spring才能一一对应
     private Integer age;
 ​
     public void setName(String name) {
         this.name = name;
     }
 ​
     public void setAge(Integer age) {
         this.age = age;
     }
 ​
     public String getName() {
         return name;
     }
 ​
     public Integer getAge() {
         return age;
     }
 ​
     @Override
     public String toString() {
         return "User{" +
                 "name='" + name + ''' +
                 ", age=" + age +
                 '}';
     }
 }
 // RequestHandler.java
 package com.zzy.springtest01.controller;
 ​
 import com.zzy.springtest01.pojo.User;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 ​
 /**
  * 请求处理类
  */
 @RestController
 public class RequestHandle {
 ​
     @RequestMapping("/getParamsPOJO")
     public String getParamsPOJO(User user){ // 直接用实体类来接收请求参数
         System.out.println(user);
         return user.toString();
     }
 }
 // Postman GET http://localhost:8080/getParamsPOJO?name=Tom&age=21

image-20231101174805703

5. 请求-数组集合参数

在表单中,我们常会遇到多选框,如hobby栏可以多选game, drama, running等等。在GET请求时,请求参数会是:hobby=game&hobby=drama&hobby=running

这时,就可以使用数组来接收参数,或者使用集合来接收参数

// 使用数组来接收参数
// Postman GET http://localhost:8080/arrayParams?hobby=java&hobby=python&hobby=coding&hobby=exercising

// 通过数组来接收相同的参数
    @RequestMapping("/arrayParams")
    public String arrayParams(String[] hobby){ // 注意方法中形参的名称必须和请求参数名称一致,Spring才能识别
        String str = Arrays.toString(hobby); // 数组转字符串的方法
        System.out.println(str);
        return str;
    }
// 使用集合来接收参数
// 将方法的形参改为:
@RequestParam List<String> hobby
// !!!注意
// @RequestParam用于绑定请求参数到形参hobby
// 形参的集合名称(hobby)要和请求参数的名称(hobby)一致
6. 请求-日期参数

有时我们前端会提交时间信息给服务器,时间的格式多种多样

// 接收时间参数
// Postman GET http://localhost:8080/dateParams?paramDate=2023-11-01 20:05:00
    @RequestMapping("/dateParams")
    public String dateParams(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")LocalDateTime paramDate){
        String str = paramDate.toString();
        System.out.println(str);
        return str;
    }
// @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")指定了时间的格式
// LocalDateTime是一个官方的java类
7. 请求-JSON参数

当浏览器发出json类型的请求参数时,使用的是POST请求,请求参数放在请求体(RequestBody)中

// Postman POST
/*
{
    "name":"Tom",
    "age":21,
    "address":{
        "province":"Sichuan",
        "city":"Chengdu"
    }
}
*/


// pojo包下
// Address.java
public class Address{
  private String province;
  private String city;
  setter();
  getter();
  toString();
}

// Person.java
public class Person{
  private String name;
  private Integer age;
  private Address address;
  setter();
  getter();
  toString();
}

// controller包下
...
// 接收JSON格式数据
    @RequestMapping("/jsonParam")
    public String jsonParam(@RequestBody Person person){
        String str = person.toString();
        System.out.println(person);
        return str;
    }  
...
8. 请求-路径参数

有时客户端url地址http://localhost:8080/pathParam/1~...,这里参数变成了url地址的一部分,如何获取到这样的参数呢?

 // 接受路径参数 http://localhost:8080/pathParam/1
     @RequestMapping("/pathParam/{id}") // {id}表示id不是固定值
     public String pathParam(@PathVariable Integer id){ // 指定id是一个PathVariable
         System.out.println(id);
         return id.toString();
     }
 ​
 // 接受多个路径参数 http://localhost:8080/pathParam/1/Tom
     @RequestMapping("/pathParam/{id}/{name}") 
     public String pathParam(@PathVariable Integer id, @PathVariable String name){ // 指定id是一个PathVariable
         System.out.println(id+name);
         return "OK";
     }

!!!注意:我们上面写的每一个方法都是一个功能接口

9. 响应-@ResponseBody & 统一响应结果

接收完各式各样的请求数据后,我们来设置响应结果

响应依赖于@ResponseBody注解。前8小节我们接收到浏览器请求数据后都给了浏览器响应,但好像没有用到@ResponseBody

image-20231101205236784

image-20231101210347190

 /**
  * 创建一个统一响应结果封装类Result.java(可放在pojo包下)
  */
 public class Result {
     private Integer code ;//1 成功 , 0 失败
     private String msg; //提示信息
     private Object data; //响应的数据 data
 ​
     public Result() { // 无参构造方法
     }
   
     public Result(Integer code, String msg, Object data) { // 有参构造方法
         this.code = code;
         this.msg = msg;
         this.data = data;
     }
   
     public Integer getCode() {
         return code;
     }
   
     public void setCode(Integer code) {
         this.code = code;
     }
   
     public String getMsg() {
         return msg;
     }
   
     public void setMsg(String msg) {
         this.msg = msg;
     }
   
     public Object getData() {
         return data;
     }
   
     public void setData(Object data) {
         this.data = data;
     }
 ​
     public static Result success(Object data){ // success的静态方法(带返回值)
         return new Result(1, "success", data);
     }
   
     public static Result success(){ // success的静态方法(无返回值)
         return new Result(1, "success", null);
     }
   
     public static Result error(String msg){ // error的静态方法
         return new Result(0, msg, null);
     }
 ​
     @Override
     public String toString() { 
         return "Result{" +
                 "code=" + code +
                 ", msg='" + msg + ''' +
                 ", data=" + data +
                 '}';
     }
 }
// 这样就可以将响应结果统一

		// 接收时间参数
    @RequestMapping("/dateParams")
    public Result dateParams(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")LocalDateTime paramDate){
        return Result.success(paramDate);
    }

    // 接收JSON格式数据
    @RequestMapping("/jsonParam")
    public Result jsonParam(@RequestBody Person person){
        return Result.success(person);
    }

    // 接受路径参数
    @RequestMapping("/pathParam/{id}")
    public Result pathParam(@PathVariable Integer id){
        return Result.success(id);
    }
10. 响应-案例

前端发起GET请求,请求获取员工信息。后端读取员工信息emp.xml中的数据,修改gender为“男”/“女”,job为“讲师”,“班主任”,“就业指导”,将最终结果返回给前端。

<!-- emp.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<emps>
    <emp>
        <name>金毛狮王</name>
        <age>55</age>
        <image>https://web-framework.oss-cn-hangzhou.aliyuncs.com/web/1.jpg</image>
        <!-- 1: 男, 2: 女 -->
        <gender>1</gender>
        <!-- 1: 讲师, 2: 班主任 , 3: 就业指导 -->
        <job>1</job>
    </emp>

    <emp>
        <name>白眉鹰王</name>
        <age>65</age>
        <image>https://web-framework.oss-cn-hangzhou.aliyuncs.com/web/2.jpg</image>
        <gender>1</gender>
        <job>1</job>
    </emp>

    <emp>
        <name>青翼蝠王</name>
        <age>45</age>
        <image>https://web-framework.oss-cn-hangzhou.aliyuncs.com/web/3.jpg</image>
        <gender>1</gender>
        <job>2</job>
    </emp>

    <emp>
        <name>紫衫龙王</name>
        <age>38</age>
        <image>https://web-framework.oss-cn-hangzhou.aliyuncs.com/web/4.jpg</image>
        <gender>2</gender>
        <job>3</job>
    </emp>
</emps>
// 查看前端请求的路径
axios.get('/listEmp').then(res=>{
	if(res.data.code){
		this.tableData = res.data.data;
	}
});

//前端请求路径为 /listEmp
// empController.java
package com.zzy.springtest01.controller;

import com.zzy.springtest01.pojo.Emp;
import com.zzy.springtest01.pojo.Result;
import com.zzy.springtest01.utils.XmlParserUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class EmpController {

    @RequestMapping("/listEmp")
    public Result list() {
        // 1. 加载并解析emp.xml
        String file = this.getClass().getClassLoader().getResource("emp.xml").getFile(); // 动态加载文件
        System.out.println(file);
        List<Emp> empList = XmlParserUtils.parse(file, Emp.class); // 黑马提供的xml解析工具类

        // 2. 对数据进行处修改(gender, job)
        empList.stream().forEach(emp -> {
            // 2.1 处理gender
            if (emp.getGender().equals("1")) {
                emp.setGender("男");
            } else {
                emp.setGender("女");
            }

            // 2.2 处理job
            if (emp.getJob().equals("1")) {
                emp.setJob("讲师");
            } else if (emp.getJob().equals("2")) {
                emp.setJob("班主任");
            } else if (emp.getJob().equals("3")) {
                emp.setJob("就业指导");
            }
        });

        // 3. 响应请求
        return Result.success(empList);
    }
}

empList.stream().forEach( emp -> {......} );这里用到了List类中自带的遍历forEach()方法。

forEach()方法中使用了lambda表达式来编写匿名函数

forEach( emp -> { 对遍历的每个对象emp进行的操作 } )

!!!注意:前端项目可以放在resource/static目录下。后端启动后,前端可以访问浏览器localhost:8080/emp.html(无需加/static/

image-20231102195251398

2.2.6 分层解耦
1. 三层架构

为何引入三层架构?

  1. 简化接口的设计

  2. 便于接口维护与复用(比如说,目前我能够访问xml文件数据,但现在我想扩展功能:既能访问xml数据,又能访问云端数据,还能访问数据库中的数据,不分层的话,逻辑处理就会很复杂)

  3. 尽量使得各接口的职责单一

    1. controller层:接收请求,调用对应的数据访问与逻辑处理程序,最后响应请求
    2. service层:已得到要处理的数据,进行处理并返回处理后的数据
    3. dao层:只专注于如何获取数据

image-20231102212550250

三层架构具体内容:

image-20231102214014782

对响应案例进行修改:

将原接口的三大部分:数据访问、逻辑处理、响应请求分为三层

数据访问:

 // dao层
 // 先创建数据访问的接口,定义数据访问的接口方法。再对接口进行不同实现
 ​
 ​
 /**
  * dao层的接口
  * 只关注如何加载数据
  */
 public interface LoadData {
 ​
     /**
      * 加载数据
      * @return 加载后的数据(原始数据)
      */
     public List<Emp> empList();
 }
 ​
 ​
 // 再用不同方式实现这个接口的方法
 // LoadDataV1.java:第一种实现数据访问接口的具体实现
 public class LoadDataV1 implements LoadData {
     @Override
     public List<Emp> empList() {
         String file = this.getClass().getClassLoader().getResource("emp.xml").getFile(); // 动态加载文件
         System.out.println("file laods successfully: " + file);
         List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
         return empList;
     }
 }

逻辑处理:

 // service层
 // 先创建逻辑处理的接口,定义逻辑处理的接口方法。再对逻辑处理接口进行不同实现
 ​
 /**
  * 修改数据的接口
  */
 public interface ModifyData {
     /**
      * 修改数据
      * @return 返回修改好的数据
      */
     public List<Emp> modifyData();
 }
 ​
 ​
 public class ModifyDataV1 implements ModifyData {
     private LoadData loadData = new LoadDataV1(); // 获取加载数据的类
     @Override
     public List<Emp> modifyData() {
         List<Emp> empList = loadData.empList(); // 获取加载好的数据
 ​
         empList.stream().forEach(emp -> {
             // 2.1 处理gender
             if (emp.getGender().equals("1")) {
                 emp.setGender("男");
             } else {
                 emp.setGender("女");
             }
 ​
             // 2.2 处理job
             if (emp.getJob().equals("1")) {
                 emp.setJob("讲师");
             } else if (emp.getJob().equals("2")) {
                 emp.setJob("班主任");
             } else if (emp.getJob().equals("3")) {
                 emp.setJob("就业指导");
             }
         });
 ​
         return empList;
     }
 }

响应请求:

 @RestController
 public class EmpController {
 ​
     @RequestMapping("/listEmp")
     public Result list() {
         // 调用对应请求处理方法,响应请求
         return Result.success(new ModifyDataV1().modifyData());
     }
 }
2. 分层解耦

内聚: 软件中各个功能模块内部的功能联系

耦合: 衡量软件中各个层/模块之间的依赖、关联的程度

我们追求高内聚,低耦合

在上一节三层架构中,我们将代码分为dao层、service层、controller层。每一层都只专注于自己的功能。

在controller层中,我们new ModifyDataV1().modifyData(),即创建了service层的一个对象,这称之为耦合

如果我们想使用另一个业务逻辑,则需对service层进行修改,并且controller层创建的对象也要进行修改,不想这样,要对模块之间解耦

Solution:

控制反转 inversion of control

依赖注入 dependency injection

Bean对象:容器中创建管理的对象

image-20231104155347466

3. IOC & DI入门

如何实现控制反转IOC和依赖注入DI:

控制反转:

service层的ModifyDataV1.java依赖dao层的LoadDataV1.java

LoadDataV1类上加上@Component注解,表示该类交由IOC容器管理

controller层的EmpController.java依赖service层的ModifyDataV1.java

ModifyDataV1类上加上@Component注解,表示该类交由IOC容器管理

依赖注入:

ModifyDataV1中声明LoadDataV1对象时,无需new,直接:

@Autowired
private LoadData obj; // IOC容器知道给obj赋为一个LoadDataV1对象

EmpController中声明ModifyData对象时,无需new,直接:

 @Autowired
 private ModifyData obj; // IOC容器知道给obj赋为一个ModifyDataV1对象

IOC & DI实现解耦了吗?

当前我的service层有一个实现:ModifyDataV1类,我想创建一个新的实现ModifyDataV2。只需取消ModifyDataV1类上的@Component注解,在ModifyDataV2类上加上@Component注解就行,这样controller层使用@Autowired声明ModifyData对象时就知道,我们需要的是ModifyDataV2

注:Bean的声明

image-20231104170201206

2.3 MySQL

2.3.1 SQL简介

image-20231104205419519

image-20231104205710962

!!!注意:SQL语句记得加上;

2.3.2 DDL(数据定义语言)

DataBase Definition Language

!!!注意:DataBaseSchema是一样的意思

image-20231106192632732

1. 数据库操作

image-20231104210905883

2. 表操作

image-20231106170317969

3. 数据类型

MySQL支持3大类数据类型

  • 数值类型

    image-20231106173831132

  • 字符串类型

  • image-20231106174243904

  • 日期时间类型

    image-20231106174612547

4. 创建表的一般步骤

image-20231106181157699

2.3.3 DML(数据操作语言)

DataBase Manipulate Language

对数据库进行增删改功能

image-20231106192658332

1. 增加数据 insert

image-20231106194129868

2. 删除数据 delete

image-20231106194744323

3. 修改数据 update

image-20231106194548313

2.3.4 DQL(数据库查询语言)

DataBase Query Language 多表操作 使用频率最高

image-20231110201843191

1. 基本查询 select from

image-20231110202612060

2. 条件查询 where

image-20231110204158425

3. 分组查询 group by

聚合函数:不对null值进行统计

image-20231110210241107

# 统计个数
	# count(*)写法——推荐(MySQL底层有针对性优化)
	select count(*) from tb_emp;
	
	# count(字段)——会自动忽略null
	select count(id) from tb_emp;

分组查询

分组查询返回的表的字段主要有两类:

  1. 分组字段(分组的依据)
  2. 聚合函数(如统计总数)

image-20231110211527422

# 先查询入职时间在‘2015-01-01’之前的员工,并对结果根据职位分组,获取员工数量大于等于2的职位
select job, count(*) from tb_emp where entryDate <= '2015-01-01' group by job having count(*) >= 2;

面试题wherehaving后面都跟的是条件,这两个条件有什么区别?

  1. where是分组之前进行过滤,不符合where条件的不参加过滤
  2. having是分组之后再进行的一次过滤
  3. where不能对聚合函数进行判断,而having可以

数据 -> where条件 --(符合where条件的数据)–>分组 --> having条件 --> 最终结果

4. 排序查询 order by

image-20231110213919696

5. 分页查询 limit

image-20231115191559337

起始索引的公式:(页码 - 1)* 每页展示的记录数

6. 几个流程控制函数
  • if(查询条件, true时取值, false时取值)

    • select if(gender = 1,'男','女') as '员工性别', count(*)
      from tb_emp
      group by gender;
      
  • case 表达式

    when 值1 then 结果1

    when 值2 then 结果2

    ...

    else 结果n

    end

    • select case job
                 when 1 then '班主任'
                 when 2 then '讲师'
                 when 3 then '学工主管'
                 when 4 then '教研主管'
          end         as '职位'
           , count(*) as '人数'
      from tb_emp
      group by job;
      
2.3.5 多表设计
1. 一对多(外键)

如有一张部门表和一张员工表

部门表记录了部门id和对应的部门名称

员工表记录了员工的各种信息和所属部门

分析知,部门表为主表

如何在数据库层面将这两张表关联起来?

image-20231115200904425

image-20231115201357896

2. 一对一

image-20231115201643441

3. 多对多

image-20231115212504862

2.3.6 多表查询

直接select * from tb1,tb2会将两张表做笛卡尔积:

image-20231122012645660

添加where限制条件能消除笛卡尔积

多表查询主要内容:

image-20231122013334146

1. 内连接

image-20231122013646442

隐式内连接:image-20231122014117873

显式内连接:

image-20231122014500346

2. 外连接

image-20231122014918342

左外连接:

image-20231122015721609

右外连接:

image-20231122020047789

左右外连接可以相互转换,项目中一般使用左外连接

3. 子查询

将查询合理地分解为多步

image-20231122020544351

标量子查询: 返回的是单个数据

image-20231122155422145

image-20231122154313537

列子查询: 返回的是一列多行的数据

image-20231122155439079

image-20231122155942892

行子查询: 返回的是一行多列的数据

image-20231122160718916

image-20231122160934960

表子查询: 返回的是一个临时的表

image-20231122161145394

image-20231122162017676

2.3.7 事务

场景: 对多表进行操作时,需要格外关注数据一致性的问题

image-20231122171959063

如何解决数据一致性问题?

image-20231122172246035

image-20231122172357454

 start transaction; # 开启事务
 ​
 delete from tb_dept where id = 1;
 delete from tb_emp where dept_id = 1;
 ​
 # 若所有的SQL语句执行成功,则:
 commit; # 提交事务(需要提交给数据库服务器)
 ​
 # 若有SQL执行失败,则手动回滚:
 rollback; # 回滚数据

事务的四大特性ACID: 面试题

image-20231122173455731

2.3.8 索引

数据库优化策略之一

当数据库表的数据很多时,查询会明显耗费更多时间

对600 0000条数据进行select

不用索引:13s

使用索引:4ms

image-20231122175057710

image-20231122175447988

image-20231122175648174

image-20231122180120484

image-20231122180212346


2.4 Mybatis

优秀的持久层/数据访问层/dao/(Mybatis中叫Mapper)框架,用于简化JDBC的开发

image-20231122183627033


2.4.1 入门
1. Mybatis全流程
  1. 准备工作:

    • 创建springboot工程,引入Mybatis相关依赖

      •  <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
                 <dependency>
                     <groupId>org.mybatis.spring.boot</groupId>
                     <artifactId>mybatis-spring-boot-starter</artifactId>
                     <version>2.3.1</version>
                 </dependency>
        
      •  <!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
                 <dependency>
                     <groupId>com.mysql</groupId>
                     <artifactId>mysql-connector-j</artifactId>
                     <version>8.0.33</version>
                     <scope>runtime</scope>
                 </dependency>
        
    • 创建数据库表user

    • 创建实体类User(实体类的属性和数据库表一一对应,建议类型都用封装类型,如int->Integer)

      •  /**
          * 与test04数据库中的user表对应
          */
         public class User {
             private Integer id;
             private String name;
             private Short age;
             private Short gender;
             private String phone;
         ​
         //  constructors
             public User() {} // 无参构造
         ​
             public User(Integer id, String name, Short age, Short gender, String phone) {...} // 有参构造
         ​
         //  setters and getters
             ...
         ​
         //  toString
             @Override
             public String toString() {...}
         }
        
  2. 配置数据库连接(4点,在springboot的application.properties)

    • 配置数据库驱动类:spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    • 配置数据库服务器url地址:spring.datasource.url=jdbc:mysql://localhost:3306/mybatis(/后为服务器地址,若本机作为数据库服务器,则用localhost:3306;mybatis为数据库的名称)
    • 配置数据库用户名:spring.datasource.username=root
    • 配置密码:spring.datasource.password=123456
  3. 编写SQL语句(注解/XML)

    编写SQL语句可采用注解@XML映射文件的形式

    1. 在Mybatis中,创建一个package名为mapper(相当于dao数据访问层,只不过mapper层专门访问数据库)

    2. 在Mapper中创建一个访问user表的接口UserMapper

      /**
       * 与数据库中的user表交互
       */
      @Mapper // 将该接口交给Mybatis管理,运行时会自动生成相应对象,并交给IOC容器管理,使用时直接@Autowired来创建对象
      public interface UserMapper {
      
          @Select("select * from user")
          public List<User> list();
      }
      
    3. 访问数据库user表时:

      @Autowired
      private UserMapper userMapper; // 先Autowired一个UserMapper对象
      
      // 然后再获取到数据库操作结果:
      public void testUserMapperlist(){
          List<User> userlist = userMapper.list();
          userlist.forEach(user -> {
              System.out.println(user);
          });
      }
      
2. lombok

简化实体类的编写,无需再手写setter, getter, toString, constructors,只需使用几个注解

image-20231123214917512

2.4.2 基础操作

可配置Mybatis的日志信息,并指定在控制台输出

# application.properties
# 配置Mybatis日志,并指定输出到控制台中
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

image-20231126163747623

1. select
 @Mapper // 将该接口交给Mybatis管理,运行时会自动生成相应对象,并交给IOC容器管理,使用时直接@Autowired来创建对象
 public interface EmpMapper {
 ​
     /**
      * 选择所有的员工信息
      * @return
      */
     @Select("select * from emp")
     public List<Emp> list();
 }
2. delete
 @Mapper // 将该接口交给Mybatis管理,运行时会自动生成相应对象,并交给IOC容器管理,使用时直接@Autowired来创建对象
 public interface EmpMapper {
 ​
     /**
      * 根据id删除某员工
      */
     @Delete("delete from emp where id = #{id}")
     public void deleteEmp(Integer id); // @Delete其实有返回值,会返回操作(删除)了多少条数据,但一般就用void
 }

#{}为SQL的占位符,使用${}也可以,但是存在SQL注入的问题。所以一般若需给SQL语句传参,都使用#{},它会在编译时生成预编译的SQL,更高效也更安全

面试题区别:

  • delete from emp where id = #{id} id=3

    • 编译后会生成预编译语句:delete from emp where id = ? parameters = 3
  • delete from emp where id = ${id} id=3

    • 编译后会生成:delete from emp where id=3
3. insert
@Mapper // 将该接口交给Mybatis管理,运行时会自动生成相应对象,并交给IOC容器管理,使用时直接@Autowired来创建对象
public interface EmpMapper {

    /**
     * 插入一条员工信息
     * @param emp
     */
    @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) value (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{dept_id},#{create_time},#{update_time})")
    public void insertEmp(Emp emp); // #{}内的内容是Emp对象的属性名
}

主键返回

有些应用场景需要我们获取到刚刚插入的数据的主键值。比如维护中间关系表时。

image-20231127174017928

4. update

常见的应用场景为弹出一个表单,用于修改当前已有的数据。

@Mapper // 将该接口交给Mybatis管理,运行时会自动生成相应对象,并交给IOC容器管理,使用时直接@Autowired来创建对象
public interface EmpMapper {

    /**
     * 传入更新后的Emp对象,对数据库中相应id的元组进行更新
     * @param emp
     */
    @Update("update emp set username=#{username}, name=#{name},gender=#{gender},image=#{image},job=#{job},entrydate=#{entrydate},dept_id=#{dept_id},update_time=#{update_time} where id=#{id}")
    public void updateEmp(Emp emp);
}
5. 查询
@Mapper // 将该接口交给Mybatis管理,运行时会自动生成相应对象,并交给IOC容器管理,使用时直接@Autowired来创建对象
public interface EmpMapper {

    /**
     * 根据id查询员工信息
     * @param id
     */
    @Select("select * from emp where id=#{id}")
    public Emp queryEmpById(Integer id);
}

查询时,有时会出现查询到的字段值为null的情况

原因:当字段名和属性名一致时,Mybatis会自动封装;当不一致时,Mybatis无法自动封装

解决办法:

  • 方案一:在SQL语句中,给字段名和属性名不一致的字段起别名

    @Select("select dept_id deptId, update_time updateTime from emp where id=#{id}")
    // 其中dept_id, update_time为数据库中的字段名,deptId, updateTime为封装的Emp类中的属性名
    
  • 方案二:通过@Results, @Result注解进行手动映射(麻烦不推荐)

    @Results({
      @Result(column="dept_id", property="deptId"),
      @Result(column="update_time", property="updateTime")
    })
    @Select("select * from emp where id=#{id}")
    
  • 方案三:开启Mybatis的下划线命名法<->驼峰命名法自动映射开关

    // application.properties
    mybatis.configuration.map-underscore-to-camel-case=true
    

image-20231127183007006

2.4.3 使用XML映射文件编写SQL语句

使用Mybatis在Java中编写SQL语句,既可以采用注解@的形式,也可采用XM了映射文件的形式

XML映射文件更适用于较复杂的SQL语句的编写

image-20231127232733441

  1. 定义XML映射文件

    1. 建好EmpMapper.xml

    2. 建好xml映射文件的结构:

      <!DOCTYPE mapper
              PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.zzy.springtest01.mapper.EmpMapper" >
          ...
      </mapper>
      
    3. 编写SQL语句

       <!DOCTYPE mapper
               PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
               "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
       <mapper namespace="com.zzy.springtest01.mapper.EmpMapper">
           <insert id="insertEmp">
               insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) value (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{dept_id},#{create_time},#{update_time})
           </insert>
       ​
           <update id="updateEmp">
               update emp set username=#{username}, name=#{name},gender=#{gender},image=#{image},job=#{job},entrydate=#{entrydate},dept_id=#{dept_id},update_time=#{update_time} where id=#{id}
           </update>
       ​
           <select id="list" resultType="com.zzy.springtest01.pojo.Emp">
               select *
               from emp;
           </select>
       ​
           <delete id="deleteEmp">
               delete
               from emp
               where id = #{id}
           </delete>
       ​
       </mapper>
      
2.4.4 动态SQL

随着用户的输入或外部条件的变化而变化的SQL语句称为动态SQL

1. if标签

image-20231130003244261

 原:
 <select id="listSomeEmp" resultType="com.zzy.springtest01.pojo.Emp">
         select *
         from emp
         where
             name like concat('%', #{name}, '%')
             and gender = #{gender}
             and entrydate between #{begin} and #{end}
         order by update_time desc;
 </select>
 ​
 现:
 <select id="listSomeEmp" resultType="com.zzy.springtest01.pojo.Emp">
         select *
         from emp
         <where> // <where>标签能自动判断<if>标签中的条件并相应地生成SQL语句,并且删掉多余的"and"
             <if test="name!=null"> // test属性中填条件
                 name like concat('%', #{name}, '%')
             </if>
             <if test="gender!=null">
                 and gender = #{gender}
             </if>
 ​
             <if test="begin!=null and end!=null">
                 and entrydate between #{begin} and #{end}
             </if>
         </where>
         order by update_time desc;
 </select>
2. where标签

标签用于:

  • 要是select * from emp where...的where条件全为null,则不生成where关键字---->select * from emp
  • 自动删除多余的and关键字和多余的,等符号
3. set标签

标签用于update语句:

  • 自动删除多余的,等符号

  •  原:
     <update id="updateEmp">
             update emp
             set
             <if test="username!=null">
                 username=#{username},
             </if>
             <if test="name!=null">
                 name=#{name},
             </if>
             <if test="gender!=null">
                 gender=#{gender},
             </if>
             <if test="image!=null">
                 image=#{image},
             </if>
             <if test="job!=null">
                 job=#{job},
             </if>
             <if test="entrydate!=null">
                 entrydate=#{entrydate},
             </if>
             <if test="dept_id!=null">
                 dept_id=#{dept_id},
             </if>
             <if test="update_time!=null">
                 update_time=#{update_time}
             </if>
             where id = #{id}
     </update>
    
     现:
     <update id="updateEmp">
             update emp
             <set>
                 <if test="username!=null">
                     username=#{username},
                 </if>
                 <if test="name!=null">
                     name=#{name},
                 </if>
                 <if test="gender!=null">
                     gender=#{gender},
                 </if>
                 <if test="image!=null">
                     image=#{image},
                 </if>
                 <if test="job!=null">
                     job=#{job},
                 </if>
                 <if test="entrydate!=null">
                     entrydate=#{entrydate},
                 </if>
                 <if test="dept_id!=null">
                     dept_id=#{dept_id},
                 </if>
                 <if test="update_time!=null">
                     update_time=#{update_time}
                 </if>
             </set>
             where id = #{id}
     </update>
    
4. foreach标签
 <!-- delete from emp where id in (15,16,17) -->
     <delete id="deleteInBulk">
         delete
         from emp
         where id in
         <foreach collection="ids" item="id" separator="," open="(" close=")">
             #{id}
         </foreach>
     </delete>

属性:

  • collection=“Mapper传入的要遍历的集合的名称”
  • item=“遍历出来的每一项,自行命名”
  • separator=“遍历出来的每一项要拼接在一起,用什么分隔符来拼接”
  • open=“遍历出来第一项的前面需要加什么符号”
  • close=“遍历出来最后一项的后面需要加什么符号”
5. SQL抽取与include

在真实的应用场景中,不推荐使用select *,查询效率低下,而是建议将要查询的所有字段都显式地写出来

这样在xml映射文件中就会出现大量重复的例如:select id, username, name, gender, entrydate, job,... from emp等语句

能否将这些重复的部分给封装起来,以便复用?

SQL抽取:

 <sql id="CommonSelect">
   select id, username, name, gender, entrydate, job,... from emp
 </sql>

include:

 <include refid="CommonSelect" />

例子:

 <mapper ... >
   <sql id="CommonSelect">
     select id, username, name, gender, entrydate, job,... from emp
   </sql>
   
   <select id="..." resultType="...">
     <include refid="CommonSelect"/>
     <where>
             <if test="name!=null">
                 name like concat('%', #{name}, '%')
             </if>
             <if test="gender!=null">
                 and gender = #{gender}
             </if>
 ​
             <if test="begin!=null and end!=null">
                 and entrydate between #{begin} and #{end}
             </if>
         </where>
         order by update_time desc;
   </select>
 </mapper>
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_69573732/article/details/137046674

智能推荐

TCGA数据下载和整理工具----GDCRNATools_gdcrnatools软件包进行差异基因分析-程序员宅基地

文章浏览阅读1.9w次,点赞7次,收藏62次。TCGA数据下载和整理的网站及软件发表很多了,比如Broad GDAC Firehose, Oncomine, TCGAbiolinks,TCGA-Assembler, TCGA2STAT,RTCGAToolbox等等,这些网站或软件要么使用的是TCGA更新前的数据,要么运行起来比较繁琐。当然各个工具都有其优势所在。之前在论坛里分享了自己下载和整理TCGA数据的Python代码。最近忙里偷_gdcrnatools软件包进行差异基因分析

win7更改计算机时间,win7系统自动更改日期时间是怎么回事-程序员宅基地

文章浏览阅读1.9k次。工具/原料硬件:计算机操作系统:Windows7方法/步骤1.Windows7系统不能更改日期和时间的解决方法2.在本地组策略编辑器窗口,展开Windows设置 - 安全设置 - 本地策略;3.在本地策略中找到:用户权限分配,左键点击:用户权限分配,在用户权限分配对应的右侧窗口找到:更改系统时间,并左键双击:更改系统时间;4.在打开的更改系统时间 属性窗口,我们点击:添加用户或组(U);5.在选择..._win7系统时间老是自己跳变

Python-Django-模型_pycharm怎么创建orm模型-程序员宅基地

文章浏览阅读1k次。一、ORM 模型介绍1 、 ORM 模型对象关系映射(英语:(Object Relational Mapping,简称ORM,或ORM,或OR mapping),是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系数据库则是从数学理论发展而来的,两套理论存在显著的区别。为了解决这个不匹配的现象,对象关系映射技术应运而生。对象关系映射(Object-Relational Mapping)提供了概念性的、_pycharm怎么创建orm模型

如何搭建一套完整的智能安防视频监控平台?关于设备与软件选型的几点建议_前端摄像头的选型依据-程序员宅基地

文章浏览阅读250次。球机摄像头:球机为一体化设备,可以通过云台控制进行转动、变倍和自动聚焦等操作,若需要对设备周边切换场景监控,如大门口、户外活动场所等,可以选择球机。_前端摄像头的选型依据

前端学PHP之正则表达式基础语法_正则表达式主要用于字符串模式匹配或字符串匹配,即什么操作-程序员宅基地

文章浏览阅读177次。前面的话  正则表达式是用于描述字符排列和匹配模式的一种语法规则。它主要用于字符串的模式分割、匹配、查找及替换操作。在PHP中,正则表达式一般是由正规字符和一些特殊字符(类似于通配符)联合构成的一个文本模式的程序性描述。正则表达式有三个作用:1、匹配,也常常用于从字符串中析取信息;2、用新文本代替匹配文本;3、将一个字符串拆分为一组更小的信息块。本文将详细介绍PHP中的正则表达式基础语法 _正则表达式主要用于字符串模式匹配或字符串匹配,即什么操作

桌面上计算机未响应,win7系统打开计算机未响应的解决方法-程序员宅基地

文章浏览阅读415次。很多小伙伴都遇到过win7系统打开计算机未响应的困惑吧,一些朋友看过网上零散的win7系统打开计算机未响应的处理方法,并没有完完全全明白win7系统打开计算机未响应是如何解决的,今天小编准备了简单的解决办法,只需要按照1、点击win7 32位旗舰版系统电脑的开始菜单,打开控制面板; 2、在控制面板中选择外观和个性化;的顺序即可轻松解决,具体的win7系统打开计算机未响应教程就在下文,一起来看看吧!..._电脑打开计算机未响应

随便推点

linux启动进入bios设置密码,通过bios怎么设置开机密码-程序员宅基地

文章浏览阅读1.2k次。电脑不想被他人乱动,来设置下BIOS管理员密码和开机密码,就让学习啦小编来告诉大家通过bios怎么设置开机密码的方法吧,希望对大家有所帮助。通过bios设置开机密码方法计算机开机以后,按键盘的Delete键进入BIOS的设置画面,如下图所示。因为开机可以按Delete键进入设置画面的时间很短,您可以在计算机一开机就慢慢的重复按Delete键,以免错过进入设置画面又要重新再开机。按键盘向下箭头键移到..._bios开机密码 画面

批处理获取所有文件、文件夹名字_bat获取文件夹下所有文件名和文件夹名称-程序员宅基地

文章浏览阅读1.6w次,点赞14次,收藏45次。已收藏下面这个链接的方法也不错excel批处理技巧:如何制作文件档案管理系统excel批处理技巧:如何制作文件档案管理系统http://www.360doc.com/content/18/0913/13/18781560_786337463.shtml有时候我们整理文件的时候需要列出文件夹里面所有的文件名或者文件夹名,生成一个文件目录,一个个重命名然后复制到word或者记事本的方法显示有点太繁琐了。网上有一些自动生成文件目录的程序,比如我之前一直在用的DirIndex.exe。但最近我发现_bat获取文件夹下所有文件名和文件夹名称

计算机视觉图像检测之从EasyDL到BML_easydl paddlex bml-程序员宅基地

文章浏览阅读914次,点赞18次,收藏18次。部署方式选择公有云部署,训练方式均可。增量训练的意思是在之前训练的模型基础上再次进行训练,如果事先没有进行过训练,这一项为不可选中状态。回到Postman,参数栏按如下方式填写,其中第一个KEY-VALUE值直接照写,client_id和client_secret的VALUE值分别为上一步获取的AK、SK。如果数据集质量够高,每种标签标注效果都很好,也可以在模型训练时再进行数据增强,或者直接跳过这一步。在导入界面配置导入信息,选择本地导入,导入压缩包(其他导入方式请自行测试),如图1.1.2。_easydl paddlex bml

红帽oracle关系,redhat和oracle linux kernel对应关系-程序员宅基地

文章浏览阅读1.4k次。Red Hat Enterprise Linux Version / UpdateRed Hat Enterprise Linux – Kernel version / redhat-release stringOracle Linux – Kernel version / release stringsRed Hat Enterprise Linux 7Red Hat Enterprise Li..._oracle linux redhat 对应关系

2020年中南大学研究生招生夏令营机试题_中南大学 计算机 夏令营 笔试-程序员宅基地

文章浏览阅读804次。2020年中南大学研究生招生夏令营机试题题目链接A题题目描述众所周知,彩虹有7种颜色,我们给定七个 字母和颜色 的映射,如下所示:‘A’ -> “red”‘B’ -> “orange”‘C’ -> “yellow”‘D’ -> “green”‘E’ -> “cyan”‘F’ -> “blue”‘G’ -> “purple”但是在某一..._中南大学 计算机 夏令营 笔试

Cmake的option与cmake_dependent_option-程序员宅基地

文章浏览阅读2.9k次。一、介绍cmake提供了一组内置宏,用户可以自己设置。只有当该集合中的其他条件为真时,该宏才会向用户提供一个选项。语法include(CMakeDependentOption)CMAKE_DEPENDENT_OPTION(USE_FOO "Use Foo" ON "USE_BAR;NOT USE_ZOT" OFF)如果USE_BAR为true而USE_ZOT为false,则提供一个默认为ON的选项USE_FOO。否则,它将USE_FOO设._cmake_dependent_option