侧边栏壁纸
博主头像
不做科研废物🌸

行动起来,活在当下

  • 累计撰写 17 篇文章
  • 累计创建 8 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

JavaWeb学习笔记

AlaskaGulf
2025-04-14 / 0 评论 / 0 点赞 / 2 阅读 / 0 字

Web入门

SpringBootWeb入门

需求:使用SpringBoot开发一个web应用,浏览器发起请求/hello后,给浏览器返回字符串"Hello World~"。

开发步骤:

· 创建SpringBoot工程,并勾选web开发相关依赖。

· 定义HelloController类,添加方法hello,并添加注解。

· 运行测试

 // 请求处理类
 @RestController
 public class HelloController {
     @RequestMapping("/hello")
     public String hello(){
         System.out.println("Hello World");;
         return "Hello World";
     }
 }
 ​
 // Spring启动类
 @SpringBootApplication
 public class SpringbootWeb1Application {
 ​
     public static void main(String[] args) {
         SpringApplication.run(SpringbootWeb1Application.class, args);
     }
 ​
 }
 ​
 // 控制台输出
 ​
 //  .   ____          _            __ _ _
 // /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
 //( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 // \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
 //  '  |____| .__|_| |_|_| |_\__, | / / / /
 // =========|_|==============|___/=/_/_/_/
 //
 // :: Spring Boot ::                (v3.4.3)
 //
 //2025-03-13T09:59:14.390+08:00  INFO 25572 --- [Springboot-Web-1] [           main] r.i.s.SpringbootWeb1Application          : Starting SpringbootWeb1Application using Java 22.0.2 with PID 25572 (C:\JavaProject\Springboot-Web-1\target\classes started by 11035 in C:\JavaProject\Springboot-Web-1)
 //2025-03-13T09:59:14.392+08:00  INFO 25572 --- [Springboot-Web-1] [           main] r.i.s.SpringbootWeb1Application          : No active profile set, falling back to 1 default profile: "default"
 //2025-03-13T09:59:15.069+08:00  INFO 25572 --- [Springboot-Web-1] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
 //2025-03-13T09:59:15.080+08:00  INFO 25572 --- [Springboot-Web-1] [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
 //2025-03-13T09:59:15.080+08:00  INFO 25572 --- [Springboot-Web-1] [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.36]
 //2025-03-13T09:59:15.117+08:00  INFO 25572 --- [Springboot-Web-1] [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
 //2025-03-13T09:59:15.117+08:00  INFO 25572 --- [Springboot-Web-1] [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 688 ms
 //2025-03-13T09:59:15.412+08:00  INFO 25572 --- [Springboot-Web-1] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
 //2025-03-13T09:59:15.416+08:00  INFO 25572 --- [Springboot-Web-1] [           main] r.i.s.SpringbootWeb1Application          : Started SpringbootWeb1Application in 1.368 seconds (process running for 1.933)
 //2025-03-13T09:59:50.352+08:00  INFO 25572 --- [Springboot-Web-1] [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
 //2025-03-13T09:59:50.353+08:00  INFO 25572 --- [Springboot-Web-1] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
 //2025-03-13T09:59:50.353+08:00  INFO 25572 --- [Springboot-Web-1] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms
 //Hello World

image-20250313100108907

HTTP协议

概述

HTTP:Hyper Text Transfer Protocol,超文本传输协议,规定了服务器和服务器之间数据传输的规则。

特点:

· 基于TCP协议:面向连接,安全

· 基于请求-响应模型:一次请求对应一次响应

· HTTP协议是无状态的协议:对于处理事务没有记忆能力。每次请求-响应都是独立的。

· 优点:速度快

· 缺点:多次请求间不能共享数据

image-20250313101526215

HTTP请求协议

image-20250313102551971

image-20250313102227420

HTTP响应协议

image-20250313103018147

image-20250313103053907

image-20250313103647701

Tomcat

Web服务器是一个软件程序,对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作,让Web开发更加便捷。主要功能是“提供网上信息浏览服务”。

image-20250313114053205

image-20250313130643989

请求响应

image-20250313132627459

请求

postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件

作用:常用于进行接口测试

简单参数

原始方式:

 @RestController
 public class RequestController {
     @RequestMapping("/simpleParam")
     public String simpleParam(HttpServletRequest request){
         // 获取请求参数
         String name = request.getParameter("name");
         String ageStr = request.getParameter("age");
 ​
         int age = Integer.parseInt(ageStr);
         System.out.println(name + ":" + age);
         return "OK";
     }
 }

Springboot方式:

 //    @RequestMapping("/simpleParam")
 //    public String simpleParam(String name, Integer age){
 //        System.out.println(name + ":" + age);
 //        return "OK";
 //    }
 ​
     @RequestMapping("/simpleParam")
     public String simpleParam(@RequestParam(name = "name") String username, Integer age){
         System.out.println(username + ":" + age);   
         // 如果参数列表为{String username, ...},则结果null:10 参数对应不上
         // 此时需要添加注释@RequestParam
         return "OK";
     }
 }

@RequestParam中的requied属性默认为true,代表该请求参数必须传递,如果不传递将报错。如果该参数是可选的,可以将required属性设置为falseimage-20250313140601521

image-20250313140440630

实体参数

如果参数过多,可以将所有参数封装在一个实体类中,此时请求的参数名需要与实体类的属性名保持一致。

image-20250313141532833

 @RequestMapping("/simplePojo")
 public String simplePojo(User user){
     System.out.println(user);   // User{name='Alaska', age=18}
     return "OK";
 }

对于复杂的实体对象,按照对象层次结构关系即可接受嵌套POJO属性参数。

image-20250313142250111

 @RequestMapping("/simplePojo")
 public String simplePojo(User user){
     System.out.println(user);   // User{name='Alaska', age=18, address=Address{province='Anhui', city='Hefei'}}
     return "OK";
 }

数组集合参数

数组参数:请求参数名与行参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数

image-20250313143735213

 @RequestMapping("/arrayParam")
     public String arrayParam(String[] hobby){
         System.out.println(Arrays.toString(hobby)); // [game, java]
         return "OK";
     }

