Java开发 - 尚硅谷JDBC学习笔记

Java开发 - 尚硅谷JDBC学习笔记

序言

请认真看代码块里的注释,这个是帮助理解代码和原理的最有用的文字

一、JDBC技术概念

(零)从USB的角度来引入JDBC

1.USB技术介绍

在这里插入图片描述

2.USB技术演示

在这里插入图片描述

3.USB技术总结

在这里插入图片描述

(一)JDBC技术概念和理解

1.JDBC技术理解

(1)JDBC技术介绍

在这里插入图片描述

(2)JDBC技术演示

在这里插入图片描述

(3)JDBC本质理解

在这里插入图片描述

(4)JDBC总结
1)总结:
  • JDBC是Java连接数据库技术的统称
  • JDBC是由两部分组成的:
    • Java提供的jdbc规范(接口)
    • 各个数据库厂商实现的驱动jar包
  • JDBC技术是一种典型的面向接口编程
2)优势:
  • 我们只需要学习JDBC接口规定的方法,即可操作所有的数据库软件
  • 项目中期如果需要切换数据库,那我们只需要更新第三方jar包,不用更改代码

2.JDBC概念总结

  • JDBC是 Java DataBase Connectivity 的缩写,直译为 Java数据库连接
  • JDBC是Java程序连接数据库的技术统称
  • JDBC由 Java语言的规范(接口) 和各个数据库厂商的实现驱动(jar包)组成

(二)JDBC核心API和使用路线

1.JDBC技术组成

(1) JDK下的JDBC规范接口,存储在java.sql和javax.sql包中的API里
  • 为了项目代码的可移植性和可维护性,SUN公司最初就制定了Java程序连接各种数据库的统一接口规范。这样的话,不管是连接哪一种DBMS(DataBase Management System)软件,Java代码都可以保持一致性
(2)各个数据库厂商提供的驱动jar包
  • 因为各个数据库厂商的DBMS软件各有不同,那么内部如何通过sql,来实现增删改查等管理数据的操作,只有这个数据库厂商自己更清楚,因此把接口规范的实现交给各个数据库厂商自己实现。
(3)jar包是什么?
  • 将Java程序打包成一种压缩包的格式,可以将这些jar包引入自己的Java项目中,然后就可以使用这个jar包里的类里面的方法和属性了

2.涉及的具体核心类以及接口

(1)连接过程

在这里插入图片描述
DriverManager获取连接 → 建立连接 → PreparedStatement(最常用) 发送SQL语句 →若是查询操作,则对应的查询结果放在Result类

(2)DriverManager
  • 1)将第三方数据库厂商实现的驱动jar包注册到自己的Java程序中
  • 2)根据数据库连接信息来获取Connection
(3)Connection(建立连接)
  • 1)和数据库建立连接,并且可以在连接的对象上多次执行数据库CURD操作
  • 2)可以获取Statement,PreparedStatement,以及CallableStatement对象(Callable - 可调用的,可随时支取的)
(4)Statement,PreparedStatement,CallableStatement之间的异同
  • Statement适用于静态的没有动态值的sql语句
  • PreparedStatement适用于预编译的有动态值的sql语句
  • CallableStatement适用于存储过程 要执行的sql语句
  • 这三个都是用于发送sql语句到数据库管理软件的具体的实例化出来的对象
  • PreparedStatement是主要使用的对象
