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操作
     略。见老师的资料。