作者:Teo

SpringBoot框架





Java Web -- SpringBoot框架        - 2023.9.16 - 




一. 创建 spring 项目

    File --> new project --> spring Initializr

        Location: D:\teo\springboot-web-quickstart
        Language: java
        Type: Maven
        Group: com.ssyy99
        Artifact: boot-web-quickstart      // 写完这个 上面的 name 属性 会跟着变
        Packaging: Jar

        Version: 2.7.15
        Dependencies:  web --> Spring Web --> Create

    # vim pom.xml                 // 存放 项目的 信息
        <parent>                  // 所有工程的 父工程
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.7.15</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>


    src/main/java/com/ssyy99/SpringbootWebQuickstartApplication.java   // 启动类


二. 创建 springboot 入门程序

    @RestController              // 注解  定义 此类为 请求处理类
    @RequestMapping("/hello")    // 注解  定义 此方法为 请求处理方法 并且 浏览器访问/hello 调用此方法

    注: No usages(没有用法)通常指与指定的变量、方法或类相关联的代码中未找到对其使用的任何内容。
          它意味着该变量、方法或类未被其他部分调用或引用。

    # vim com/ssyy99/controller/HelloController.java
        @RestController
        public class HelloController {
            @RequestMapping("/hello")
                public String hello(){
                    System.out.println("hello world");
                    return "hello teo";
            }
        }
    http://localhost:8080/hello


三. 参数的接收:❶ ❷ ❸ ❹ ❺ ❻ ❼ ❽ ❾ ❿

  ❶. 简单参数的接收       // 简单参数就是 传递的比较少  只有 1个 或 2个
    postman请求: http://127.0.0.1:8080/hello?name=Tom&age=10 // 请求时携带的两个参数 name =Tom  age=10
    get    http://127.0.0.1:8080/simpleParam?name=Tom&age=22
    post  http://127.0.0.1:8080/simpleParam              // 传递参数在body 里面设置 name 和age

    第一种方法    // 使用 HttpServletRequest    类  接收  传递过来的参数 name 和 age

        @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 方式 接收 请求  只要 传过来的名和接收的保持一致即  name 和 age  一致 

        @RestController
        public class RequestController {
            @RequestMapping ("/simpleParam")
            public String simpleParam(String name,Integer age){
                System.out.println(name + ":" + age);
                return "OKey";
            }
        }

        // 发送名和 接收的名字 不一致 需要使用 @RequestParam注解
        @RestController
        public class RequestController {
            @RequestMapping ("/simpleParam")
            public String simpleParam(@RequestParam(value = "name", required = false) String username,@RequestParam(name = "age") Integer age){
                System.out.println(username + ":" + age);
                return "OKey";
            }
        }

    注:
        @RequestParam(name = "name", required = false)
        //  将请求参数绑定到 控制器方法的 参数上。它有以下几个属性:
            @RequestParam 属性:
                name 或 value:指定请求参数的名称,如果省略,则默认使用方法参数的名称。 "name" 是 客户端 发过来的实际参数 名字
                required: 指定请求参数是否必须,如果为 true,则请求中必须包含该参数,否则会报错。默认为 true。
                defaultValue:指定请求参数的默认值,如果设置了该值,则 required 会自动设为 false。


  ❷. 简单实体参数接收           // 用于参数较多 10个  20个的时候  把所有参数封装到一个标准类当中 在使用该类的对象调用即可

    首先创建一个 标准类 pojo/User  里面的属性对应的 客户端发来的 所有 参数  get/set/tostring方法   不会问机器人
    @RestController
    public class RequestController {
        @RequestMapping("/simplePojo")
        public String simplePojo(User user){
            System.out.println(user);
            return "okey!!111";
        }
    }

  ❸. 复杂实体参数接收         // 用于复杂的参数 address.province    address.city

    首先创建一个标准类 pojo/Address 里面的属性 有province 及 city
    利用上面的 pojo/User 类 在该类中添加 private Address address; 属性 并 重新添加  get/set/tostring



  ❹. 数组 和 集合参数 接收

    // 数组 请求的参数名与形参中数组变量名想通 可以直接使用数组封装
    @RequestMapping("/arrayParam")
    public String arrayParam(String[] hobby){
        System.out.println(Arrays.toString(hobby));
        return "okey!!!!3333";
    }

    // 集合 请求的参数名与形参中数组变量名想通 通过 @RequestParam绑定参数关系
    @RequestMapping("/listParam")
    public String listParam(@RequestParam List<String> hobby){
        System.out.println(hobby);
        return "okey!!!!4444";
    }


  ❺. 时间参数 接收

    @DataTimeFormat 注解用于将一个字符串转换为一个日期或时间对象,或者将一个日期或时间对象格式化为一个字符串。它有以下属性:
    iso:使用 ISO 标准的日期时间格式,例如 DateTimeFormat.ISO.DATE 表示 yyyy-MM-dd 的格式。
    pattern:使用自定义的日期时间格式,例如 pattern = “yyyy/MM/dd HH:mm:ss” 表示 2023/09/03 08:31:08 的格式。
    style:使用预定义的日期时间样式,例如 style = “MM” 表示中等长度的日期和时间。
    fallbackPatterns:当主要的 iso、pattern 或 style 属性无法解析时,使用备用的自定义格式。

    http://127.0.0.1:8080/dateParam?updateTime=2023-09-03 08:40:06   // 注意时间格式

    @RequestMapping("/dateParam")
    public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime){
        System.out.println(updateTime);
        return "ookk!!!!555";
    }

  ❻. json参数接收                        // 只能是post
    http://127.0.0.1:8080/jsonParam     // postman 中 设置
        body --> raw --> JSON
        {
            "name":"Janice",
            "age":10,
            "address":{
                "province":"shanxi",
                "city":"xian"
            }
        }

    创建标准类 User 此类中要与json中的key 保持一致  和 Address 要有  province 及 city  及get set tostring方法

    public class User {
        private String name;
        private Integer age;
        private Address address;
    }
    public class Address {
        private String province;
        private String city;
    }

    @RequestMapping("/jsonParam")
    public String jsonParam(@RequestBody User user){
        System.out.println(user);
        return "okey!!! 6666";
    }

    注:
        @RequestBody 是一个 Spring 注解,它用于将 HttpRequest 的请求体映射到一个 Java 对象。
        它通常与 POST 方法一起使用,接收来自前端的 JSON 数据


  ❼. 路径参数 
    http://127.0.0.1:8080/path/1                 // 1个
    http://127.0.0.1:8080/path/1/janice          // 多个

    @RequestMapping("/path/{id}")
    public String pathParam(@PathVariable Integer id){
        System.out.println(id);
        return "oooo!!!!7777";
    }
    @RequestMapping("/path/{id}/{name}")
    public String pathParam(@PathVariable Integer id,@PathVariable String name){
        System.out.println(id);
        System.out.println(name);
        return "oooo!!!!8888";
    }

    注:
        @PathVariable  写在方法参数上, 是一个 Spring 注解,它用于将 URI 路径中的变量绑定到控制器方法的参数上。
        它适合用于 RESTful web 服务,其中 URL 包含一些值


        @RestController  写在类上,  由 @Controller 和 @ResponseBody 注解的组合  定义 此类为 请求处理类
            @ResponseBody   作用是将方法返回值直接响应,如果返回值类型是 实体对象、集合 将会转换为JSON格式响应


        @RequestBody 写在方法参数上,是一个 Spring 注解,它用于将 HttpRequest 的请求体映射到一个 Java 对象。
        它通常与 POST 方法一起使用,接收来自前端的 JSON 数据



        @RequestParam
            @RequestParam(name = "name", required = false)
            写在方法参数上 将请求参数绑定到 控制器方法的 参数上。它有以下几个属性:
            @RequestParam 属性:
            name 或 value:指定请求参数的名称,如果省略,则默认使用方法参数的名称。 "name" 是 客户端 发过来的实际参数 名字
            required: 指定请求参数是否必须,如果为 true,则请求中必须包含该参数,否则会报错。默认为 true。
            defaultValue:指定请求参数的默认值,如果设置了该值,则 required 会自动设为 false。

        @RequestMapping("/hello")    // 注解  卸载方法前面 定义 此方法为 请求处理方法 并且 浏览器访问/hello 调用此方法