(5)Result对象
  • 只有通过查询语句才能产生的对象,用来接收从数据库中查询到的数据
  • 是面对对象思维的查为奴(Result对象被抽象为数据库里查询结果的表
  • 存储DQL查询数据库结果的对象
  • 需要我们进行解析,获取其具体的数据库数据

3.JDBC的API使用路线

JDBC API使用路线
静态SQL语句 - 没有动态值语句和条件值语句
预编译SQL语句 - 有动态值语句,最常用
执行 标准存储过程的 SQL语句
DriverManager -> Connection -> Statement,静态SQL语句 -> Result
DriverManger -> Connection -> PreparedStatement -> Result
DriverManger -> Connection -> CallableStatement -> Result

(三)为什么选择MySQL8+版本的JDBC驱动

1.支持8.0+版本的MySQL数据管理软件

MySQL软件知名版本迭代时间
版本号迭代时间大小
mysql-8.0.254,30,2021435.7M
mysql-5.7.251,10,2019387.7M
mysql-5.5.309,19,2012201.5M

2.MySQL8.x版本数据库性能提升介绍:

  • 性能提升级。官方表示MySQL 8.0 的速度要比 MySQL 5.7 快 2 倍。
  • MySQL 8.0 在读/写工作负载、IO 密集型工作负载、以及高竞争工作负载时相比MySQL5.7有更好的性能。
    在这里插入图片描述

3.支持Java JDBC规范 4.2+版本的新特性

(1)Java JDBC规范驱动版本和更新时间

在这里插入图片描述

(2)JDBC规范版本更新内容(了解)

在这里插入图片描述

4.支持JDK1.8版本语法变更的新特性

  • Connector/J 8.0是专门为了在Java8平台上允许而创建的
  • 众所周知,Java8与早期的Java版本高度兼容
  • 但是还是存在少量的不兼容性,所有驱动技术版本尽量选择支持JDK8.0+

5.支持全新的驱动API,增加自动时区和默认UTF-8编码格式等配置

二、JDBC核心API

(一)引入mysql-jdbc驱动的jar包

1.驱动jar包的版本选择

  • 我们选择8.0.27的版本
mysql版本推荐驱动版本备注
mysql 5.5.x5.0.xcom.mysql.jdbc.Driver
mysql 5.7.x5.1.xcom.mysql.jdbc.Driver
mysql 8.x8.0.x建议8.0.25+,能够省略时区设置 com.mysql.jdbc.Driver

2.Java工程导入依赖

(1)项目创建lib文件夹

在这里插入图片描述
在这里插入图片描述

(2)导入驱动依赖jar包

在这里插入图片描述

(3)对jar包右键 - 添加为项目依赖

在这里插入图片描述

(二)JDBC基本使用步骤分析(6步)

在这里插入图片描述
在这里插入图片描述

(三)基于statement演示查询

1.准备数据库数据

CREATE DATABASE atguigu;

USE atguigu;

CREATE TABLE t_user(
   id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户主键',
   account VARCHAR(20) NOT NULL UNIQUE COMMENT '账号',
   PASSWORD VARCHAR(64) NOT NULL COMMENT '密码',
   nickname VARCHAR(20) NOT NULL COMMENT '昵称');
   
INSERT INTO t_user(account,PASSWORD,nickname) VALUES
  ('root','123456','经理'),('admin','666666','管理员');

2.查询目标

在这里插入图片描述

3.基于statement实现查询(演示步骤)

package com.atguigu.api.Statement;

import com.mysql.cj.jdbc.Driver;

import java.sql.*;

/**
 * Description: 使用statement查询t_user表下,全部的数据
 * <p>
 * TODO: 步骤总结 (6步)
 *    1. 注册驱动
 *    2. 获取连接
 *    3. 创建statement
 *    4. 发送SQL语句,并获取结果
 *    5. 结果集解析
 *    6. 关闭资源
 */
public class StatementQueryPart {

    public static void main(String[] args) throws SQLException {
        //1.注册驱动
        /**
         * 依赖:
         *     驱动版本 MySQL 8+ -> com.mysql.cj.jdbc.Driver
         *     驱动版本 MySQL 5+ -> com.mysql.jdbc.Driver
         */
        DriverManager.registerDriver(new Driver());//该方法是静态方法

        //2.获取连接
        /**
         * Java程序要和数据库创建连接!
         * Java程序连接数据库,肯定是要调用某个方法,方法也需要填入连接数据库的基本信息
         * 本电脑MySQL数据库的基本信息:
         *    数据库ip地址 127.0.0.1(或localhost) 注:127.0.0.1/localhost都是默认的本机数据库ip地址
         *    数据库端口号:3306(默认)
         *    账号:root
         *    密码:root
         *    连接数据库的名称:atguigu
         */
        /**
         * .getConnection()的第一个参数:url - Uniform Resource Locator
         *      url的书写格式 - jdbc:数据库厂商名://ip地址:port/数据库名
         *              举例 - jdbc:mysql://127.0.0.1:3306/atguigu
         */
        //java.sql的接口 = 实现类
        Connection connection = DriverManager.getConnection
                ("jdbc:mysql://127.0.0.1:3306/atguigu",
                        "root",
                        "root");//静态方法

        //3.创建发送sql语句对象 - 创建statement
        Statement statement = connection.createStatement();

        //4.发送sql语句,并且获取返回结果
        String sql = "select * from t_user";
        ResultSet resultSet = statement.executeQuery(sql);

        //5.进行结果集解析
        //.next()的作用:看看有没有下一行数据,有的话就可以获取
        while (resultSet.next()) {
            int id = resultSet.getInt("id");
            String account = resultSet.getString("account");
            String password = resultSet.getString("password");
            String nickname = resultSet.getString("nickname");
            System.out.println(id + "--" + account + "--" + password + "--" + nickname);
        }

        //6.关闭(释放)资源
        resultSet.close();
        statement.close();
        connection.close();
    }
}

(四)基于statement方式的问题

0.本案例目标

在这里插入图片描述

1.准备数据库数据

  • 与上个案例(基于statement演示查询)相同的数据库
CREATE DATABASE atguigu;

USE atguigu;

CREATE TABLE t_user(
   id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户主键',
   account VARCHAR(20) NOT NULL UNIQUE COMMENT '账号',
   PASSWORD VARCHAR(64) NOT NULL COMMENT '密码',
   nickname VARCHAR(20) NOT NULL COMMENT '昵称');
   
INSERT INTO t_user(account,PASSWORD,nickname) VALUES
  ('root','123456','经理'),('admin','666666','管理员');

2.演示目标

在这里插入图片描述

3.类加载知识补充

【Java类加载机制 - CSDN App】

  • 类加载:就是将类的class文件读入内存,并创建一个Java.lang.class对象。
  • 类在运行期第一次使用时,首先会被类加载器动态加载至JVM
  • 类在运行期过程又分为了五个阶段:加载、验证、准备、解析、初始化。
  • 类的生命周期被分为了七个阶段:加载、验证、准备、解析、初始化、使用、限载

4.基于statement实现模拟登录

package com.atguigu.api.Statement;

import java.sql.*;
import java.util.Properties;
import java.util.Scanner;

/**
 * Description:
 * 输入账号,实现模拟用户登录
 * <p>
 * TODO:
 *      1.明确JDBC的使用流程 和 详细讲解内部设计api方法
 *      2.发现问题,引出preparedStatement
 * TODO:
 *      输入账号和密码
 *      进行数据库信息查询(t_user)
 *      反馈登录成功还是登陆失败
 * TODO:
 *      1.键盘输入事件,收集账号和密码信息
 *      2.注册驱动
 *      3.获取连接
 *      4.创建statement
 *      5.发送查询SQL语句,并获取返回结果
 *      6.结果判断,显示登录成功还是失败
 *      7.关闭资源
 */
public class StatementUserLoginPart {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        //1.键入信息
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入账号:");
        String account = scanner.nextLine();
        System.out.print("请输入密码:");
        String password = scanner.nextLine();

        //2.注册驱动
        /**
         * 方案1:
         *  DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver())
         *   问题:注册了两次驱动
         *   第一次 - DriverManager.registerDriver() 该方法本身会注册一次
         *   第二次 - Driver.static{ static{... DriverManager.registerDriver() ...}静态代码块也会注册一次
         *      想法:只想注册一次驱动来减少系统开销
         *      解决方法:只触发Driver类里的 静态代码块 即可
         *          触发静态代码块 -> 使用类加载机制:(类加载的时刻,会触发静态代码块)
         *              1.加载 [class 文件 -> jvm虚拟机的class对象]
         *              2.连接 [验证(检查文件类型) -> 准备(静态变量默认值)-> 解析(触发静态代码块)]
         *              3.初始化 [静态属性赋上真实值]
         *                  触发类加载的方式:
         *                      1.new 关键字
         *                      2.调用静态方法
         *                      3.调用静态属性
         *                      4.接口 JDK版本1.8以上 用default默认值实现
         *                      5.反射
         *                      6.子类触发父类
         *                      7.程序的入口main
         */
        //方案1:
//        DriverManager.registerDriver(new Driver());

        //方案2:
        //不推荐,不雅,而且 如果mysql要改成oracle的话要改代码的
//        new Driver();
        //推荐 - 利用反射,解决实现代码不用更改的需求 - 更加灵活地完成数据库的切换
        Class.forName("com.mysql.cj.jdbc.Driver");

        //3.获取数据库连接
        /**
         * getConnection(1,2,3)方法,是一个重载方法
         * 允许开发者用不同的形式传入数据库连接的核心参数
         *
         * 核心属性:
         *      1.数据库软件所在的主机的ip地址: localhost 或 127.0.0.1
         *      2.数据库软件所在的主机的端口号: 3306
         *      3.连接的具体数据库(的名字): atguigu
         *      4.连接的账号: root
         *      5.连接的密码: root
         *      6.可选的信息 没有
         *
         * 三个参数的getConnection(url,user,password):
         *  1.String url  包括:数据库软件所在的信息,连接的数据库,以及其它可选信息
         *              语法:  jdbc:数据库管理软件[mysql/oracle/等]://ip地址
         *                              或
         *                     主机名:port(端口号)/数据库名?key=value&key=value&...
         *              举例:  jdbc:mysql://127.0.0.1:3306/atguigu
         *                     jdbc:mysql://localhost:3306/atguigu?user=root&password=root
         *
         *              本机的省略写法:
         *                  如果数据库软件安装到了本机,则可以进行一些省略:
         *                      jdbc:mysql://127.0.0.1:3306/atguigu = jdbc:mysql:///atguigu
         *                          省略了[本机地址]和[3306(默认端口号)]
         *                          强调:必须是本机并且端口号是3306才可省略并用///来代替
         *
         *  2.String user             数据库的账号 root
         *  3.String password         数据库的密码 root
         *
         * 两个参数的getConnection(url,info):
         *      1.String url:此url和 三个参数 的url的作用一样,即数据库ip,端口号,具体的数据库和可选信息
         *      2.Properties info:存储账号和密码
         *                   (Properties类似于Map只不过”key=value“都是字符串形式的)
         *                   key user:账号信息
         *                   key password:密码信息
         *
         * 一个参数的getConnection(url):
         *  String url: 数据库ip,端口号,具体的数据库,可选信息(账号密码等)
         *          语法: jdbc:数据库软件名://ip:port/数据库?key=value&key=value&...
         *          具体: jdbc:mysql:///atguigu?user=root&password=root
         *          规定: 可选信息中,一定要携带固定的参数名 user,password 传递账号和密码信息
         *
         * url的路径属性里的可选信息:
         *      url?user=账号&password=密码 ->可选信息: 1.user=账号 2.password=密码
         *
         * 8.0.27的驱动版本中,可选属性如下:
         *  serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&usrSSL=true
         *  - useUnicode=true
         *  - characterEncoding=utf8
         *  - usrSSL=true
         *   (8.0.25以后,会自动识别时区,因此"serverTimezone=Asia/Shanghai"不用添加,
         *            如果是8.0.25之前的版本,"serverTimezone=Asia/Shanghai"还是要添加的)
         *
         *   8版本以后,
         *      默认使用的就是uft-8格式,那"useUnicode=true&characterEncoding=uft8&useSSL=true"这东西就可以省略了
         */
        //三个参数
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");

        //两个参数
        Properties info = new Properties();
        info.put("user", "root");
        info.put("password", "root");
        Connection connection2 = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", info);

        //一个参数
        Connection connection3 = DriverManager.getConnection("jdbc:mysql://localhost:3306/atguigu?user=root&password=root");

        //4.创建statement
        //statement可以发送SQL语句到数据库,并且获取返回结果
        Statement statement = connection.createStatement();

        //5.发送SQL语句(5.1编写SQL语句  5.2发送SQL语句)
        String sql = "select * from t_user where account = '" + account + "' and password = '" + password + "';";
        /**
         * SQL分类:DDL(容器创建,修改,删除) DML(插入,修改,删除) DQL(查询) DCL(权限控制) TPL(事务控制语言)
         *
         * .executeQuery(sql)里的sql分两种状况:
         * 参数:sql 是 非DQL
         *  返回:int
         *    情况1:DML 返回影响的行数,例如:删除了3条数据就return 3,插入了2条数据就return 2,修改了0条数据就return 0
         *    情况2:非DML return 0
         *
         * 参数:sql 是 DQL
         *  返回:resultSet 结果封装对象
         *  ResultSet resultset = executeQuery(sql);
         */
        //非DQL
//        int i = statement.executeUpdate(sql);
        //DQL
        ResultSet resultSet = statement.executeQuery(sql);

        //6.查询结果集解析 resultSet
        /**
         * Java是一种面向对象的思维,将查询结果封装成了resultSet对象,我们应该理解,内部一定也是有行和列的
         *
         * resultSet -> 逐行获取数据 -> 行和列的数据
         *
         * A ResultSet object maintains a cursor pointing to its current row of data.
         * Initially the cursor is positioned before the first row.
         * The next method moves the cursor to the next row, and
         * because it returns false when there are no more rows in the ResultSet object,
         * it can be used in a while loop to iterate through the result set.
         * 译文:
         * ResultSet对象保持一个指向当前数据行的光标。最初,光标位于第一行之前。next 方法会将光标移动到下一行,
         * 当 ResultSet 对象中没有更多行时,该方法会返回 false,因此可以在 while 循环中使用该方法遍历结果集。
         *
         * 想要进行数据解析,我们需要进行两件事情: 1.移动游标指定获取数据行 2.获取指定数据行的列数据
         * 1.游标移动问题
         *      resultSet内部包含一个游标,指定当前数据
         *      默认游标指定的是第一行数据之前
         *      我们可以调用next方法向后移动一行游标
         *      如果我们有很多行数据,我们可以使用while(next){获取每一行数据}
         *      boolean = next()  返回 true:有更多行数据,并且向下移动一行
         *                        返回 false:没有更多行数据,但不一定
         *
         *      移动游标的方法有很多,只需要记next()即可,配合while循环获取全部数据!
         *      真正要改的不是如何移动游标,而是要改你查询的SQL语句,让你查询的数据都是正确的,而不是让游标来回跳
         *
         * 2.获取列的数据问题(获取游标指定的那一行里列的数据)
         *      resultSet.get类型(String columnLabel 或 int columnIndex);
         *        columnLable: 列名 如果起别名了就 写别名 select * ...
         *                                             select id as aid, account as ac ...
         *
         *        columnIndex: 列的下角标 从左向右 从1开始
         */
        //遍历ResultSet里的数据
//        while (resultSet.next()){
//            //第一次的resultSet.next()使游标指向第一行数据
//            int id = resultSet.getInt(1);
//            String account1 = resultSet.getString("account");
//            String password1 = resultSet.getString(3);
//            String nickname = resultSet.getString("nickname");
//            System.out.println(id+"--"+account1+"--"+password1+"--"+nickname);
//        }

        //移动一次游标,只要有数据,就代表登陆成功
        if (resultSet.next()) {
            System.out.println("登陆成功!");
        } else {
            System.out.println("登陆失败!");
        }

        //7.关闭(释放)资源
        resultSet.close();
        statement.close();
        connection.close();

        return;
    }
}

