JDBC_1500行学习笔记,真的超级详细!!!

1. ## 为什么要JDBC

   - java和数据库必要纽带
   - 数据库层框架底层原理jdbc概念理解

2. ## jbdb概念理解

   - JDBC:Java Datebase Connectivity | Java链接数据库技术的统称!
     通俗点说,在Java代码中,使用JDBC提供的方法,可以发送字符串类型的SQL语句到**数据库管理软件**(MySQL,Oracle等),并且获取语句执行结果!进而实现数据库数据CURD操作的技术
   - java语言只提供规范(接口),规定数据库操作方法!标准的类库存在于java.sql,javax.sql包下
     个数据库厂商,根据java的jdbc规范(接口),完成具体的实现驱动代码(jar)!实现代码可以不同!但是方法都相同!
     JDBC由java提供的jdbc规范(接口)+ 各个数据库厂商的实现驱动jar包(里面装的就是JDBC的实现类)!
   - JDBC是一种典型的面向接口编程!
   - JDBC的优势:
     1. 我们只需要学习jdbc接口规定方法,即可操作所有数据库软件
     2. 项目中期需要切换数据库,我们只需要更新第三方驱动jar包,不需要更改代码!

3. ## 核心api和使用路线

   - jar包是java程序打成的一种压缩包格式,你可以将这些jar包引入你的项目中,然后你可以使用这个java程序中类和方法以及属性了!
   - 设计具体核心类和接口
     1. DriverManager
        - 将第三方数据库厂商的实现驱动jar注册到程序中
        - 可以根据数据库链接信息获取connection
     2. Connection
        - 和数据库建立的链接,在链接对象上,可以多次执行数据库curd动作
        - 可以获取 statement 和 preparedstatement,callablestatement 对象
     3. Statement(查询静态SQL路线(没有动态值语句) | PreparedStatement(预编译SQL路线(有动态值语句) | CallableStatement(执行标准存储过程SQL路线)
        - 具体发送SQL语句到数据库管理软件的对象
        - 不同发送方式稍有不同!**preparedstatement** 使用为重点!
     4. Result
        - 面向对象思维的产物(抽象成数据库的查询结果表)
        - 存储DQL查询数据库结果的对象
        - 需要我们进行解析,获取具体的数据库数据

4. ## 为什么选择8x版本驱动

5. ## 使用步骤总结

   - 驱动jar版本选择

   - java工程导入依赖

     ​    a. 项目创建lib文件夹

     ​    b. 导入驱动依赖jar包

     ​    c. jar包右键-添加为项目依赖(Add as Library)//因为此时和程序还没有什么关联
     ​    如果出现了倒三角,可以展开,即说明关联成功,拥有能链接的环境

   - jdbc基本使用步骤分析(6步)

     1. 注册驱动
     2. 获取链接
     3. 创建发送sql语句对象
     4. 发送sql语句,并获取返回结果
     5. 结果集解析
     6. 自愿关闭

6. ## statement_基本步骤演示

   - PS:在setting-Editor-File and Code Templates--Class里可以设置自动添加注释

     ```java
     package com.atguigu.api.statement;
     
     import com.mysql.cj.jdbc.Driver;
     
     import java.sql.*;
     import java.util.Collection;
     
     public class StatementQueryPart {
         public static void main(String[] args) throws SQLException {
             //1.注册驱动
             /**
              * TODO:
              *   注册驱动
              *   依赖:驱动版本8+ com.mysql.cj.jdbc.Driver
              *        驱动版本5+ com.mysql.jdbc.Driver
              */
             DriverManager.registerDriver(new Driver());
     
             //2.获取连接
             /**
              * TODO
              *   java程序要和数据库创建连接!
              *   java程序,连接数据库,肯定是调用某个方法,方法也需要填入连接数据库的基本信息:
              *         数据库ip地址 127.0.0.1 //本地机ip
              *         数据库端口号 3306
              *         账号 root
              *         密码 123456
              *         连接数据库的名称:atguigu
              * DriverManager.getConnection会返回一个链接,用Connection接口去接收后面的实现类(多态)
              */
     
             /**
              * 参数1:url
              *      jdbc:数据库厂商名://ip地址:prot/数据库名
              *      jdbc:mysql://127.0.0.1:3306/itheima
              * 参数2:username 数据库软件的账号 root
              * 参数3:password 数据库软件的密码 root
              */
             // java.sql 接口 = 实现类
             Connection connection = DriverManager.
                     getConnection("jdbc:mysql://127.0.0.1:3306/itcast", "root", "123456");
     
             //3.创建statement
             Statement statement = connection.createStatement();
     
             //发送sql语句,并获取返回结果
             String sql = "select * from employee;";
             ResultSet resultSet = statement.executeQuery(sql);
     
             //5.进行结果解析
             //看看有没有下一行数据,有,你就可以获取
             while (resultSet.next()) {
                 /*int id = resultSet.getInt("id");
                 String workno = resultSet.getString("workno");
                 String name = resultSet.getString("name");
                 String gender = resultSet.getString("gender");
                 int age = resultSet.getInt("age");
                 String idcard = resultSet.getString("idcard");
                 String work = resultSet.getString("work");
                 System.out.println(id + "--" + workno
                         + "--" + name + "--" + gender + "--" + age + "--" + idcard + "--" + work);*/
     
                 int id = resultSet.getInt("id");
                 int salary = resultSet.getInt("salary");
                 System.out.println(id + "--" + salary);
             }
     
             //关闭资源(从内往外关)
             resultSet.close();
             statement.close();
             connection.close();
         }
     }
     ```

   7. ## and 8. 使用步骤详解_上 and 下

   ```java
   package com.atguigu.api.statement;
   
   import com.mysql.cj.jdbc.Driver;
   
   import java.sql.*;
   import java.util.Properties;
   import java.util.Scanner;
   
   /**
    * TODO:
    *  1.明确jdbc的使用流程 和 详解讲解内部设计api方法
    *  2.发现问题,引出preparedstatement
    *
    * TODO
    *  输入密码账号
    *  进行数据库信息查询
    *  反馈登录成功还是登录失败
    *
    *  TODO:
    *      1.键盘输入事件,收集账号和密码信息
    *      2.注册驱动
    *      3.获取链接
    *      4.创建statement
    *      5.发送查询SQL语句,并获取返回结果
    *      6.结果判断,显示登陆成功还是失败
    *      4.关闭资源
    */
   
   public class StatementUserLoginPart {
       public static void main(String[] args) throws SQLException, ClassNotFoundException {
           //获取用户输入信息
           Scanner scanner = new Scanner(System.in);
           System.out.println("请输入账号");
   //        String account = scanner.nextLine();
           /**
            * PS:next()和nextLine()区别:
            * 相同点是他们返回值都是String类型
            * 不同的是nextLine()支持空格
            */
           System.out.println("请输入密码");
   //        String password = scanner.nextLine();
           //Tabnine Starter插件:按Tab会自动补全
   
   
           //2.注册驱动
           /**
            * 方案1:DriverManager.registerDriver(new com.cj.jdbc.Driver())
            *       注意:8+ com.mysql.cj.jdbc.Driver
            *            5+ com.mysql.jdbc.Driver
            *      问题:在DriverManager.registerDriver方法中,会注册一次驱动: registerDriver(driver, null);
            *           在Driver类中的静态代码块中,也会注册一次驱动:DriverManager.registerDriver(new Driver());
            *           这样就造成了一个性能消耗的问题
            *      解决问题:只想注册一次
            *              只触发静态代码块即可!Driver
            *      触发静态代码块:
            *              类加载机制:类加载的时刻,会触发静态代码块!
            *              加载 [class文件 -> jvm虚拟机的class对象]
            *              链接 [验证(检查文件类型) -> 准备(静态变量默认值) -> 解析(触发静态代码块)]
            *              初始化 (静态属性赋真实值)
            *      触发类加载:
            *          1.new 关键字
            *          2.调用静态方法
            *          3.调用静态属性
            *          4.接口 1.8以后新特性 加default默认实现
            *          5.反射
            *          6.子类触发父类
            *          7.程序的入口main
            */
           //方案1:
   //        DriverManager.registerDriver(new Driver());//填对应驱动的实现对象,注意要选择带cj的,它属于新更新的一个驱动内容,异常抛出即可
   
           //方案2:
           //new Driver();//只会触发一次,但这样写非常不雅,代码就很固定化
           //因为当前导的是mysql新版本的驱动,但如果有一天换成了oracle数据库,这个类就要改成oracle的类,就很不方便
   
           //字符串 -> 提取到外部的配置文件 -> 可以在不改代码的情况下,完成数据库驱动的切换! -> 这样就很灵活
           Class.forName("com.mysql.cj.jdbc.Driver");//通过反射机制,触发类加载,触发静态代码块的调用
   
           //获取数据库链接
           /**
            * 此时会发现,DriverManager.getConnection()是一个重载方法,即同名不同参,有三种参数传递形式
            * 允许开发者,用不同的形式传入数据库连接的核心参数!
            * PS:这三种只是参数传递的形式不同,但最终选择哪种都无所谓
            *
            * 核心属性:
            *      1.数据库软件所在的主机的ip地址:127.0.0.1,或者本机的主机名:localhost
            *      2.数据库软件所在的主机的端口号:3306//默认的端口号
            *      3.连接的具体库:itcast
            *      4.连接的账号:root
            *      5.连接的密码:123456
            *      6.可选的信息:稍后说
            *
            * 三个参数:
            *   String url         数据库软件所在的信息,连接的具体库,以及其他可选信息
            *                      语法:jdbc:数据库管理软件名称[mysql,oracle]://ip地址 | 主机名:port端口号/数据库名?key=value&key=value 可选信息!
            *                      具体的: jdbc:mysql://127.0.0.1:3306/itcast
            *                              jdbc:mysql://localhost:3306/itcast
            *                      本机的省略写法:(强调:必须是本机,并且端口号是3306方可省略 使用///
            *                      PS:如果没省略却使用了///则一定会报错
            *                      如果你的数据库软件安装到本机,可以进行一些省略
            *                      jdbc:mysql:///itcast
            *
            *
            *
            *                      PS:以jdbc开头:代表与jdbc连接的具体协议
            *   String user       数据库的账号 root
            *   String password    数据库的密码 123456
            *
            * 两个参数(更麻烦一些,推荐三个参数):
            *    String url :次url和三个参数的url的作用一样!
            *    Properties info:存储账号和密码
            *                    Properties 类似于 Map 只不过key = value 都是字符串形式的
            *                    key user:账号信息
            *                    key password:密码信息
            *
            * 一个参数:
            *  String url:数据库ip,端口号,具体的数据库  可选信息(账号密码)
            *              jdbc:mysql/localhost:3306/itcast?user=root&password=123456;
            *              携带固定的参数名 user password  传递账号和密码信息![规定!]
            *
            *   url的路径属性可选信息:
            *          url?user=账号&password=密码
            *
            *          8.0.27版本驱动,下面都是一些可选属性!
            *              8.0.25以后,自动识别时区!serverTimezone=Asia/Shanghai 不用添加!8.0.25之前版本,下面一句话还是要加的!
            *              8版本以后,默认使用的就是utf-8格式,useUnicode=true&characterEncoding=utf8&useSSL=true 都可以省略了!
            */
           Connection connection = DriverManager.getConnection("jdbc:mysql:///itcast", "root", "123456");
   
           Properties info = new Properties();
           info.put("user", "root");
           info.put("password", "123456");
           Connection connection1 = DriverManager.getConnection("jdbc:mysql://localhost:3306/itcast", info);
   
           Connection connection2 = DriverManager.getConnection("jdbc:mysql://localhost:3306/itcast?user=root&password=123456");
   
           //3.创建发送SQL语句的statement对象
           //statement 可以发送SQL语句到数据库,并且可以返回结果!
           Statement statement = connection1.createStatement();
   
           //4.发送SQL语句(1.编写SQL语句 2.发送SQL语句)
   //        String sql = "select * from employee where account = '" + account + "' and password = '" + password + "';";
           String sql = "select * from employee;";
   
           /**
            * SQL分类:DDL(容器创建,修改,删除) DML(增删改) DQL(查询) DCL(权限控制) TPL(事务控制语句)
            *
            * 参数:sql 非DQL
            * 返回结果:int
            *      情况1:DML 返回影响的行数,例如;删除了三条数据 return 3;插入了两条 return 2;
            *      情况2:非DML return 0;
            *
            * 参数:sql DQL
            * 返回:resultSet 结果封装对象
            * ResultSet resultSet = executeQuery(sql);
            */
   //        int i = statement.executeUpdate(sql);
           ResultSet resultSet = statement.executeQuery(sql);
   
           //5.查询结果集解析 resultSet
           /**
            * Java是一种面向对象的思维,将查询结果封装成了resultSet对象,我们应该理解,内部一定也是有行和有列的!和可视化工具是一样的!
            *
            * resultSet ->逐行获取数据,行-> 行的列的数据
            * 最初resultSet的cursor(光标)会指向第一行的前一行,next()方法即将cursor移向下一行
            * 若下一行有数据,返回true,若下一行没数据,则返回false
            *
            * tip:Translation插件可以支持源码翻译
            *
            * 想要进行数据解析,我们需要进行两件事情:1.移动游标指定获取数据行  2.获取指定数据行的列数据即可
            * 1.游标移动问题
            *      resultSet内部包含一个邮包,指定当前行数据!
            *      默认游标指定的是第一行数据之前!
            *      我们可以调用next()方法向后移动一行游标!
            *      如果我们有很多行数据,我们可以使用while(next){获取每一行数据}
            *
            *      boolean = next() true:有更多行数据,并且向下移动一行
            *                       false:没有更多行数据,不移动!
            *
            *    TODO:移动光标的方法有很多,只需要记next即可,配合while循环获取全部数据!
            *          resultSet.get类型(String columnLabel | int columnIndex);//columnIndex不用加""
            *              columnLabel:列名 如果有别名 写别名  select * | (id, account, password, nickname)
            *                                               select id as aid, account as ac from
            *              columnIndex:列的下标获取 从左向右 从1开始
            *
            * 2.获取列的数据问题(获取光标指定的行的列的数据)
            */
           while(resultSet.next()) {
               int id = resultSet.getInt("id");
               int salary = resultSet.getInt("salary");
               System.out.println(id + "--" + salary);
           }
   
           //移动一次光标,只要有数据,就代表登录成功
           /*if (resultSet.next()) {
               System.out.println("登陆成功");
           } else {
               System.out.println("登录失败");
           }*/
   
           //关闭资源
           connection1.close();
           statement.close();
           resultSet.close();
       }
   }
   ```

   9. ## preparedstatement基本使用流程

      - 使用statement实现模拟登录会存在以下问题:

        1. SQL语句需要字符串拼接,比较麻烦

        2. 只能凭借字符串类型,其他的数据库类型无法处理

        3. 可能发生注入攻击

           > 动态值充当了SQL语句结构,影响了原有的查询结果!

      - 具体的实现代码以及使用步骤:

        ```java
        package com.atguigu.api.statement.preparedstatement;
        
        import java.sql.*;
        import java.util.Scanner;
        
        /**
         * TODO:防止注入攻击  |   演示ps的使用流程
         */
        
        public class PSUserLoginPart {
            public static void main(String[] args) throws ClassNotFoundException, SQLException {
                Scanner scanner = new Scanner(System.in);
                System.out.println("请输入id");
                int id = scanner.nextInt();
                /*System.out.println("请输入账号:");
                String account = scanner.nextLine();
                System.out.println("请输入密码:");
                String password = scanner.nextLine();*/
        
                Class.forName("com.mysql.cj.jdbc.Driver");
        
                Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/itcast", "root", "123456");
        
                /**
                 * statement
                 *      1.创建statement
                 *      2.拼接SQL语句
                 *      3.发送SQL语句,并且获取返回结果
                 *
                 *  preparedstatement
                 *      1.编写SQL语句结果 不包含动态值部分的语句,动态值部分使用占位符 ? 替代
                 *        PS:? 只能替代动态值
                 *      2.创建preparedstatement,并且传入动态值
                 *      3.动态值 占位符 赋值 ? 单独赋值即可
                 *      4.发送SQL语句即可,并获取返回结果
                 */
        
                //3,。编写SQL语句结果
                String sql = "select * from employee where id = ?;";
        
                //4.创建预编译statement并且设置SQL语句结果
                PreparedStatement preparedStatement = connection.prepareStatement(sql);
                //PS:statement是在发送SQL语句的时候传入的sql,而preparedstatement是在创建预编译statement的时候就发送了sql语句!
        
                //5.单独占位符进行赋值
                /**
                 * 参数1:index 占位符的位置 从左向右数 从1 开始
                 * 参数2:object 占位符的值 可以设置任何类型的数据,避免了我们凭借和类型更加丰富
                 */
                //其实是可以写getInt的,但建议统一写Object,因为这样就不需要考虑了
                preparedStatement.setObject(1, id);
                //如果有多个 ? 占位符的话,需要使用多个preparedStatement.set
        
                //6.发送SQL语句,并返回返回结果!
                ResultSet resultSet = preparedStatement.executeQuery();//此时就不需要传sql了,因为它已经知道语句动态值了!
        
                //结果集解析
                while(resultSet.next()) {
                    int id1 = resultSet.getInt("id");
                    String salary = resultSet.getString("salary");
                    System.out.println(id1 + "--" + salary);
                }
        
                //8.关闭资源
                connection.close();
                preparedStatement.close();
                resultSet.close();
            }
        }
        ```

## 10.AND11.preparedstatement执行 **dml** 语句练习

- PS:curd是增删改查的缩写

- 新学知识:如何获取一列的数据

- 具体代码实现:

  ```java
  package com.atguigu.api.statement.preparedstatement;
  
  import org.junit.Test;
  
  import java.sql.*;
  import java.util.*;
  
  /**
   * 写四个Test方法(public开头 并且 Test方法不能有返回值,方法名随便写,但不能有形参列表)
   */
  public class PSCURDPart {
  
      //测试方法需要导入junit的测试包
      @Test
      public void testInsert() throws ClassNotFoundException, SQLException {
          /**
           * employee插入一条数据!
           *      id
           *      salary
           */
  
          //1.注册驱动
          Class.forName("com.mysql.cj.jdbc.Driver");
  
          //2.获取链接
          Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/itcast?user=root&password=123456");
  
          //3.编写sql语句,动态值的部分使用?代替
          String sql = "insert into employee(id, salary) values(?, ?);";
  
          //4.创建preparedStatement,并且传入SQL语句
          PreparedStatement preparedStatement = connection.prepareStatement(sql);
  
          //5.占位符赋值
          //其实是可以写getInt的,但建议统一写Object,因为这样就不需要考虑了
          preparedStatement.setObject(1, 1);
          preparedStatement.setObject(2, 23798347);
  
          //6.发送sql语句
          int rows = preparedStatement.executeUpdate();//返回的是影响的一个行数
  
          //7.输出结果
          if (rows > 0) {
              System.out.println("插入成功");
          } else {
              System.out.println("插入失败");
          }
  
          //8.关闭资源
          connection.close();
          preparedStatement.close();
      }
  
      @Test
      public void testUpdate() throws ClassNotFoundException, SQLException {
          //1.注册驱动
          Class.forName("com.mysql.cj.jdbc.Driver");
  
          //2.获取连接
          Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/itcast?user=root&password=123456");
  
          //3.编写sql语句
          String sql = "update employee set id = ?, salary = ? where id = ?";
  
          //4.创建preparedstatement对象,并传入sql语句
          PreparedStatement preparedStatement = connection.prepareStatement(sql);
  
          //5.给?赋值
          preparedStatement.setObject(1, 10);
          preparedStatement.setObject(2, 199);
          preparedStatement.setObject(3, 1);
  
          //6.发送sql语句
          int rows = preparedStatement.executeUpdate();
  
          //7.处理返回结果
          if (rows > 0) {
              System.out.println("修改成功");
          } else {
              System.out.println("修改失败");
          }
          
          //8.关闭资源
          connection.close();
          preparedStatement.close();
      }
  
      @Test
      public void testDelete() throws ClassNotFoundException, SQLException {
          //1.注册驱动
          Class.forName("com.mysql.cj.jdbc.Driver");
  
          //2.提供连接
          Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/itcast?user=root&password=123456");
  
          //3.编写sql语句
          String sql = "delete from Employee where id = ?;";
  
          //4.创建preparedStatement对象
          PreparedStatement preparedStatement = connection.prepareStatement(sql);
  
          //5.给?赋值
          preparedStatement.setObject(1, 3);
  
          //6.发送sql语句
          int rows = preparedStatement.executeUpdate();
  
          //7.处理返回结果
          if (rows > 0) {
              System.out.println("删除成功");
          } else {
              System.out.println("删除失败");
          }
  
          //8.关闭资源
          connection.close();
          preparedStatement.close();
      }
  
  
      /**
       * TODO:查询所有用户数据,并且封装到一个List<Map> list 集合中
       *
       * 解释:
       *      查询的数据一定有很多行
       *      数据库 -> resultSet ->java -> ->一行 - map(key=列名,value=列的内容) -> List<Map> list
       *
       *  实现思路:
       *      遍历行数据,一行对应一个map?获取一行的列名和对应的属性,装配即可!
       *      将map装到一个集合就可以了!
       *
       *  难点:
       *      如何获取列的名称?
       */
      @Test
      public void testSelect() throws ClassNotFoundException, SQLException {
          /*HashMap<String, Objects> map = new HashMap<>();
          List<HashMap<Objects, Objects>> list = new ArrayList<>();*/
          //1.注册驱动
          Class.forName("com.mysql.cj.jdbc.Driver");
  
          //2.获取连接
          Connection connection = DriverManager.getConnection("jdbc:mysql:///itcast?user=root&password=123456");
  
          //3.编写sql语句
  //        String sql = "select * from Employee where id = ?;";
          String sql = "select * from Employee;";
  
          //4.创建preparedStatement对象
          PreparedStatement preparedStatement = connection.prepareStatement(sql);
  
          //5.给占位符赋值
  //        preparedStatement.setObject(1, 5);
  
          //6.发送sql
          ResultSet resultSet = preparedStatement.executeQuery();
  
          //7.处理返回结果
          List<Map> list = new ArrayList<>();
  
          //获取列的信息对象
          //TODO:metaData 装的当前 结果集 列的信息对象!(他可以获取列的名称 根据下角标,可以获取列的数量)
          ResultSetMetaData metaData = resultSet.getMetaData();
  
          //有了它以后,我们可以水平遍历列!
          int columnCount = metaData.getColumnCount();
  
          while(resultSet.next()) {
              Map map = new HashMap();
              //注释掉的是我乱写的。。。
              /*int id = resultSet.getInt("id");
              Object o1 = new Object(id);
              String name = resultSet.getString(2);
              map.put("id", id);
              map.put("name", name);
              System.out.println(id);*/
  
              //一行数据对应一个map
              //纯手动写值//不推荐!
              /*map.put("id", resultSet.getInt("id"));
              map.put("name", resultSet.getString(2));
              list.add(map);*/
  
              //智能一点:自动遍历列,注意,要从1开始,并且小于等于总列数!
              for (int i = 1; i <= columnCount; i++) {
                  //获取指定列下角标的值都是通过resultSet对象
                  Object value = resultSet.getObject(i);
                  /**
                   * 获取指定列下角标的列的名称!如果要获取列中下角标的名称,都要使用ResultSetMetaData对象
                   * 选的时候会碰到 getColumnLabel 和 getColumnName:但一定要选择 getColumnLabel
                   * 因为:getColumnLabel:会获取别名,如果没有写别名才是列的名称 不用用getColumnName:只会获取列的名称!
                   */
                  String columnLabel = metaData.getColumnLabel(i);
                  map.put(columnLabel, value);
              }
              list.add(map);
          }
          System.out.println(list);
  
          //8.关闭资源
          connection.close();
          preparedStatement.close();
      }
  }
  ```

12. jdbc使用步骤总结和api回顾总结
    1. 注册驱动
    2. 获取链接
    3. 编写sql语句,动态值的部分使用?代替
    4. 创建preparedStatement,并且传入SQL语句
    5. 占位符赋值
    6. 发送sql语句
    7. 输出结果
    8. 关闭资源

13. 拓展提升_主键回显和主键值获取

    - 代码实现:

      ```java
      package com.atguigu.api.statement.preparedstatement;
      
      import org.junit.Test;
      
      import java.sql.*;
      
      /**
       * Description:练习ps的特殊使用情况
       */
      public class PSOtherPart {
          /**
           * TODO:
           *      t_user插入一条数据!并且获取数据库自增长的主键!
           * <p>
           * TODO:使用总结
           *       1. 创建preparedStatement的时候,告知 携带回数据库自增长的主键(sql, Statement.RETURN_GENERATED_KEYS);
           *       2. 获取司机装主键值的结果集对象,它是一行一列的,
           *          获取对应的数据即可 ResultSet resultSet = statement.getGeneratedKeys();
           */
          @Test
          public void returnPrimaryKey() throws Exception {
              //1.注册驱动
              Class.forName("com.mysql.cj.jdbc.Driver");
      
              //2.获取连接
              Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/itcast?user=root&password=123456");
      
              //3.编写sql语句
              String sql = "insert into employee(name, salary, departmentId) values(?, ?, ?);";
      
              //4.创建statement
              PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
      
              //5.占位符赋值
              preparedStatement.setObject(1, "TOM");
              preparedStatement.setObject(2, 5);
              preparedStatement.setObject(3, 2);
      
              //6.发送sql语句,并且获取结果
              int rows = preparedStatement.executeUpdate();
      
              //7.结果解析
              if (rows > 0) {
                  System.out.println("插入成功");
      
                  //可以获取回显的主键
                  //获取司机装主键的结果集对象,一行 一列 id = 值
                  ResultSet resultSet = preparedStatement.getGeneratedKeys();
                  resultSet.next();//移动下光标,指向第一行!
                  int id = resultSet.getInt(1);//再拿到第一行里的数据
                  System.out.println(id);
              } else {
                  System.out.println("插入失败");
              }
      
              //8.关闭资源
              connection.close();
              preparedStatement.close();
          }
      }
      ```

14. 批量插入数据优化

    - 具体代码实现:

      ```java
      /**
       * 使用普通的方式插入10000条数据
       */
          @Test
          public void testInsert() throws Exception {
              //1.注册驱动
              Class.forName("com.mysql.cj.jdbc.Driver");
      
              //2.获取连接
              Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/itcast?user=root&password=123456");
      
              //3.编写sql语句
              String sql = "insert into employee(name, salary, departmentId) values(?, ?, ?);";
      
              //4.创建statement
              PreparedStatement preparedStatement = connection.prepareStatement(sql);
      
              //5.占位符赋值
              long start = System.currentTimeMillis();
              for (int i = 0; i < 10000; i++) {
                  preparedStatement.setObject(1, "dd" + i);
                  preparedStatement.setObject(2, 5);
                  preparedStatement.setObject(3, 2);
      
                  //6.发送sql语句,并且获取结果
                  preparedStatement.executeUpdate();
              }
      
              long end = System.currentTimeMillis();
      
              System.out.println("执行100000次数据插入消耗的时间" + (end - start) + "ms");
              //8.关闭资源
              connection.close();
              preparedStatement.close();
          }
      --------------------------------------------分隔线--------------------------------------------------------
          /**
           * 使用批量插入的方式插入100000条数据
           * TODO:总结批量插入
           *      1.路径后面添加?rewriteBatchedStatements=true 允许批量插入
           *      2.insert into 必须写values, 语句末尾 不能 添加 “;” 结束
           *      3.不是执行语句每条,是批量添加 addBatch();
           *      4.遍历添加完毕以后,统一批量执行 executeBatch()
           */
          @Test
          public void testBatchInsert() throws Exception {
              //1.注册驱动
              Class.forName("com.mysql.cj.jdbc.Driver");
      
              //2.获取连接
              Connection connection = DriverManager.getConnection(
                      "jdbc:mysql://localhost:3306/itcast?rewriteBatchedStatements=true", "root", "123456");
      
              //3.编写sql语句
              String sql = "insert into employee(name, salary, departmentId) values(?, ?, ?)";
      
              //4.创建statement
              PreparedStatement preparedStatement = connection.prepareStatement(sql);
      
              //5.占位符赋值
              long start = System.currentTimeMillis();
              for (int i = 0; i < 10000; i++) {
                  preparedStatement.setObject(1, "dd" + i);
                  preparedStatement.setObject(2, 5);
                  preparedStatement.setObject(3, 2);
      
      //            preparedStatement.executeUpdate();
                  preparedStatement.addBatch();//不执行,追加到values后面!
              }
              preparedStatement.executeBatch();//执行批量操作!
              //这里可能会有另外的一个方法:statement.executeLargeBatch();//假的,jdbc里面没有任何操作!!
      
              long end = System.currentTimeMillis();
      
              System.out.println("执行100000次数据插入消耗的时间" + (end - start) + "ms");
              //8.关闭资源
              connection.close();
              preparedStatement.close();
          }
      ```

15. 事务回顾和设计转账类结构

    - 前提概要:jdbc技术处理事物的方式是 try-catch

      ```java
      //呼应jdbc技术
      try {
          connection.setAutoCommit(false);//关闭自动提交
          
          //注意,只要当前connection对象,进行数据库操作,都不会自动提交事务
          //数据库动作!
          //statement - 单一的数据库动作 c u r d
          
          connection.commit();
      } catch(Exception e) {
          connection.rollback();
      }
      ```

    - 建表语句

      ```mysql
      create table t_bank(
              id int primary key auto_increment comment '账号主键',
              account varchar(20) not null unique comment '账号',
              money int unsigned comment '金额,不能为负值');
      insert into t_bank(account, money) values ('ergouzi', 1000), ('lvdandan', 1000);
      ```

    - Dao是存数据操作方法的 类的 一个简称。
      `dao = DataBase Access Object` :数据库访问对象缩写
      一般来说:表名+Dao,代存存储某个表存储数据操作的方法。

16. 转账事务的实现

    - BankDao类代码:
    
      ```java
      package com.atguigu.api.statement.transaction;
      
      import java.sql.Connection;
      import java.sql.DriverManager;
      import java.sql.PreparedStatement;
      import java.sql.SQLException;
      import java.util.Properties;
      
      public class BankDao {
          /**
           * 加钱的数据库操作方法(jdbc)
           * @param account 加钱的账号
           * @param money 加钱的金额
           */
          public void add(String account, int money, Connection connection) throws Exception {
      
      //        String sql = "update t_bank set money = money - money where account = account;";
              //可恶!忘记了后面要给?赋值
              String sql = "update t_bank set money = money + ? where account = ?;";
              PreparedStatement preparedStatement = connection.prepareStatement(sql);
      
              preparedStatement.setObject(1, money);
              preparedStatement.setObject(2, account);
              preparedStatement.executeUpdate();
      
              preparedStatement.close();
              System.out.println("加钱成功");
          }
      
          /**
           * 减钱的数据库操作方法(jdbc)
           * @param account 减钱的账号
           * @param money 减钱的金额
           */
          public void sub(String account, int money, Connection connection) throws Exception {
              String sql = "update t_bank set money = money - ? where account = ?;";
              PreparedStatement preparedStatement = connection.prepareStatement(sql);
      
              preparedStatement.setObject(1, money);
              preparedStatement.setObject(2, account);
              preparedStatement.executeUpdate();
      
      
              preparedStatement.close();
              System.out.println("减钱成功");
          }
      }
      ```
    
      - BankService代码:
    
        ```java
        package com.atguigu.api.statement.transaction;
        
        import org.junit.Test;
        
        import java.sql.Connection;
        import java.sql.DriverManager;
        import java.util.Properties;
        
        /**
         * 银行卡业务方法,调用Dao方法
         */
        public class BankService {
            /**
             * TODO:
             *  事务添加是在业务方法中,利用try catch 代码块,开启事务和提交事务,和事务回滚!
             *  将connection传入dao层即可!dao只负责使用,不要close()!
             *
             *
             * @param addAccount
             * @param subAccount
             * @param money
             * @throws Exception
             */
            public void transfer(String addAccount, String subAccount, int money) throws Exception {
                /**出现的问题:
                 *      减钱的账户没钱时,会报异常
                 *      但是加钱的账户却依旧正常加了,这是因为这两个操作处于两个事务中。
                 *
                 *  解决办法:变成同一个事务
                 *   //一个事务的最基本要求,必须是同一个链接对象 connection
                 */
                Class.forName("com.mysql.cj.jdbc.Driver");
                Properties info = new Properties();
                info.put("user", "root");
                info.put("password", "123456");
                Connection connection = DriverManager.getConnection("jdbc:mysql:///itcast", info);
        
                try {
                    //开启事务
                    connection.setAutoCommit(false);
                    //执行数据库动作
                    BankDao bankDao = new BankDao();
                    bankDao.add(subAccount, money, connection);
                    bankDao.sub(addAccount, money, connection);
        
                    //事务提交
                    connection.commit();
                } catch (Exception e) {
                    //事务回滚
                    connection.rollback();
        
                    throw e;
                } finally {
                    connection.close();
                }
        
            }
        
            @Test
            public void start() throws Exception {
                transfer("lvdandan", "ergouzi", 100);
            }
        }
        ```

17. 连接池介绍和druid连接池使用

    - 连接性能消耗问题分析

      connection 可以复用

    - 数据库连接池作用
      太长了懒得打

    - 创建连接池
      记得导入 Druid 工具类 jar!

      1. 硬编码方式(了解,不推荐)
         程序运行起来之后改不了

      2. 软编码
         具体代码实现:

         ```java
         package com.atguigu.api.statement.druid;
         
         import com.alibaba.druid.pool.DruidDataSource;
         import com.alibaba.druid.pool.DruidDataSourceFactory;
         import com.alibaba.druid.pool.DruidPooledConnection;
         import org.junit.Test;
         
         import javax.sql.DataSource;
         import java.io.IOException;
         import java.io.InputStream;
         import java.sql.Connection;
         import java.sql.SQLException;
         import java.util.Properties;
         
         /**
          * 连接池使用类
          */
         public class DruidUsePart {
             /**
              * 直接使用代码设置连接池连接参数方式!
              * 1.创建一个druid连接池对象
              *
              * 2.设置连接池参数 [必须 | 非必须 ]
              *
              * 3.获取连接 [通用方法,所有连接池都一样]
              *
              * 4.回收链接 [通用方法,所有连接池都一样]
              */
             public void testHard() throws SQLException {
                 //连接池对象
                 DruidDataSource dataSource = new DruidDataSource();
         
                 //设置参数
                 //必须 连接数据库驱动类的全限定符[注册驱动] | url | user | password
                 dataSource.setUrl("jdbc:mysql://localhost:3306/itcast");
                 dataSource.setUsername("root");
                 dataSource.setPassword("123456");
                 dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");//帮助我们进行驱动注册和获取连接
                 //非必须 初始化连接数量,最大的连接数量...
                 dataSource.setInitialSize(5);//初始化连接数量
                 dataSource.setMaxActive(10);//最大的数量
         
                 //获取连接
                 Connection connection = dataSource.getConnection();
         
                 //数据库curd
         
                 //回收连接
                 connection.close();//连接池提供的连接,close,就是回收连接!
             }
         
             /**
              * 通过读取外部配置稳健的方法,实例化druid连接池对象
              */
             @Test
             public void testSoft() throws Exception {
                 //1.读取外部配置文件 到 Properties对象中
                 Properties properties = new Properties();
         
                 //获取当前类的类加载器,它有个方法叫getResourceAsStream();只要在里面写 配置的名字即可
                 //PS:如果是放在src下的配置文件就可以只写名字,但如果不是放在src下的配置文件,就需要加上路径:xxx/druid.properties
                 InputStream ips = DruidUsePart.class.getClassLoader().getResourceAsStream("druid.properties");
         
                 properties.load(ips);
         
                 //2.使用连接池的工具类的工厂模式,创建连接池//ps:不是ibatis下的!
                 DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
         
                 Connection connection = dataSource.getConnection();
         
                 //3.数据库curd
         
                 //4.收回连接
                 connection.close();
             }
         }
         ```

18. 获取连接工具类v1

    我们封装一个工具类,内部包含用软编码的形式创建连接池对象,同时对外提供连接的方法和回收连接的方法!

    - 外部配置文件
      位置:src/druid.properties
      具体代码:

      ```java
      # key = value => java Properties??(key | value)
      # druid连接池需要的配置参数,key必须为固定命名
      driverClassName=com.mysql.cj.jdbc.Driver
      username=root
      password=123456
      url=jdbc:mysql://localhost:3306/itcast
      ```

    - 工具类具体代码:

      ```java
      package com.atguigu.api.statement.utils;
      
      import com.alibaba.druid.pool.DruidDataSourceFactory;
      
      import javax.sql.DataSource;
      import java.io.IOException;
      import java.io.InputStream;
      import java.sql.Connection;
      import java.sql.PreparedStatement;
      import java.sql.SQLException;
      import java.util.Properties;
      
      /**
       * v1.0版本工具类
       *  内部包含一个连接池对象,并且对外提供获取连接和回收连接的方法!
       *
       *  小建议:
       *       工具类的方法,推荐写成静态,外部调用会更加方便!
       *
       *  实现:
       *      属性 -> 连接池对象 [实例化一次]
       *          两种处理方式:
       *             1.单例模式
       *             2.静态代码块(静态代码块是单例模式的一种实现方式)
       *
       *      两个方法:
       *          对外提供连接的方法
       *          回收外部传入连接方法
       */
      public class JdbcUtils {
          private static DataSource dataSource = null;//连接池对象
      
          static {
              //初始化连接池对象
              Properties properties = new Properties();
      
              InputStream isp = JdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties");
      
              /*try {
                  properties.load(isp);//static代码块里的异常没地方抛,只能try-catch
              } catch (IOException e) {
                  throw new RuntimeException(e);
              }
      
              try {
                  DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
              } catch (Exception e) {
                  throw new RuntimeException(e);
              }
      
              }*/
      
              //直接一个try-catch包围
              try {
                  properties.load(isp);//static代码块里的异常没地方抛,只能try-catch
                  dataSource = DruidDataSourceFactory.createDataSource(properties);
              } catch (Exception e) {
                  throw new RuntimeException(e);
              }
      
          }
      
          /**
           * 对外听连接的方法
           * @return
           */
          public static Connection getConnection() throws SQLException {
              return dataSource.getConnection();
          }
      
          /**
           *
           */
      
          public static void freeConnection(Connection connection) throws SQLException {
              connection.close();//连接池的连接,调用close就是回收!
          }
      }
      ```

    - 使用的代码:

      ```java
      package com.atguigu.api.statement.utils;
      
      import java.sql.Connection;
      import java.sql.SQLException;
      
      /**
       * 基于jdbc的工具类
       */
      
      public class JdbcCURD {
          public void testInsert() throws SQLException {
              Connection connection = JdbcUtils.getConnection();
      
              //数据库curd动作
      
              JdbcUtils.freeConnection(connection);
          }
      }
      ```

19. 工具类封装_获取连接工具类v2

    - v1版本的问题:同一个线程不同的方法getConnection()拿到的也都是新的一个对象
      v2版本目的:同一个线程不同方法获取同一个连接!

    - 前要知识:ThreadLocal

      > JDK 1.2的版本中就提供java.lang.ThreadLocal,为解决多线程程序的并发问题提供了一种新的思路。
      >
      > 使用这个工具类可以很简洁地编出优美的多线程程序。通常用来在多线程中管理共享数据库连接、Session等。

      > ThreadLocal用于保存某个线程共享变量,原因是在Java中,每一个线程对象中都有一个ThreadLoalMap<ThreadLocal, Object>,其key就是一个ThreadLocal,而Object即为该线程的共享变量。而这个map是通过ThreadLocal的set和get方法操作的。对于同一个staticThreadLocal,不同线程只能从get,set,remove自己的变量,而不会影响其他线程的变量。

      > 1. ThreadLocal对象.get:获取ThreadLocal中当前线程共享变量的值。
      > 2. ThreadLocal对象.set:设置ThreadLoxal中当前线程共享变量的值。
      > 3. ThreadLocal对象.remove:移除ThreadLocal中当前线程共享变量的值。

    具体代码实现:

    ```java
    package com.atguigu.api.statement.utils;
    
    import com.alibaba.druid.pool.DruidDataSourceFactory;
    
    import javax.sql.DataSource;
    import java.io.InputStream;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.util.Properties;
    
    /**
     * TODO:
     *      利用线程本地变量,存储连接信息!确保一个线程的多个方法可以获取同一个connection!
     *      优势:实务操作的时候 service 和 dao 属于同一个线程,不同再传递参数了!
     *      大家都可以调用getConnection自动获取的是相同的连接池!
     */
    public class JdbcUtilsV2 {
        private static DataSource dataSource = null;//连接池对象
    
        //声明一个全局的线程本地变量
        private static ThreadLocal<Connection> tl = new ThreadLocal<>();
    
        static {
            //初始化连接池对象
            Properties properties = new Properties();
    
            InputStream isp = JdbcUtilsV2.class.getClassLoader().getResourceAsStream("druid.properties");
    
            try {
                properties.load(isp);//static代码块里的异常没地方抛,只能try-catch
                dataSource = DruidDataSourceFactory.createDataSource(properties);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
    
        }
    
        /**
         * 对外听连接的方法
         * @return
         */
        public static Connection getConnection() throws SQLException {
            //先查看线程本地变量中是否存在
            Connection connection = tl.get();
    
            //第一次没有
            if (connection == null) {
                //线程本地变量没有,连接池获取
                connection = dataSource.getConnection();
    
                tl.set(connection);
            }
            return connection;
        }
    
        public static void freeConnection() throws SQLException {
            Connection connection = tl.get();
            if (connection != null) {
                tl.remove();//清空线程本地变量数据
                connection.setAutoCommit(true);//回归事务状态 false
                connection.close();//回收到连接池即可
    
            }
    
        }
    }
    ```

20. 工具类封装_baseDao概念和非DQL方法封装

    - BaseDao完整代码:

      ```java
      package com.atguigu.api.statement.utils;
      
      import java.sql.Connection;
      import java.sql.PreparedStatement;
      import java.sql.SQLException;
      import java.util.List;
      import java.util.Objects;
      
      /**
       * 封装Dao数据库重复代码!
       * TODO:
       *  封装两个方法  一个简化非DQL
       *              一个简化DQL
       *
       *              List<Object> params
       */
      
      public class BaseDao {
          /**
           * 封装简化非SQL 语句
           * @param sql   带占位符的SQL语句
           * @param params    占位符的值   PS:传入占位符的值,必须等于SQL语句?位置!
           * @return  执行影响的行数
           */
          public int executeUpdate(String sql, Objects... params) throws Exception {//可变参数必须存在于形参列表的最后一位
              //获取连接
              Connection connection = JdbcUtilsV2.getConnection();
      
              PreparedStatement preparedStatement = connection.prepareStatement(sql);
              //5.占位符赋值
              //可变参数可以当数组使用
              for (int i = 1; i <= params.length; i++) {
                  preparedStatement.setObject(i, params[i]);
              }
      
              int rows = preparedStatement.executeUpdate();
      
              preparedStatement.close();
              //是否回收连接,需要考虑是不是事务
              if (connection.getAutoCommit()) {
                  //没有开启事务
                  //没有开启事务 正常回收连接!
                  JdbcUtilsV2.freeConnection();
              }
      //        connection.setAutoCommit(false);//开启事务了 不要管连接即可! 由业务层来处理
      
              return rows;
          }
      }
      ```

    - 使用代码:

      ```java
      package com.atguigu.api.statement.utils;
      
      import org.junit.Test;
      
      import java.sql.*;
      import java.util.*;
      
      public class PSCURDPart extends BaseDao{
      
          @Test
          public void testInsert() throws ClassNotFoundException, SQLException {
              String sql = "insert into employee(id, salary) values(?, ?);";
      
              int rows = executeUpdate(sql, 1, 111);
          }
      }
      ```

21. 工具类封装_DQL查询方法封装

    - 完整BaseDao代码:

      ```java
      package com.atguigu.api.statement.utils;
      
      import java.lang.reflect.Field;
      import java.sql.*;
      import java.util.*;
      
      /**
       * 封装Dao数据库重复代码!
       * TODO:
       *  封装两个方法  一个简化非DQL
       *              一个简化DQL
       *
       *              List<Object> params
       */
      
      public class BaseDao {
          /**
           * 封装简化非SQL 语句
           * @param sql   带占位符的SQL语句
           * @param params    占位符的值   PS:传入占位符的值,必须等于SQL语句?位置!
           * @return  执行影响的行数
           */
          public int executeUpdate(String sql, Objects... params) throws Exception {//可变参数必须存在于形参列表的最后一位
              //获取连接
              Connection connection = JdbcUtilsV2.getConnection();
      
              PreparedStatement preparedStatement = connection.prepareStatement(sql);
              //5.占位符赋值
              //可变参数可以当数组使用
              for (int i = 1; i <= params.length; i++) {
                  preparedStatement.setObject(i, params[i - 1]);//注意一定要是i - 1,否则会数组下标越界
              }
      
              int rows = preparedStatement.executeUpdate();
      
              preparedStatement.close();
              //是否回收连接,需要考虑是不是事务
              if (connection.getAutoCommit()) {
                  //没有开启事务
                  //没有开启事务 正常回收连接!
                  JdbcUtilsV2.freeConnection();
              }
      //        connection.setAutoCommit(false);//开启事务了 不要管连接即可! 由业务层来处理
      
              return rows;
          }
      
          /**
           * 非DQL语句封装方法 ->返回值 固定位int
           *
           * DQL语句封装方法 -> 返回值 是什么类型呢?
           *      DQL -> List<Map> -> 一行 -> map ->List<Map>
           *      but,并不是List<Map> map key 和 value都是自定义的,不用先设定好!
           *                  但是map是没有数据校验机制的
           *                  map 不支持反射操作
           *
           *         实际上:  数据库中的数据 -> 对应到java的实体类
           *         数据库中的表等于 java类中的一个对象
           *         多行数据可以等于Java实体类中的集合
           *
           * 所以DQL语句返回值应该为List<T> list
           *
           * <T> 声明一个泛型,不确定类型
           *          1.确定泛型 User.class T = User
           *          2.要使用反射技术属性赋值
           * public <T>  List<T> executeQuery(Class<T> clazz, String sql, Object... params);
           */
      
          /**
           * 将查询结果封装到一个实体类集合!
           * @param clazz 要接值的实体类集合的模板对象
           * @param sql   查询语句,要求列名或者别名等于实体类的属性名!
           * @param params  占位符的值,要和?位置对象传递
           * @return 查询的实体类集合
           * @param <T> 声明的结果的泛型
           * @throws Exception
           */
      
          public <T> List<T> executeQuery(Class<T> clazz, String sql, Object... params) throws Exception {
              //获取连接
              Connection connection = JdbcUtilsV2.getConnection();
              
              PreparedStatement preparedStatement = connection.prepareStatement(sql);
      
              if (params != null && params.length != 0) {
                  for (int i = 1; i <= params.length; i++) {
                      preparedStatement.setObject(i, params[i - 1]);
                  }
              }
      
              ResultSet resultSet = preparedStatement.executeQuery();
      
              List<T> list = new ArrayList<>();
      
              //TODO:metaData 装的当前 结果集 列的信息对象!(他可以获取列的名称 根据下角标,可以获取列的数量)
              ResultSetMetaData metaData = resultSet.getMetaData();
      
              //有了它以后,我们可以水平遍历列!
              int columnCount = metaData.getColumnCount();
              //一行数据对应一个 T 类型的对象
      
              while(resultSet.next()) {
                  T t = clazz.newInstance();//调用类的无参构造函数实例化对象!
                  
                  for (int i = 1; i <= columnCount; i++) {
                      //对象的属性值
                      Object value = resultSet.getObject(i);
                      
                      //对象的属性名
                      String propertyName = metaData.getColumnLabel(i);
                      
                      //反射给对象的属性值
                      Field field = clazz.getDeclaredField(propertyName);
                      field.setAccessible(true);//属性可以设置,打破private的修饰限制
                      /**
                       * 参数1:要赋值的对象 如果属性是静态属性,第一个参数 可以为null!
                       * 参数2:具体的属性值 
                       * 
                       */
                      field.set(t, value);
                  }
                  list.add(t);
              }
              
              //关闭资源
              resultSet.close();
              preparedStatement.close();
              if (connection.getAutoCommit()) {
                  //没有事务,可以关闭
                  JdbcUtilsV2.freeConnection();
              }
              
              return list;
          }
      }
      ```

22. 综合练习_cms项目改造jdbc操作
    略。见老师的资料。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/52365.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RTSP/Onvif视频服务器EasyNVR安防视频云服务调用接口录像会被自动删除的问题解决方案

EasyNVR安防视频云服务是基于RTSP/Onvif协议接入的视频平台&#xff0c;可支持将接入的视频流进行全平台、全终端的分发&#xff0c;分发的视频流包括RTSP、RTMP、HTTP-FLV、WS-FLV、HLS、WebRTC等。平台丰富灵活的视频能力&#xff0c;可应用在智慧校园、智慧工厂、智慧水利等…

数字流的秩、单词频率(哈希实现)

题目1&#xff1a;数字流的秩 假设你正在读取一串整数。每隔一段时间&#xff0c;你希望能找出数字 x 的秩(小于或等于 x 的值的个数)。请实现数据结构和算法来支持这些操作&#xff0c;也就是说&#xff1a; 实现 track(int x) 方法&#xff0c;每读入一个数字都会调用该方法…

Hugo托管到Github Pages

Github通过其Github Pages服务可以user、project或organization提供免费快速的静态托管&#xff0c;同时使用Github Actions自动化开发工作流和构建。 1.创建Github仓库 可见性为public。 命名为username.github.io&#xff0c;username为你的Github用户名。 2.添加远程仓库…

vue3-ts- element-plus新增组件-过滤

新增组件-所有值为空时过滤 <el-form-item label"家庭成员"><divclass"username-box"v-for"(item, index) in form.namelist":key"index"><div>姓名&#xff1a;<el-input v-model"item.name" placeho…

Docker的革命:容器技术如何重塑软件部署之路

引言 在过去的几年中&#xff0c;容器技术已经从一个小众的概念发展成为软件开发和部署的主流方法。Docker&#xff0c;作为这一变革的先驱&#xff0c;已经深深地影响了我们如何构建、部署和运行应用程序。本文将探讨容器技术的起源&#xff0c;Docker如何崛起并改变了软件部…

Lombok生成的Getter和Setter的名称对于“eMail”或“xAxis”等属性存在大小写转换异常

问题 最新开发中&#xff0c;遇到一个字段映射问题。我们先看问题案例&#xff1a; 明明代码中第二个字母是大写&#xff0c;结果测试接口时发现变成了小写字母。 分析 通过网上查询发现&#xff0c;这属于Lombok的bug。而且早在2015年就有人在GitHub上提出了issues。 Names o…

基于spring boot校园疫情信息管理系统/疫情管理系统

摘要 随着计算机技术&#xff0c;网络技术的迅猛发展&#xff0c;Internet 的不断普及&#xff0c;网络在各个领域里发挥了越来越重要的作用。特别是随着近年人民生活水平不断提高&#xff0c;校园疫情信息管理系统给学校带来了更大的帮助。 由于当前疫情防控形势复杂&#xff…

高性能MySQL实战(三):性能优化

大家好&#xff0c;我是 方圆。这篇主要介绍对慢 SQL 优化的一些手段&#xff0c;而在讲解具体的优化措施之前&#xff0c;我想先对 EXPLAIN 进行介绍&#xff0c;它是我们在分析查询时必要的操作&#xff0c;理解了它输出结果的内容更有利于我们优化 SQL。为了方便大家的阅读&…

LA@向量组间的表示关系

文章目录 2个向量组间的表示关系向量组的相互表出向量组用另一个向量组表示&#x1f47a;线性表示的系数矩阵矩阵乘法与线性表出列向量组线性表示行向量组线性表示 向量组等价&#x1f47a;向量组等价的性质推论 等价矩阵与向量组等价的关系行等价矩阵的行向量组等价列等价矩阵…

R语言09-R语言中的字符函数和分布相关函数

字符函数 paste() 和 paste0(): 将多个字符向量连接成一个字符串&#xff0c;paste0() 直接连接&#xff0c;而 paste() 可以通过 sep 参数指定分隔符。 vector1 <- c("Hello", "world") vector2 <- c("R", "programming") re…

贪心算法:简单而高效的优化策略

在计算机科学中&#xff0c;贪心算法是一种简单而高效的优化策略&#xff0c;用于解决许多组合优化问题。虽然它并不适用于所有问题&#xff0c;但在一些特定情况下&#xff0c;贪心算法能够产生近似最优解&#xff0c;而且计算成本较低。在本文中&#xff0c;我们将深入探讨贪…

C++中机器人应用程序的行为树(ROS2)

马库斯布赫霍尔茨 一、说明 以下文章为您提供了对机器人应用程序或框架中经常使用的行为树的一般直觉&#xff1a;ROS&#xff0c;Moveit和NAV2。了解行为 Tress &#xff08;BT&#xff09; 框架的原理为您提供了在游戏领域应用知识的绝佳机会。BT可以与Unity或Unreal集成。 由…

深度学习知识总结2:主要涉及深度学习基础知识、卷积神经网络和循环神经网络

往期链接&#xff1a;Summer 1 : Summarize linear neural networks and multi-layer perceptron Summer 2: Summarize CNN and RNN 文章目录 Summer 2: Summarize CNN and RNNPart 1 Deep Learning> 层和块> 参数管理和延后初始化> 读写文件和GPU Part 2 CNN> 从…

前端性能优化之浏览器渲染优化

文章目录 引言一、浏览器渲染流程二、回流1. 什么是回流2. 哪些操作会导致回流 三. 针对回流如何优化1. 使用transform和opacity代替top、left和width等属性来进行动画效果的实现。因为transform和opacity不会引起回流。2. 尽量使用绝对定位&#xff08;position: absolute&…

在VS中使用格式化工具

在VS中使用格式化工具 官网地址: https://clang.llvm.org/ 最后更新时间&#xff1a;2023.8.25 这里以windows为例&#xff0c;使用的环境为VS。 &#xff08;一&#xff09;下载安装LLVM 下载地址: https://github.com/llvm安装&#xff08;自己选择安装路径&#xff09; &…

【Azure】Virtual Hub vWAN

虚拟 WAN 文档 Azure 虚拟 WAN 是一个网络服务&#xff0c;其中整合了多种网络、安全和路由功能&#xff0c;提供单一操作界面。 我们主要讨论两种连接情况&#xff1a; 通过一个 vWAN 来连接不通的 vNET 和本地网络。以下是一个扩展的拓扑 结合 vhub&#xff0c;可以把两个中…

高并发网站的负载均衡设计

大型高并发网站的负载均衡设计通常包含以下方面: 1. 硬件负载均衡器 在入口使用专业的硬件F5等负载均衡器,实现流量分发,并承担第一层保护。 2. DNS轮询/一致性哈希 结合DNS,使用轮询或一致性哈希方式将请求分散到后端不同的真实服务器。 3. CDN负载均衡 针对静态资源,使用C…

Spring+redis集成redis缓存

1、引入maven依赖 <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.7.0</version></dependency><dependency><groupId>org.springframework.data</groupId><art…

vue 报错

报错 npm install 报错:code ELIFECYCLE errno 1 This is probably not a problem with npm. There is Vue前端项目用npm install 报错:code ELIFECYCLE errno 1 This is probably not a problem with npm. There is

深入理解Linux内核--Ext2和Ext3文件系统

Ext2的一般特征 类Unix操作系统使用多种文件系统。尽管所有这些文件系统都有少数POSIX API(如state())所需的共同的属性子集&#xff0c;但每种文件系统的实现方式是不同的。 Linux的第一个版本是基于MINIX文件系统的。当Linux成熟时&#xff0c;引入了扩展文件系统(Extended …