四. 参数的返回:          // 定义一个 参数返回的结果类 Result        com/ssyy99/pojo/Result.java
    统一响应结果: 返回Result.success 方法
    http://127.0.0.1:8080/hello
    http://127.0.0.1:8080/getAddr
    http://127.0.0.1:8080/listAddr

    @RestController
    public class HelloController {
        @RequestMapping("/hello")
        public Result hello(){
            System.out.println("hello world");
            return Result.success("hello teo....");
    }

    @RequestMapping("/getAddr")
    public Result getAddr(){
        Address addr =new Address();
        addr.setProvince("jilin");
        addr.setCity("dashitou");
        return Result.success(addr);
    }

    @RequestMapping("/listAddr")
    public Result listAddr(){
        List<Address> list = new ArrayList<>();
        Address addr1 = new Address();
        addr1.setProvince("吉林");
        addr1.setCity("大石头");

        Address addr2 = new Address();
        addr2.setProvince("海南");
        addr2.setCity("东方");
        list.add(addr1);
        list.add(addr2);
        return Result.success(list);
    }


五. 基于 xml 文件 返回数据

    1. # vim pom.xml
        <dependencies>
            <dependency>
                <groupId>org.dom4j</groupId>
                <artifactId>dom4j</artifactId>
                <version>2.1.3</version>
            </dependency>
        </dependencies>
    2. com/ssyy99/uitls/XmlParserUtils.java       // 导入工具类 
        XmlParserUtils.java
        parse(String file, Class<T> targetClass) // parse 在使用的时候传递两个参数1. 指定解析的文件2. 解析完之后封装到哪个类
    3. com/ssyy99/pojo/Emp.java     // 导入员工的实体类
    4. resources/emp.xml            // 要解析的 xml 文件
    5. resources/static/            // 导入前端页面  这里没有给名字 资料有

    @RestController
    public class EmpController {
        @RequestMapping("/listEmp")
        public Result list(){
            // 1. 加载并解析emp.xml  返回一个文件路径 用 String file 接收
            String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
            System.out.println(file);
            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("就业指导");
                }

            });
            return Result.success(empList);
        }
    }

    类加载器 lassLoader() 类加载器,它负责加载 Java 类的字节码,并将其转换为一个 Java 类对象。
    this.getClass().getClassLoader()   获得类加载器


    this.getClass().getClassLoader().getResource("emp.xml").getFile()     是一个Java方法,用于获取一个特定资源的URL。
        这个方法会首先获取当前类的类加载器(this.getClass().getClassLoader()),然后调用这个类加载器的 getResource 方法。
        getResource 方法会查找并返回一个表示该资源的URL对象。这里的“资源”指的是存储在类路径(classpath)上的文件或目录,
        比如Java类文件、属性文件、文本文件等。
    getFile()  会获取表示请求资源的URL对象。该方法会调用该URL对象的 getFile()方法,该方法返回表示URL对象的文件路径的String


    XmlParserUtils.parse()  工具类里 是一个用于解析 XML 文件的方法,它使用了 XML 解析器来解析 XML 文件


六. 三层架构

    第一层: controller  调用 service     直接调用 IOC容器去  @Autowired
    第二层: service     调用 dao         直接调用 IOC容器去  @Autowired
    第三层: dao         取出数据

    #vim com/ssyy99/service/EmpService               // 定义一个service 接口

        public interface EmpService {
            public List<Emp> listEmp();
        }

    #vim com/ssyy99/service/impl/EmpServiceA.java          // 实现上面接口
        public class EmpServiceA implements EmpService {
            private EmpDao empDao = new EmpDaoA();
            @Override
            public List<Emp> listEmp() {
                List<Emp> empList = empDao.listEmp();
                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("就业指导");
                    }
                });
                return empList;
            }
        }

    #vim com/ssyy99/dao/EmpDao.java                // 接口
        public interface EmpDao {
            public List<Emp> listEmp();
        }

    #vim com/ssyy99/dao/impl/EmpDaoA.java
        public class EmpDaoA implements EmpDao {

            @Override
            public List<Emp> listEmp() {
                // 1. 加载并解析emp.xml  返回一个文件路径 用 String file 接收
                String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
                System.out.println(file);
                List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
                return empList;
            }
        }

    #vim com/ssyy99/controller/EmpController.java

        @RestController
        public class EmpController {
            private EmpService empService =new EmpServiceA();
            @RequestMapping("/listEmp")
            public Result list(){
                List<Emp> empList = empService.listEmp();
                return Result.success(empList);
            }
        }


七. 低耦合  高内聚

    控制反转:  
        @Component 简称IOC 对象的创建控制权由程序自身转移到外部(容器),这中思想称为控制反转
        IOC容器: 拥有创建对象的权利, 一个类把自己创建对象权利交给IOC. 使用 @Component
        @Component /kəmˈpəʊnənt/    //  在类上使用,  将当前类交给IOC容器管理  称为IOC容器中的Bean
        此注解包含三个 衍生注解   一般在 三层使用的时候 用下面的小弟. 在三层之外的类 还使用IOC容器 使用 主注解 
        @Component("daoA")  指定 Bean的名字 默认的名字为类名的首字母小写 一般不用指定。 下面三个小弟也同样可指定
        在 Console 控制台 旁边的 Actuator --> Beans --> 查看左右的 Bean 白色的是自定义的 点开有名字
        @Controller     标注在控制器类上  就是标注在 controller 上  @RestController 还包含 Controller 所有也不用标注
        @Service        标注在业务类上    就是标注在 service 上     用的最多
        @Repository     标注在数据访问类上 就是标注在 dao上 由于与mybatis整合用的少 

        注: @SpringBootApplication 在启动类上的标注

        @SpringBootApplication 是一个用于标记和启动 Spring Boot 应用程序的主类的注解,包含了以下三个注解
        @SpringBootConfiguration:表示这是一个 Spring Boot 的配置类
        @EnableAutoConfiguration:表示启用 Spring Boot 的自动配置机制
        @ComponentScan:表示对当前包及其子包进行组件扫描,自动发现和注册 bean

        @ComponentScan 在程序启动时 自动扫描 主类所在包下的所有的类的bean 如果 声明 bean的类在主类包外的话会报错
        可以使用 在主类上 从新定义 扫描的 范围: @ComponentScan({"dao","com.ssyy99"})

    依赖注入:  
        @Autowired 简称DI  容器为应用程序提供运行时,所依赖的资源,称为依赖注入
          一个接口由多个 类去实现, 另一个类中 需要此接口的 对象 通过 依赖注入
        1. @Autowired        // 在类里面使用, 只有一个实现接口时候使用

        2. @Primary , @Component  // 在类上使用, 声明此类是 实现接口的主类, 有多个实现的时候 使用这个实现类

        3. @Qualifier("empServiceA") , @Autowired  // 在类里面使用, 指明 使用哪个类对象

        4. @Resource(name = "empServiceA")      // 在类里面使用,单独使用 @Resource 使用哪个类对象

    Bean对象: IOC容器中管理的对象叫Bean

        1. 将service层及dao层的实现类交给IOC管理
            @Component 
        2. 为Controller及service注入运行时 依赖的对象
            @Autowired  在 类里面写 调用 IOC容器 得到 对象

                手动获取 某个类的 Bean对象
        
        @SpringBootTest
        class SpringbootStudentServerApplicationTests {

            @Autowired ApplicationContext applicationContext;        // IOC容器对象

            @Test
            void contextLoads() {
                // 根据bean 的名获取bean  默认情况下 bean的名字 是 类名的首字母小写
                // 使用getBean 得到的 Bean是 object类型的 需要强制转换
                DeptController bean1 = (DeptController) applicationContext.getBean("deptController");
                System.out.println(bean1);

                // 根据bean的类型获取
                DeptController bean2 = applicationContext.getBean(DeptController.class);
                System.out.println(bean2);

                // 根据bean的名称 和 类型 获取
                DeptController bean3 = applicationContext.getBean("deptController", DeptController.class);
                System.out.println(bean3);
            }
        }
                
                
    bean作用域    spring 支持五种作用域, 后三种在web环境才生效
        @Scope(prototype)   类的作用域 在实体类上交给IOC托管的类上加此注解
            singleton  容器内同 名称 的 bean 只有一个实例 (单列) 默认   只有一个对象
            prototype  每次使用该bean 时会创建新的实例(非单例)   都会创建新的对象
            request    每个请求范围内会创建新的实例(web环境中,了解)
            session    每个会话范围内会创建新的实例(web环境中,了解)
            application 每个应用范围内会创建新的实例(web环境中,了解)
            
            @Lazy   在类上使用, 延迟初始化bean 延迟到第一次使用的时候初始化   没有此注解 在IOC容器启动时候 初始化bean 
                    
    第三方bean对象 管理  使用第三方插件的时候(不是自己创建的类) 要使用其中的对象 不
    能直接使用@Component等注解 交给IOC管理 要单独定义之后才能使用
    
        实例: 管理 dom4j 插件的 对象
            #vim pom.xml
                <dependency>
                    <groupId>org.dom4j</groupId>
                    <artifactId>dom4j</artifactId>
                    <version>2.1.3</version>
                </dependency>
            
            #vim com/ssyy99/config/CommonConfig.java          // 创建第三方bean对象 管理类
                package com.ssyy99.config;

                import org.dom4j.io.SAXReader;
                import org.springframework.beans.factory.annotation.Configurable;
                import org.springframework.context.annotation.Bean;

                @Configurable //配置类  注解允许你将 Spring 管理的对象保存到数据库,并且可以从数据库加载对象
                public class CommonConfig {
                    @Bean        // 将当前方法的返回值对象交给IOC容器管理,成为IOC容器的bean对象
                                 // 通过@Bean注解的name/value属性指定bean名称,如果未指定,默认是方法名
                    public SAXReader saxReader(){        // 如果在声明第三方bean时候需要依赖其他bean对象 可以直接在 
                        return new SAXReader();          // 方法参数上直接声明这个类型的参数
                    }                    // public SAXReader saxReader(DeptService deptservice)  SAXReader(deptservice)
                }
                
            至此可以直接在要使用的类上 @Autowired 注入对象了 