5.存在问题

  • SQL语句需要字符串拼接的时候,会比较麻烦
    • 在执行动态SQL语句时,需要字符串拼接
  • 只能拼接字符串类型,其它的数据库里包含的数据类型无法处理
  • 可能发生注入攻击
    • 注入攻击:动态值充当了SQL语句结果,影响力原有的查询结构

(五)基于preparedStatement方式进行优化

  • 重点掌握:
    • 利用PreparedStatement解决上述案例的注入攻击SQL语句的拼接问题
package com.atguigu.api.PreparedStatement;

import java.sql.*;
import java.util.Scanner;

/**
 * Description:
 * 使用预编译statement完成用户登录
 * <p>
 * 防止注入攻击 并 演示ps的使用流程
 */
public class PSUserLoginPart {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        //1.键入信息
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入账号:");
        String account = scanner.nextLine();
        System.out.print("请输入密码:");
        String password = scanner.nextLine();

        //2.preparedStatement的数据库流程
        //2.1注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2.2获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?user=root&password=root");

        /**
         *
         * statement
         *  1.创建statement
         *  2.拼接SQL语句
         *  3.发送SQL语句,并且获取返回结果
         *
         * preparedstatement
         *  1.编写SQL语句结果 - 不包含动态值部分的语句,动态值部分使用占位符'?'来代替。 -> '?'只能代替动态值
         *  2.创建preparedstatement,并且传入动态值
         *  3.发送SQL语句即可,并获取返回结果
         *  注:? 只能替代值,不能替代关键字和容器名
         */

        //3.编写SQL语句结果
        String sql = "select * from t_user where account = ? and password = ? ;";

        //4.创建预编译statement并且设置SQL语句结果
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //5.对单独的占位符进行赋值
        /**
         * .setObject(parameterindex, Object x):
         * 参数1:parameterindex - 占位符的位置 从左向右 从1开始
         *  例: String sql = "select * from t_user where account = ? and password = ? ;"
         * 参数2:object 占位符的(动态)值 可以设置任何类型的数据,避免了我们拼接,并且让类型更加丰富
         */
        //account的要占的是第1个占位符的位置
        preparedStatement.setObject(1, account);
        //password的要占的是第2个占位符的位置
        preparedStatement.setObject(2, password);

        //6.发送SQL语句,并获取返回结果
        //TODO:此时系统已经知道语句和语句的动态值
        //preparedStatement.executeUpdate() / preparedStatement.executeQuery(); 
        ResultSet resultSet = preparedStatement.executeQuery();

        //7.结果集解析
        if (resultSet.next()) {
            System.out.println("登陆成功!");
        } else {
            System.out.println("登陆失败!");
        }

        //8.关闭(释放)资源
        resultSet.close();
        preparedStatement.close();
        connection.close();

        return;
    }
}

在这里插入图片描述

(六)基于preparedStatement演示CURD

  • 记得先创建一个测试类
/**
 * ClassName: PS_CURD_Part
 * Package: com.atguigu.api.PreparedStatement
 * Description:
 * 使用preparedStatement进行t_user表的CURD操作 - Create Update Read Delete
 */
public class PS_CURD_Part {

}

1.数据库数据插入

    //测试方法需要导入junit的测试包
    /**
     * 向t_user表中插入一条数据
     */
    @Test
    public void testInsert() throws ClassNotFoundException, SQLException {
        //1.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2.获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "root");
        //3.编写SQL语句结果,动态值的部分用'?'代替
        String sql = "insert into t_user(account,password,nickname) values(?,?,?);";
        //4.创建preparedStatement,并且传入SQL语句结果
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //5.占位符赋值
        preparedStatement.setObject(1, "test");
        preparedStatement.setObject(2, "test");
        preparedStatement.setObject(3, "二狗子");
        //6.发送SQL语句
        //DML类型的操作
        int rows = preparedStatement.executeUpdate();
        //7.输出结果
        if (rows > 0) {
            System.out.println("数据插入成功!");
        } else {
            System.out.println("数据插入失败!");
        }
        //8.关闭(释放)资源
        preparedStatement.close();
        connection.close();
    }

2.数据库数据修改

    /**
     * 修改 id=3 的用户nickname为三狗子
     */
    @Test
    public void testUpdate() throws ClassNotFoundException, SQLException {
        //1.注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2.获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?user=root&password=root");
        //3.编写SQL语句结果,动态值的部分用'?'代替
        String sql = "update t_user set nickname = ? where id = ?;";
        //4.创建preparedStatement,并且传入SQL语句结果
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //5.占位符赋值
        preparedStatement.setObject(1, "三狗子");
        preparedStatement.setObject(2, 3);
        //6.发送SQL语句
        int rows = preparedStatement.executeUpdate();
        //7.输出结果
        if (rows > 0) {
            System.out.println("修改成功!");
        } else {
            System.out.println("修改失败!");
        }
        //8.关闭(释放)资源
        preparedStatement.close();
        connection.close();
    }

3.数据库数据删除

  • 一样的逻辑,就不写注释了
    /**
     * 删除 id = 3 的用户
     */
    @Test
    public void testDelete() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "root");
        String sql = "delete from t_user where id = ?;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1, 3);
        int i = preparedStatement.executeUpdate();
        if (i > 0) {
            System.out.println("删除成功!");
        } else {
            System.out.println("删除失败!");
        }

        preparedStatement.close();
        connection.close();
    }

