美文网首页算法刷题笔记
Java - Web 开发笔记 Ⅱ

Java - Web 开发笔记 Ⅱ

作者: Du1in9 | 来源:发表于2024-05-20 15:37 被阅读0次

    👉 在线笔记:https://du1in9.github.io/javaweb.github.io/

    第4章 - SpringBootWeb & 原理

    4.1 SpringBootWeb

    4.1.1 快速入门

    Spring 发展到今天已经形成了一种开发生态圈,Spring 提供了若干个子项目,每个项目用于完成特定的功能

    Spring Boot 可以帮助我们非常快速的构建应用程序、简化开发、提高效率

    // 需求:浏览器发起请求 /hello 后,给浏览器返回字符串 "Hello World"
    
    package com.itheima.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class hellocontroller {
        @RequestMapping("/hello")
        public String hello() {
            return "hello world";
        }
    }
    

    4.1.2 HTTP 协议

    ① HTTP - 概述

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

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

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

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

      缺点:多次请求间不能共享数据;优点:速度快

    ② HTTP - 请求协议

    请求头 解释
    Host 请求的主机名
    User-Agent 浏览器版本
    Accept 表示浏览器能接收的资源类型
    Accept-Language 表示浏览器偏好的语言
    Accept-Encoding 表示浏览器可以支持的压缩类型
    Content-Type 请求主体的数据类型
    Content-Length 请求主体的大小,单位 byte

    ③ HTTP - 响应协议

    状态码 英文描述 解释
    200 OK 客户端请求成功,即处理成功,这是我们最想看到的状态码
    302 Found 资源已移动到由 Location 响应头给定的 URL,浏览器会自动重新访问到这个页面
    304 Not Modified 请求资源至上次取得后,服务端并未更改,你直接用你本地缓存吧。隐式重定向
    400 Bad Request 客户端请求有语法错误,不能被服务器所理解
    403 Forbidden 服务器收到请求,但是拒绝提供服务,比如:没有权限访问相关资源
    404 Not Found 请求资源不存在,一般是URL输入有误,或者网站资源被删除了
    405 Method Not Allowed 请求方式有误,比如应该用GET请求方式的资源,用了POST
    428 Precondition Required 服务器要求有条件的请求,告诉客户端要想访问该资源,必须携带特定的请求头
    429 Too Many Requests 指示用户在给定时间内发送了太多请求,配合 Retry-After响应头一起使用
    431 Request Header Fields Too Large 请求头太大,服务器不愿意处理,因为它的头部字段太大,减少大小后重新提交
    500 Internal Server Error 服务器发生不可预期的错误。服务器出异常了,赶紧看日志去吧
    503 Service Unavailable 服务器尚未准备好处理请求,服务器刚刚启动,还未初始化好
    响应头 解释
    Content-Type 表示该响应内容的类型,例如 text/html,application/json
    Content-Length 表示该响应内容的长度,单位 byte
    Content-Encoding 表示该响应压缩算法,例如 gzip
    Cache-Control 指示客户端应如何缓存,例如 max-age=300 表示最多缓存300秒
    Set-Cookie 告诉浏览器为当前页面所在的域设置 cookie

    4.1.3 WEB 服务器 - Tomcat

    ① 简介

    1. Web 服务器:对 HTTP 协议操作进行封装,简化 web 程序开发;部署 web 项目,对外提供网上信息浏览服务

    2. Tomcat:一个轻量级的 web 服务器,支持 servlet、jsp 等少量 javaEE 规范;也被称为 web 容器、servlet 容器

    ② 基本使用

    • 启动:双击 bin\startup.bat
    • 停止:双击 bin\shutdown.bat 或 Ctrl+C
    • 部署:将项目放置到 webapps 目录下, 即部署完成
    • 乱码:修改 conf\logging.properties 为:java.util.logging.ConsoleHandler.encoding = GBK
    • 注意:HTTP 协议默认端口号为80,如果将 Tomcat 端口号改为80,则访问时将不用输入端口号

    ③ 入门程序解析

    • spring-boot-starter-web:包含了 web 应用开发所需要的常见依赖
    • spring-boot-starter-test:包含了单元测试所需要的常见依赖

    基于 Springboo t开发的 web 应用程序,内置了 tomcat 服务器,当启动类运行时会自动启动

    4.1.4 SpringBootWeb 请求

    ① Postman

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

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

    ② 简单参数

    1. 原始方式获取请求参数

    Controller 方法形参中声明 HttpServletRequest 对象,调用对象的 getParameter (参数名)

    @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";
    }
    
    1. SpringBoot 中接收简单参数

    请求参数名与方法形参变量名相同,会自动进行类型转换

    @RequestMapping("/simpleParam")
    public String simpleParam(String name, Integer age){
        System.out.println(name+ ":" + age);
        return "OK";
    }
    // get: url: http://localhost:8080/simpleParam?name=Tom&age=20
    // post: url: http://localhost:8080/simpleParam; body: name=Tom, age=20
    
    1. @RequestParam 注解

    方法形参名称与请求参数名称不匹配,通过该注解完成映射

    @RequestMapping("/simpleParam")
    public String simpleParam(@RequestParam(name = "name") String username, Integer age){
        System.out.println(username+ ":" + age);
        return "OK";
    }
    

    该注解的 required 属性默认是 true,代表请求参数必须传递

    ③ 实体参数

    1. 简单实体对象:请求参数名与形参对象属性名相同,定义 POJO 接收即可

      @RequestMapping("/simplePojo")
      public void simplePojo(User user){
          System.out.println(user);
      }        // get: ...?name=Tom&age=20
      
      public class User {
          private String name;
          private Integer age;
          ...(javabean)
      }
      
    2. 复杂实体对象:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套 POJO 属性参数

      @RequestMapping("/complexPojo")
      public void complexPojo(User user){
          System.out.println(user);
      }        // get: ...?name=Tom&age=20&Address.province=beijing&Address.city=beijing
      
      public class User {
          private String name;
          private Integer age;
          private Address address;
          ...(javabean)
      }
      
      public class Address {
          private String province;
          private String city;
          ...(javabean)
      }
      

    ④ 数组集合参数

    • 数组参数:请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数
    • 集合参数:请求参数名与形参集合名称相同且请求参数为多个,@RequestParam 绑定参数关系
    @RequestMapping("/arrayParam")
    public void arrayParam(String[] hobby){
        System.out.println(Arrays.toString(hobby));     // [rap, dance]
    }           // get: ...?hobby=rap&hobby=dance
    
    @RequestMapping("/listParam")       
    public void listParam(@RequestParam List<String> hobby){
        System.out.println(hobby);                      // [rap, dance]
    }           // get: ...?hobby=rap&hobby=dance
    

    ⑤ 日期参数

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

    @RequestMapping("/dateParam")
    public void dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime time){
        System.out.println(time);       // 2022-12-02T10:05:51
    }           // get: ...?time=2022-12-02 10:05:51
    

    ⑥ JSON 参数

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

    @RequestMapping("/jsonParam")
    public void jsonParam(@RequestBody User user){
        System.out.println(user);
    }           // User{name='tom', age=20, address=Address{province='beijing', city='beijing'}}
    
    // post: body: raw:
    {
        "name":"tom",
        "age":20,
        "address":{
            "province":"beijing",
            "city":"beijing"
        }
    }
    

    ⑦ 路径参数

    通过请求 URL 直接传递参数,使用 {…} 来标识该路径参数,需要使用 @PathVariable 获取路径参数

    @RequestMapping("/path/{id}")
    public void pathParam(@PathVariable Integer id){
        System.out.println(id);                 // 123
    }       // get: http://localhost:8080/path/123
    
    @RequestMapping("/path/{id}/{name}")
    public void pathParam2(@PathVariable Integer id , @PathVariable String name){
        System.out.println(id + ":" + name);    // 123:Alice
    }       // get: http://localhost:8080/path/123/Alice
    

    4.1.5 SpringBootWeb 响应

    ① @ResponseBody

    • 类型:方法注解、类注解
    • 位置:Controller 方法上 / 类上
    • 作用:将方法返回值直接响应,如果返回值类型是实体对象/集合 ,将会转换为 JSON 格式响应
    • 说明:@RestController = @Controller + @ResponseBody

    ② 统一响应结果

    @RequestMapping("/hello")
    public Result hello(){
        ...
        return Result.success("Hello World ~");
    }
    
    @RequestMapping("/getAddr")
    public Result getAddr(){
        ...
        return Result.success(addr);
    }
    
    @RequestMapping("/listAddr")
    public Result listAddr(){
        ...
        return Result.success(list);
    }
    
    public class Result {
        private Integer code ;  // 1 成功, 0 失败
        private String msg;     // 提示信息
        private Object data;    // 数据 date
        
        public static Result success(){
            return new Result(1, "success", null);
        }
        public static Result success(Object data){
            return new Result(1, "success", data);
        }
        public static Result error(String msg){
            return new Result(0, msg, null);
        }
        ...(javabean)
    }
    

    ③ 案例

    需求:a. 获取员工数据,b. 返回统一响应结果,c. 在页面渲染展示

    1. 在 pom.xml 文件中引入 dom4j 的依赖,用于解析 XML 文件
    <!-- pom.xml: 引入依赖, 用来解析xml文件 -->
    <dependency>
        <groupId>org.dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>2.1.3</version>
    </dependency>
    
    1. 引入资料中提供的解析 XML 的工具类 XMLParserUtils、对应的实体类 Emp、XML 文件 emp.xml
    // XMLParserUtils.java: 工具类, 用来解析xml文件
    public class XmlParserUtils {...}
    
    <!-- emp.xml: 员工数据 -->
    <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>
        ...
    </emps>
    
    // Emp.java: 员工类
    public class Emp {
        private String name;
        private Integer age;
        private String image;
        private String gender;
        private String job;
        ...(javabean)
    }
    
    1. 引入资料中提供的静态页面文件,放在 resources 下的 static 目录下
    <!-- emp.html: 展示页面, 用来接收统一结果, c.并渲染展示 -->
    <body>
        <div id="app">
            <el-table :data="tableData" style="width: 100%"  stripe border >
                ...         
            </el-table>
        </div>
    </body>
    
    <script>
        new Vue({
            mounted(){
                axios.get('/listEmp').then(res=>{
                    if(res.data.code){                  <!-- 根据 Result 的 code 判断 -->
                        this.tableData = res.data.data; <!-- 根据 Result 的 data 渲染 -->
                    }
                });
            }
        });
    </script>
    

    注意:Springboot 项目的静态资源(html,css,js 等前端资源)默认存放目录为:classpath : /static 、/public、/resources

    1. 编写 Controller 程序,处理请求,响应数据
    // EmpController.java: Controller程序, a.用来获取员工数据, b.并返回统一响应结果
    
    @RestController
    public class EmpController {
        @RequestMapping("/listEmp")
        public Result list(){
            //1. 加载并解析emp.xml
            String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
            List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
            //2. 对数据进行转换处理 - gender, job
            empList.stream().forEach(emp -> {
                String gender = emp.getGender();
                if("1".equals(gender)){
                    emp.setGender("男");
                }else if("2".equals(gender)){
                    emp.setGender("女");
                }
                String job = emp.getJob();
                if("1".equals(job)){
                    emp.setJob("讲师");
                }else if("2".equals(job)){
                    emp.setJob("班主任");
                }else if("3".equals(job)){
                    emp.setJob("就业指导");
                }
            });
            //3. 响应数据
            return Result.success(empList);
        }
    }
    

    4.1.6 SpringBootWeb 分层解耦

    ① 三层架构

    • Controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据
    public class EmpController {
        private EmpService empService = new EmpServiceA();  // 调用service获取数据
        @RequestMapping("/listEmp")
        public Result list(){                           
            List<Emp> empList = empService.listEmp();   
            return Result.success(empList);                 // 3. 响应数据
        }
    }
    
    • Service:业务逻辑层,处理具体的业务逻辑
    public interface EmpService {
        public List<Emp> listEmp();             
    }
    
    public class EmpServiceA implements EmpService {
        private EmpDao empDao = new EmpDaoA();      // 调用dao获取数据
        @Override
        public List<Emp> listEmp() {
            List<Emp> empList = empDao.listEmp();   
            empList.stream().forEach(emp -> {       
                ...                                 // 2. 对数据进行处理
            }); 
            return empList;                         // 返回service
        }
    }
    
    • Dao:数据访问层 (Data Access Object)(持久层),负责数据访问操作,包括数据的增、删、改、查
    public interface EmpDao {
        public List<Emp> listEmp();                 
    }
    
    public class EmpDaoA implements EmpDao {        
        @Override
        public List<Emp> listEmp() {                // 1. 加载并解析emp.xml
            String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
            List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
            return empList;                         // 返回 dao
        }
    }
    

    ② 分层解耦

    • 内聚:软件中各个功能模块内部的功能联系
    • 耦合:衡量软件中各个层 / 模块之间的依赖、关联的程度
    • 软件设计原则:高内聚低耦合

    分层解耦

    • 控制反转: Inversion Of Control,简称 IOC。对象的创建控制权由程序自身转移到外部容器,这种思想称为控制反转
    • 依赖注入: Dependency Injection,简称 DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入
    • Bean 对象:IOC 容器中创建、管理的对象,称之为 bean

    ③ IOC & DI

    1. Service 层及 Dao 层的实现类,交给 IOC 容器管理
    @Component          // 将当前对象交给 IOC 容器管理,成为 IOC 容器的 bean
    public class EmpDaoA implements EmpDao {
    }
    
    @Component          // 将当前对象交给 IOC 容器管理,成为 IOC 容器的 bean
    public class EmpServiceA implements EmpService {
    }
    
    1. 为 Controller 及 Service 注入运行时,依赖的对象
    @Component
    public class EmpServiceA implements EmpService {
        @Autowired      // 运行时,需要从 IOC 容器中获取该类型对象,赋值给该变量
        private EmpDao empDao;
    }
    
    public class EmpController {
        @Autowired      // 运行时,需要从 IOC 容器中获取该类型对象,赋值给该变量
        private EmpService empService;
    }
    

    1. IOC 详解

    注解 说明 位置
    @Component 声明bean的基础注解 不属于以下三类时,用此注解
    @Controller @Component的衍生注解 标注在控制器类上
    @Service @Component的衍生注解 标注在业务类上
    @Repository @Component的衍生注解 标注在数据访问类上(与 mybatis 整合,用的少)
    • 声明 bean 的时候,可以通过 value 属性指定 bean 的名字,如果没有指定,默认为类名首字母小写
    • 使用以上四个注解都可以声明 bean,但是在 springboot 集成web开发中,声明控制器 bean 只能用 @Controller
    @RestController
    public class EmpController {...}
    @Service
    public class EmpServiceA implements EmpService {...}
    @Repository("daoA")
    public class EmpDaoA implements EmpDao {...}
    

    @ComponentScan 注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解 @SpringBootApplication 中

    // @ComponentScan({"com.itheima.dao","com.itheima"})
    @SpringBootApplication      //默认扫描当前包及其子包
    public class SpringbootWebReqRespApplication {...}
    

    2. DI 详解

    @Autowired 注解,默认是按照类型进行,如果存在多个相同类型的 bean,将会报出错误

    通过以下几种方案来解决:@Primary、@Qualifier、@Resource

    public class EmpController {
        @Qualifier("empServiceA")
        @Autowired
        private EmpService empService;
    }
    public class EmpController {
        @Resource(name = "empServiceA")
        private EmpService empService;
    }
    
    @Primary
    @Service
    public class EmpServiceA implements EmpService {...}
    @Service
    public class EmpServiceB implements EmpService {...}
    

    @Resource 与 @Autowired 区别:(面试题)

    • @Autowired 是 spring 框架提供的注解,而 @Resource 是 JDK 提供的注解
    • @Autowired 默认是按照类型注入,而 @Resource 默认是按照名称注入

    4.2 SpringBootWeb AOP

    4.2.1 Spring 事务管理

    ① 事务管理

    事务 是一组操作的集合,它是一个不可分割的工作单位,这些操作 要么同时成功,要么同时失败

    案例:解散部门:删除部门,同时删除该部门下的员工

    • 注解:@Transactional
    • 位置:业务(service)层的方法上、类上、接口上
    • 作用:将当前方法交给 spring 进行事务管理,方法执行前开启事务;成功执行完毕提交事务;出现异常回滚事务
    // main.java.com.itheima.service\impl\EmpServiceImpl.java
    
    @Transactional
    @Override
    public void delete(Integer id) {
        deptMapper.deleteById(id);
        int i = 1/0;                    // 回滚事务, 部门删除失败
        empMapper.deleteByDeptId(id);   // 异常处理, 未删除部门员工
    }
    
    // main.java.com.itheima.mapper\EmpMapper.java
    
    @Delete("delete  from emp where dept_id = #{deptId}")
    void deleteByDeptId(Integer deptId);
    
    • 默认情况只有 RuntimeException 才回滚异常。rollbackFor 属性用于控制出现何种异常类型,回滚事务
    // main.java.com.itheima.service\impl\EmpServiceImpl.java
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delete(Integer id) throws Exception {
        deptMapper.deleteById(id);
        if(true){throw new Exception("出错啦...");}
        empMapper.deleteByDeptId(id);
    }
    

    ② 事务传播行为

    事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制

    属性值 含义
    REQUIRED 需要事务,有则加入,无则创建新事务【默认值】
    REQUIRES_NEW 需要新事务,无论有无,总是创建新事务
    // REQUIRED :大部分情况下都是用该传播行为即可
    @Transactional(propagation = Propagation.REQUIRED)
    // REQUIRES_NEW: 当我们不希望事务之间相互影响时,可以使用该传播行为
    // 比如: 下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    

    4.2.2 AOP 基础

    ① AOP 概述

    • AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程

    • 场景:统计各个业务层方法执行耗时

      案例部分功能运行较慢,定位执行耗时较长的业务方法,此时需要统计每一个业务方法的执行耗时

    • 实现:动态代理是面向切面编程最主流的实现

      而 SpringAOP 是 Spring 框架的高级技术,旨在管理 bean 对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程

    ② AOP 快速入门

    • 导入依赖:在 pom.xml 中导入 AOP 的依赖

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-aop</artifactId>
      </dependency>
      
    • 编写 AOP 程序:针对于特定方法根据业务需要进行编程

      // main.java.com.itheima\aop\TimeAspect.java
      
      @Aspect   // AOP类
      public class TimeAspect {
          @Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")    // 切入点表达式
          public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
              long begin = System.currentTimeMillis();  // 1. 获取方法运行开始时间
              Object result = joinPoint.proceed();      // 2. 运行原始方法 
              long end = System.currentTimeMillis();        // 3. 获取方法运行结束时间,计算执行耗时
              log.info(joinPoint.getSignature()+"方法执行耗时: {}ms", end-begin);
              return result;
          }
      }
      

    ③ AOP 核心概念

    1. AOP 核心概念

      • 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)

      • 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)

      • 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用

      • 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)

      • 目标对象:Target,通知所应用的对象

    1. AOP 执行流程

    4.2.3 AOP 进阶

    ① 通知类型

    @Slf4j
    @Component
    @Aspect
    public class MyAspect1 {
        // 该注解将公共的切点表达式抽取出来,需要用到时引用该切点表达式即可
        @Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
        private void pt(){}
    
        @Before("pt()")             // 前置通知
        public void before(){log.info("before ...");}
    
        @After("pt()")              // 后置通知
        public void after(){log.info("after ...");}
    
        @AfterReturning("pt()")     // 返回后通知, 了解
        public void afterReturning(){log.info("afterReturning ...");}
    
        @AfterThrowing("pt()")      // 异常后通知, 了解
        public void afterThrowing(){log.info("afterThrowing ...");}
    
        @Around("pt()")             // 环绕通知, 重点
        public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            log.info("around before ...");
            Object result = proceedingJoinPoint.proceed();
            log.info("around after ...");
            return result;
        }
    }
    

    ② 通知顺序

    当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行

    1. 不同切面类中,默认按照切面类的类名字母排序:

      • 目标方法前的通知方法:字母排名靠前的先执行

      • 目标方法后的通知方法:字母排名靠前的后执行

    2. 用 @Order(数字)加在切面类上来控制顺序:

      • 目标方法前的通知方法:数字小的先执行

      • 目标方法后的通知方法:数字小的后执行

    @Order(2)
    public class MyAspect1 {
        @Before("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
        public void before(){log.info("before ...1");}
    
        @After("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
        public void after(){log.info("after ...1");}
    }
    
    @Order(3)
    public class MyAspect2 {
        @Before("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
        public void before(){log.info("before ...2");}
        
        @After("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
        public void after(){log.info("after ...2");}
    }
    
    @Order(1)
    public class MyAspect3 {
        @Before("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
        public void before(){log.info("before ...3");}
    
        @After("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
        public void after(){log.info(" ...3");}
    }
    
    com.itheima.aop.MyAspect3           : before ...3
    com.itheima.aop.MyAspect1           : before ...1
    com.itheima.aop.MyAspect2           : before ...2
    com.itheima.aop.MyAspect2           : after ...2
    com.itheima.aop.MyAspect1           : after ...1
    com.itheima.aop.MyAspect3           : after ...3
    

    ③ 切入点表达式

    • 切入点表达式:描述切入点方法的一种表达式
    • 作用:主要用来决定项目中的哪些方法需要加入通知
    1. execution (……):根据方法的签名来匹配

      语法格式:execution (访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)

      @Pointcut("execution(public void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
      @Pointcut("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
      @Pointcut("execution(void delete(java.lang.Integer))")   // 包名.类名不建议省略
      @Pointcut("execution(void com.itheima.service.DeptService.delete(java.lang.Integer))")
      

      * :单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分

      .. :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数

      @Pointcut("execution(void com.itheima.service.DeptService.*(java.lang.Integer))")
      @Pointcut("execution(* com.*.service.DeptService.*(*))")
      @Pointcut("execution(* com.itheima.service.*Service.delete*(*))")
      
      @Pointcut("execution(* com.itheima.service.DeptService.*(..))")
      @Pointcut("execution(* com..DeptService.*(..))")
      @Pointcut("execution(* com..*.*(..))")
      @Pointcut("execution(* *(..))")      // 慎用
      

      根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式

      @Pointcut("execution(* com.itheima.service.DeptService.list()) || " +
                "execution(* com.itheima.service.DeptService.delete(java.lang.Integer))")
      

      书写建议

      • 所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如:查询类方法都是 find 开头,更新类方法都是 update开头
      • 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性
      • 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名匹配尽量不使用 ..,使用 * 匹配单个包
    2. @annotation (……) :根据注解匹配

      @annotation 切入点表达式,用于匹配标识有特定注解的方法

      // com.itheima.aop\MyAspect.java
      
      public class MyAspect {
          @Pointcut("@annotation(com.itheima.aop.MyLog)")
          private void pt(){}
      }
      
      // com.itheima.aop\MyLog.java
      
      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.METHOD)
      public @interface MyLog {}
      
      // com.itheima.service.impl\DeptServiceImpl.java
      
      public class DeptServiceImpl implements DeptService {
          @MyLog
          public List<Dept> list() {...}
          @MyLog
          public void delete(Integer id) {...}
      }
      

    ④ 连接点

    在 Spring 中用 JoinPoint 抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等

    • 对于 @Around 通知,获取连接点信息只能使用 ProceedingJoinPoint

    • 对于其他四种通知,获取连接点信息只能使用 JoinPoint ,它是 ProceedingJoinPoint 的父类型

    // com.itheima.aop\MyAspect.java
    
    @Before("pt()")
    public void before(JoinPoint joinPoint){}
    
    @Around("pt()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        String className = joinPoint.getTarget().getClass().getName();
        log.info("目标对象的类名:{}", className);
        String methodName = joinPoint.getSignature().getName();
        log.info("目标方法的方法名: {}",methodName);
        Object[] args = joinPoint.getArgs();
        log.info("目标方法运行时传入的参数: {}", Arrays.toString(args));
        Object result = joinPoint.proceed();
        log.info("目标方法运行的返回值: {}",result);
        return result;
    }
    

    4.2.4 AOP 案例

    需求:将 SpringBootWeb 案例中 增、删、改 相关接口的操作日志记录到数据库表中

    日志信息包含:操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长

    思路:

    • 需要对所有业务类中的增、删、改 方法添加统一功能,使用 AOP 技术最为方便

    • 由于增、删、改 方法名没有规律,可以自定义 @Log 注解完成目标方法匹配

    准备:

    • 在案例工程中引入 AOP 的起步依赖

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-aop</artifactId>
      </dependency>
      
    • 导入资料中准备好的数据库表结构,并引入对应的实体类

      create table operate_log(
          id int unsigned primary key auto_increment comment 'ID',
          operate_user int unsigned comment '操作人ID',
          operate_time datetime comment '操作时间',
          class_name varchar(100) comment '操作的类名',
          method_name varchar(100) comment '操作的方法名',
          method_params varchar(1000) comment '方法参数',
          return_value varchar(2000) comment '返回值',
          cost_time bigint comment '方法执行耗时, 单位:ms'
      ) comment '操作日志表';
      
      // com.itheima.pojo\OperateLog.java
      
      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class OperateLog {
          private Integer id;               // ID
          private Integer operateUser;      // 操作人ID
          private LocalDateTime operateTime;    // 操作时间
          private String className;             // 操作类名
          private String methodName;            // 操作方法名
          private String methodParams;      // 操作方法参数
          private String returnValue;       // 操作方法返回值
          private Long costTime;                // 操作耗时
      }
      
      // com.itheima.mapper\OperateLogMapper.java
      
      @Mapper
      public interface OperateLogMapper {
          @Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
          public void insert(OperateLog log);
      }
      

    编码:

    • 自定义注解 @Log

      // com.itheima.anno\Log.java
      
      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.METHOD)
      public @interface Log {}
      
    • 定义切面类,完成记录操作日志的逻辑

      @Component
      @Aspect       // 切面类
      public class LogAspect {
          @Autowired
          private HttpServletRequest request;
          @Autowired
          private OperateLogMapper operateLogMapper;
      
          @Around("@annotation(com.itheima.anno.Log)")
          public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
              // 操作人ID
              String jwt = request.getHeader("token");  // 获取请求头中的jwt令牌, 解析令牌
              Claims claims = JwtUtils.parseJWT(jwt);
              Integer operateUser = (Integer) claims.get("id");
              // 操作时间
              LocalDateTime operateTime = LocalDateTime.now();
              // 操作类名
              String className = joinPoint.getTarget().getClass().getName();
              // 操作方法名
              String methodName = joinPoint.getSignature().getName();
              // 操作方法参数
              Object[] args = joinPoint.getArgs();
              String methodParams = Arrays.toString(args);
              long begin = System.currentTimeMillis();
              Object result = joinPoint.proceed();      // 调用原始目标方法运行
              long end = System.currentTimeMillis();
              // 操作方法返回值
              String returnValue = JSONObject.toJSONString(result);
              // 操作耗时
              Long costTime = end - begin;
              // 记录操作日志
              OperateLog operateLog = new OperateLog
                  (null,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime);
              operateLogMapper.insert(operateLog);
              return result;
          }
      }
      
      // com.itheima.controller\DeptController.java
      @Log
      public Result delete(@PathVariable Integer id) throws Exception {...}
      @Log
      public Result add(@RequestBody Dept dept){...}
      
      // com.itheima.controller\EmpController.java
      @Log
      public Result delete(@PathVariable List<Integer> ids){...}
      @Log
      public Result save(@RequestBody Emp emp){...}
      @Log
      public Result update(@RequestBody Emp emp){...}
      

    4.3 SpringBoot 原理

    4.3.1 配置优先级

    1. SpringBoot 中支持三种格式的配置文件:application.properties、application.yml、application.yaml

      虽然 springboot 支持多种格式配置文件,但是在项目开发时,推荐统一使用一种格式的配置 (yml 是主流)

    2. SpringBoot 除了支持配置文件属性配置,还支持 Java 系统属性和命令行参数的方式进行属性配置

      • 打包前如何配置:
    • 打包后如何配置:java -Dserver.port=9000 -jar xxxx.jar --server.port=10010
    1. 优先级(低→高):yaml、yml、properties、java 系统属性、命令行参数

    4.3.2 Bean 管理

    ① 获取 Bean

    默认情况下,Spring 项目启动时,会把 bean 都创建好放在 IOC 容器中,如果想要主动获取这些 bean,可以通过如下方式:

    @Autowired
    private ApplicationContext applicationContext;  // IOC 容器对象
    
    @Test
    public void testGetBean(){  // 获取 bean 对象
        // 根据 bean 的名称获取
        DeptController bean1 = (DeptController) applicationContext.getBean("deptController");
        // 根据 bean 的类型获取
        DeptController bean2 = applicationContext.getBean(DeptController.class);
        // 根据 bean 的名称及类型获取
        DeptController bean3 = applicationContext.getBean("deptController", DeptController.class);
    }
    

    ② Bean 作用域

    作用域 说明
    singleton 容器内同名称 的 bean 只有一个实例(单例)(默认)
    prototype 每次使用该 bean 时会创建新的实例(非单例)
    @Scope("prototype")
    public class DeptController {
        public DeptController(){
            System.out.println("DeptController constructor ....");
        }
    }
    
    @Test
    public void testScope(){
        for (int i = 0; i < 3; i++) {
            DeptController deptController = applicationContext.getBean(DeptController.class);
            System.out.println(deptController);
        }
    }
    // DeptController constructor ....
    // com.itheima.controller.DeptController@34d9df9f
    // DeptController constructor ....
    // com.itheima.controller.DeptController@35c8be21
    // DeptController constructor ....
    // com.itheima.controller.DeptController@60807fd9
    

    ③ 第三方 Bean

    • 如果要管理的 bean 对象来自于第三方,是无法用 @Component 及衍生注解声明 bean 的,就需要用到 @Bean 注解

    • 若要管理的第三方 bean 对象,建议对这些 bean 进行集中分类配置,可以通过 @Configuration 注解声明一个配置类

    // com.itheima.config\CommonConfig.java
    
    @Configuration  // 配置类
    public class CommonConfig {
        @Bean   // 将当前方法的返回值对象交给 IOC 容器管理, 成为 IOC 容器 bean
        public SAXReader reader(){
            return new SAXReader();
        }
    }
    
    @Autowired
    private SAXReader saxReader;
    
    @Test   // 第三方 bean 的管理
    public void testThirdBean() throws Exception {...}
    
    • 通过 @Bean 注解的 name 或 value 属性可以声明 bean 的名称,如果不指定,默认 bean 的名称就是方法名

    • 如果第三方 bean 需要依赖其它 bean 对象,直接在 bean 定义方法中设置形参即可,容器会根据类型自动装配

    @Configuration
    public class CommonConfig {
        @Bean   
        public SAXReader reader(DeptService deptService){
            System.out.println(deptService);
            return new SAXReader();
        }
    }
    

    4.3.3 SpringBoot 原理

    ① 起步依赖

    ② 自动配置

    当 spring 容器启动后,一些配置类、bean 对象就自动存入到了 IOC 容器中,从而简化了开发,省去了繁琐的配置操作

    @Autowired
    private DataSource dataSource;
    
    @Test
    public void testDataSource(){...}
    

    A. 原理

    方案一:@ComponentScan 组件扫描

    @ComponentScan({"com.example","com.itheima","com.alibaba","com.google","org.springframework",...})
    @SpringBootApplication
    public class SpringbootWebConfig2Application {}     // => 使用繁琐, 性能低
    

    方案二:@Import 导入。使用 @Import 导入的类会被 Spring 加载到 IOC 容器中,导入形式主要有以下几种:

    @Import({TokenParser.class})                        // 1. 导入普通类
    @Import({HeaderConfig.class})                       // 2. 导入配置类
    @Import({MyImportSelector.class})                   // 3. 导入 ImportSelector 接口实现类
    @EnableHeaderConfig                                 // 4. @Enablexxxx 注解,封装 @Import 注解 
    @SpringBootApplication
    public class SpringbootWebConfig2Application {}     // => 方便, 优雅
    
    @Component                              // a. 普通类
    public class TokenParser {}
    
    @Configuration                          // b. 配置类
    public class HeaderConfig {             
        @Bean
        public HeaderParser headerParser(){return new HeaderParser();}
        @Bean
        public HeaderGenerator headerGenerator(){return new HeaderGenerator();}
    }
    
    public class HeaderParser {}
    public class HeaderGenerator {}
    
                                            // c. ImportSelector 接口实现类
    public class MyImportSelector implements ImportSelector {
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{"com.example.HeaderConfig"};
        }
    }
    
    @Retention(RetentionPolicy.RUNTIME)     
    @Target(ElementType.TYPE)
    @Import(MyImportSelector.class)         
    public @interface EnableHeaderConfig {} // d. 封装 @Import 注解
    

    B. 源码跟踪

    @SpringBootApplication 标识在 SpringBoot 工程引导类上,是 SpringBoot 中最最最重要的注解。该注解由三个部分组成:

    • @SpringBootConfiguration:该注解与 @Configuration 注解作用相同,用来声明当前也是一个配置类
    • @ComponentScan:组件扫描,默认扫描当前引导类所在包及其子包
    • @EnableAutoConfiguration:SpringBoot实现自动化配置的核心注解

    C. @Conditional

    • 作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的 bean 对象到 Spring IOC 容器中
    • 位置:方法、类
    • @Conditional 本身是一个父注解,派生出大量的子注解:
      • @ConditionalOnClass:判断环境中是否有对应字节码文件,才注册 bean 到 IOC 容器
      • @ConditionalOnMissingBean:判断环境中没有对应的 bean(类型或名称),才注册 bean 到 IOC 容器
      • @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册 bean 到 IOC 容器

    ③ 配置案例

    • 场景:在实际开发中,经常会定义一些公共组件,提供给各个项目团队使用

      而在 SpringBoot 的项目中,一般会将这些公共组件封装为SpringBoot 的 starter

    需求:自定义 aliyun-oss-spring-boot-starter,完成阿里云 OSS 操作工具类 AliyunOSSUtils 的自动配置

    目标:引入起步依赖引入之后,要想使用阿里云 OSS,注入 AliyunOSSUtils 直接使用即可

    1. 基础工程
    • 创建 aliyun-oss-spring-boot-starter 模块
    • 创建 aliyun-oss-spring-boot-autoconfigure 模块,在 starter 中引入该模块
    <!-- aliyun-oss-spring-boot-starter\pom.xml -->
    
    <dependency>
        <groupId>com.aliyun.oss</groupId>
        <artifactId>aliyun-oss-spring-boot-autoconfigure</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
    
    • 在 aliyun-oss-spring-boot-autoconfigure 模块中定义自动配置功能
    <!-- aliyun-oss-spring-boot-autoconfigure\pom.xml -->
    
    <!-- 阿里云 OSS -->
    <dependency>
        <groupId>com.aliyun.oss</groupId>
        <artifactId>aliyun-sdk-oss</artifactId>
        <version>3.15.1</version>
    </dependency>
    <dependency>
        <groupId>javax.xml.bind</groupId>
        <artifactId>jaxb-api</artifactId>
        <version>2.3.1</version>
    </dependency>
    <dependency>
        <groupId>javax.activation</groupId>
        <artifactId>activation</artifactId>
        <version>1.1.1</version>
    </dependency>
    <!-- no more than 2.3.3-->
    <dependency>
        <groupId>org.glassfish.jaxb</groupId>
        <artifactId>jaxb-runtime</artifactId>
        <version>2.3.3</version>
    </dependency>
    
    // java.com.aliyun.oss\AliOSSProperties.java
    
    @ConfigurationProperties(prefix = "aliyun.oss")
    public class AliOSSProperties {
        private String endpoint;
        private String accessKeyId;
        private String accessKeySecret;
        private String bucketName;
        
        ...(javabean)
    }
    
    // java.com.aliyun.oss\AliOSSUtils.java
    
    public class AliOSSUtils {
        private AliOSSProperties aliOSSProperties;
        
        public AliOSSProperties getAliOSSProperties() {
            return aliOSSProperties;
        }
        public void setAliOSSProperties(AliOSSProperties aliOSSProperties) {
            this.aliOSSProperties = aliOSSProperties;
        }
        public String upload(MultipartFile file) throws IOException {...}
    }
    
    // java.com.aliyun.oss\AliOSSAutoConfiguration.java
    
    @Configuration
    @EnableConfigurationProperties(AliOSSProperties.class)
    public class AliOSSAutoConfiguration {
        @Bean
        public AliOSSUtils aliOSSUtils(AliOSSProperties aliOSSProperties){
            AliOSSUtils aliOSSUtils = new AliOSSUtils();
            aliOSSUtils.setAliOSSProperties(aliOSSProperties);
            return aliOSSUtils;
        }
    }
    
    • 定义自动配置文件 META-INF/spring/xxxx.imports
    // resources.META-INF.spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports
    
    com.aliyun.oss.AliOSSAutoConfiguration
    
    1. 测试工程
    <!-- springboot-autoconfiguration-test\pom.xml -->
    <dependency>
        <groupId>com.aliyun.oss</groupId>
        <artifactId>aliyun-oss-spring-boot-starter</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
    
    # resources\application.yml
    aliyun:
      oss:
        endpoint: https://oss-cn-beijing.aliyuncs.com
        accessKeyId: LTAI5tSH18FLKGFhWgsuJsGS
        accessKeySecret: 5wPyUojv5seKI5XqsTbrexcHHIoCwD
        bucketName: web-framework2024-5-8-01
    
    @RestController
    public class UploadController {
        @Autowired
        private AliOSSUtils aliOSSUtils;
    
        @PostMapping("/upload")
        public String upload(MultipartFile image) throws Exception {
            String url = aliOSSUtils.upload(image);
            return url;
        }
    }
    // postman: post: body: form-data: image(file) => test.jpg
    // https://web-framework2024-5-8-01.oss-cn-beijing.aliyuncs.com/dbb28c6d-9dfe-4e87-a6e0-4bc392ebd02f.jpg
    

    第5章 - MySQL & Mybatis

    5.1 MySQL

    5.1.1 MySQL 概述

    ① 课程介绍

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

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

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

    MySQL 连接:mysql -uroot -p1234

    MySQL 企业开发使用方式:mysql -h192.168.150.101 -P3306 -uroot -p1234

    ② 数据模型

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

    ③ SQL 简介

    SQL:一门操作关系型数据库的编程语言,定义操作所有关系型数据库的统一标准

    SQL 通用语法

    • SQL 语句可以单行 / 多行书写,以分号结束
    • SQL 语句中可以增加缩进 / 空格来增强可读性
    • SQL 语句中的关键字不区分大小写
    • SQL 语句注释:单行注释(-- 注释),多行注释(/* 注释 */)
    分类 全称 说明
    DDL Data Definition Language 数据定义语言,用来定义数据库对象 (数据库,表,字段)
    DML Data Manipulation Language 数据操作语言,用来对数据库表中的数据进行增删改
    DQL Data Query Language 数据查询语言,用来查询数据库中表的记录
    DCL Data Control Language 数据控制语言,用来创建数据库用户、控制数据库的访问权限

    5.1.2 数据库设计 - DDL

    ① 项目开发流程

    ② 数据库操作

    show databases;         -- 查询所有数据库
    select database();      -- 查询当前数据库
    create database db01;   -- 创建数据库
    use  db01;              -- 使用数据库
    drop  db01;             -- 删除数据库
    # 注: 上述语法中的 database, 也可以替换成 schema. 如:create schema db01
    

    图形化工具:

    ③ 表结构操作

    1. 表结构 - 创建

    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 '用户表';
    
    约束 描述 关键字
    非空约束 限制该字段值不能为null not null
    唯一约束 保证字段的所有数据都是唯一、不重复的 unique
    主键约束 主键是一行数据的唯一标识,要求非空且唯一 primary key
    默认约束 保存数据时,如果未指定该字段值,则采用默认值 default
    外键约束 让两张表的数据建立连接,保证数据的一致性和完整性 foreign key
    类型 大小 (byte) 有符号范围 无符号范围 描述
    tinyint 1 (-128,127) (0,255) 小整数值
    smallint 2 (-32768,32767) (0,65535) 大整数值
    mediumint 3 (-8388608,8388607) (0,16777215) 大整数值
    int 4 (-2147483648,2147483647) (0,4294967295) 大整数值
    bigint 8 (-263,263-1) (0,2^64-1) 极大整数值
    float 4 单精度浮点数值
    double 8 双精度浮点数值
    decimal 小数值 (精度更高)
    类型 大小 描述
    char 0-255 bytes 定长字符串
    varchar 0-65535 bytes 变长字符串
    tinyblob 0-255 bytes 不超过255个字符的二进制数据
    tinytext 0-255 bytes 短文本字符串
    blob 0-65 535 bytes 二进制形式的长文本数据
    text 0-65 535 bytes 长文本数据
    mediumblob 0-16 777 215 bytes 二进制形式的中等长度文本数据
    mediumtext 0-16 777 215 bytes 中等长度文本数据
    longblob 0-4 294 967 295 bytes 二进制形式的极大文本数据
    longtext 0-4 294 967 295 bytes 极大文本数据
    类型 大小 范围 格式 描述
    date 3 1000-01-01 至 9999-12-31 YYYY-MM-DD 日期值
    time 3 -838:59:59 至 838:59:59 HH:MM:SS 时间值或持续时间
    year 1 1901 至 2155 YYYY 年份值
    datetime 8 1000-01-01 00:00:00 至 9999-12-31 23:59:59 YYYY-MM-DD HH:MM:SS 混合日期和时间值
    timestamp 4 1970-01-01 00:00:01 至 2038-01-19 03:14:07 YYYY-MM-DD HH:MM:SS 混合日期和时间值,时间戳

    2. 案例

    需求:参考资料中提供的页面原型,设计员工管理模块的表结构 (暂不考虑所属部门字段)

    注:添加员工时,会给员工设置一个默认的密码 123456,添加完成后,员工就可以通过该密码登录该后台管理系统了

    注:create_time 记录的是当前这条数据插入的时间,update_time 记录当前这条数据最后更新的时间

    3. 表结构 - 查询、修改、删除

    -- 查看: 当前数据库下的表
    show tables;
    -- 查看: 查看指定表结构
    desc tb_emp;
    -- 查看: 数据库的建表语句         # 右键 -> Edit Source
    show create table tb_emp;       
    -- 修改: 为表 tb_emp 添加字段 qq varchar(11)
    alter table tb_emp add qq varchar(11) comment 'QQ';
    -- 修改: 修改 tb_emp 字段类型 qq varchar(13)                # 右键 -> Modify Table
    alter table tb_emp modify qq_num varchar(13) comment 'QQ';
    -- 修改: 修改 tb_emp 字段名 qq 为 qq_num varchar(13)        # 右键 -> rename
    alter table tb_emp change qq_num 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;
    -- 删除: 删除 tb_emp 表          # 右键 -> Drop
    drop table if exists tb_emp;
    

    5.1.3 数据库操作 - DML

    ① 增加(insert)

    -- 1. 为 tb_emp 表的 username, name, gender 字段插入值
    insert into tb_emp(username,name,gender,create_time,update_time) values ('wuji','张无忌',1,now(),now());
    
    -- 2. 为 tb_emp 表的 所有字段插入值
    insert into tb_emp(id, username, password, name, gender, image, job, entrydate, create_time, update_time)
                values (null,'zhiruo','123','周芷若',2,'1.jpg',1,'2010-01-01',now(),now());
    insert into tb_emp values (null,'zhiruo2','123','周芷若',2,'1.jpg',1,'2010-01-01',now(),now());
    
    -- 3. 批量为 为 tb_emp 表的 username , name , gender 字段插入数据
    insert into tb_emp(username,name,gender,create_time,update_time) values
                  ('weifuwang','韦一笑',1,now(),now()),('xieshiwang','谢逊',1,now(),now());
    
    # 插入数据时,指定的字段顺序需要与值的顺序是一一对应的
    # 字符串和日期型数据应该包含在引号中
    # 插入的数据大小,应该在字段的规定范围内
    

    ② 修改(update)

    -- 1. 将 tb_emp 表的ID为1员工 姓名name字段更新为 '张三'
    update tb_emp set name = '张三' , update_time = now() where id = 1;
    -- 2. 将 tb_emp 表的所有员工的入职日期更新为 '2010-01-01'
    update tb_emp set entrydate = '2010-01-01', update_time = now();
    
    # 修改语句的条件可以有,也可以没有,如果没有条件,则会修改整张表的所有数据
    

    ③ 删除(delete)

    -- 1. 删除 tb_emp 表中 ID为1的员工
    delete from tb_emp where id = 1;
    -- 2. 删除 tb_emp 表中的所有员工
    delete from tb_emp;
    
    # DELETE 语句的条件可以有,也可以没有,如果没有条件,则会删除整张表的所有数据
    # DELETE 语句不能删除某一个字段的值(如果要操作,可以使用UPDATE,将该字段的值置为NULL)
    

    5.1.4 数据库操作 - DQL

    ① 介绍

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

    ② 基本查询

    -- 1. 查询指定字段 name,entrydate 并返回
    select name,entrydate from emp ;
    
    -- 2. 查询返回所有字段
    select id, username, password, name, gender, image, job, entrydate, create_time, update_time from emp;
    select * from emp;      # * 号代表查询所有字段, 在实际开发中尽量少用(不直观、影响效率)
    
    -- 3. 查询所有员工的 name,entrydate, 并起别名(姓名、入职日期)
    select name as '姓名' ,entrydate as '入职日期' from emp ;
    select name '姓名' ,entrydate '入职日期' from emp ;
    
    -- 4. 查询员工有哪几种职位(不要重复) -- distinct
    select distinct job from emp;
    select * from emp where id = 1;
    

    ③ 条件查询

    -- 1. 查询 姓名 为 杨逍 的员工
    select * from emp where name = '杨逍';
    -- 2. 查询在 id小于等于5 的员工信息
    select * from emp where id <= 5;
    -- 3. 查询 没有分配职位 的员工信息  -- 判断 null , 用 is null
    select * from emp where job is null;
    -- 4. 查询 有职位 的员工信息  -- 判断 不是null , 用 is not null
    select * from emp where job is not null ;
    -- 5. 查询 密码不等于 '123456' 的员工信息
    select * from emp where password <> '123456';
    select * from emp where password != '123456';
    -- 6. 查询入职日期 在 '2000-01-01' (包含) 到 '2010-01-01'(包含) 之间的员工信息
    select * from emp where entrydate between '2000-01-01' and '2010-01-01' ;
    -- 7. 查询 入职时间 在 '2000-01-01' (包含) 到 '2010-01-01'(包含) 之间 且 性别为女 的员工信息
    select * from emp where (entrydate between '2000-01-01' and '2010-01-01') and  gender = 2;
    -- 8. 查询 职位是 2 (讲师), 3 (学工主管), 4 (教研主管) 的员工信息
    select * from emp where job = 2 or job = 3 or job = 4;
    select * from emp where job in (2,3,4);
    -- 9. 查询姓名为两个字的员工信息
    select * from emp where name like '__';
    -- 10. 查询姓 '张' 的员工信息
    select * from emp where name like '张%';
    -- 11. 查询姓名中包含 '三' 的员工信息
    select * from emp where name like '%三%';
    

    ④ 分组查询

    1. 聚合函数
    -- 1. 统计该企业员工数量
    -- A. count(字段)
    select count(id) from emp;
    -- B. count(*) -- 推荐
    select count(*) from emp;
    -- C. count(值)
    select count(1) from emp;
    -- 2. 统计该企业员工 ID 的平均值
    select avg(id) from emp;
    -- 3. 统计该企业最早入职的员工的入职日期
    select min(entrydate) from emp;
    -- 4. 统计该企业最近入职的员工的入职日期
    select max(entrydate) from emp;
    -- 5. 统计该企业员工的 ID 之和
    select sum(id) from emp;
    
    1. 分组查询
    -- 1. 根据性别分组 , 统计男性和女性员工的数量
    select gender,count(*) from emp group by gender;
    -- 2. 先查询入职时间在 '2015-01-01' (包含) 以前的员工 , 并对结果根据职位分组 , 获取员工数量大于等于2的职位
    select job,count(*)  from emp where entrydate <= '2015-01-01'  group by job having count(*) >= 2;
    
    # 分组之后,查询的字段一般为聚合函数和分组字段,查询其他字段无任何意义
    # 执行顺序: where  >  聚合函数 > having
    

    where 与 having 区别:(面试题)

    • 执行时机不同:where 是分组之前进行过滤,不满足 where 条件,不参与分组;而 having 是分组之后对结果进行过滤
    • 判断条件不同:where 不能对聚合函数进行判断,而 having 可以

    ⑤ 排序查询

    -- 1. 根据入职时间, 对员工进行升序排序  -- 排序条件
    select * from emp order by entrydate ;
    -- 2. 根据入职时间, 对员工进行降序排序
    select * from emp order by entrydate desc;
    -- 3. 根据 入职时间 对公司的员工进行 升序排序 , 入职时间相同 , 再按照 ID 进行降序排序
    select * from emp order by entrydate asc , id desc ;
    

    ⑥ 分页查询

    -- 1. 查询第1页员工数据, 每页展示10条记录
    select * from emp limit 0,10;
    select * from emp limit 10;
    -- 2. 查询第2页员工数据, 每页展示10条记录
    select * from emp limit 10,10;
    
    -- 公式 : 起始索引 = (页码 - 1) * 每页记录数
    

    ⑦ 案例

    # 需求 : 员工管理列表查询 , 根据最后操作时间, 进行倒序排序
    # 条件 : name , gender , entrydate
    
    select *
    from tb_emp
    where name like '%张%'
      and gender = 1
      and entrydate between '2000-01-01' and '2010-01-01'
    order by update_time desc
    limit 0,10;
    
    # 需求: 男性与女性员工的人数统计 (1 : 男性员工 , 2 : 女性员工)
    
    -- 函数: if(条件表达式 , true , false)
    select if(gender = 1, '男性员工', '女性员工') '性别',
           count(*) '人数'
    from emp
    group by gender;
    
    # 需求: 员工职位信息
    
    -- 函数: case when ... then ... when ... then ... else ... end
    select (case
            when job = 1 then '班主任'
            when job = 2 then '讲师'
            when job = 3 then '教研主管'
            when job = 4 then '学工主管'
            else '无职位' end) '职位',
           count(*)
    from emp
    group by job;
    
    -- 函数: case ... when ... then ... when ... then ... else ... end
    select (case job
            when 1 then '班主任'
            when 2 then '讲师'
            when 3 then '教研主管'
            when 4 then '学工主管'
            else '无职位' end) '职位',
           count(*)
    from emp
    group by job;
    

    5.1.5 多表设计 & 查询

    ① 多表设计

    1. 一对多

    一对多关系实现:在数据库表中多的一方,添加字段,来关联一的一方的主键

    # 根据页面原型及需求文档,完成部门及员工模块的表结构设计
    # 部门名称,必填,唯一,长度为2-10位
    
    -- 部门
    create table tb_dept (
        id int unsigned primary key auto_increment comment 'ID',
        name varchar(10) not null unique comment '部门名称',
        create_time datetime not null comment '创建时间',
        update_time datetime not null comment '修改时间'
    ) comment '部门表';
    
    -- 员工
    create table tb_emp (
        dept_id int unsigned comment '归属的部门ID'
        ...
    ) comment '员工表';
    

    目前上述的两张表,在数据库层面,并未建立关联,所以是无法保证数据的一致性和完整性的

    2. 一对一

    一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他字段放在另一张表中,以提升操作效率

    • 案例 : 用户 与 身份证信息 的关系
    • 实现 : 在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的 (UNIQUE)

    3. 多对多

    一个学生可以选修多门课程,一门课程也可以供多个学生选择

    • 案例 : 学生 与 课程的关系
    • 实现 : 建立第三张中间表,中间表至少包含两个外键,分别关联两方主键

    4. 案例

    参考资料中提供的页面原型,设计分类管理、菜品管理、套餐管理模块的表结构

    1.阅读页面原型及需求文档,分析各个模块涉及到的表结构,及表结构之间的关系

    2.根据页面原型及需求文档,分析各个表结构中具体的字段及约束

    • 分类表

      新增菜品 / 套餐名称:限制字符范围: 2-20字符

      排序不能为空,内容限制:0-99整数数字

      新增菜品 / 套餐分类,状态默认为 停用

    • 菜品表
    • 套餐表
    • 套餐菜品关系表

    ② 多表查询

    1. 概述

    • 多表查询 : 指从多张表中查询数据
    select * from tb_emp,tb_dept;
    
    • 笛卡尔积 : 两个集合的所有组合情况 (在多表查询时,需要消除无效的笛卡尔积)
    select * from tb_emp,tb_dept where tb_emp.dept_id = tb_dept.id;
    
    • 连接查询 : 内连接、外连接、子查询

    2. 内连接

    -- A. 查询员工的姓名 , 及所属的部门名称 (隐式内连接实现)
    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;          -- 起别名
    -- B. 查询员工的姓名 , 及所属的部门名称 (显式内连接实现)
    select tb_emp.name,tb_dept.name from tb_emp  join tb_dept on tb_emp.dept_id = tb_dept.id;
    

    3. 外连接

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

    4. 子查询

    -- A. 标量子查询: 子查询返回的结果为单个值. 常用的操作符: =, <>, >, >=, <, <=    
    -- 查询 "教研部" 的所有员工信息
    select * from tb_emp where dept_id = (select id from tb_dept where name = '教研部');
    -- 查询在 "方东白" 入职之后的员工信息
    select * from tb_emp where entrydate > (select entrydate from tb_emp where name = '方东白');
    
    -- B. 列子查询: 子查询返回的结果为一列. 常用的操作符: in, not in
    -- 查询 "教研部" 和 "咨询部" 的所有员工信息
    select * from tb_emp where dept_id in (select id from tb_dept where name = '教研部' or name = '咨询部');
    
    -- C. 行子查询: 子查询返回的结果为一行. 常用的操作符: =, <>, in, not in
    -- 查询与 "韦一笑" 的入职日期 及 职位都相同的员工信息 ;
    select * from tb_emp where (entrydate,job)=(select entrydate,job from tb_emp where name = '韦一笑');
    
    -- D. 表子查询: 子查询返回的结果为多行多列. 
    -- 查询入职日期是 "2006-01-01" 之后的员工信息 , 及其部门名称
    select e.*, d.name from (select * from tb_emp where entrydate > '2006-01-01') e, tb_dept d where e.dept_id = d.id;
    

    5. 案例

    -- 1. 查询价格低于10元的菜品的名称、价格及其菜品的分类名称
    -- 表: dish , category
    select d.name, d.price, c.name 
    from dish d, category c
    where d.category_id = c.id and d.price < 10;
    
    -- 2. 查询所有价格在10元到50元之间且状态为'起售'的菜品, 展示出菜品的名称、价格及其菜品的分类名称
    -- 表: dish , category
    select d.name, d.price, c.name 
    from dish d left join category c on d.category_id = c.id
    where d.price between 10 and 50 and d.status = 1;
    
    -- 3. 查询每个分类下最贵的菜品, 展示出分类的名称、最贵的菜品的价格
    -- 表: dish , category
    select c.name, max(d.price)
    from dish d, category c
    where d.category_id = c.id group by c.name;
    
    -- 4. 查询各个分类下菜品状态为'起售', 并且该分类下菜品总数量大于等于3的分类名称 .
    -- 表: dish, category
    select c.name, count(*)
    from dish d, category c
    where d.category_id = c.id and d.status = 1
    group by c.name having count(*) >= 3;
    
    -- 5. 查询出 "商务套餐A" 中包含了哪些菜品 (展示出套餐名称、价格, 包含的菜品名称、价格、份数).
    -- 表: setmeal, setmeal_dish, dish
    select s.name, s.price, d.name, d.price, sd.copies
    from setmeal s, setmeal_dish sd, dish d
    where s.id = sd.setmeal_id and sd.dish_id = d.id and s.name = '商务套餐A';
    
    -- 6. 查询出低于菜品平均价格的菜品信息 (展示出菜品名称、菜品价格).
    -- 表: dish
    select * from dish where price < (select avg(price) from dish);
    

    5.1.6 事务 & 索引

    ① 事务

    1. 事务介绍

      事务是一组操作的集合,它是一个不可分割的工作单位

      事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败

    2. 事务操作

      # 场景:学工部 整个部门解散了,该部门及部门下的员工都需要删除了
      # 问题:如果删除部门成功了,而删除该部门的员工时失败了,就造成了数据的不一致
      
      -- 开启事务
      start transaction;
      -- 删除部门
      delete from tb_dept where id = 2;
      -- 删除部门下的员工
      delete from tb_emp where dept_id == 2;
      -- 提交事务
      commit;
      -- 回滚事务
      rollback;
      
    3. 事务四大特性(面试题)

      • 原子性(Atomicity):事务是不可分割的最小单元,要么全部成功,要么全部失败

      • 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态

      • 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行

      • 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的

    ② 索引

    1. 介绍

      索引(index)是帮助数据库 高效获取数据 的 数据结构

    select * from tb_sku where sn = '100000003145008';   -- 14s
    select count(*) from tb_sku;                     -- 6000000
    create index idx_sku_sn on tb_sku(sn);               
    select * from tb_sku where sn = '100000003145008';   -- 6ms
    

    优点

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

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

    缺点(忽略不计)

    • 索引会占用存储空间

    • 索引大大提高了查询效率,同时却也降低了insert、update、delete的效率

    • 结构

      MySQL 数据库支持的索引结构有很多,如果没有特别指明,都是指默认的 B+Tree 结构组织的索引

      无论是二叉搜索树,还是红黑树,在大数据量情况下,层级深,检索速度慢

      B+Tree(多路平衡搜索树):

      • 每一个节点,可以存储多个key(有n个key,就有n个指针)
      • 所有的数据都存储在叶子节点,非叶子节点仅用于索引数据
      • 叶子节点形成了一颗双向链表,便于数据的排序及区间范围查询
    1. 语法

      -- 创建 : 为 tb_emp 表的 name 字段建立一个索引 
      create index idx_emp_name on tb_emp(name);
      -- 查询 : 查询 tb_emp 表的索引信息 
      show index from tb_emp;
      -- 删除: 删除 tb_emp 表中 name 字段的索引 
      drop index idx_emp_name on tb_emp;
      
      • 主键字段,在建表时,会自动创建主键索引
      • 添加唯一约束时,数据库实际上会添加唯一索引

    5.2 Mybatis

    5.2.1 快速入门

    ① 入门程序

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

    需求:使用 Mybatis 查询所有用户数据

    1. 准备工作(创建 springboot 工程、数据库表 user、实体类 User)
    // src\main\java\com.itheima\pojo\User.java
    
    public class User {
        private Integer id;
        private String name;
        private Short age;
        private Short gender;
        private String phone;
        ...(javabean)
    }
    
    1. 引入 Mybatis 的相关依赖,配置 Mybatis(数据库连接信息)

      <!--mybatis 的起步依赖-->
      <dependency>
          <groupId>org.mybatis.spring.boot</groupId>
          <artifactId>mybatis-spring-boot-starter</artifactId>
          <version>2.2.2</version>
      </dependency>
      <!-- mysql 驱动包-->
      <dependency>
          <groupId>com.mysql</groupId>
          <artifactId>mysql-connector-j</artifactId>
          <scope>runtime</scope>
      </dependency>
      
      # 驱动类名称
      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=123456
      
    2. 编写SQL语句(注解 / XML)

      // src\main\java\com.itheima\mapper\UserMapper.java
      
      @Mapper
      public interface UserMapper {
          @Select("select * from user")
          public List<User> list();
      }
      
      // src\test\java\com.itheima\Test.java
      
      @SpringBootTest 
      class SpringbootMybatisQuickstartApplicationTests {
          @Autowired
          private UserMapper userMapper;
          @Test
          public void testListUser(){
              List<User> userList = userMapper.list();
              userList.stream().forEach(user -> {
                  System.out.println(user);
              });
          }
      }
      

    ② 配置 SQL 提示

    1. 默认在 mybatis 中编写 SQL 语句是不识别的。可以做如下配置:
    1. 数据库表识别不了

      • 产生原因:Idea 和数据库没有建立连接,不识别表信息

      • 解决方式:在 Idea 中配置 MySQL 数据库连接

    ③ JDBC 简介

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

    5.2.2 连接池 & lombok

    ① 连接池

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

    • 优势:资源复用、提升系统响应速度

    • 接口:DataSource

    • 产品:Druid、Hikari

      Druid(德鲁伊)连接池是阿里巴巴开源的数据库连接池项目,是 Java 语言最好的数据库连接池之一

      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid-spring-boot-starter</artifactId>
          <version>1.2.8</version>
      </dependency>
      

    ② lombok

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

    注解 作用
    @Getter / @Setter 为所有的属性提供 get / set 方法
    @ToString 会给类自动生成易阅读的 toString 方法
    @EqualsAndHashCode 根据类所拥有的非静态字段自动重写 equals 方法和 hashCode 方法
    @Data 提供了更综合的生成代码功能(@Getter + @Setter + @ToString + @EqualsAndHashCode)
    @NoArgsConstructor 为实体类生成无参的构造器方法
    @AllArgsConstructor 为实体类生成除了static修饰的字段之外带有各参数的构造器方法
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
        private Integer id;
        private String name;
        private Short age;
        private Short gender;
        private String phone;
    }
    

    5.2.3 基础操作

    ① 需求

    根据资料中提供的页面原型及需求,完成员工管理的需求开发

    ② 准备

    • 准备数据库表 emp
    • 创建一个新的 springboot 工程,选择引入对应的起步依赖(mybatis、mysql 驱动、lombok)
    • application.properties 中引入数据库连接信息
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
    spring.datasource.username=root
    spring.datasource.password=123456
    
    • 创建对应的实体类 Emp(实体类属性采用驼峰命名)
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Emp {
        private Integer id;         // ID
        private String username;    // 用户名
        private String password;    // 密码
        private String name;        // 姓名
        private Short gender;       // 性别, 1 男, 2 女
        private String image;       // 图像 url
        private Short job;          // 职位, 1 班主任, 2 讲师, 3 学工主管, 4 教研主管, 5 咨询师
        private LocalDate entrydate;        // 入职日期
        private Integer deptId;             // 部门 ID
        private LocalDateTime createTime;   // 创建时间
        private LocalDateTime updateTime;   // 修改时间
    }
    
    • 准备 Mapper 接口 EmpMapper
    @Mapper
    public interface EmpMapper {
    }
    

    ③ 删除

    @Mapper
    public interface EmpMapper {
        @Delete("delete from emp where id = #{id}")
        public void delete(Integer id);
    }
    
    @SpringBootTest
    class Test {
        @Autowired
        private EmpMapper empMapper;
        @Test
        public void testDelete(){
            empMapper.delete(16);
        }
    }
    

    可以在 application.properties 中,打开 mybatis 的日志,并指定输出到控制台

    mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    

    预编译 SQL 优点

    性能更高:

    更安全(防止 SQL 注入):

    select count(*) from emp where user name = '?' and password = '?';                  -- 正常登录
    select count(*) from emp where user name = 'abcd' and password = '0' or '1' = '1';  -- 注入成功
    select count(*) from emp where user name = ? and password = ?;                      -- 预编译 SQL
    select count(*) from emp where user name = abcd and password = 0' or '1' = '1;      -- 注入失败
    

    ④ 新增

    @Mapper
    public interface EmpMapper {
        // 自动将生成的主键值,赋值给 emp 对象的 id 属性
        @Options(useGeneratedKeys = true, keyProperty = "id")
        @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) values (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})")
        public void insert(Emp emp);
    }
    
    @SpringBootTest
    class Test {
        @Autowired
        private EmpMapper empMapper;
        @Test
        public void testInsert(){
            Emp emp = new Emp();
            emp.setUsername("Tom3");
            ...
            empMapper.insert(emp);
            System.out.println(emp.getId());
        }
    }
    

    ⑤ 更新

    @Mapper
    public interface EmpMapper {
        @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);
    }
    
    @SpringBootTest
    class Test {
        @Autowired
        private EmpMapper empMapper;
        @Test
        public void testInsert(){
            Emp emp = new Emp();
            emp.setUsername("Tom3");
            ...
            empMapper.insert(emp);
            System.out.println(emp.getId());
        }
    }
    

    ⑥ 查询

    @Mapper
    public interface EmpMapper {
        @Select("select * from emp where id = #{id}")
        public Emp getById(Integer id);
    }
    
    @SpringBootTest
    class Test {
        @Autowired
        private EmpMapper empMapper;
        @Test
        public void testGetById(){
            Emp emp = empMapper.getById(20);
            System.out.println(emp);
        }
    }
    

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

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

    mybatis.configuration.map-underscore-to-camel-case=true
    

    5.2.4 XML 配置文件

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

    // java\com\itheima\mapper\EmpMapper.java
    @Mapper
    public interface EmpMapper {
        public List<Emp> list(String name, Short gender, LocalDate begin , LocalDate end);
    }
    
    <!-- 1. XML 映射文件名称与 Mapper 接口一致,并且放置在相同包下 -->
    <!-- resources\com\itheima\mapper\EmpMapper.xml -->
    
    <!-- 2. XML 映射文件的 namespace 属性为 Mapper 接口全限定名一致 -->
    <mapper namespace="com.itheima.mapper.EmpMapper">
        <!-- 3. XML 映射文件中 sql 语句的 id 与 Mapper 接口中的方法名一致, 且返回类型一致 -->
        <select id="list" resultType="com.itheima.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>
    

    5.2.5 动态 SQL

    ① 介绍

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

    ② 动态 SQL - if

    @Mapper
    public interface EmpMapper {
        public List<Emp> list(String name, Short gender, LocalDate begin , LocalDate end);
    }
    
    <mapper namespace="com.itheima.mapper.EmpMapper">
        <select id="list" resultType="com.itheima.pojo.Emp">
            select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time, from emp
            <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>
    
    @Mapper
    public interface EmpMapper {
        public void update(Emp emp);
    }
    
    <mapper namespace="com.itheima.mapper.EmpMapper">
        <update id="update">
            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="deptId != null">dept_id = #{deptId},</if>
                <if test="updateTime != null">update_time = #{updateTime}</if>
            </set>
            where id = #{id}
        </update>
    </mapper>
    

    ③ 动态 SQL - foreach

    @Mapper
    public interface EmpMapper {
        public void deleteByIds(List<Integer> ids);
    }
    
    <mapper namespace="com.itheima.mapper.EmpMapper">
        <delete id="deleteByIds">
            delete  from emp where id in
            <foreach collection="ids" item="id" separator="," open="(" close=")">
                #{id}
            </foreach>
        </delete>
    </mapper>
    

    ④ 动态 SQL - sql & include

    <mapper namespace="com.itheima.mapper.EmpMapper">
        <sql id="commonSelect">
            select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp
        </sql>
    
        <select id="list" resultType="com.itheima.pojo.Emp">
            <include refid="commonSelect"/> <where> ... </where> order by update_time desc
        </select>
    </mapper>
    

    5.3 课程总结

    参考链接:https://www.bilibili.com/video/BV1m84y1w7Tb?p=1&vd_source=ed621eaa6bcf9bf6acb7d0527c30489a

    相关文章

      网友评论

        本文标题:Java - Web 开发笔记 Ⅱ

        本文链接:https://www.haomeiwen.com/subject/ozqofjtx.html