八. Servle

    连接 Servlet 实现方式
    第一种: 实现Servlet接口 实现所有的抽象方法.
    第二种: 继承GenericServlet 抽象类 ,必须重写service方法,其他方法可选择重写.使开发Servlet变简单.但是这种方式和HTTP协议无关.
    第三种: 继承HttpServlet抽象类,需要重写doGet和doPost方法.该方法表示请求和响应都需要和HTTP协议相关.

    第一种实现方法: 
        # vim /WEB-INF/web.xml       // 在此文件中声明 servlet 才能访问servlet
            <web-app>
                <servlet>
                    <servlet-name>studentServlet</servlet-name>
                    <servlet-class>com.teo.servlet.StudentServlet</servlet-class>
                </servlet>
                <servlet-mapping>
                    <servlet-name>studentServlet</servlet-name>
                    <url-pattern>/studentServlet</url-pattern>
                </servlet-mapping>
            </web-app>

            注: 在</web-app>标签上面添加
                <!--    servlet声明-->
                    <servlet>
                <!--        声明 servlet  填写 类的名字-->
                        <servlet-name>studentServlet</servlet-name>
                <!--        通过上面的name 找到此路径的servlet类 ... 包名 + 类名-->
                        <servlet-class>com.teo.servlet.StudentServlet</servlet-class>
                    </servlet>
                <!--servlet映射-->
                    <servlet-mapping>
                <!--        此名字和 声明中的 name 保持一致-->
                        <servlet-name>studentServlet</servlet-name>
                <!--        浏览器地址栏中访问servlet的 路径-->
                        <url-pattern>/studentServlet</url-pattern>
                    </servlet-mapping>
                </web-app>

        # vim /src/com/teo/servlet/StudentServlet
            package com.teo.servlet;

            import javax.servlet.*;
            import java.io.IOException;

            public class StudentServlet implements Servlet {
                @Override
                public void init(ServletConfig servletConfig) throws ServletException {

                }

                @Override
                public ServletConfig getServletConfig() {
                    return null;
                }

                @Override
                public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
                    System.out.println("这是我的第一个Servlet入门案例.... 学习使我快乐 ... ");
                }

                @Override
                public String getServletInfo() {
                    return null;
                }

                @Override
                public void destroy() {

                }
            }

    第二种实现方法:                   //   /WEB-INF/web.xml 同上
        # vim /src/com/teo/servlet/StudentServlet   
            package com.teo.servlet;

            import javax.servlet.ServletException;
            import javax.servlet.http.HttpServlet;
            import javax.servlet.http.HttpServletRequest;
            import javax.servlet.http.HttpServletResponse;
            import java.io.IOException;

            public class StudentServlet2 extends HttpServlet {
                @Override
                protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                    System.out.println("学习使我快乐... 我是doget方法");
                }

                @Override
                protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                    doGet(req,resp);
                }
            }


九. Mybatis     操作数据库

  ❶. Mybatis 入门程序

    springboot 创建 Mybatis 项目
        Name: springboot-mybatis-quicksstart
        Location: D:\teo\springboot-mybatis-quicksstart
        Language: java
        Type: Maven

        Group: com.ssyy99
        Artifact: springboot-mybatis-quicksstart
        Package name: com.ssyy99

        Spring Boot 2.7.15
        SQL     MyBatis Framework     勾选
                MySQL Driver          勾选

    # vim resources/application.properties
        #spring.datasource.driver-class-name=com.mysql.jdbc.Driver
        spring.datasource.url=jdbc:mysql://192.168.10.11/mybatis
        spring.datasource.username=root
        spring.datasource.password=123456
        #配置mybatis的日志, 指定输出到控制台
        mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
        #开启mybatis的驼峰命名自动映射开关 a_column ------> aCloumn
        mybatis.configuration.map-underscore-to-camel-case=true

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

        @Mapper
        public interface EmpMapper {
            @Delete("delete from emp where id = #{id}")
            public int delete(Integer id);
        }

    注:
        @Mapper // 用在接口上, 在运行时,框架会自动生成该接口的实现类对象(代理对象),并且将该对象交给IOC容器管理

        @Select("select * from tb_user")  在方法上, 是它用于标记一个方法为一个 SQL 查询语句。
            @Select 注解可以让 MyBatis 框架自动执行括号内的 SQL 语句,
            并将结果映射到返回值类型或参数类型中. 如果调用该方法 就执行该查询语句,返回的结果
            上面的sql语句不能被idea检测语法是否正确,可在sql语句中右键选择Show Context Actions
            --> Inject language or reference --> Mysql, 此时就可以sql语句可以被识别,但是不能识别表名会 标红
            可以在  右侧工具栏 --> Data Source --> MySQL 中设置数据库信息 即可
            jdbc:mysql://192.168.10.11/mybatis 注意这个 url
            如不想使用此功能 可 右键 Show Context Actions --> Uninjece language or reference
        #{id} 占位符 会把下面方法的参数 传递到此占位符 如果接口方法形参只有一个普通类型参数, #{...}里面属性可随便写 最好与形参一样


    #vim com/ssyy99/SpringbootMybatisQuicksstartApplicationTests.java
        @SpringBootTest
        class SpringbootMybatisQuicksstartApplicationTests {
            @Autowired
            private UserMapper userMapper;
            @Test
            public void testListUser(){
                List<User> userList = userMapper.list();
                userList.stream().forEach(user -> {
                    System.out.println(user);
                });
            }
        }
    注:
        @SpringBootTest  在类上,标识测试类, 将测试类作为应用程序的入口点,并创建一个完整的 Spring 上下文环境。
        @Test   在方法上, 它用于标识一个测试方法,使其可以被 JUnit 执行和运行。


  ❷. 数据库连接池  springboot默认为 Hikari追光者连接池  Druid德鲁伊连接池是阿里巴巴的。 如果要改变 在pom.xml 下添加如下代码即可   
    <dependency>
       <groupId>com.alibaba</groupId>
       <artifactId>druid-spring-boot-starter</artifactId>
       <version>1.2.8</version>
    </dependency>

  ❸. Mybatis 动态sql   对于查询条件不是固定死的 是动态的
    XML映射文件定义动态sql   
        XML文件的约束: https://mybatis.net.cn/getting-started.html  --> 探究已映射的 SQL 语句  --> 复制xml文件代码
        
        1. 同包同名 需要在resources 下创建一个包. 就是要使用sql的类所在的包的全名 并且 创建一个xml配置文件 名字与该类的文件名相同
            resources/com/ssyy99/mapper/EmpMapper.xml
            
        2. XML约束文件中 namespace属性 与接口(使用sql的类)的全类名匹配
            <mapper namespace="com/ssyy99/mapper/EmpMapper.java">
        
        3. sql语句的 id 要与方法名(使用sql的类的方法)保持一致
           sql语句中 resultType 返回类型保持一致(resultType 是单条记录所封装的类型,如果是实体类,直接拷贝实体类的全路径)
            <select id="page" resultType="com.ssyy99.pojo.Emp">
            
    Mybatis 的 XML文件的格式
        vim resources/com/ssyy99/mapper/EmpMapper.xml
            <?xml version="1.0" encoding="UTF-8" ?>
            <!DOCTYPE mapper
                    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
            <mapper namespace="com.ssyy99.mapper.EmpMapper">
                <select id="page" resultType="com.ssyy99.pojo.Emp">
                    select *
                     from emp
                     <where>
                         <if test="name != null and name != ''">
                             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
                     limit #{start},#{pageSize};
                </select>
            </mapper>

        注: <where>标签的作用  1. 自动根据里面的条件判断是否要生成 where 子句
                               2. 自动去掉第一个条件多余的and 或者是or 

            <delete id="delete">
                delete from emp where id in
                <foreach collection="ids" item="id" separator="," open="(" close=")">
                    #{id}
                </foreach>
            </delete>
        
            <foreac> 标签 用于遍历集合
    
            collection 属性是要遍历的集合 写遍历集合的名字即可
            item 属性是集合中每一个元素的名字
            separator 属性是元素之间的分隔符 {1,2,3} 这里是使用逗号分隔符
            open 和 close 属性分别是整个集合的开头和结尾。
            这里的例子会生成一个括号内由逗号分隔的 id 列表,例如 (1,2,3)。
    
  ❹. Mybatis 操作数据库流程

    ①. 查询
        请求路径:/emps                   @GetMapping("/emps")
        请求方式:GET
        
        接口描述:该接口用于员工列表数据的条件分页查询

        需要查询:总数据数 和 emp对象    
        
        接收: 键值对   使用 参数接收
        返回: jsion    返回一个 封装类
            @RequestParam(name = "name", required = false) // 1. 名字不一致时使用, 2. 需要默认值的使用 , 将请求参数绑定到 控制器方法的 参数上
            @DateTimeFormat(pattern = "yyyy-MM-dd")        // 设置日期格式

        @Slf4j                      // 日志记录
        @RestController
        
        Ⅰ. 创建一个 方法 返回值 是 Result  参数是 六个参数  2个有默认值  2个时间
           调用 server 中的 查询 方法   返回值是 封装类 (创建一个实体类 一个 int值 一个 list<emp> 集合)
           返回 这个封装类
           @GetMapping("/emps")
           @Autowired
           
           
        Ⅱ. 查询  总条数    调用   dao类   的结果存到  int 封装在对象里
           查询  所有数据  调用   dao类   的结果存到  list 集合  与 上面的一同封装
           封装  调用 构造方法
           返回  封装好的 实体类
        
        Ⅲ. 查询 直接查询总数   有条件查询 使用 动态sql语句
            
    ②. 删除
        请求路径/emps/{ids}
        
        路径参数    @PathVariable   有多个参数  使用 集合来接收到

        @DeleteMapping("/emps/{ids}")

        @PathVariable
        
        使用 动态sql语句  删除条数不一定是几条
        
        接收: @PathVariable 接收路径类型 并 储存到 List数组中
        返回:不用返回

    ③. 新增
        请求路径 @PostMapping("/emps")
        接收:  @RequestBody  接收jsion类型 并 封装到 实体类中 
        返回:  不用返回
        
        2. 设置两个默认值
            
        3. 插入数据  insert into


    ④. 文件 图片 视频 音频 上传 本地存储
        前端页面上传文件的三要素:
            表单项 type = "file"
            表单提交方式  post
            表单的enctype 属性 multipart/form-data
        服务端接收文件  会存到临时文件.请求响应完会自动删除. 要把文件拷贝出来
            使用 MultipartFle 类型来接收

        文件上传示例一: 使用原始文件名
            @PostMapping("/upload")
            public Result upload(MultipartFile image) throws Exception {
                String originalFilename = image.getOriginalFilename();            // 获取文件的 原始名
                image.transferTo(new File("D:\\image\\"+originalFilename));       // 此方法 保存文件
                return Result.success();
            }

        文件上传示例二: 使用uuid作为新的文件名
            @PostMapping("/upload")
            public Result upload(MultipartFile image) throws Exception {
                String originalFilename = image.getOriginalFilename();
                int index = originalFilename.lastIndexOf(".");     // 字符串 查找最后一个点 . 的位置
                String extname = originalFilename.substring(index);    // 字符截取  从点的 位置开始截 来获得文件扩展名
                String newFileName = UUID.randomUUID().toString() + extname;  // 随机获取一个 uuid 拼接上 一个 后缀 为新的名字
                image.transferTo(new File("D:\\image\\"+newFileName));
                return Result.success();
            }


        vim application.properties
            spring.servlet.multipart.max-file-size=110MB            // 设置上传单个上传文件大小 最大是110MB 默认为 1MB
            spring.servlet.multipart.max-request-size=310MB         // 设置上传总文件大小 最大是310MB  多个文件 可用 集合来接收


    ⑤. 阿里云 OSS 存储         // 没学 暂时用不上p148-p150   配置文件中 自动注入 阿里云相关的代码 也没学 p155
            SDK  工具包
            bucket 存储空间
            AccessKey 秘钥
            
  ❺. <pageHelper> 分页查询 插件 没有学 以后用的可以补充 P142