4.数据库数据查询

    /**
     * 目标:查询所有用户数据,并且封装到List<Map>中
     * <p>
     * 实现目标的流程:
     * 数据库 -> resultSet -> java -> 一行 -> map(key=别名,value=列的内容) -> List<Map> list
     * 什么是一行?
     * 答:
     * 行   id account password nickname
     * 行   id account password nickname
     * ......
     * <p>
     * 实现思路:
     * 遍历数据,一行对应一个map,获取一行的列名和对应的列的属性,装配即可,最后将map装到一个List里就可以了
     * <p>
     * 难点:如何获取表中每一个列的名称?
     */
    @Test
    public void testSelect() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "root");
        String sql = "select id,account as AC from t_user";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //这里不需要占位符,因为是查询操作而非其它的操作
        ResultSet resultSet = preparedStatement.executeQuery();

        //结果集解析
        /**
         * 回顾
         * resultSet: 有行和有列
         *             获取数据的时候,一行一行数据来获取
         *             我们可以利用next()方法移动游标,来指向数据行,并获取行中的列的数据
         */
        //先定义出来一个装Map的List
        List<Map> list = new ArrayList<>();

        // 获取列的信息的对象
        // TODO: metaData里装的是 当前结果集中的列的信息对象。 它可以根据下角标获取列的名称,也可以获取列的数量
        ResultSetMetaData metaData = resultSet.getMetaData();

        // 有了该对象以后,我们可以水平(方向)地来遍历列
        //获取该表的总列数
        int columnCount = metaData.getColumnCount();
        while (resultSet.next()) {
            Map map = new HashMap();
            //一行数据 对应一个map

            //不推荐
            //纯手动取值(纯手动拿数据)
//            map.put("id", resultSet.getInt("id"));
//            map.put("account", resultSet.getString("account"));
//            map.put("password", resultSet.getString("password"));
//            map.put("nickname", resultSet.getString("nickname"));

            //推荐
            // 获取列的信息对象(在该循环语句的的上面书写) - ResultSetMetaData metaData = resultSet.getMetaData();
            // 自动遍历列 - 注意:要从i=1开始遍历,循环条件要小于等于总列数
            for (int i = 1; i <= columnCount; i++) {
                //获取指定列下角标的值 resultSet
                Object value = resultSet.getObject(i);
                //获取指定列的下角标的列名! ResultSetMetaData
                //不要使用getColumnName 因为它只会获取列的名称,不会获取别名
                //getColumnLabel会获取别名  如果没有写别名才是列的名称
                String columnLabel = metaData.getColumnLabel(i);

                map.put(columnLabel, value);
            }

            //程序执行到这里时,一行数据的所有列就全部存到了map中
            //将map存储到集合中即可
            list.add(map);
        }
        System.out.println("List = " + list);

        resultSet.close();
        preparedStatement.close();
        connection.close();
    }

(七)preparedStatement使用方式总结

1.使用步骤总结

//1.注册驱动

//2.获取连接

//3.编写SQL语句

//4.创建preparedstatement并且传入SQL语句结构

//5.占位符赋值

//6.发送SQL语句,并且获取结果 

//7.结果集解析

//8.关闭资源

2.使用API总结

//1.注册驱动
方案1: 调用静态方法,但是会注册两次
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
方案2: 反射触发 - 注册一次
Class.forName("com.mysql.cj.jdbc.Driver");

//2.获取连接

Connection connection = DriverManager.getConnection();

3 (String url,String user,String password)
2 (String url,Properties info(user password))
1 (String url?user=账号&password=密码 )

//3.创建statement

//静态
Statement statement = connection.createStatement();
//预编译
PreparedStatement preparedstatement = connection.preparedStatement(sql语句结构);

//4.占位符赋值

preparedstatement.setObject(?的位置从左向右从1开始,?的值)

//5.发送sql语句获取结果

int rows = executeUpdate(); //非DQL
Resultset = executeQuery(); //DQL

//6.查询结果集解析

//移动光标指向行数据 next();  if(next())  while(next())
//获取列的数据即可   get类型(列的下标 从1开始 | 别名或列名)
//获取列的信息   getMetadata(); ResultsetMetaData对象 包含的就是列的信息
                getColumnCount(); //列的数量
                getCloumnLebal(index); //列的别名,没有别名直接取列名
//7.关闭资源
close(); 

三、JDBC扩展提示

(一)自增长主键回显的实现

1.功能需求

  • 主键回显
    • 定义:在数据库里的表中插入一条记录时,如果该表设置了适当的参数,那么数据库会在插入操作完成后返回生成的主键值
      • 这个主键可以是有数据库自动生成的,也可以是由开发人员明确指定的。
    • 作用:主键回显能够允许开发者在执行数据库中表的插入操作后,立即在Java后端中获取到新插入记录的主键(值),方便后续的操作和数据管理
  • 这里要解决的问题:
    • 主表默认增长,但是从表不知道值,所以要主键回显,让从表知道主表的主键增长了
    • 解决思路
      • 在多表关联插入数据时,一般主表的主键都是自动生成的,因此在插入数据之前我们无法知道这条数据的主键,但是从表需要在插入数据之前就绑定主表的主键,这时可以使用主键回显技术

2.功能实现

  • 继续沿用之前的数据库里的表的数据
    /**
     * TODO:
     *     t_user插入一条数据,并且 -- 获取数据库自增长的主键 --
     * <p>
     * 使用总结:
     *  1.创建prepareStatement的时候,在.prepareStatement()的参数列表中加入一个参数,
     *      来告知数据库,返回数据的时候记得携带数据库自增长的主键。
     *    加入的参数:Statement.RETURN_GENERATED_KEYS
     * <p>
     *  2.获取 插入数据之后,其主键值也已经更新完后的 结果集对象,
     *      获取对应的数据即可 - ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
     */
    @Test
    //主键回显 和 主键值获取
    public void returnPrimaryKey() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1/atguigu", "root", "root");
        String sql = "insert into t_user(account,password,nickname) value(?,?,?)";

        //创建preparedStatement
        //注意:"PreparedStatement.Statement.RETURN_GENERATED_KEYS"的目的:要数据库返回数据时把KEYS的内容也带回来
        PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

        preparedStatement.setObject(1, "test1");
        preparedStatement.setObject(2, 123456);
        preparedStatement.setObject(3, "驴蛋蛋");
        int i = preparedStatement.executeUpdate();

        //结果分析
        if (i > 0) {
            System.out.println("插入成功!");
            //可以获取回显的主键
            //获取搞完主键后的结果集对象,一行 一列 ,id = 值
            ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
            generatedKeys.next();
            int id = generatedKeys.getInt(1);
            System.out.println("id = " + id);
        } else {
            System.out.println("插入失败!");
        }

        preparedStatement.close();
        connection.close();
    }

(二)批量数据插入的性能提升

1.功能需求

  • 批量数据插入优化
  • 提升大量数据插入效率

2.功能实现

(1)使用普通的方式插入10000条数据所需的时间
    @Test
    //使用普通的方式插入10000条数据所需的时间
    public void testInsert() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1/atguigu", "root", "root");
        String sql = "insert into t_user(account,password,nickname) value(?,?,?)";

        //创建preparedStatement
        //注意:"PreparedStatement.Statement.RETURN_GENERATED_KEYS"的目的:要数据库返回数据时把KEYS的内容也带回来
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //占位符赋值
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            preparedStatement.setObject(1, "dd" + i);
            preparedStatement.setObject(2, "dd" + i);
            preparedStatement.setObject(3, "驴蛋蛋" + i);

            //TODO: 占位符赋值之后,发送SQL语句,并返回结果
            preparedStatement.executeUpdate();
        }
        long end = System.currentTimeMillis();
        //结果分析
        System.out.println("执行10000次数据插入消耗的时间: " + (end - start));//26816毫秒

        preparedStatement.close();
        connection.close();
    }
(2)优化:使用 批量插入 的方式插入10000条数
    @Test
    //批量插入数据优化
    //使用 批量插入 的方式插入10000条数据所需的时间
    /**
     * 总结
     * 批量输入
     *      1.路径后面添加 ?rewriteBatchedStatements=true 允许批量输入
     *      2.insert into values[必须要写被插入数据的列] 且 SQL语句的最后不能添加';'
     *      3.不是执行每条语句,而是利用addBatch()来批量添加数据到SQL语句中的values后面
     *      4.遍历添加数据完以后,统一执行executeBatch()来执行SQL语句
     */
    public void testBatchInsert() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        //TODO:这里添加了一个url的路径属性里的可选信息 - "rewriteBatchedStatements=true"
        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1/atguigu?rewriteBatchedStatements=true", "root", "root");

        //TODO:这里不能写value,要写values,一直写values就行,记住
        //TODO: 这里SQL语句最后不能加上 ';' 否则会执行失败。 
        //        原因:不加';'是因为 - 批量插入的原理是在原来的sql语句后面继续追加相应的sql语句
        String sql = "insert into t_user(account,password,nickname) values(?,?,?)";

        //创建preparedStatement,TODO:注意"com.atguigu.api.PreparedStatement.Statement.RETURN_GENERATED_KEYS"的目的是要数据库返回数据时把KEYS的内容也带回来
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //测试发送的时间
        long start = System.currentTimeMillis();

        //占位符赋值
        for (int i = 0; i < 10000; i++) {
            preparedStatement.setObject(1, "ddd" + i);
            preparedStatement.setObject(2, "ddd" + i);
            preparedStatement.setObject(3, "驴蛋蛋d" + i);

            //TODO: 占位符赋值之后,发送SQL语句,并返回结果
            //TODO:删去preparedStatement.executeUpdate();

            //TODO:赋值一次就传给数据库一次的想法就不用了
            //TODO:而是用 addBatch() 把赋值后的数据直接追加到SQL语句中的values后面
            preparedStatement.addBatch();
        }
        //TODO:最后执行批量操作
        preparedStatement.executeBatch();

        long end = System.currentTimeMillis();

        //结果分析
        System.out.println("执行10000次数据插入消耗的时间: " + (end - start));//310毫秒

        preparedStatement.close();
        connection.close();
    }

(三)JDBC中数据库事务的实现