集合参数:请求参数名与行参数组名称相同且请求参数为多个,通过@RequestParam绑定参数关系

 @RequestMapping("/listParam")
 public String listParam(@RequestParam List<String>hobby){
     System.out.println(hobby); // [game, java]
     return "OK";
 }

日期参数

日期参数:使用@DateTimeFormat完成日期参数格式转换

image-20250313145318443

 @RequestMapping("/dateParam")
 public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime){
     System.out.println(updateTime); //  2025-03-13T14:51:08
     return "OK";
 }

Json参数

Json参数:Json数据键名与形参对象属性名相同,定义POJO类型形参即可接收参数,需要使用RequestBody标识。

image-20250313151131772

@RequestMapping("/jsonParam")
public String dateParam(@RequestBody User user){
    System.out.println(user); //  User{name='Alaska', age=18, address=Address{province='Hefei', city='beijing'}}
    return "OK";
}

路径参数

路径参数:通过请求URL直接传递参数,使用{...}来表示该路径参数,需要使用PathVariable获取路径参数。

image-20250329150058998

@RequestMapping("path/{id}")
public String pathParam(@PathVariable Integer id){
    System.out.println(id);
    return "OK";
}

如果需要传输多个路径参数,只需要使用斜杠/分割即可。

image-20250329150357824

 @RequestMapping("path/{id}/{name}")
 public String pathParam(@PathVariable Integer id,@PathVariable String name){
     System.out.println(id+ ":" + name);
     return "OK";
 }

响应

image-20250329150907586

@RestController
public class ResponseController {

    @RequestMapping("/hello")
    public String hello(){
        System.out.println("Hello World");
        return "Hello World";
    }

    @RequestMapping("/getAddr")
    public Address getAddr(){
        Address addr = new Address();
        addr.setCity("合肥");
        addr.setProvince("安徽");
        return addr;
    }

    @RequestMapping("/listAddr")
    public List<Address> listAddr(){
        List<Address> list = new ArrayList<>();

        Address addr = new Address();
        addr.setCity("合肥");
        addr.setProvince("安徽");

        Address addr2 = new Address();
        addr2.setCity("运城");
        addr2.setProvince("山西");

        list.add(addr);
        list.add(addr2);
        return list;
    }
}

image-20250329152615503

image-20250329152627432

image-20250329152632273

在实际的开发过程中,往往会统一响应结果,以满足更多的业务场景。

image-20250329165235315

 /**
  * 统一响应结果封装类
  */
 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){
         return new Result(1, "success", data);
     }
     public static Result success(){
         return new Result(1, "success", null);
     }
     public static Result error(String msg){
         return new Result(0, msg, null);
     }
 ​
     @Override
     public String toString() {
         return "Result{" +
                 "code=" + code +
                 ", msg='" + msg + '\'' +
                 ", data=" + data +
                 '}';
     }
 }
 @RequestMapping("/hello")
 public Result hello(){
     System.out.println("Hello World");
     //return new Result(1,"success","Hello World");
     return Result.success("Hello World");
 }
 ​
 @RequestMapping("/getAddr")
 public Result getAddr(){
     Address addr = new Address();
     addr.setCity("合肥");
     addr.setProvince("安徽");
     return Result.success(addr);
 }
 ​
 @RequestMapping("/listAddr")
 public Result listAddr(){
     List<Address> list = new ArrayList<>();
 ​
     Address addr = new Address();
     addr.setCity("合肥");
     addr.setProvince("安徽");
 ​
     Address addr2 = new Address();
     addr2.setCity("运城");
     addr2.setProvince("山西");
 ​
     list.add(addr);
     list.add(addr2);
     return Result.success(list);
 }
 ​
 /*
 {
     "code": 1,
     "msg": "success",
     "data": "Hello World"
 }
 ​
 {
     "code": 1,
     "msg": "success",
     "data": {
         "province": "安徽",
         "city": "合肥"
     }
 }
 ​
 {
     "code": 1,
     "msg": "success",
     "data": [
         {
             "province": "安徽",
             "city": "合肥"
         },
         {
             "province": "山西",
             "city": "运城"
         }
     ]
 }
 */

分层解耦

三层架构

image-20250329171952668

· controller: 控制层,接受前端发送的请求,对请求进行处理,并响应数据。

· service: 业务逻辑层,处理具体的业务逻辑。

· dao: 数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增、删、改、查。

三层架构通过单一职责原则可以使得代码复用性强、便于维护、利于扩展

image-20250329173701458

// dao层代码,通过接口实现不同的数据访问方式
public class EmpDaoA implements EmpDao {
    @Override
    public List<Emp> listEmp() {
        //1. 加载并解析emp.xml
        String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
        System.out.println(file);
        List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
        return empList;
    }
}
// service层代码
public class EmpServiceA implements EmpService {

    private EmpDao empDao = new EmpDaoA();

    @Override
    public List<Emp> listEmp() {
        //1. 调用dao, 获取数据
        List<Emp> empList = empDao.listEmp();

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

            //处理job - 1: 讲师, 2: 班主任 , 3: 就业指导
            String job = emp.getJob();
            if("1".equals(job)){
                emp.setJob("讲师");
            }else if("2".equals(job)){
                emp.setJob("班主任");
            }else if("3".equals(job)){
                emp.setJob("就业指导");
            }
        });
        return empList;
    }
}
// controller层
@RestController
public class EmpController {

    private EmpService empService = new EmpServiceA();