十. SpringBoot中 的配置文件      名字必须是 application  只是扩展名不同

    ❶. application.properties
        server.port=8080               // key=value
        server.address=127.0.0.1 
    ❷. application.yml
        server:
          port: 8080             // 有缩进  port 和 address 是平级的
          address: 127.0.0.1
    
    ❸. application.yaml
    
    yml配置文件
        1. 大小写敏感
        2. 数值前边必须有空格, 作为分隔符
        3. 使用缩进表示层级关系, 缩进时,不允许使用Tab键,是能用空格,(idea中会自动将Tab转换为空格)
        4. 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
        5. #表示注释,从这个字符一直到行尾,都会被解析器忽略
        6. 如果是框架用到的是有提示的 自定义的配置没有提示 如配置 阿里云OSS
        application.yml
            server:
              port: 8080
            #定义 对象/Map集合
            user:
              name: Tom
              age: 20
              address: beijing

            # 定义 数组/List集合/Set集合
            hobby:
              - java
              - c
              - game
              - sport
              
        application.yml           // 从application.properties配置文件改用  yml配置文件
            # 数据库连接
            spring:
              datasource:
                url: jdbc:mysql://192.168.10.11/hahadb
                username: root
                password: 123456
            # 文件上传配置
              servlet:
                multipart:
                  max-file-size: 110MB
                  max-request-size: 310MB
            # mybatis配置
            mybatis:
              configuration:
                log-impl: org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl
                map-underscore-to-camel-case: true

    注: 三种配置文件优先级最高的是 application.properties  >  application.yml  >  application.yaml