1.章节目标

  • 使用JDBC代码,添加数据库事务动作
  • 开启事务
  • 事务提交 / 事务回滚

2.事务概念回顾

// 事务概念
   数据库事务就是一种SQL语句执行的缓存机制,不会单条执行完毕就更新数据库数据,最终根据缓
   存内的多条语句执行结果统一判定!
   一个事务内所有语句都成功及事务成功,我们可以触发commit提交事务来结束事务,更新数据!
   一个事务内任意一条语句失败,及事务失败,我们可以触发rollback回滚结束事务,
   数据回到事务之前状态!
   
   举个例子: 
           临近高考,你好吃懒做,偶尔还瞎花钱,父母也只会说'你等着!',待到高考完毕!
           成绩600+,翻篇,庆祝!
           成绩200+,翻旧账,男女混合双打!
           
//优势
   允许我们在失败情况下,数据回归到业务之前的状态! 
   
//场景
   一个业务涉及多条修改数据库语句!
   例如: 经典的转账案例,转账业务(加钱和减钱)   
         批量删除(涉及多个删除)
         批量添加(涉及多个插入)     
         
// 事务特性
  1. 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,
  要么都不发生。 

  2. 一致性(Consistency)事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

  3. 隔离性(Isolation)事务的隔离性是指一个事务的执行不能被其他事务干扰,
  即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

  4. 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,
  接下来的其他操作和数据库故障不应该对其有任何影响

// 事务类型
  
  自动提交 : 每条语句自动存储到一个事务中,执行成功自动提交,执行失败自动回滚! (MySQL)
  手动提交:  手动开启事务,添加语句,手动提交或者手动回滚即可!
  
// sql开启事务方式【事务都在一个连接中】
   推荐 - 针对自动提交: 关闭自动提交即可,多条语句添加以后,最终手动提交或者回滚!
     
      SET autocommit = off; //关闭当前连接connection自动事务提交方式
      # 只有当前连接有效
      # 编写SQL语句即可
      SQL
      SQL
      SQL
      #手动提交或者回滚 【结束当前的事务】
      COMMIT / ROLLBACK ;  
     
   不推荐 - 手动开启事务: 开启事务代码,添加SQL语句,事务提交或者事务回滚!

// 呼应jdbc技术
 
  try{
    connection.setAutoCommit(false); //关闭自动提交了
    //connection.setAutoCommit(false)也就类型于SET autocommit = off
    
    //注意,只要当前connection对象,进行数据库操作,都不会自动提交事务
    //JDBC中XxxxStatement和Connection对于数据库的操作:
    //	XxxxStatement - 单一的数据库操作:C U R D
    //	Connection - 操作事务 
    
    connection.commit();
  }catch(Execption e){
    connection.rollback();
  }

3.数据库表的数据

-- 继续在atguigu的库中创建银行表
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);

4.代码结构设计图

在这里插入图片描述

5.JDBC事务实现

(1)测试类
public class BankTest {

    @Test
    public void testBank() throws Exception {
        BankService bankService = new BankService();
        bankService.transfer("ergouzi", "lvdandan",
                500);
    }

}
(2)数据库表对应的实体类
package com.atguigu.api;

/**
 * ClassName: User
 * Package: com.atguigu.api.PreparedStatement
 * Description:
 * 数据库表t_bank对应的实体类
 */
public class T_Bank {
    public int id;
    public String account;
    public int money;
}
(3)BankService
package com.atguigu.api.TransAction;

import org.testng.annotations.Test;

import java.sql.Connection;
import java.sql.DriverManager;

/**
 * ClassName: BankService
 * Package: com.atguigu.api.PreparedStatement.TransAction
 * Description:
 * 银行卡业务方法,调用dao方法
 */
public class BankService {
    @Test
    public void testStart() throws Exception {
        //二狗子给驴蛋蛋转500
        transfer("lvdandan", "ergouzi", 500);

    }

    /**
     * TODO:
     *      事务的添加要在业务方法(Service层)中!
     *      利用try-catch-finally代码块,来开启事务和提交事务,以及事务回滚!
     *      将connection传入dao层即可!
     *      dao层只负责使用,不要把connection给.close()掉,finally最后会关闭回收的
     *
     * @param addAccount
     * @param subAccount
     * @param money
     * @throws Exception
     */
    public void transfer(String addAccount, String subAccount, int money) throws Exception {

        BankDao bankDao = new BankDao();

        //一个事务的最基本要求,必须是同一个连接对象connection
        //一个转账方法 属于一个事务(加钱、减钱)

        //注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "root");

        try {
            //开启事务
            //关闭事务提交!
            connection.setAutoCommit(false);

            //执行数据库动作
            bankDao.add(addAccount, money, connection);
            System.out.println("-------------------");
            bankDao.sub(subAccount, money, connection);

            //事务提交
            connection.commit();
        } catch (Exception e) {
            //如果报错了,就事务回滚
            connection.rollback();
            //抛出异常
            throw e;
        } finally {
            //关闭(释放)连接
            connection.close();
        }
    }
}
(4)BankDao:具体操作方法
package com.atguigu.api.TransAction;

import java.sql.Connection;
import java.sql.PreparedStatement;

/**
 * ClassName: BankDao
 * Package: com.atguigu.api.PreparedStatement.TransAction
 * Description:
 * bank表的数据库操作方法存储类
 */
public class BankDao {

    /**
     * 加钱的数据库操作方法 (JDBC)
     *
     * @param account 加钱的行号
     * @param money   加钱的金额
     */
    public void add(String account, double 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);
        int i = preparedStatement.executeUpdate();
        preparedStatement.close();

        System.out.println("加钱成功!");
    }


    /**
     * 减钱的数据库操作方法 (JDBC)
     *
     * @param account 减钱的行号
     * @param money   减钱的金额
     */
    public void sub(String account, double 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);
        int i = preparedStatement.executeUpdate();
        preparedStatement.close();

        System.out.println("减钱成功!");
    }
}

6.代码结构设计

在这里插入图片描述

在业务类中创建连接

在这里插入图片描述
在这里插入图片描述

四、Druid连接池技术

(一)连接性能消耗问题的分析

在这里插入图片描述

(二)数据库连接池的作用

在这里插入图片描述
在这里插入图片描述

(三)市面上常见的连接池产品及其对比

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(四)国货之光Druid连接池的使用

  • 记得导入Druid工具类的jar包
  • 记得创建一个学习Druid的测试类
/**
 * ClassName: DruidUsePar
 * Package: com.atguigu.api.druid
 * Description:
 * druid连接池使用类
 */
public class DruidUsePart {

}

1.硬编码方式(了解,不推荐)

    /**
     * 硬编码实现
     * <p>
     * 1.创建一个druid连接池对象
     * 2.设置连接池参数[必须 | 非必须]
     * 3.获取连接[通用方法,所有连接池都一样]
     * 4.回收连接[通用方法,所有连接池都一样] TODO:这里不是 “关闭(释放)连接” 而是 “回收连接”
     */
    @Test
    public void testHard() throws SQLException {
        //连接池对象
        //DruidDataSource实现了Java规定标准的的DataSource接口
        DruidDataSource druidDataSource = new DruidDataSource();
        //设置参数
        //必须: 连接数据库驱动类的全限定符[注册驱动] | url | user | password
        druidDataSource.setUrl("jdbc:myql://127.0.0.1:3306/atguigu");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("root");
        druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");//帮助我们进行驱动注册和获取连接
        //非必须:初始化连接数量 , 最大的连接数量 , ......
        druidDataSource.setInitialSize(5);//初始化连接数量
        druidDataSource.setMaxActive(10);//最大的连接数量
        //获取连接
        //DruidPooledConnection实现了Java规定标准的Connection接口
        //因此现在 用Connection类 来 实例化druidPooledConnection对象后,
        //TODO:当调用.close()方法时,已经 不是关闭(释放)连接 ,而是 回收连接 了
        Connection druidPooledConnection = druidDataSource.getConnection();
        //数据库CURD

        //回收连接
        druidPooledConnection.close();//连接池提供的连接的.close()就是回收连接
    }

2.软编码方式

(1)外部配置
  • 存放位置:src/druid.properties

在这里插入图片描述

# 注:外部配置文件后缀必须是properties!
# druid连接池需要的配置参数,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///atguigu
(2)Druid声明代码
    /**
     * 软编码实现
     * 通过读取外部配置文件的方法,实例化druid连接池对象
     */
    @Test
    public void testSoft() throws Exception {
        //1.读取外部配置文件 Properties
        Properties properties = new Properties();
        //src下的文件,可以使用类加载器提供的方法实现装载(properties.load(param))
        InputStream inputPropertiesStream = DruidUsePart.class.getClassLoader().getResourceAsStream("druid.properties");
        properties.load(inputPropertiesStream);
        //2.使用连接池的工具类的工厂模式(DruidDataSourceFactory)来创建连接池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
        Connection connection = dataSource.getConnection();
        
        //数据库CURD
        //......
        
        //回收连接
        connection.close();
    }