    @RequestMapping("/listEmp")
    public Result list(){
        //1. 调用service, 获取数据
        List<Emp> empList = empService.listEmp();

        //3. 响应数据
        return Result.success(empList);
    }

分层解耦

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

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

软件设计原则:高内聚低耦合

image-20250329175607963

在之前编写的代码中,如果改变了service层的方法名(由EmpServiceA改为EmpServiceB),那么此时controller层中的代码也要跟着改动,这说明controller层和service层是耦合的。

如果要是实现解耦,则不能new对象:

可以提供一个容器,用来存储对象。

让controller程序运行时去从容器中取这个empService对象。

这里有两个问题,同时引出Spring框架重要的两个概念:

对象怎么样交给容器来管理?——控制反转

容器如何为程序提供所依赖的资源?——依赖注入

控制反转:Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。

依赖注入:Dependenvy Injection,简称DI。容器为应用程序提供运行时所依赖的资源,称之为依赖注入。

Bean对象:IOC容器中创建、管理的对象,称之为bean

IOC&DI入门

通过IOC&DI完成controller层、service层、Dao层的解耦:

· Service层与Dao层的实现类,交给IOC容器管理。@Component

· 为Controller及Service注入运行时依赖的对象。@Autowired

image-20250329181840499

@Component	// 将当前类交给IOC容器管理,成为IOC管理中的bean
public class EmpServiceA implements EmpService {
// private EmpDao empDao = new EmpDaoA();
    @Autowired	// 运行时,IOC容器会提供该类型的bean对象,并赋值给该变量 - 依赖注入
    private EmpDao empDao;
    @Override
    public List<Emp> listEmp() {
        //1. 调用dao, 获取数据
        List<Emp> empList = empDao.listEmp();
        // ...
@Component	// 将当前类交给IOC容器管理,成为IOC管理中的bean
public class EmpDaoA implements EmpDao {
    @Override
    public List<Emp> listEmp() {
        //1. 加载并解析emp.xml
        String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
        System.out.println(file);
        List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
        return empList;
    }
}
@RestController
public class EmpController {
	@Autowired	// 运行时,IOC容器会提供该类型的bean对象,并赋值给该变量 - 依赖注入
    // private EmpService empService = new EmpServiceA();
	private EmpService empService;
    @RequestMapping("/listEmp")
    public Result list(){
        //1. 调用service, 获取数据
        List<Emp> empList = empService.listEmp();

        //3. 响应数据
        return Result.success(empList);
    }

IOC

要把某个对象交给IOC容器管理,需要在对应的类上加上如下注解之一:

image-20250401131317563

@Service	// 将当前类交给IOC容器管理,成为IOC管理中的bean
public class EmpServiceA implements EmpService {
// private EmpDao empDao = new EmpDaoA();
    @Autowired	// 运行时,IOC容器会提供该类型的bean对象,并赋值给该变量 - 依赖注入
    private EmpDao empDao;
    @Override
    public List<Emp> listEmp() {
        //1. 调用dao, 获取数据
        List<Emp> empList = empDao.listEmp();
        // ...
@Repository	// 将当前类交给IOC容器管理,成为IOC管理中的bean
public class EmpDaoA implements EmpDao {
    @Override
    public List<Emp> listEmp() {
        //1. 加载并解析emp.xml
        String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
        System.out.println(file);
        List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
        return empList;
    }
}
@RestController	// RestController中已经包括了Controller
public class EmpController {
	@Autowired	// 运行时,IOC容器会提供该类型的bean对象,并赋值给该变量 - 依赖注入
    // private EmpService empService = new EmpServiceA();
	private EmpService empService;
    @RequestMapping("/listEmp")
    public Result list(){
        //1. 调用service, 获取数据
        List<Emp> empList = empService.listEmp();

        //3. 响应数据
        return Result.success(empList);
    }

bean的名字默认为首字母小写的类名,如果需要指定bean的名字,可以使用@Component(value = "beanName")或者@Component("beanName")

Bean组件扫描:

bean注解想要生效,还需要被组件扫描注解@ComponentScan扫描,@ComponentScan注解虽然没有显式配置,但实际上被包含在了启动类声明注解@SpringBootApplication中,默认扫描的范围是启动类所在包及其子包。(默认程序规范),扫描范围可以通过配置@ComponentScan("package1","package2)

DI

Bean注入通过@Autowired注解,默认是按照类型进行,如果存在多个相同类型的bean,则会报错。

1.@Primary注解设置优先级,来让对应bean对象生效。

 @Primary
 @Service
 public class EmpServiceA implements EmpService{
 }

2.@Qualifier注解按照类型来指定注入bean对象。

 @RestController
 public class EmpController{
     @Qualifier("empServiceA")
     @Autowired
     private EmpService empService;
 }

3.@Resource注解按照名称来指定注入bean对象。

 @RestController
 public class EmpController{
     @Resource(name = "empServiceB")
     private EmpService empService;
 }

MySQL

· 数据库:DataBase(DB),是存储和管理数据的仓库。

· 数据库管理系统:DataBase Management System(DBMS),操纵和管理数据库的大型软件。

· SQL:Structured Query Language,操作关系型数据库的编程语言,定义了一套操作关系型数据库统一标准

image-20250401135508791

image-20250401135527354

MySQL概述

关系型数据库:建立在关系模型基础上,由多张相互连接的二维表组成的数据库。

特点:

· 使用表存储数据,格式统一,便于维护。

· 使用SQL语言操作,标准统一,使用方便,可用于复杂查询

image-20250401150024224

SQL语句可以单行或多行书写,以分号结尾。

SQL语句可以使用空格/缩进来增强语句的可读性。

MySQL数据库的SQL语句不区分大小写。

注释:

单行注释:-- 注释内容# 注释内容(MySQL持有)

多行注释:/* 注释内容*/

SQL语句通常被分为四类:

DDL(Data Definition Language),数据定义语言,用来定义数据库对象(数据库,表,字段)

DML(Data Manipulation Language),数据操作语言,用来对数据库表中的数据进行增删改

DQL(Data Query Language),数据查询语言,用来查询数据库中表的记录

DCL(Data Control Language),数据控制语言,用来创建数据库用户、控制数据库的访问权限

image-20250401152941899

MySQL数据类型

数值类型

image-20250401181948977

默认为有符号类型,unsigned为无符号类型

字符串类型

image-20250401182721082

char(10)表示最多只能存10个字符,不足10个字符也会占用10个字符的空间。(空间换时间:性能高,浪费空间)

varchar(10)表示最多只能存10个字符,不足10个字符,按照实际长度存储。(时间换空间:性能低,节省空间)

日期时间类型

image-20250401183243930

DDL

DDL(Data Definition Language),数据定义语言,用来定义数据库对象(数据库,表,字段)

数据库操作

image-20250401153128882

表结构操作

创建表

create table 表名(
    字段1 字段类型 [约束] [comment 字段1注释],
    ...
    字段n 字段类型 [约束] [comment 字段n注释],
)[comment 表注释];

约束:作用于表上字段上的规则,用于限制存储在表中的数据,保证数据库中数据的正确性、有效性和完整性。

image-20250401180647311

create table tb_user(
    id int primary key auto_increment comment 'ID,唯一标识',   -- 主键约束
    username varchar(20) not null unique comment  '用户名',    -- 非空且唯一
    name varchar(10) not null comment '姓名', -- 非空
    age int comment '年龄',
    gender char(1) default '男' comment '性别' -- 默认性别为男
)comment '用户表';

image-20250401180347328

image-20250401203020125image-20250401203240994

 -- 修改:为表tb_emp添加字段qq,类型为varchar(11),注释为qq
 alter table tb_emp add qq varchar(11) comment `qq` ;
 -- 修改:修改tb_emp字段qq,类型由varchar(11)改为varchar(13)
 alter table tb_emp modify qq varchar(13) comment `qq`;
 -- 修改:修改tb_emp字段名,字段名由qq改为qq_num
 alter table tb_emp change qq qq_num varchar(13) comment `qq`;
 -- 修改:删除tb_emp的qq_num字段
 alter table tb_emp drop column qq_num;
 -- 修改:将tb_emp表名修改为emp
 rename table tb_emp to emp;

image-20250401204553675

-- 删除:删除tb_emp表
drop table if exists tb_emp;

再删除表时,表中的数据也会被删除。

DML

DML(Data Manipulation Language),数据操作语言,用来对数据库表中的数据进行增删改

INSERT

image-20250402183311176

-- 为tb_emp表中的username, name, gender字段插入值
insert into tb_emp(username,name,gender,creat_time,update_time) values ('Alaska','Gulf',1,now(),now());

-- 为tb_emp表中的所有字段插入值
insert into tb_emp values (null,'abc','123',18,2,now(),now());

-- 批量为tb_emp表中的username,name,gender字段插入数据
insert into tb_emp(username,name,gender,creat_time,update_time) values
                ('username1','name1',1,now(),now()),('username2','name2',2,now(),now());

image-20250402184824401

注意事项:

· 插入数据时,指定的字段顺序需要与值的顺序是一一对应的。

· 字符串和日期型市局应该包含在引号中。

· 插入的数据大小,应该在字段的规定范围内。

UPDATE

image-20250402185101767

 -- 将tb_emp表中的ID为1的员工的姓名name字段更新为->'张三'
 update tb_emp set name = '张三' ,update_time = now() where id = '1';
 ​
 -- 将tb_emp表中的所有员工的姓名name字段更新为->'iloveJava'
 update tb_emp set name = 'iloveJava' ,update_time = now();

image-20250402185932692

DELETE

image-20250402191310754

 -- 删除tb_emp表中ID为2的员工
 delete from tb_emp where id = 2;
 ​
 -- 删除tb_emp表中的所有数据
 delete from tb_emp;

注意事项:

· DELETE语句的条件可以有,也可以没有,如果没有条件,则会删除整张表中的所有数据。

· DELETE语句不能删除某一个字段的值(如果要操作,可以使用UDPATE,将该字段的值置为NULL)。

DQL

DQL(Data Query Language),数据查询语言,用来查询数据库中表的记录

image-20250402192520211

基本查询

image-20250402192853734

 -- 查询返回所有字段
 select id, username, password, name, gender, image, job, entrydate, create_time, update_time from tb_emp; -- 推荐方式
 select * from tb_emp; -- 不推荐,性能低

image-20250402194030499

 -- 查询所有员工的 name, entrydate, 并起别名(姓名、入职日期)
 -- select name as 姓名,entrydate as 入职日期 from tb_emp; as可以省略
 -- 如果别名有特殊符号或者空格,需要给别名加上引号
 select name '姓 名',entrydate 入职日期 from tb_emp; 

image-20250402194039250

 -- 查询指定字段 name, entrydate并返回
 select name,entrydate from tb_emp;

image-20250402194048757

 -- 查询已有的员工关联了哪几种职位(不要重复)
 select distinct job from tb_emp;

image-20250402194158509

条件查询

image-20250402194845699

-- 查询 姓名 为 杨逍 的员工
select * from tb_emp where name = '杨逍';

image-20250402200126129

-- 查询 id ≤ 5 的员工信息
select * from tb_emp where id <= 5;

image-20250402200222879

 -- 查询 没有分配职位 的员工信息
 select * from tb_emp where job is null;

image-20250402200302333

 -- 查询 有职位 的员工信息
 select * from tb_emp where job is not null;

image-20250402200520780

 -- 查询 密码不等于 '123456' 的员工信息
 select * from tb_emp where password != '123456';

image-20250402200736936

-- 查询 入职日期 在 '2000-01-01'(包含) 到'2010-01-01'(包含)之间的员工信息
select * from tb_emp where entrydate >= '2000-01-01' and entrydate <= '2010-01-01';
select * from tb_emp where entrydate between '2000-01-01' and '2010-01-01';

image-20250402200951533

-- 查询 入职日期 在 '2000-01-01'(包含) 到'2010-01-01'(包含)之间 且 性别为女 的员工信息
select * from tb_emp where entrydate between '2000-01-01' and '2010-01-01' and gender =2;

image-20250402201641375

-- 查询 职位是 2(讲师),3(学工主管),4(教材主管)的员工信息
select * from tb_emp where job = 2 or job = 3 or job = 4;
select * from tb_emp where job in (2,3,4);

image-20250402201851976

-- 查询 姓名 为两个字的员工信息
select * from tb_emp where name like '__';

image-20250402202203814

-- 查询 姓 '张' 的员工信息
select * from tb_emp where name like '张%'

image-20250402202101953

分组查询

image-20250402202846443

-- 统计该企业员工数量
-- count(字段)
select count(id) from tb_emp; -- 29
select count(job) from tb_emp; -- 28 聚合函数不对null值进行运算
-- count(常量)
select count(0) from tb_emp; -- 29
select count('A') from tb_emp; -- 29
-- count(*) -- 推荐
select count(*) from tb_emp; -- 29

-- 统计该企业最早入职的员工
select min(entrydate) from tb_emp; -- 2000-01-01

-- 统计该企业最迟入职的员工
select max(entrydate) from tb_emp; -- 2020-01-01

-- 统计该企业员工 ID 的平均值
select avg(id) from tb_emp; -- 15.0000

-- 统计该企业员工的 ID 之和
select sum(id) from tb_emp; -- 435

image-20250403125943485

-- 根据性别分组,统计男性和女性员工的数量
select gender,count(*) from tb_emp group by gender;

image-20250403125201274

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

image-20250403125744846

排序查询

image-20250403131721297

-- 根据入职时间,对员工进行升序排序
select * from tb_emp order by entrydate;

-- 根据入职时间,对员工进行降序排序
select * from tb_emp order by entrydate desc;

-- 根据入职时间对公司的员工进行升序排序,入职时间相同,再按照更新时间进行降序排序
select * from tb_emp order by entrydate,update_time desc ;

分页查询

image-20250403132405128

-- 从起始索引0开始查询员工数据,每页展示5条记录
select * from tb_emp limit 0,5;

-- 查询第1页的员工数据,每页展示5条记录
select * from tb_emp limit 0,5;

-- 查询第2页的员工数据,每页展示5条记录
select * from tb_emp limit 5,5;

-- 查询第3页的员工数据,每页查询5条记录
select * from tb_emp limit 10,5

-- 起始索引 =  (页码 - 1) * 每页展示记录数

image-20250403132343022

image-20250403132354051

image-20250403132347391

流程控制函数:

if(条件表达式, true取值, false取值)

case 表达式 when 值1 then 结果1 when 值2 then 结果2 ... else ... end

多表设计

项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系,基本上分为三种:

· 一对多(多对一)

· 多对多

· 一对一

一对多

image-20250403140207318

image-20250403141357722

image-20250403142012700

image-20250403142034370

image-20250403143000499

一对一

image-20250403143223364

create table tb_user(
    id int unsigned  primary key auto_increment comment 'ID',
    name varchar(10) not null comment '姓名',
    gender tinyint unsigned not null comment '性别, 1 男  2 女',
    phone char(11) comment '手机号',
    degree varchar(10) comment '学历'
) comment '用户信息表';

create table tb_user_card(
    id int unsigned  primary key auto_increment comment 'ID',
    nationality varchar(10) not null comment '民族',
    birthday date not null comment '生日',
    idcard char(18) not null comment '身份证号',
    issued varchar(20) not null comment '签发机关',
    expire_begin date not null comment '有效期限-开始',
    expire_end date comment '有效期限-结束',
    user_id int unsigned not null unique comment '用户ID',
    constraint fk_user_id foreign key (user_id) references tb_user(id) -- 外键
) comment '用户信息表';

多对多

image-20250403154606437

create table tb_student(
    id int auto_increment primary key comment '主键ID',
    name varchar(10) comment '姓名',
    no varchar(10) comment '学号'
) comment '学生表';

create table tb_course(
    id int auto_increment primary key comment '主键ID',
    name varchar(10) comment '课程名称'
) comment '课程表';

create table tb_student_course(
    id int auto_increment comment '主键' primary key,
    student_id int not null comment '学生ID',
    course_id  int not null comment '课程ID',
    constraint fk_courseid foreign key (course_id) references tb_course (id),
    constraint fk_studentid foreign key (student_id) references tb_student (id)
)comment '学生课程中间表';

多表查询

image-20250403173849808

 -- 多表查询
 select * from tb_emp,tb_dept; -- 17 x 5 笛卡尔积
 select * from tb_emp,tb_dept where tb_emp.dept_id = tb_dept.id; -- 16 有一个没有分配部门

image-20250403173915318

内连接

image-20250403174327072

-- 查询员工的姓名,及所属的部门名称(隐式内连接实现)
select tb_emp.name,tb_dept.name from tb_emp,tb_dept where tb_emp.dept_id = tb_dept.id;

-- 起别名,起了别名之后必须使用别名,不能再使用原来的字段名
select e.name,d.name from tb_emp e, tb_dept d where  e.dept_id = d.id;

-- 查询员工的姓名,及所属的部门名称(显式内连接实现)
select tb_emp.name,tb_dept.name from tb_emp inner join tb_dept on tb_emp.dept_id = tb_dept.id;

image-20250405114320388

只有16条数据(有一个员工没有部门数据)

外连接

image-20250405113934694

-- 查询员工表所有员工的姓名,和对应的部门名称(左外连接)
select e.name,d.name from tb_emp e left join tb_dept d on e.dept_id = d.id;

image-20250405114247183

17条数据,由于是左外连接,所以会显示所有的员工的部门信息(即使没有所属部门)

-- 查询部门表所有部门的名称,和对应的员工名称(右外连接)
select e.name,d.name from tb_emp e right join tb_dept d on e.dept_id = d.id;
-- 与下面的左外连接效果相同
select e.name,d.name from tb_dept d left join tb_emp e on e.dept_id = d.id;

image-20250405114659156

由于是右外连接,所以会显示所有部门下的员工信息(即使这个部门下没有员工)

子查询

image-20250405115547507

-- 标量子查询 子查询返回的结果是单个值
-- 查询教研部的所有员工信息
-- 1.查询教研部的部门ID
select id from tb_dept where name = '教研部';
-- 2.再查询该部门ID下的员工信息
select * from tb_emp where dept_id = 2;
-- 3.子查询
select * from tb_emp where dept_id = (select id from tb_dept where name = '教研部');

image-20250405120516853

-- 查询在方东白入职之后的员工信息
-- 1.查询方东白的入职日期
select tb_emp.entrydate from tb_emp where name = '方东白';
-- 2.在查询该入职日期之后的员工信息
select * from tb_emp where entrydate > '2012-11-01';
-- 3.子查询
select * from tb_emp where entrydate > (select tb_emp.entrydate from tb_emp where name = '方东白');

image-20250405120529540

-- 列子查询 子查询返回的结果是一列
-- 查询教研部和咨询部的所有员工信息
-- 1.查询教研部和咨询部的部门ID
select id from tb_dept where name in('教研部','咨询部');
-- 2.根据部门ID,查询该部门下的员工信息
select * from tb_emp where dept_id in (2,3);
-- 3.子查询
select * from tb_emp where dept_id in(select id from tb_dept where name in('教研部','咨询部'));

image-20250405121038838

-- 行子查询 子查询返回的结果是一行
-- 查询与韦一笑的入职日期及职位都相同的员工信息
-- 1.查询韦一笑的入职日期和职位
select entrydate,job from tb_emp where name = '韦一笑';
-- 2.根据查询到的入职日期和职位,查询该条件下的员工信息
select * from tb_emp where entrydate = '2007-01-01' and job = 2;
select * from tb_emp where (entrydate,job) = ('2007-01-01', 2);
-- 3.子查询
select * from tb_emp where(entrydate = (select entrydate from tb_emp where name = '韦一笑')and job = (select job from tb_emp where name = '韦一笑'));
select * from tb_emp where (entrydate,job) = (select entrydate,job from tb_emp where name = '韦一笑');

image-20250405124104788

-- 表子查询	子查询返回的结果是一张表,当做临时表来使用
-- 查询入职日期是2006-01-01之后的员工信息,及其部门名称
-- 1.查询入职日期是2006-01-01之后的员工信息
select * from tb_emp where entrydate > '2006-01-01';
-- 2.查询这部分员工信息及其部门名称
select e.*,d.name from (select * from tb_emp where entrydate > '2006-01-01') e, tb_dept d where e.dept_id = d.id;

事务

事务控制

image-20250405134833115

事务是一组操作的集合,是一个不可分割的工作单位。事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败

默认是MySQL的事务是自动提交的。当执行一条DML语句,MySQL会立即隐式的提交事务。

image-20250405153957715

四大特性

image-20250405154503109

索引

索引是帮助数据库高效获取数据的数据结构。

优点:

提高数据查询的效率,降低数据库的IO成本。

通过索引列对数据进行排序,降低数据排序的成本,降低GPU消耗。

缺点:

索引会占用存储空间

索引大大提高了查询效率,但是降低了insert、update、delete的效率

image-20250405160724906

image-20250405160211457

image-20250405170719940

 select count(*) from tb_sku;    -- 6000000
 -- 无索引:
 select * from tb_sku where sn = '100000003145003'; -- 耗时13s
 -- 创建索引:
 create index idx_sku_su on tb_sku(sn); -- 耗时45s(构建了600万条索引,只需构建一次)
 -- 有索引:
 select * from tb_sku where sn = '100000003145003'; -- 耗时4ms
 -- 查看索引
 show index from tb_sku;
 -- 删除索引
 drop index idx_sku_su on tb_sku;

MySQL数据库支持的索引结构有很多,如:Hash索引、B+Tree索引、Full-Text索引等。我们平常所说的索引,默认指的是B+Tree结构组织的索引

image-20250405162907511

Mybatis

入门

Mybatis是一款持久层(Dao)框架,用于简化JDBC的开发。

image-20250405171250182

创建实体类:

package ren.imuzi0221.springbootmybatis.pojo;

public class User {
    private Integer id;
    private String name;
    private short age;
    private short gender;

    public User() {
    }

    public User(Integer id, String name, short age, short gender, String phone) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                ", phone='" + phone + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public short getAge() {
        return age;
    }

    public void setAge(short age) {
        this.age = age;
    }

    public short getGender() {
        return gender;
    }

    public void setGender(short gender) {
        this.gender = gender;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    private String phone;
}

编写SpringBoot配置文件

spring.application.name=springboot-mybatis
# 配置数据库的连接信息 - 四要素
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
#连接数据库的用户名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=1234

编写Java(SQL)语句

@Mapper // 在运行时,会自动生成该接口的实现类对象(代理对象),并且将对象交给IOC容器管理
public interface UserMapper {
    // 查询全部用户信息
    @Select("select * from user")
    public List<User> list();

}

@SpringBootTest // SpringBoot整合单元测试的注解
class DemoApplicationTests {
    @Autowired
    private UserMapper userMapper;

    @Test
    public void testListUser(){
        List<User> userList = userMapper.list();
        userList.stream().forEach(user -> {
            System.out.println(user);
        });
    }

}

/**
User{id=1, name='白眉鹰王', age=55, gender=1, phone='18800000000'}
User{id=2, name='金毛狮王', age=45, gender=1, phone='18800000001'}
User{id=3, name='青翼蝠王', age=38, gender=1, phone='18800000002'}
User{id=4, name='紫衫龙王', age=42, gender=2, phone='18800000003'}
User{id=5, name='光明左使', age=37, gender=1, phone='18800000004'}
User{id=6, name='光明右使', age=48, gender=1, phone='18800000005'}
**/

JDBC

image-20250405190158528

// 了解即可
@Test
public void testJdbc() throws Exception {
    //1. 注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");

    //2. 获取连接对象
    String url = "jdbc:mysql://localhost:3306/mybatis";
    String username = "root";
    String password = "1234";
    Connection connection = DriverManager.getConnection(url, username, password);

    //3. 获取执行SQL的对象Statement,执行SQL,返回结果
    String sql = "select * from user";
    Statement statement = connection.createStatement();
    ResultSet resultSet = statement.executeQuery(sql);

    //4. 封装结果数据
    List<User> userList = new ArrayList<>();
    while (resultSet.next()){
        int id = resultSet.getInt("id");
        String name = resultSet.getString("name");
        short age = resultSet.getShort("age");
        short gender = resultSet.getShort("gender");
        String phone = resultSet.getString("phone");

        User user = new User(id,name,age,gender,phone);
        userList.add(user);
    }

    //5. 释放资源
    statement.close();
    connection.close();
}

image-20250405190905712

数据库连接池

数据库连接池是个容器,负责分配、管理数据库连接(Connection)

它允许应用程序重复使用一个现有的数据库连接,而不是在重新建立一个新连接

释放空间时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接泄露

数据库连接池可以实现:

· 资源重用

· 提升系统响应速度

· 避免数据库连接遗漏。

image-20250405191523054

image-20250405192021488

Lombok

Lombok是一个使用的Java类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,并可以自动化生成日志变量,简化Java开发,提高效率。

image-20250405192559670

image-20250405192738967

@Data   // get、set、equals、toString
@NoArgsConstructor  // 无参构造
@AllArgsConstructor // 有参构造
public class User {
    private Integer id;
    private String name;
    private short age;
    private short gender;
    private String phone;
}

基础操作

删除

 @Mapper
 public interface EmpMapper {
     // 根据ID删除数据
     // SQL语句+接口方法
     @Delete("delete from emp where id = #{id}")
     // public void delete(Integer id);
     public int delete(Integer id);  // 返回的是受影响的数据的个数
 }
 ​
 ​
 @SpringBootTest // SpringBoot整合单元测试的注解
 class DemoApplicationTests {
     @Autowired
     private EmpMapper empMapper;
     @Test
     public void testDelete(){
         System.out.println(empMapper.delete(17));   // 1
     }
 }

image-20250406144812819

ID为17的员工信息被删除。

image-20250406145402977

image-20250406145751206

预编译SQL语句采用?占位符,可以有效利用缓存,提高性能。

image-20250406151440913

SQL注入是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。

image-20250406150312062

校验用户名和密码本质上是SQL的查询操作

select count(*) from emp where username = 'zhangwuji' and password = '123456';

image-20250406150401631

username字段和password字段来自于网页端的输入

count(*)为1,说明数据库中存在对应的账户名和密码,判定为登录成功。

count(*)为0,说明数据库中不存在对应的账户名和密码,判定为登录失败。

此时通过SQL注入达到成功登录的目的:

image-20250406150607071

select count(*) from emp where username = 'wueiuwieuiwueiw' and password = '' or '1' = '1';

此时密码的输入变成了'' or '1' = '1'(加粗的为输入的内容),这会让系统出现错误判定:'1' = '1'恒为真,导致整个SQL语句的where条件永远成立

image-20250406151130858

此时需要使用预编译SQL来防止SQL注入

image-20250406151315186

新增

 // 新增员工
 @Options(keyProperty = "id",useGeneratedKeys = true)    // 会自动将生成的主键值,复制给emp的id属性
 @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time)" +
         "value (#{username},#{name},#{gender},#{image},#{job},#{entryDate},#{deptId},#{createTime},#{updateTime})") // 一定要是对象的属性名
 public void insert(Emp emp);
 ​
 @Test
     public void testInsert(){
         // 构造员工对象
         Emp emp = new Emp();
         emp.setUsername("Tom");
         emp.setName("汤姆");
         emp.setImage("1.jpg");
         emp.setGender((short)1);
         emp.setJob((short)1);
         emp.setEntryDate(LocalDate.of(2000,1,1));
         emp.setCreateTime(LocalDateTime.now());
         emp.setUpdateTime(LocalDateTime.now());
         emp.setDeptId(1);
 ​
         // 执行新增员工信息操作
         empMapper.insert(emp);
         System.out.println(emp.getId());    // 18
         /*
         ==>  Preparing: insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time)value (?,?,?,?,?,?,?,?,?)
         ==> Parameters: Tom(String), 汤姆(String), 1(Short), 1.jpg(String), 1(Short), 2000-01-01(LocalDate), 1(Integer), 2025-04-06T15:33:22.167075300(LocalDateTime), 2025-04-06T15:33:22.167075300(LocalDateTime)
         <==    Updates: 1
         */

image-20250406153520354

更新

 // 更新员工
     @Update("update emp set username = #{username}, name = #{name}, gender = #{gender}, image = #{image}, job = #{job}, entrydate = #{entryDate}, dept_id = #{deptId}, update_time = #{updateTime} where id = #{id};")
     public void update(Emp emp);
 ​
 @Test
 public void testUpdate(){
     // 构造员工对象
     Emp emp = new Emp();
     emp.setId(18);
     emp.setUsername("Alaska");
     emp.setName("阿拉斯加");
     emp.setImage("1.jpg");
     emp.setGender((short)1);
     emp.setJob((short)1);
     emp.setEntryDate(LocalDate.of(2000,1,1));
     emp.setUpdateTime(LocalDateTime.now());
     emp.setDeptId(1);
 ​
     // 执行新增员工信息操作
     empMapper.update(emp);
     /*
      * ==>  Preparing: update emp set username = ?, name = ?, gender = ?, image = ?, job = ?, entrydate = ?, dept_id = ?, update_time = ? where id = ?;
      * ==> Parameters: Alaska(String), 阿拉斯加(String), 1(Short), 1.jpg(String), 1(Short), 2000-01-01(LocalDate), 1(Integer), 2025-04-06T15:51:20.532233200(LocalDateTime), 18(Integer)
      * <==    Updates: 1
      */

image-20250406155234442

查询

根据ID查询

 // 根据ID查询员工
     @Select("select * from emp where id = #{id}")
     public Emp getById(Integer id);
 ​
 @Test
 public void testGetById(){
     Emp emp = empMapper.getById(18);
     System.out.println(emp);    // Emp(id=18, username=Alaska, password=123456, name=阿拉斯加, gender=1, image=1.jpg, job=1, entryDate=2000-01-01, deptId=null, createTime=null, updateTime=null)
 }

注意到deptId=null, createTime=null, updateTime=null,这几项数据没有被封装进来

image-20250406160001306

 // 方案一:给字段起别名,让别名与实体类属性一致
 @Select("select id, username, password, name, gender, image, job, entrydate, dept_id deptId, create_time createTime, update_time updateTime from emp where id = #{id}")
 public Emp getById(Integer id);
 ​
 // 方案二:通过@Results,@Result注解手动映射封装
     @Results({
             @Result(column = "dept_id", property = "deptId"),
             @Result(column = "create_time", property = "createTime"),
             @Result(column = "update_time", property = "updateTime")
     })
     @Select("select * from emp where id = #{id}")
     public Emp getById(Integer id);
 // Emp(id=18, username=Alaska, password=123456, name=阿拉斯加, gender=1, image=1.jpg, job=1, entryDate=2000-01-01, deptId=1, createTime=2025-04-06T15:33:22, updateTime=2025-04-06T15:51:21)
 ​
 // 方案三:开启Mybatis的驼峰命名自动映射开关 a_column ---> aColumn
 // 在application.properties配置文件中,开启mybatis的驼峰命名自动映射开关
 // mybatis.configuration.map-underscore-to-camel-case=true

条件查询

image-20250406162933997

 // 条件查询员工信息
 //    @Select("select * from emp where name like '%${name}%' and gender = #{gender} //and entrydate " +
 //            "between #{begin} and #{end} order by update_time desc ;")
     @Select("select * from emp where name like concat('%',#{name},'%') and gender = #{gender} and entrydate " +
     "between #{begin} and #{end} order by update_time desc ;")
     public List<Emp> list(String name, short gender, LocalDate begin, LocalDate end);   // 此处由于单引号内不能使用#{},而直接使用${},会产生SQL注入问题,可以使用concat字符串拼接函数concat('%',#{name},'%')
 ​
 @Test
 public void testList(){
     List<Emp> empList = empMapper.list("张", (short)1,LocalDate.of(2010,1,1),LocalDate.of(2020,1,1));
     System.out.println(empList);    // [Emp(id=2, username=zhangwuji, password=123456, name=张无忌, gender=1, image=2.jpg, job=2, entryDate=2015-01-01, deptId=2, createTime=2025-04-05T19:51:56, updateTime=2025-04-05T19:51:56)]
 }

image-20250406163114217

XML映射文件

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java注解不仅力不从心,还会让本就复杂的SQL语句更加混乱,所以如果需要做一些很复杂的操作,最好是用XML来映射语句。

image-20250406163422875

 <?xml version="1.0" encoding="UTF-8" ?>
 <!DOCTYPE mapper
         PUBLIC "-//mybatis.org//DTD Mapper 3.-//EN"
         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="ren.imuzi0221.mybatis.mapper.EmpMapper">
 <!--    resultType: 单条记录所封装的类型全类名-->
     <select id="list" resultType="ren.imuzi0221.mybatis.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>
 </mapper>

动态SQL

image-20250406175155420

如果只选择一个或两个条件,那么此时没有被输入条件的字段会被传入NULL,会导致查询不到数据,因此需要动态SQL。

< if >

<if>:用于判断条件是否成立。使用test属性进行条件判断,如果条件为true,则执行SQL语句。

 <mapper namespace="ren.imuzi0221.mybatis.mapper.EmpMapper">
 <!--    resultType: 单条记录所封装的类型全类名-->
     <select id="list" resultType="ren.imuzi0221.mybatis.pojo.Emp">
         select *
         from emp
         <where>  <!-- 动态生成where关键字,自动去除掉and和or,<set>标签自动去除掉逗号',' --> 
             <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>

< foreach >

<foreach>:常用于批量操作

 // 批量删除员工
 public void deleteByIds(List<Integer> ids);
 <!--    批量删除员工 (1,2,3) -->
 <!--
     collection: 遍历的集合
     item: 遍历出来的元素
     separator: 分隔符
     open: 遍历开始前拼接的SQL片段
     close: 遍历结束后拼接的SQL片段
 -->
 <delete id="deleteByIds">
     delete from emp where id in
     <foreach collection="ids" item="id" separator="," open="(" close=")">
         #{id}
     </foreach>
 </delete>

image-20250406183325691

< sql >< include >

<sql>片段可以增加代码的复用型

image-20250406183810494

案例:tlias智能学习辅助系统

待填坑

登录认证

会话技术

会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。

回话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据

回话跟踪方案:

  • 客户端会话跟踪技术:Cookie

  • 服务端会话跟踪技术:Session

  • 令牌技术

image-20250411205641003

Cookie:HTTP请求报头包含存储先前通过与所述服务器发送的HTTP cookies

Set-Cookie:HTTP响应报头被用于从服务器向用户代理发送cookie

image-20250411211009859

  • 优点:

    HTTP协议中本身支持的技术

  • 缺点:

    移动端APP无法使用Cookie

    不安全,用户可以自己禁用Cookie

    Cookie不能跨域(请求的协议、IP/域名、端口不同)

Session

image-20250412162356459

  • 优点:

    存储在服务端,安全

  • 缺点:

    服务器集群环境下无法直接使用Session

    移动端APP无法使用Session

    不安全,用户可以自己禁用Session

    Session也不能跨域

令牌

image-20250412165842826

  • 优点:

    支持PC端、移动端

    解决集群环境下的认证问题

    减轻服务器端存储压力

  • 缺点:

    需要自己实现

JWT令牌

JWT(JSON Web Token):定义了一种简洁的、自包含的格式,用于在通信双方json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。

  • 组成

    第一部分:Header(头),记录令牌类型、签名算法等。例如:{"alg":"HS256","type":"JWT"}

    第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:{"id":"1","username":"Tom"}

    第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定密钥,通过指定签名算法计算而来。

image-20250412182049631

 // 生成JWT
 public void testGetjwt(){
     Map<String,Object> claims = new HashMap<>();
     claims.put("id",1);
     claims.put("name","tom");
     String jwt = Jwts.builder()
             .signWith(SignatureAlgorithm.HS256,"itheima")   // 签名算法
             .setClaims(claims)  // 自定义内容
             .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))  // 设置有效期为1h
             .compact();
     System.out.println(jwt);
 //eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTc0NDUzNzY2MH0.HMp9TW1nn3333WgiVIuB9zpTOgQyF3Vsk8pKfn5rv-s
 }
 // 解析JWT
     public void testParseJwt(){
         Claims claims = Jwts.parser()
             .setSigningKey("itheima")       .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTc0NDUzNzY2MH0.HMp9TW1nn3333WgiVIuB9zpTOgQyF3Vsk8pKfn5rv-s")
             .getBody();
         System.out.println(claims);
        // {name=tom, id=1, exp=1744537660}
 }

过滤器

过滤器(Filter):是JavaWet三大组件(Servlet、Filter、Listener)之一,过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能,一般用于完成通用的操作,例如:登录校验、统一编码处理、敏感字符处理等。

image-20250413172219775

image-20250413173613954

放行后访问对应资源,资源访问完成后,还会回到Filter中,并且继续执行放行后的逻辑。

image-20250413174304091

过滤器链:一个Web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链。

image-20250413181049715

注解配置的Fliter,优先级是按照过滤器类名(字符串)的自然排序。

拦截器

拦截器(Interceptor):是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的用来动态拦截控制器方法的执行。

作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码。

image-20250413184604712image-20250413185327471image-20250413185813932

过滤器与拦截器:

接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口。

拦截范围不同:过滤器Filter会拦截所有的资源,而拦截器Interceptor只会拦截Spring环境中的资源。

异常处理


0

评论区