十一. JWT令牌  / 登录 / 登录效验  / 会话跟踪方案
    cookie 不允许跨域
        移动端app无法使用cookie
        不安全 用户可以自己禁用cookie
        cookie不能跨域
    session 
        服务器集群环境无法直接使用session
        cookie的缺点
    JWT令牌
        支持pc 移动
        解决集群环境下认证问题

    跨越 协议  ip/域名  端口    有任何一个不同  就是 跨越


  JWT令牌 生成
    https://jwt.io/                        JWT令牌官网 可以解析令牌
    https://c.runoob.com/front-end/693/    Base64 编码的 解码
    
    vim pom.xml        // 加入工具类的引用
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        
        <dependency>     // 没有会报错 程序在运行时找不到javax.xml.bind.DatatypeConverter类 
            <groupId>jakarta.xml.bind</groupId>      // JDK 11或更高版本 JDK版本过高,JAXB被移出了JDK的默认类路径
            <artifactId>jakarta.xml.bind-api</artifactId>  
            <version>2.3.3</version>  
        </dependency>

    vim com.ssyy99.utils                  // 生成JWT令牌 类  之后使用 调用此类即可
    
        package com.ssyy99.utils;

        import io.jsonwebtoken.Claims;
        import io.jsonwebtoken.Jwts;
        import io.jsonwebtoken.SignatureAlgorithm;
        import java.util.Date;
        import java.util.Map;

        public class JwtUtils {

            private static String signKey = "itheima";    // 签名的秘钥   秘钥可以随便写 这里是 itheima

            private static Long expire = 43200000L;       // 指定过期时间为 12小时

            /**
             * 生成JWT令牌
             * @param claims JWT第二部分负载 payload 中存储的内容
             * @return
             */
            public static String generateJwt(Map<String, Object> claims){
                String jwt = Jwts.builder()  // builder()方法 是生成 jwt 令牌
                        .addClaims(claims)   // 自定义内容 载荷 ,这里是 在参数上加了一个Map集合来存储自定义部分 如 id name
                        .signWith(SignatureAlgorithm.HS256, signKey)   // 指定jwt算法   .hs256是算法, 和秘钥 上面有定义
                        .setExpiration(new Date(System.currentTimeMillis() + expire))   // 设置有效期 需要一个date对象 
                        .compact();         // 拿到一个字符串返回值     // ↑这里表示当前时间毫秒值 + 3600 *1000 表示1小时
                return jwt;
       

            /**
             * 解析JWT令牌
             * @param jwt JWT令牌
             * @return JWT第二部分负载 payload 中存储的内容
             */
            public static Claims parseJWT(String jwt){
                Claims claims = Jwts.parser()           // parser方法是解析令牌
                        .setSigningKey(signKey)         // 设置签名秘钥 是上面定义的
                        .parseClaimsJws(jwt)            // 添加 解析的 令牌
                        .getBody();                     // 拿到 自定义的内容 Map 集合
                return claims;
            }
        }


十二. 过滤器 filter
    
    过滤器 连接所有请求  根据 JWT令牌 是否放行通过   学的不是很深入

    vim com/ssyy99/filter/LoginCheckFilter.java
        @Slf4j
        @WebFilter(urlPatterns = "/*")
        public class LoginCheckFilter implements Filter {
        //    @Override
        //    public void init(FilterConfig filterConfig) throws ServletException {
        ////        Filter.super.init(filterConfig);
        //        System.out.println("init 方法执行了");
        //    }

            @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 
              throws IOException, ServletException {
                System.out.println("拦截到了请求");

                HttpServletRequest req = (HttpServletRequest) servletRequest;
                HttpServletResponse resp = (HttpServletResponse) servletResponse;
                // 强制转换的目的是为了让req变量能够调用HttpServletRequest接口中定义的方法,
                // 而不仅仅是ServletRequest接口中定义的方法。这样可以更方便地处理HTTP请求。
                String url = req.getRequestURI().toString();       // 获取请求的url
                log.info("请求的url: {}", url);

                if (url.contains("login")) {   //String 的方法是用于判断一个字符串中是否包含另一个字符串或字符序列的方法
                    log.info("登录操作: 直接放行");
                    filterChain.doFilter(servletRequest,servletResponse);
                    return;                          // 放行之后 不需要在往后执行 跳出方法
                }
                String jwt = req.getHeader("token");       // 可以拿到 请求头里面的信息      // ↓要没有值或者null才执行if
                if (!StringUtils.hasLength(jwt)) {      // spring中提供的方法 判断的 字符串是否为空  有值返回true 前面加个非 
                    log.info("请求头token为空,返回未登录的信息"); // ↓在之前的配置有注解会自动转换成jison 这个需要手动转jison
                    Result error = Result.error("NOT_LOGIN");   // Result 这个是对象类 不是jison 
                    String notLogin = JSONObject.toJSONString(error);  // 阿里巴巴的工具栏类 可以把对象转成 jison格式
                    resp.getWriter().write(notLogin);                  // 把 notLogin 返回给浏览器
                    return;
                }
                try {
                    JwtUtils.parseJWT(jwt);       // 效验jwt 令牌  如果有异常 证明令牌解析失败
                } catch (Exception e) {
                    e.printStackTrace();                  // 捕获并打印异常信息
                    log.info("解析令牌失败,返回未登录的信息");
                    Result error = Result.error("NOT_LOGIN");
                    String notLogin = JSONObject.toJSONString(error);
                    resp.getWriter().write(notLogin);
                    return;
                }

                log.info("令牌合法,放行");
                filterChain.doFilter(servletRequest,servletResponse);         // 放行

            }
        //    @Override
        //    public void destroy() {
        ////        Filter.super.destroy();
        //        System.out.println("destroy 方法 执行了");
        //    }
        }


    注: init 初始化方法 只调用一次 和 destroy销毁方法 只调用一次 都有默认的实现 可以不用实现 这里 三个方法都实现了
        doFilter() 方法 每一次拦截到请求都会调用一次
        javax.servlet.Filter 这个包下的filter
        @ServletComponentScan     // 需要在启动类上加上注解 开启对 servlet组件的支持
        @WebFilter(urlPatterns = "/*")  // 过滤器拦截类 /* 代表所有的请求
        filterChain及后面两个参数  是 方法参数传递过来的
        过滤器执行顺序 先进 doFilter 执行 放行前的代码 通过chain调用dofilter方法放行再执行要访问页面代码之后回来执行放行后的代码
        拦截路径:  /* 拦截所有  /login 拦截具体目录 只有访问/login才会拦截   /emps/* 访问 emps下的所有资源都会拦截
        多个过滤器形成 过滤器链 优先级是 按照过滤器的字母顺序


    拦截器 interceptor   没学   168 169   170

    全局异常处理器  # vim com/ssyy99/exception/GlobalExceptionHandler.java  用到在补充


十二. SpringBoot 事务
    注解: @Transactional                                      // 只有 RuntimeException才回滚  运行异常
          @Transactional(rollbackFor = Exception.class)       // 所有异常都 回滚
            propagation = required // 当一个事物去调用另一个事务方法:此注解的 参数 默认值 需要事务,有则加入,无则创建新事务 
                                   // 就是两个事物会合并一个事物 只要有代码抛出异常 就会回滚
                requires_new           // 需要新事物 无论有无 总是创建新事物  新创建事务 不受其他的事务影响
                supports           // 支持事务 有则加入 无则在无事务状态中运行
                not_supported      // 不支持事务, 在无事务状态下运行,如果当前存在事务,则挂起当前事务
                mandatory          // 必须有事务 否则抛出异常
                mever              // 必须没有事务 否则抛出异常
        业务(service)层的 方法上 类上 接口上 都可以    一般加在 方法上
        将当前方法交给spring 进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务


十三. AOP 面向特定方法编程

    应用场景: 记录操作日志    权限控制   事务管理
    优势: 代码无侵入  减少重复代码  提高开发效率  维护方便
    案例1: 统计各个业务层方法执行耗时

    # vim pom.xml
        <dependency>           // aop的依赖
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        
    # vim com/ssyy99/aop/TimeAspect.java
        @Slf4j
        @Component      // 把此类交给 aop容器 处理
        @Aspect         // 当前类是 apo类
        public class TimeAspect {

            @Around("execution(* com.ssyy99.service.*.*(..))")  // 切入点表达式. 这个方法作用于 包下的所有的类 下的 所有方法
            public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
                // 1.记录开始时间
                long begin = System.currentTimeMillis();  // 获取当前时间的 毫秒值
                // 2. 调用原始方法
                Object result = joinPoint.proceed();   // 运行原始的方法  会出现异常 抛出即可  使用object 接收返回值

                // 3. 记录结束时间,计算方法执行时间
                long end = System.currentTimeMillis();
                log.info(joinPoint.getSignature() + "方法执行耗时: {}ms", end-begin);   // 拿到原始方法的签名...
                return result;  //把原始的方法返回值 返回回去
            }
        }

    切入点表达式: 
        1. execution 主要根据方法的返回值 包名 类名 方法名 方法参数等信息来匹配,语法为:        // ↓用 参数类型的全名
            execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)   // 方法参数要 java.lang.Integer  
                其中带?的表示 前面可以省略的部分: 访问修饰符  包名.类名.   异常  这三部分可以省略
                访问修饰符: 可省略 如 public protected
                包名.类名: 可省略
                throws 异常: 可省略 注意是方法上声明抛出的异常,不是实际抛出的异常
                
            @Pointcut("execution(* com.ssyy99.service.impl.DeptServiceImpl.*(..))")
            @Pointcut("execution(* com.ssyy99.service.*.*(..))")          // 这个方法作用于 包下的所有的类 下的 所有方法
            @Pointcut("execution(void com.ssyy99.service.DeptService.delete(java.lang.Integer))")   
                // 基于一个接口的表达式 java.lang.Interger 是参数类型的全名
            @Pointcut("execution(void com.ssyy99.service.DeptService.*(java.lang.Integer))")        
                // 通配类下的所有方法 但是 返回值是void  参数类型是Integer    
            @Pointcut("execution(void com.ssyy99.service.DeptService.*(*))")  // 有一个任意类型的参数  方法返回值是 void
            @Pointcut("execution(* com.ssyy99.service.DeptService.*(*))")     // 有一个任意类型的参数  任意返回值 
            @Pointcut("execution(* com.ssyy99.service.DeptService.*(..))")    // 任意个类型的参数 包括0个
            @Pointcut("execution(* *(..))")                                   // 当前环境下 所有的方法  基本用不上
            @Pointcut("execution(* *(..)) || execution(* *(..))")             // 匹配两个表达式 或者的关系   
            
            *  : 单个独立的任意符号, 可以通配任意返回值  报名  类名  方法名  任意类型的一个参数 也可以通配包 类 方法名的一部分
                 execution(* com.*.service.*.update*{*})
            .. : 多个连续的任意符号, 可以通配任意层级的包 或任意类型 任意个数的参数
                execution(* com.itheima..DeptService.*(..))
        
        2. @annotation 切入点表达式 自定义注解法 用于匹配标识有特定注解的方法
            vim com/ssyy99/aop/Mylog.java                       // 自定义 注解
                @Retention(RetentionPolicy.RUNTIME)             // 指定什么时候生效   runtime 是运行时生效
                @Target(ElementType.METHOD)                     // 指定注解可以用在哪些地方  method 是 方法上生效
                public @interface Mylog {                       // 定义注解 Mylog
                }
            
            @Pointcut("@annotation(com.ssyy99.aop.Mylog)")   // 要匹配 方法上有 Mylog注解的方法 在对应方法上加 @Mylog即可
                  
    通知类型:
        @Around:          环绕通知,此注解标注的通知方法在目标方法前  后 都被执行
        @Before:          前置通知,此注解标注的通知方法在目标方法前被执行
        @After:           后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
        @AfterReturning:  返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
        @AfterThrowing:   异常后通知,此注解标注的通知方法发生异常后执行
    
        切面类的执行顺序:  按类名排序 通知前方法: 字母靠前先执行...  通知后方法: 字母靠前后执行
        @Order(2)          // 在类上 控制 切面类的执行 顺序 数字越小  前置通知先执行   后置通知后执行
        
    # vim MyAspect1
        @Slf4j
        @Component         // 把此类交给 aop容器 处理
        //@Aspect          // 当前类是 apo类   这里注释了 没有开启  这部分代码不会执行
        public class MyAspect1 {


            @Pointcut("execution(* com.ssyy99.service.impl.DeptServiceImpl.*(..))")   // @pointcut 抽取 切入点表达式
            public void pt(){}   // 声明一个空方法  名字随便定义 无参方法 没有方法体      // ↑其他地方 可以调用 pt() 方法即可

            @Before("pt()")      // 前置通知  调用pt()方法 使用上面的 且入点表达式
            public void before(){
                log.info("before ...");
            }

            @Around("pt()")               // 环绕通知
            public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
                log.info("around before ...");

                //调用目标对象的原始方法执行
                Object result = proceedingJoinPoint.proceed(); // 运行原始的方法 会出现异常 抛出即可 使用object 接收返回值

                log.info("around after ...");
                return result;
            }

            @After("pt()")              // 后置通知
            public void after(){
                log.info("after ...");
            }

            @AfterReturning("pt()")     // 返回后通知
            public void afterReturning(){
                log.info("afterReturning ...");
            }

            @AfterThrowing("pt()")     // 异常后通知
            public void afterThrowing(){
                log.info("afterThrowing ...");
            }
        }

    连接点: 可以被AOP控制的方法 就是连接点   
        在 spring 中用 JoinPoint抽象了连接点 用它可以获得方法执行时的相关信息 如目标类名 方法名 方法参数等
        对于 @Around通知 获取连接点信息只能使用 ProceedingJoinPoint
        对于其他四种通知 获取连接点信息只能使用 JoinPoint 它是 ProceedingJoinPoint的父类型
        
        vim com/ssyy99/aop/MyAspect8.java
            //切面类
            @Slf4j
            //@Aspect
            @Component
            public class MyAspect8 {

                @Pointcut("execution(* com.ssyy99.service.DeptService.*(..))")
                private void pt(){}

                @Before("pt()")
                public void before(JoinPoint joinPoint){         // 这个JoinPoint 有两个包 要选org.aspectj.lang
                    log.info("MyAspect8 ... before ...");
                }

                @Around("pt()")
                public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
                    log.info("MyAspect8 around before ...");

                    //1. 获取 目标对象的类名 .
                    String className = joinPoint.getTarget().getClass().getName();
                    log.info("目标对象的类名:{}", className);

                    //2. 获取 目标方法的方法名 .
                    String methodName = joinPoint.getSignature().getName();
                    log.info("目标方法的方法名: {}",methodName);

                    //3. 获取 目标方法运行时传入的参数 .
                    Object[] args = joinPoint.getArgs();
                    log.info("目标方法运行时传入的参数: {}", Arrays.toString(args));

                    //4. 放行 目标方法执行 .
                    Object result = joinPoint.proceed();

                    //5. 获取 目标方法运行的返回值 .
                    log.info("目标方法运行的返回值: {}",result);

                    log.info("MyAspect8 around after ...");
                    return result;
                }
            }

    AOP综合案例   将案例中的增 删 改 相关 接口的操作日志 记录到数据库表中  // P182 之后在看


十四. java 项目的打包       // 需要在xml中引入spring-boot-maven-plugin插件 基于官网骨架创建的项目,会自动添加
    打包: 右侧Maven工具栏 -->  声明周期 --> 双击 package --> target 目录下会有 打包好的jar包
    
    运行jar包
        # java -jar springboot-student-server-0.0.1-SNAPSHOT.jar                     // java -jar 包名
        # java -jar springboot-student-server-0.0.1-SNAPSHOT.jar --server.port=9000  // 在命令行参数上设置 端口号为9000
















Mysql 数据库操作





Mysql 数据库操作         - 2023.9.16 -




DDL  数据定义语言  create 创建  alter 修改表(增加列 删除列等) dorp 删除 rename 重命名 truncate清空(有表结构)    
DML  数据操作语言  insert 增加  delete 删除         update 修改数据(某一个具体的值)   select 查询   
DLC  数据控制语言  commit 提交  rollback 回滚 撤销  savepoint 保存点  grant设置权限   revoke回收权限
DQL  数据查询语言  select 查询  

一. DDL: 数据库设计   // database  可以替换成 schema

    ❶. 对数据的操作   增 删 改 查

        show databases;                         // 查询所有数据库 
        create database db01;                   // 创建数据库
        create database if not exists db01;     // 如不不存在则创建
        use db01;                               // 使用数据库 db01
        select database();                      // 查看当前操作的数据库  函数的调用形式    
        drop database db01;                     // 删除数据库
        drop database if exists db01;           // 如果存在
        show tables;                            // 查看表
        desc tb_emp;                            // 查看某一个 表的 结构
        show create table tb_emp;               // 查看 表的 建表语句

    ❷. 对表的操作    增 删 改 查

      ①. 创建表      // 注意:最后一个字段后面 没有,逗号  []方括号内容 可写可不写
        create table 表名(
            字段1 字段类型 [约束] [comment '字段1注释'],
            ...
            字段n 字段类型 [约束] [comment '字段n注释']
        )
        create table user2(
             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 '性别'
        )
        约束: 约束是作用于表中字段上的规则,用于限制存储在表中的数据 保证数据库中数据的正确性 有效性 完整性
              约束是作用于表中字段上的,可以在创建表/修改表的时候添加约束。
            not null        非空约束    限制该字段不能为null
            unique          唯一约束    保证字段的所有数据都是唯一、不重复的
            primary key     主键约束    主键是一行数据的唯一标识,要求非空且唯一
                primary key auto_increment  从 1 自动增长
            default         默认约束    保存数据时,如果未指定该字段值,则采用默认值   default '指定的默认值'
            foreign key     外键约束    让两张表的数据建立连接,保证数据的一致性和完整性
        
      ②. 修改表结构  删除表

        1. 添加字段  alter table 表名 add 字段名  类型(长度) [comment '注释'] [约束];
            alter table tb_emp add qq varchar(10) comment 'QQ';      // 为表 tb_emp 添加字段qq varchar(11)
        2. 修改字段类型  alter table 表名 modify 字段名 新数据类型(长度);
            alter table tb_emp modify qq varchar(13) comment 'QQ'     // 修改 表 tb_emp qq的字段类型 varchar(13)
        3. 修改字段名和字段类型:alter table 表名 change 旧字段名 新字段名 类型(长度) [comment '注释'] [约束]
            alter table tb_emp change qq qq_num varchar(13) comment 'QQ';
        4. 删除字段  alter table 表名 drop column 字段名;
            alter table tb_emp drop column qq_num;
        5. 修改表名  rename table  表名 to 新表名;
            rename table tb_emp to emp;
        6. 删除表中 自动增长  auto_increment   表中 自动增长字段 只能有一个 必须是主键
            alter table tb_emp modify id int;
        7. 删除表中主键  alter table 表名 drop primary key;      // 如果 表中有 自动增长属性 先要删除 自动增长属性
            alter table tb_emp drop primary key;                // 
        8. 给表的字段 增加主键 alter table tb_emp add primary key(字段1,字段2);    // 主键可以有多个
            alter table tb_emp add primary key (id);            // 这里只添加了一个
        9. 给表的主键 字段设置 自动增长 alter table 表名 modify 字段名 int auto_increment;
            alter table tb_emp modify id int auto_increment;    // 设置 自动增长
            alter table emp01 auto_increment = 1;               // 设置 自动增长 为 1  默认为1

二. DML: 数据操作语言

    ❶. 添加数据    insert into 表名(字段列表) values (字段值列表);
        1. 指定字段添加数据  insert into 表名(字段1,字段2) values ('值1','值2');
            insert into tb_emp(username,name,gender,create_time) values ('janice','小龙女',now(),now());
            insert into tb_emp(id,username,name,gender,create_time) values (null,'janice','小龙女','2020-02-05',now());
        2. 全部字段添加数据  insert into 表名 values('值1','值2');
        3. 批量添加数据(指定字段)  insert into 表名(字段1,字段2) values ('值1','值2'),('值1','值2');
        4. 批量添加数据(全部字段)  insert into 表名 values ('值1','值2'),('值1','值2');

        注: 赋值的时候 字符串 和 时间 要加 单引号
            如果给 自动增长的主键赋值的时候  null 即可
            now()  函数  获取系统当前时间

    ❷. 修改数据     update 表名 set 字段名=字段值 [where 条件];
        1. 修改表中的数据 单个字段  update 表名 set 字段1=值1, 字段2=值2 [where 条件];
            update tb_emp set name='任盈盈' where id = 18;
        2. 修改表中 某个字段的所有数据 update 表名 set 字段1=值1;
            update tb_emp set qq = '113487606';     // 修改所有数据 不用加where 但是会有黄色的提示 点击确认修改Execute即可

    ❸. 删除数据     delete from 表名 [where 条件];
        删除一行数据  delete from 表名 [where 条件];
            delete from tb_emp where id = 17;
        删除表中所有数据  delete from 表名;     会有黄色的提示
            delete from tb_emp; 

三. DQL: 查询语句 数据库查询
    
    ❶. 基本查询
        select      字段列表
        from        表名列表         基本查询
        where       条件列表         条件查询
        group by    分组字段列表     分组查询
        having      分组后条件列表
        order by    排序字段列表     排序查询
        limit       分页查询         分页查询

        select 1;
        select 1 + 1;
        select 3 * 2 from dual;    // dual 是个 伪表 不存在的 只是为了结构完整
        select * from employees;   // * 代表 表中的所有的字段或列

        ①. 查询多个字段  select 字段1,字段2,字段3 from 表名;
            select name,entrydate from tb_emp;

        ②. 查询所有字段(通配符)  select * from 表名;
            select * from  tb_emp;

        ③. 别名 as   可加在 字段名  表名
            加双引号   不要加单引号(虽然也好使,只不过sql语言不那么敏感)  as   alisa缩写   as可以省略
            select 字段1 [as 别名1], 字段2[as 别名2] from 表名;
            最好加双引号 代表一个整体   不要加单引号(虽然也好使,只不过sql语言不那么敏感) as是alisa缩写  as可以省略

            select name as 姓名, entrydate as 入职日期 from tb_emp;
            select student_id "stu" from user "u"
            select student_id as stu from user as u

        ④. 去重 distinct        在字段前面加  表示 去重复 该字段
            select distinct 字段列表 from 表名;
            select distinct job from tb_emp;
            select distinct sutdent_id from user           // 去重复 sutdent_id 字段
            select distinct sutdent_id, salary from user   // 去重复 sutdent_id和salary 整体去重

        ⑤. 空值:  null为空值  null参与计算  计算值为 null
            ifnull(salary,0)  如果salary值不是空值,用salary,如果是空值用0计算

        ⑥. 着重号:  ` `     反引号  当出现字段跟系统关键字冲突时候 加 着重号

        ⑦. 查询常数
            select '哈哈',123,student_id from user         // '哈哈'和123为常数 的会给每一行都加一个 123

        ⑧. 显示表结构 describe 或 desc
            describe user;        // 查看 user 的表中 字段的 详细信息
            desc user;            // 作用同上
                Type   null   Key   Default   Extra

        ⑨. 过滤 数据  where      where关键字紧跟在from后面
            select * from cust_user_info where parent_id = 41       // where 是条件

    ❷. 聚合函数: 将一列数据作为一个整体,进行纵向计算

        select 聚合函数(字段列表) from 表名

        count(*)         统计数量     不对 null 值 进行计算
        count(cust_id)   计数
        max()            最大值
        min()            最小值
        avg()            平均值
        sum()            求和
        select count(*) from tb_emp;                      // 结果跟上面都一样 推荐使用 count(*)
        select count(*) from shop_order_addr;             // 这个表一共多少行
        select count(*) as addr from shop_order_addr;     // as 换个名 addr 
        select count(*) from `shop_order_addr` where province_code > 60;  // where 是条件 查询 province_code > 60 有多少
        select AVG(cust_id) from shop_order_addr;

        select min(entrydate) from tb_emp;  // 查询 最早入职时间  查询 最小值
        select max(entrydate) from tb_emp;
        select avg(id) from tb_emp;
        select sum(id) from tb_emp;
        select count(id) from tb_emp;    
        select count(1) from tb_emp;        // 1  和 a 都是常量  结果和 字段一样
        select count("a") from tb_emp;

    ❸. 分组查询  group by   可以把有共性的字段整合起来
        group by     在分组查询中, 主要查询一类是: 分组字段    另外一类是聚合函数
        having       分组之后的查询条件
        select 字段列表 from 表名[where 条件] group by 分组字段名 [having 分组后过滤条件];
        select gender,count(*) from tb_emp group by gender;       // 查询男 和女 分别有多少人
        select job,count(*) from tb_emp where entrydate <= "2015-01-01" group by job;   // 查询2015年前入职 各职位的人数
        select job,count(*) from tb_emp where entrydate <= "2015-01-01" group by job having count(*) >= 2; // 大于等于2
        select receiver, count(*) from shop_order_addr group by receiver;
            // 根据 GROUP BY receiver 此字段属性 每个receiver属性 有多少个
                第一列receiver      第二列COUNT计数
        select receiver, mobile, count(*) from shop_order_addr group by receiver, mobile;
            // 查询receiver和mobile两个字段 . 完全相同则计数   不相同单独计数

    ❹. 排序查询  order by
        order by   排序    默认是 升序 asc    没有加规则就是 asc  asc可以省略
            asc  升序 默认值
            desc 降序
        select 字段列表 from 表名 [where 条件列表] [group by 分组字段] order by 字段1 排序方式, 字段2 排序方式;
            select * from tb_emp order by entrydate desc;                // 安装入职时间 进行降序
            select * from tb_emp order by entrydate, update_time desc;   // 按照入职时间 升序 如果相同 在安装 更新时间 降序
            select * from cust_user_info order by parent_id;
            select * from cust_user_info order by parent_id desc;
            select * from cust_user_info where level = 10 order by parent_id desc;
            select * from cust_user_info where level = 10 order by parent_id desc, lft; // 按parent_id 降序 在按lft 升序
            select * from cust_user_info where level = 10 order by parent_id desc, lft desc;//按parent_id 降序在按lft降序

    ❺. 分页查询  limit
        # select * from user limit 0,10;    // 显示 前十条数据  每页 10条数据
        # select * from cust_user_info where level = 10 order by parent_id desc, lft desc limit 0,10;
        limit 10,10      // 从第11条数据开始 显示 10条    第二页
        limit 20,10      // 第三页
        limit 10         // 如果 位置偏移量是0的时候  可以简写 0可以省略
        limit 位置偏移量,条目数
        公式: limit(pageNo - 1) * pageSize,pageSize
        limit 2  offset 31  //  mysql8.0中的语句  加入了 offse关键字  offset后面跟偏移量 从第32条显示 2条数据
        select * from cust_user_info where level = 10 order by parent_id desc, lft desc limit 2 offset 10;
        select 字段列表 from 表名 limit 起始索引,查询记录数;  // 起始索引从0开始  起始索引 = (页码 -1) * 每页展示记录数
        select * from tb_emp limit 0,10;
        select * from tb_emp limit 10;    // 如果查询的是第一页数据,起始索引可以省略 简写成 limit 10;

    ❻. 条件判断 if(gender = 1, '男','女')
        select gender,count(*) from emp group by gender;
        select if(gender =1,'男','女') as 性别,count(*) from emp group by gender;      // 在gender 加入了条件判断

    ❼. 流程控制 case 表达式 when 值1 then 结果1 when 值2 then 结果2 ... else ... end
        select job,count(*) from tb_emp group by job;
        select
            (case job when 1 then '班主任' when 2 then '讲师' when 3 then '学工主管' 
            when 4 then '教研主管' else '未分配职位' end)as 职位,
            count(*)
        from tb_emp group by job;


四. 表结构设计

    一对多的的关系: 父表是1 子表是多
    部门表是父表    员工表是子表
    一对多关系实现: 在数据库表中多的一方,添加字段,来关联一的一方的主键

    ❶. 外键约束
        物理外键
            创建表时指定
                create table 表名(字段名 数据类型, [constraint] [外键名称] foreign key(外键字段名) references 主表(字段名));
            建立完表后,添加外键
                alter table 表名 add constraint 外键名称 foreign key(外键字段名) references 主表(字段名);
        逻辑外键 在业务逻辑中, 解决外键关联

    ❷. 连接查询
        内连接: 相当于查询A  B交集部分数据 ... 内连接无联系数据不显示

          隐式内连接: select 字段列表 from 表1,表2 where 条件;
            select * from emp,dept where dept.id = emp.dept_id;
            select dept.name,emp.name from emp,dept where dept.id = emp.dept_id;
            select d.name,e.name from emp as e ,dept as d where d.id = e.dept_id;  // 使用别名后 不能以在用表.字段调用 
          显示内连接: select 字段列表 from 表1[inner] join 表2 on 连接条件;  // inner 可以省略            要一直使用别名
            select * from emp inner join dept on emp.dept_id = dept.id;
            select emp.name,dept.name from emp inner join dept on emp.dept_id = dept.id;

        外连接:       外连接一定显示主表   left左表    right右表
            左外连接   以表1 为准  select 字段列表 from 表1 left [outer] join 表2 on 连接条件;
            右外连接   以表2 为准  select 字段列表 from 表1 right [outer] join 表2 on 连接条件;
                select * from emp left join dept d on d.id = emp.dept_id

    ❸. 子查询 sql 语句中的嵌套 select 语句 成为 嵌套查询 又称 子查询
       子查询外部的语句可以是 insert  update delete select 的任何一个 最常见的事select

        标量子查询 select * from 表1 where 字段1 = (select 字段1 from 表2);
            select id from dept where name = '教研部';            // 两个查询 合并成 一个查询
            select name,dept_id from emp where dept_id =2;                                 // ↓↓查询 在方东白 之后入职的
            select name,dept_id from emp where dept_id =(select id from dept where name = '教研部';); // 查询教研部所有人
            select name,entrydate from emp where entrydate >= (select entrydate from emp where name = '方东白');

        列子查询  常用的操作符:  =    <>    in    not in
            用于查询一个字段中 某些筛选数据  
            select * from 表1 where  字段0 in (select 字段0 from 表2 where 字段1 = '值1' or 字段1 = '值2');
            select id from dept where name = '教研部' or name = '咨询部';      // 查询 教研部和咨询部所有的员工信息
            select name,dept_id from emp where  dept_id in (select id from dept where name = '教研部' or name = '咨询部');

        行子查询 子查询返回的结果是一行(可以是多列)   常用的操作符:  =    <>    in    not in
            用于查询 与某一个字段 的其他属性相同的 其他数据
            select * from 表名 where (字段1,字段2) = (select 字段1,字段2 from 表名 where 字段0 = '字段0);
            select dept_id,entrydate from emp where name = '韦一笑';    
            select * from emp where entrydate = (select entrydate from emp where name = '韦一笑') 
                and job = (select job from emp where name = '韦一笑');       // ↓查询与韦一笑在一个部门同时入职的员工
            select * from emp where (entrydate,job) = (select entrydate,job from emp where name = '韦一笑'); 

        表子查询  把查询的 信息 作为一张表使用 放到 from 后
            select * from (select * from emp where entrydate > '2013-01-01') e, dept d where e.dept_id = d.id;

    ❹. 事物  就是 开启了 撤销模式  在开启事务后执行的代码并没有真正提交 只有commit后才是真正提交  可以回滚
        开启事务: start transaciton;  /   begin;
        提交事务: commit;
        回滚事务: rollback;


五. 数据类型

    ❶. 数值类型
          类型   大小(byte)   有符号(SIGNED)范围  无符号(unsigned)范围   描述       // ↓在类型后面使用 unsigned 使用无符号
        tinyint    1  (-128,127)                 (0,255)         小整数值    tinyint unsigned . 
        smallint   2  (-32768,32767)             (0,65535)       大整数值    smallint unsigned
        mediumint  3  (-8388608,8388607)         (0,16777215)    大整数值    ...
        int        4  (-2147483648,2147483647)   (0,4294967295)  大整数值    ... 
        bigint     8  (-2^63,2^63-1)             (0,2^64-1)      极大整数值  ...                   // ↓2 表示小数位个数
        float      4  (-3.40 E+38,3.40 E+38)  0  (1.17 E-38,3.40 E+38)  单精度浮点数值  float(5,2):5表示整个数字长度
        double     8  (-1.79 E+308,1.79 E+308)0  (2.22 E-308,1.79 E+308)双精度浮点数值   double(5,2):5表示整个数字长度
        decima     l   在金融使用                 小数值(精度更高)    decimal(5,2):5表示整个数字长度,2 表示小数位个数

    ❷. 字符串类型
        类型                         大小      描述
        char        0-255           bytes   定长字符串
        varchar     0-65535         bytes   变长字符串
        tinyblob    0-255           bytes   不超过255个字符的二进制数据
        tinytext    0-255           bytes   短文本字符串
        blob        0-65535         bytes   二进制形式的长文本数据
        text        0-65535         bytes   长文本数据
        mediumblob  0-16777215      bytes   二进制形式的中等长度文本数据
        mediumtext  0-16777215      bytes   中等长度文本数据
        longblob    0-4294967295    bytes   二进制形式的极大文本数据
        longtext    0-4294967295    bytes   极大文本数据

        char(10): 最多只能存10个字符,不足10个字符,占用10个字符空间
        varchar(10): 最多只能存10个字符,不足10个字符, 按照实际长度存储
        phone char(11)
        username varchar(20)

    ❸. 日期时间类型
         类型 大小(byte)                范围                              格式                          描述
        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 混合日期和时间值,时间戳

        birthday date
        update_time datetime

六. 运算符

    ❶. 算术运算符:   +    -   *   /    div也是除    %取模、余数   mod取模、余数  余数的符号跟被除数相同 跟除数没有关系
        select 100 + '1'      // 结果是 101    会把字符串 1 转换成数字 隐式转换
        select 100 +  'a'      // 结果是 100    a 不能转换是数字    a看做0处理
        select 100 * 'A'      // 结果是 0
        select 100 div 0      // 结果是 null   除数不能是0

    ❷. 比较运算符:
        返回结果是 1 为真
        返回结果是 0 为假
        返回结果是 null 为其他情况  只要有 null 值 参与比较 结果为 null,null 和 null 比较 结果也是null

        >     大于
        >=    大于等于
        <     小于
        <=    小于等于
        !=    不等于     可以参与null运算
        <>    不等于     可以参与null运算
        =     等于     会把 等号 两边的 值做比较 如果相同 返回1  结果不同返回0
            select 1 = 2, 1 = '1', 1 = 'a', 0 = 'a' ;
            select update_user from cust_user_info WHERE update_user = NULL   // 有null参与运算 不会返回任何结果
        <=>   安全等于  和 = 用法一样   唯一区别可以进行 null 运算
            select 1 <=> null, null <=> null;
            select update_user from cust_user_info WHERE update_user <=> NULL;

        is null      空       判断值  字符串或表达式是否为空
        is not null  不为空   判断值  字符串或表达式是否不为空
        isnull()     空     函数   判断值  字符串或表达式是否  
            select update_user from cust_user_info WHERE update_user is null;  // 取 update_user 是null的情况
            select update_user from cust_user_info WHERE update_user is not null;
            select parent_id from cust_user_info where ISNULL(parent_id);

        between ...  and   在 ... 之间
            select parent_id from cust_user_info where parent_id between 28 and 50; // 包括28 包括50 前面的数要小于后面的数
            select parent_id from cust_user_info where parent_id not between 28 and 50; // 不包括28 不包括50 不在28-50之间
            select parent_id from cust_user_info where parent_id < 50 or parent_id >28;
            select parent_id from cust_user_info where parent_id >10 && parent_id <50;
            select cust_id, parent_id from cust_user_info where parent_id = 10 or parent_id = 50; // 出现10或50的
            select cust_id, parent_id from cust_user_info where parent_id = 10 or 50; // 查询所有  因为50不为零返回值是 1 
        
        in()           具体的 等于 某一直     或多选一
        not ... in()   不等于 某一直
            select * from tb_emp where job in (2);
            select * from tb_emp where job in (2,3,4);
            select * where parent_id in (10,20,50);
            select * where parent_id in (10);
            select * where parent_id not in (10);

        like   模糊查询
            _         下划线 代表一个字符
            %         百分号 不确定字数的字符   包括 0个
            \         反斜杠 转义字符  代表 本身的意思
            escape    定义转义字符

            select * from tb_emp where name like '美';          // 这样的结果是  是 美的 查询出来 并不是模糊查询 相当于 =
            select * from tb_emp where name like '方芝美';      //
            select * from tb_emp where name like '%美%';        // 模糊查询 带 美 字的就可以
            select * from tb_emp where name not like '%美%';    // 不带美子字的 
            select * from tb_emp where name like '_美%';        // 第二个字是美的                      
            select * from tb_emp where name like '_\_a%';       // 第二个字符是 下划线的
            select * from tb_emp where name like '_$_a%' escape '$';      //把 $符 定义成 转义字符
            select * from tb_emp where name like '__';          // 查询 两个字的员工

        least     取 最小值     如果是字母  a 最小  z 最大
        greatest  取 最大值
            select least('g','b','c','v') from dual;       // 输出 b
            select greatest('c','s','y') from dual;        // 输出 y
            select greatest('2','3','1') from dual;        // 输出 3
            select least(lft,rgt) from cust_user_info;     // 比较两个字段  取最小值的字段

        regexp       正则表达式运算符
        rlike        正则表达式运算符
            select cust_name, cust_id from cust_user_info where cust_name regexp '^陈'       // 以陈开头的
   
    ❸. 逻辑运算符
        or   ||   或      两边有一个真就可以
        and  &&   且      两边都是真 and和 or 可以一起使用 and优先级高于or   a and b or c and d  先算and
        not  !    非
        xor       亦或    两边一个真一个假的时候  成立
            select * from cust_user_info where parent_id = 50 xor lft < 20     // 这里相当于 or 了 这不常用了解就好