3.Druid配置(了解)

配置缺省说明
name配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
jdbcUrl连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username连接数据库的用户名
password连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter
driverClassName根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize0初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive8最大连接池数量
maxIdle8已经不再使用,配置了也没效果
minIdle最小连接池数量
maxWait获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatementsfalse是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements-1要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrowtrue申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturnfalse归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdlefalse建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls物理连接初始化的时候执行的sql
exceptionSorter根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

4.笔记

在这里插入图片描述

  • 连接池帮我们进行注册驱动、创建链接
    在这里插入图片描述
  • 关于外部配置文件
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

五、JDBC使用优化以及工具类封装

  • 过程:
    • 1.注册驱动
    • 2.获取连接
    • 3.编写SQL语句
    • 4.创建XxxxxStatement
    • 5.占位符赋值
    • 6.发送SQL语句
    • 7.结果解析
    • 8.回收资源
  • 下面v1.0和v2.0针对 “1.注册驱动”,“2.获取连接”,“8.回收资源” 进行封装
  • BaseDao针对 “3.编写SQL语句”,“4.创建statement”,“5.占位符赋值”,“6.发送SQL语句”,“7.结果解析” 进行封装,进行增删改查
    在这里插入图片描述

(一)JDBC工具类封装version1.0

在这里插入图片描述

1.外部配置文件

位置: src/druid.properties

# druid连接池需要的配置参数,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///atguigu

2.工具类代码

package com.atguigu.api.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.SQLException;
import java.util.Properties;

/**
 * ClassName: JDBC_Utils
 * Package: com.atguigu.api.utils
 * Description:
 * v1.0版本工具类
 * 内部包含一个连接池对象,并且对外提供获取连接和回收连接的方法
 * <p>
 * 小建议:
 * 工具类的方法推荐写成静态方法,这样的话外部调用会更加方便
 * <p>
 * 实现:
 * 属性 连接池对象[实例化一次]
 * <p>
 * 实例化一次的方法:
 * 1.单例模式
 * 2.static{全局调用一次} (静态代码块)
 * <p>
 * 方法:
 * 1.对外提供连接的方法
 * 2.回收外部传入的连接的方法
 */
public class JDBC_Utils {
    private static DataSource dataSource = null;//连接池对象

    //初始化连接池对象
    static {
        Properties properties = new Properties();
        InputStream ips = JDBC_Utils.class.getClassLoader().getResourceAsStream("druid.properties");
        try {
            properties.load(ips);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        try {
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 对外提供连接的方法
     */
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    /**
     * 回收连接
     */
    public static void RecycleConnection(Connection connection) throws SQLException {
        connection.close();//连接池的连接,调用close()就是回收
    }
}

(二)JDBC工具类封装version2.0

  • 对工具类v1.0版本的进一步优化
    • 在考虑事务的情况下,如何从一个线程里的不同方法中获取同一个连接
      在这里插入图片描述
      在这里插入图片描述

1. 工具类v2.0

package com.atguigu.api.utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 * ClassName: JDBC_Utils
 * Package: com.atguigu.api.utils
 * Description:
 * v2.0版本工具类
 * 内部包含一个连接池对象,并且对外提供获取连接和回收连接的方法
 *  TODO:
 *      利用线程本地变量(ThreadLocal)来存储连接信息!确保一个线程的多个方法可以获取同一个connection!
 *      优势: 事务操作的时候 service 和 dao 属于同一个线程,不用再传递connection的参数了
 *      大家都可以调用getConnection()自动获取相同的连接池
 */
public class JDBC_Utils_V2 {
    private static DataSource dataSource = null;//连接池对象

    //TODO:线程本地变量
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    //初始化连接池对象
    static {
        //方法一 v1.0
//        Properties properties = new Properties();
//        InputStream ips = JDBC_Utils_V2.class.getClassLoader().getResourceAsStream("druid.properties");
//        try {
//            properties.load(ips);
//        } catch (IOException e) {
//            throw new RuntimeException(e);
//        }
//
//        try {
//            dataSource = DruidDataSourceFactory.createDataSource(properties);
//        } catch (Exception e) {
//            throw new RuntimeException(e);
//        }

        //方法二 v2.0
        try {
            Properties properties = new Properties();
            properties.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 对外提供连接的方法
     */
    public static Connection getConnection() throws SQLException {
        //线程本地变量是否存在连接
        Connection connection = threadLocal.get();

        //第一次没有
        if (connection == null) {
            //线程本地变量没有,从连接池中获取连接
            connection = dataSource.getConnection();
            threadLocal.set(connection);
        }

        return connection;
    }

    /**
     * 回收连接
     */
    public static void RecycleConnection() throws SQLException {
        Connection connection = threadLocal.get();
        if (connection != null) {
            threadLocal.remove();//清空线程本地变量的数据
            connection.setAutoCommit(true);//把BankService里面,修改过的事务提交状态给恢复一下(false -> true)
            connection.close();//将拿出来的连接回收到连接池即可
        }
    }
}

2. BankService v2.0

package com.atguigu.api.TransActionV2;

import com.atguigu.api.utils.JDBC_Utils_V2;
import org.testng.annotations.Test;

import java.sql.Connection;

/**
 * ClassName: BankService
 * Package: com.atguigu.api.PreparedStatement.TransAction
 * Description:
 * 银行卡业务方法,调用dao方法
 */
public class BankService {
    @Test
    public void testStart() throws Exception {
        //二狗子给驴蛋蛋转500
        transfer("lvdandan", "ergouzi", 500);

    }

    /**
     * TODO:
     *      事务的添加要在业务方法(Service层)中!
     *      利用try-catch-finally代码块,来开启事务和提交事务,以及事务回滚!
     *      将connection传入dao层即可!
     *      dao层只负责使用,不要把connetion给.close()掉,finally最后会关闭回收的
     *
     * @param addAccount
     * @param subAccount
     * @param money
     * @throws Exception
     */
    public void transfer(String addAccount, String subAccount, int money) throws Exception {
        BankDao bankDao = new BankDao();

        Connection connection = JDBC_Utils_V2.getConnection();

        try {
            //开启事务
            //关闭事务提交!
            connection.setAutoCommit(false);

            //执行数据库动作
            bankDao.add(addAccount, money);
            System.out.println("-------------------");
            bankDao.sub(subAccount, money);

            //事务提交
            connection.commit();
        } catch (Exception e) {
            //如果报错了,就事务回滚
            connection.rollback();
            //抛出异常
            throw e;
        } finally {
            //关闭(释放)连接
            JDBC_Utils_V2.RecycleConnection();
        }
    }
}

3.BankDao v2.0

package com.atguigu.api.TransActionV2;

import com.atguigu.api.utils.JDBC_Utils_V2;

import java.sql.Connection;
import java.sql.PreparedStatement;

/**
 * ClassName: BankDao
 * Package: com.atguigu.api.PreparedStatement.TransAction
 * Description:
 * bank表的数据库操作方法存储类
 */
public class BankDao {

    /**
     * 加钱的数据库操作方法 (JDBC)
     *
     * @param account 加钱的行号
     * @param money   加钱的金额
     */
    public void add(String account, double money) throws Exception {
        Connection connection = JDBC_Utils_V2.getConnection();
        String sql = "update t_bank set money = money + ? where account = ?;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1, money);
        preparedStatement.setObject(2, account);
        int i = preparedStatement.executeUpdate();
        preparedStatement.close();

        System.out.println("加钱成功!");
    }


    /**
     * 减钱的数据库操作方法 (JDBC)
     *
     * @param account 减钱的行号
     * @param money   减钱的金额
     */
    public void sub(String account, double money) throws Exception {
        Connection connection = JDBC_Utils_V2.getConnection();
        String sql = "update t_bank set money = money - ? where account = ?;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1, money);
        preparedStatement.setObject(2, account);
        int i = preparedStatement.executeUpdate();
        preparedStatement.close();

        System.out.println("减钱成功!");
    }
}

(三)高级应用封装BaseDao

在这里插入图片描述

针对DQL查询和非DQL查询,分成两类(即,定义两个方法)

(1)老师(笔记里)给的代码
public abstract class BaseDao {
    /*
    通用的增、删、改的方法
    String sql:sql
    Object... args:给sql中的?设置的值列表,可以是0~n
     */
    protected int update(String sql,Object... args) throws SQLException {
//        创建PreparedStatement对象,对sql预编译
        Connection connection = JDBCTools.getConnection();
        PreparedStatement ps = connection.prepareStatement(sql);
        //设置?的值
        if(args != null && args.length>0){
            for(int i=0; i<args.length; i++) {
                ps.setObject(i+1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
            }
        }

        //执行sql
        int len = ps.executeUpdate();
        ps.close();
        //这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
        //connection.getAutoCommit()为false,不要在这里回收connection,由开启事务的地方回收
        //connection.getAutoCommit()为true,正常回收连接
        //没有开启事务的话,直接回收关闭即可!
        if (connection.getAutoCommit()) {
            //回收
            JDBCTools.free();
        }
        return len;
    }

    /*
    通用的查询多个Javabean对象的方法,例如:多个员工对象,多个部门对象等
    这里的clazz接收的是T类型的Class对象,
    如果查询员工信息,clazz代表Employee.class,
    如果查询部门信息,clazz代表Department.class,
    返回List<T> list
     */
    protected <T> ArrayList<T> query(Class<T> clazz,String sql, Object... args) throws Exception {
        //        创建PreparedStatement对象,对sql预编译
        Connection connection = JDBCTools.getConnection();
        PreparedStatement ps = connection.prepareStatement(sql);
        //设置?的值
        if(args != null && args.length>0){
            for(int i=0; i<args.length; i++) {
                ps.setObject(i+1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
            }
        }

        ArrayList<T> list = new ArrayList<>();
        ResultSet res = ps.executeQuery();

        /*
        获取结果集的元数据对象。
        元数据对象中有该结果集一共有几列、列名称是什么等信息
         */
         ResultSetMetaData metaData = res.getMetaData();
        int columnCount = metaData.getColumnCount();//获取结果集列数

        //遍历结果集ResultSet,把查询结果中的一条一条记录,变成一个一个T 对象,放到list中。
        while(res.next()){
            //循环一次代表有一行,代表有一个T对象
            T t = clazz.newInstance();//要求这个类型必须有公共的无参构造

            //把这条记录的每一个单元格的值取出来,设置到t对象对应的属性中。
            for(int i=1; i<=columnCount; i++){
                //for循环一次,代表取某一行的1个单元格的值
                Object value = res.getObject(i);

                //这个值应该是t对象的某个属性值
                //获取该属性对应的Field对象
                //String columnName = metaData.getColumnName(i);//获取第i列的字段名
                //这里再取别名可能没办法对应上
                String columnName = metaData.getColumnLabel(i);//获取第i列的字段名或字段的别名
                Field field = clazz.getDeclaredField(columnName);
                field.setAccessible(true);//这么做可以操作private的属性

                field.set(t, value);
            }

            list.add(t);
        }

        res.close();
        ps.close();
        //这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
        //没有开启事务的话,直接回收关闭即可!
        if (connection.getAutoCommit()) {
            //回收
            JDBCTools.free();
        }
        return list;
    }

	//目的主要是安全保障,提高代码的健壮性
    protected <T> T queryBean(Class<T> clazz,String sql, Object... args) throws Exception {
        ArrayList<T> list = query(clazz, sql, args);
        if(list == null || list.size() == 0){
            return null;
        }
        return list.get(0);
    }
}
(2)我自己写的,该代码主要看注释,促进理解
* 这个代码可能会有问题
* 如果是为了运行成功,那就用老师给的代码
package com.atguigu.api.utils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * ClassName: BaseDao
 * Package: com.atguigu.api.utils
 * Description:
 * 封装dao层重复的代码!
 *      TODO:
 *          封装两个方法: 1.简化非DQL 2.简化DQL
 */
public abstract class BaseDao {

    /**
     * 封装简化非DQL语句
     * 非DQL语句封装方法 -> 返回值 固定为int
     * @param sql    带占位符的SQL语句
     * @param params 占位符的值  注意:传入占位符的值,必须和SQL语句中'?'的位置相一致
     * @return 返回执行影响的行数
     */
    public int executeUpdate(String sql, Object... params) throws SQLException {
        //获取连接
        Connection connection = JDBC_Utils_V2.getConnection();

        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //占位符赋值
        //可变参数可以当作数组使用
        for (int i = 1; i <= params.length; i++) {
            preparedStatement.setObject(i, params[i - 1]);
        }

        //发送SQL语句
        //DML类型
        int rows = preparedStatement.executeUpdate();

        //释放与回收
        preparedStatement.close();
        //是否回收连接,需要考虑是不是事务!
        if (connection.getAutoCommit()) {
            //结果为true,说明没有开启事务(说明没有关闭自动提交-autoCommit),正常回收
            JDBC_Utils_V2.RecycleConnection();
        }
        //结果为false,开启事务的话就不要管连接,让业务层(Service)处理事务提交的开启和关闭!
        //connection.setAutoCommit(false);

        return rows;
    }

    /**
     * DQL语句封装方法 -> 该方法的返回值是什么类型? 是List<T>类型。即,返回的是某个类型的实体类集合,而不是List<map>
     *                                         虽然 map的 优点是: key和value自定义,不用先设定好!
     *                                                但是缺点是: 没有数据校验机制
     *                                                           不支持反射操作
     *             数据库类型 对应着 java的实体类
     *               例: 数据库中table是t_user 表中列名有 - id account password nickname
     *               那么 java中就会对应着有 User类 类中属性包含 id account password nickname
     *               即,表中的一行 -> jav实体类的一个对象 ,表中多行 -> java中List<Java实体类> list
     *
     *
     *  <T>表示的是声明一个泛型,即不确定类型。好处:
     *      - 反射的时候,可以确定泛型的类型 User.class T = User
     *      - 要使用反射技术为属性赋值
     *  public <T> List<T> executeQuery(Class<T> clazz, String sql, Object... params)
     */
    /**
     * 将查询结果封装到一个实体类集合
     *
     * @param clazz  要接收返回值类型是实体类集合的模板对象
     * @param sql    查询语句,要求列名或者别名 对应上 实体类的属性名 例: mysql: u_Id as uId <=> java: uId
     * @param params 占位符的值要和'?'的位置相对应
     * @param <T>    声明的结果的泛型
     * @return 返回查询的实体类集合
     * @throws SQLException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws NoSuchFieldException
     */
    public <T> List<T> executeQuery(Class<T> clazz, String sql, Object... params) throws SQLException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Connection connection = JDBC_Utils_V2.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]);
            }
        }

        //发送SQL语句
        ResultSet resultSet = preparedStatement.executeQuery();

        //结果集解析
        ArrayList<T> list = new ArrayList<>();

        //获取列的信息对象
        ResultSetMetaData metaData = resultSet.getMetaData();

        int columnCount = metaData.getColumnCount();

        while (resultSet.next()) {
            //调用类的无参构造函数来实例化对象
            T t = clazz.getDeclaredConstructor().newInstance();

            //自动遍历列 注意:要从1开始,并且小于等于总列数
            for (int i = 1; i <= columnCount; i++) {
                //获取对象的属性值
                Object value = resultSet.getObject(i);
                //TODO:根据索引下标(1,2,......)来获取与之相对应的列的列名
                String propertyName = metaData.getColumnLabel(i);
                //反射,给对象的属性值赋值
                Field field = clazz.getDeclaredField(propertyName);
                field.setAccessible(true);//属性可以设置成打破private的修饰限制
                /**
                 * field.set(param1,param2)
                 * 参数1: 要赋值的对象 如果属性是静态,那么第一个参数可以为null
                 * 参数2: 具体的属性值
                 */
                field.set(t, value);
            }

            //把实体类对象放到集合中
            list.add(t);
        }

        //关闭资源
        resultSet.close();
        preparedStatement.close();
        if (connection.getAutoCommit()) {
            //如果没有开启事务 则可以关闭
            JDBC_Utils_V2.RecycleConnection();
        }
        return list;
    }

    protected <T> ArrayList<T> query(Class<T> clazz, String sql, Object... args) throws Exception {
        //创建PreparedStatement对象,对sql预编译
        Connection connection = JDBC_Utils_V2.getConnection();
        PreparedStatement ps = connection.prepareStatement(sql);
        //设置?的值
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
            }
        };

        /*
        获取结果集的元数据对象。
        元数据对象中有该结果集的信息,例如:一共有几列、列名称是什么,等等
         */
        ResultSet res = ps.executeQuery();
        ResultSetMetaData metaData = res.getMetaData();
        int columnCount = metaData.getColumnCount();//获取结果集列数

        //遍历结果集ResultSet,把查询结果中的一条一条记录,变成一个一个 T对象 ,放到list中。
        ArrayList<T> list = new ArrayList<>();
        while (res.next()) {
            //循环一次代表遍历了表中的一行,代表获取了一个T对象
            T t = clazz.newInstance();//要求这个类型必须有公共的无参构造

            //把这条记录的每一个单元格的值取出来,设置到t对象对应的属性中。
            for (int i = 1; i <= columnCount; i++) {
                //for循环一次,则根据 索引值从小到大(1,2,……) 取表的某一行数据中的1个单元格的值
                Object value = res.getObject(i);

                //这个值应该是t对象的某个属性值
                //获取该属性对应的Field对象。 例: 类里的一个name属性 对应 其一个name属性的Field对象
//                String columnName = metaData.getColumnName(i);//获取第i列的字段名
                String columnName = metaData.getColumnLabel(i);//获取第i列的字段名或字段的别名
                Field field = clazz.getDeclaredField(columnName);
                field.setAccessible(true);//这么做可以操作private的属性

                field.set(t, value);
            }

            list.add(t);
        }

        res.close();
        ps.close();
        //这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
        //没有开启事务的话,直接回收关闭即可!
        if (connection.getAutoCommit()) {
            //回收
            JDBC_Utils_V2.RecycleConnection();
        }
        return list;
    }
}

六、基于CMS项目的JDBC实战练习

(一)CMS项目介绍和导入

1.项目介绍

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.项目导入

(1)打开项目

在这里插入图片描述

(2)配置JDK
  • 统一配置成17
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

(二)基于CMS项目添加数据库相关配置

1.准备数据库脚本

-- 员工表

CREATE TABLE t_customer(
  id INT PRIMARY KEY AUTO_INCREMENT COMMENT '客户主键',
  NAME VARCHAR(20)  COMMENT '客户名称',
  gender VARCHAR(4) COMMENT '客户性别',
  age INT  COMMENT '客户年龄',
  salary DOUBLE(8,1) COMMENT '客户工资',
  phone VARCHAR(11) COMMENT '客户电话')

2.添加配置文件

在这里插入图片描述

# druid连接池需要的配置参数,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///atguigu

3.导入jdbcv2.0工具类

package com.atguigu.cms.utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/*
这个工具类的作用就是用来给所有的SQL操作 提供“连接” 和 释放连接 。
这里使用ThreadLocal的目的是为了让同一个线程,在多个地方getConnection得到的是同一个连接。
这里使用DataSource的目的: (1)限制服务器的连接的上限 (2)连接的重用性等
 */
public class JDBCTools {
    private static DataSource ds;
    private static ThreadLocal<Connection> tl = new ThreadLocal<>();

    static {//静态代码块,JDBCToolsVersion1类初始化执行
        try {
            Properties pro = new Properties();
            pro.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
            ds = DruidDataSourceFactory.createDataSource(pro);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() throws SQLException {
        Connection connection = tl.get();
        if (connection == null) {//当前线程还没有拿过连接,就给它从数据库连接池拿一个
            connection = ds.getConnection();
            tl.set(connection);
        }
        return connection;
    }

    public static void free() throws SQLException {
        Connection connection = tl.get();
        if (connection != null) {
            tl.remove();
            connection.setAutoCommit(true);//避免还给数据库连接池的连接不是自动提交模式(建议)
            connection.close();
        }
    }
}

4.导入baseDao工具类

package com.atguigu.cms.utils;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;

public abstract class BaseDao {
    /*
    通用的增、删、改的方法
    String sql:sql
    Object... args:给sql中的?设置的值列表,可以是0~n
     */
    protected int update(String sql, Object... args) throws SQLException {
//        创建PreparedStatement对象,对sql预编译
        Connection connection = JDBCTools.getConnection();
        PreparedStatement ps = connection.prepareStatement(sql);
        //设置?的值
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
            }
        }

        //执行sql
        int len = ps.executeUpdate();
        ps.close();
        //这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
        //没有开启事务的话,直接回收关闭即可!
        if (connection.getAutoCommit()) {
            //回收
            JDBCTools.free();
        }
        return len;
    }

    /*
    通用的查询多个Javabean对象的方法,例如:多个员工对象,多个部门对象等
    这里的clazz接收的是T类型的Class对象,
    如果查询员工信息,clazz代表Employee.class,
    如果查询部门信息,clazz代表Department.class,
     */
    protected <T> ArrayList<T> query(Class<T> clazz, String sql, Object... args) throws Exception {
        //        创建PreparedStatement对象,对sql预编译
        Connection connection = JDBCTools.getConnection();
        PreparedStatement ps = connection.prepareStatement(sql);
        //设置?的值
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
            }
        }

        ArrayList<T> list = new ArrayList<>();
        ResultSet res = ps.executeQuery();

        /*
        获取结果集的元数据对象。
        元数据对象中有该结果集一共有几列、列名称是什么等信息
         */
        ResultSetMetaData metaData = res.getMetaData();
        int columnCount = metaData.getColumnCount();//获取结果集列数

        //遍历结果集ResultSet,把查询结果中的一条一条记录,变成一个一个T 对象,放到list中。
        while (res.next()) {
            //循环一次代表有一行,代表有一个T对象
            T t = clazz.newInstance();//要求这个类型必须有公共的无参构造

            //把这条记录的每一个单元格的值取出来,设置到t对象对应的属性中。
            for (int i = 1; i <= columnCount; i++) {
                //for循环一次,代表取某一行的1个单元格的值
                Object value = res.getObject(i);

                //这个值应该是t对象的某个属性值
                //获取该属性对应的Field对象
//                String columnName = metaData.getColumnName(i);//获取第i列的字段名
                String columnName = metaData.getColumnLabel(i);//获取第i列的字段名或字段的别名
                Field field = clazz.getDeclaredField(columnName);
                field.setAccessible(true);//这么做可以操作private的属性

                field.set(t, value);
            }

            list.add(t);
        }

        res.close();
        ps.close();
        //这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
        //没有开启事务的话,直接回收关闭即可!
        if (connection.getAutoCommit()) {
            //回收
            JDBCTools.free();
        }
        return list;
    }

    protected <T> T queryBean(Class<T> clazz, String sql, Object... args) throws Exception {
        ArrayList<T> list = query(clazz, sql, args);
        if (list == null || list.size() == 0) {
            return null;
        }
        return list.get(0);
    }
}

(三)基于CMS项目实战

1.CustomerService

package com.atguigu.cms.service;

import com.atguigu.cms.dao.CustomerDao;
import com.atguigu.cms.javabean.Customer;

import java.sql.SQLException;
import java.util.List;

/**
 * 这是一个具有管理功能的功能类. 内部数据不允许外部随意修改, 具有更好的封装性.
 */
public class CustomerService {

    private CustomerDao customerDao = new CustomerDao();

    /**
     * 用途:返回所有客户对象
     * 返回:集合
     */
    public List<Customer> getList() {
        try {
            return customerDao.queryList();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 用途:添加新客户
     * 参数:customer指定要添加的客户对象
     */
    public void addCustomer(Customer customer) {
        try {
            customerDao.insertCustomer(customer);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 用途:返回指定id的客户对象记录
     * 参数: id 就是要获取的客户的id号.
     * 返回:封装了客户信息的Customer对象
     */
    public Customer getCustomer(int id) {
        try {
            return customerDao.queryById(id);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 用途:修改指定id号的客户对象的信息
     *
     * @param id   客户id
     * @param cust 对象
     * @return 修改成功返回true, false表明指定id的客户未找到
     */
    public boolean modifyCustomer(int id, Customer cust) {
        int rows = 0;
        try {
            rows = customerDao.updateCustomer(cust);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return rows > 0;
    }

    /**
     * 用途:删除指定id号的的客户对象记录
     * 参数: id 要删除的客户的id号
     * 返回:删除成功返回true;false表示没有找到
     */
    public boolean removeCustomer(int id) {
        int rows = 0;
        try {
            rows = customerDao.deleteCustomer(id);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return rows > 0;
    }
}

2.CustomerDao

package com.atguigu.cms.dao;

import com.atguigu.cms.javabean.Customer;
import com.atguigu.cms.utils.BaseDao;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * Description: 客户进行数据库操作的类
 */
public class CustomerDao extends BaseDao {

    public List<Customer> queryList() throws Exception {
        ArrayList<Customer> list = query(Customer.class, "select * from t_customer");
        return list;
    }

    public void insertCustomer(Customer customer) throws SQLException {
        int rows = update("insert into t_customer(name,gender,age,salary,phone) values (?,?,?,?,?)",
                customer.getName(), customer.getGender(), customer.getAge(), customer.getSalary(), customer.getPhone());
    }

    public Customer queryById(int id) throws Exception {
        Customer customer = queryBean(Customer.class, "select * from t_customer where id = ?", id);
        return customer;
    }

    public int deleteCustomer(int id) throws SQLException {
        return update("delete from t_customer where id =?", id);
    }

    public int updateCustomer(Customer cust) throws SQLException {
        return update("update t_customer set name = ? , gender = ? , age = ? ," +
                        "salary = ? , phone = ? where id = ? ;", cust.getName(), cust.getGender(),
                cust.getAge(), cust.getSalary(), cust.getPhone(), cust.getId());
    }
}
  • 42
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值