JDBC 基础学习笔记:API 核心原理解析与 CRUD 代码实操

JDBC学习记录

日期: 11.6

📖今日知识点 ------JDBC简介

1.JDBC概念:

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

2.JDBC 本质:

  • 官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口

  • 各个数据库厂商去实现这套接口,提供数据库驱动jar包

  • 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类

3.JDBC 好处:

  • 各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发

  • 可随时替换底层数据库,访问数据库的Java代码基本不变

📖今日知识点 ------JDBC快速入门

💻练习代码

数据准备:

sql 复制代码
create database atguigu;
use atguigu;
create table t_emp
(
emp_id int auto_increment comment '员工编号' primary key,
emp_name varchar(100) not null comment '员工姓名',
emp_salary double (10,5) not null comment '员工薪资',
emp_age int not null comment '员工年龄'
);
insert into t_emp (emp_name,emp_salary,emp_age) values 
('andy' ,7777,23),
('zhangsan',8888,24),
('xiaoyu',14567,34),
('lisi',6784,25);

JDBC 连接与数据查询实现

java 复制代码
package com.hstc.base;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class JDBCQuick {
    // 【修改点 1】:在 main 方法后面加上 throws Exception,这样就不会报未处理的异常(红叉)了
    public static void main(String[] args) throws Exception {
        //1.注册驱动
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        //2.获取连接对象
        // 【修改点 2】:你原来的 url 只写了 "jdbc:mysql://localhost:3306",没指定数据库名,会导致找不到 t_emp 表。
        // 我帮你加上了 /atguigu,并加上了时区和 SSL 参数,防止连接报错
        String url = "jdbc:mysql://localhost:3306/atguigu?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true";
        String username = "root";
        String password = "1234";
        Connection connection = DriverManager.getConnection(url, username, password);

        //3.执行sql语句的对象,把sql语句发送给数据库执行
        Statement statement = connection.createStatement();

        //4.编写sql语句并执行,接收返回的结果集
        String sql = "select emp_id,emp_name,emp_salary,emp_age from t_emp";
        ResultSet resultSet = statement.executeQuery(sql);

        //5.处理结果,遍历resultSet结果集
        while(resultSet.next()){
            int empId = resultSet.getInt("emp_id");
            String empName = resultSet.getString("emp_name");
            double empSalary = resultSet.getDouble("emp_salary");
            int empAge = resultSet.getInt("emp_age");
            System.out.printf("%-5d %-15s %10.1f %5d%n", empId, empName, empSalary, empAge);
        }

        // 【修改点 3】:加上释放资源的代码(规范操作:先开的后关,后开的先关)
        resultSet.close();
        statement.close();
        connection.close();
    }
}

运行结果:

📖今日知识点 ------JDBC API 详解

注册驱动---Driver

java 复制代码
 Class.forName("com.mysql.cj.jdbc.Driver");
  • 在Java中,当使用JDBC连接数据库时,需要加载数据库特定的驱动程序,以便与数据库进行通信。加载驱动程序的目的是为了注册驱动程序,使得JDBC API能够识别并与特定的数据库进行交互。
  • 从JDK6开始,不再需要显示地调用CLass.forName()来加载JDBC驱动程序,只要在类路径中集成了对应的 jar 文件(用 Maven 时,就是在 pom.xml 里写好依赖,Maven 会自动把 jar 下载并放进类路径),会自动在初始化时注册驱动程序。
  • Driver类里的静态代码块里注册驱动:(与Class.forName("com.mysql.cj.jdbc.Driver");效果一样。)
java 复制代码
DriverManger。registerDriver(new Driver());

Connection

  • Connection接口是JDBC API的重要接口,用于建立与数据库的通信信道。换而言之,Connection对象不为空,则代表一次数据库连接。

  • 在建立连接的时候,需要指定数据库URL、用户名、密码参数。

    复制代码
     		 URL:jdbc:mysql://localhost:3306/atguigu
     		 jdbc:mysql://IP地址:端口号/数据库名称?参数键值对1&参数键值对2
  • Connection接口还负责管理事务,Connection接口提供了commit和rollback方法,用于提交事务和回滚事务

  • 可以创建Statement对象,用于执行SQL语句并与数据库进行交互

  • 在使用JDBC技术时,必须要先获取Connection对象,在使用完毕后,要释放资源,避免资源占用浪费及泄漏。

Statement

  • Statement接口用于执行SQL语句并与数据库进行交互。它是JDBC API中的一个重要接口。通过Statement对象,可以向数据库发送SQL语句并获取执行结果。

  • 结果可以是一个或多个结果

    复制代码
     	增删改:受影响行数单个结果。
     	查询:单行单列、多行多列、单行多列等结果。
  • 但是Statement接口在执行SQL语句时,会产生SQL注入攻击问题:

    复制代码
      当使用Statement执行动态构建的SQL查询时,往往需要将查询条件与SQL语句拼接在一起,直接将参数和SQL语句一并生成,让SQL的查询条件始终为true得到结果。

如何避免sql注入攻击问题,我们引入了Prepared Statement👇

4.Prepared Statement

  • PreparedStatement是Statement接口的子接口,用于执行预编译的SQL的查询,作用如下:

    复制代码
      1.预编译SQL语句:在创建PreparedStatement时,就会预编译SQL语句,也就是SQL语句已经固定
      2.防止SQL注入:PreparedStatement支持参数化查询,将数据作为参数传递到SQL语句中,采用?占位符的方式,将传入的参数用一对单引号包裹起来'',无论传递什么都作为值,有效防止传入关键字或值导致SQL注入问题。
      3.性能提升:PreparedStatement是预编译SQL语句,同一SQL语句多次执行的情况下,可以直接复用,不必重新编译和解析
  • 后续的学习我们都是基于PreparedStatement进行实现,更安全,效率更高!

ResultSet

  • ResultSet是JDBC API 中的一个接口,用于表示从数据中执行查询语句所返回的结果集。他提供了一种用于遍历和访问查询结果的方式。
  • 遍历结果:ResultSet可以使用next()方法将游标移动到结果集的下一行,逐行遍历数据库查询的结果,返回Boolean类型,true代表下一行有结果,false则代表没有。
  • 获取单列结果:可以通过getXxx的方法获取单列的数据,该方法为重载方法,支持索引和列名进行获取。

💻练习代码

SELECT emp_id,emp_name,emp_salary,emp_age FROM t_emp WHERE emp_name = 'abc' or '1' = '1'

这里的 \' 转义字符有什么作用?

  1. 让单引号变成普通字符,而不是字符串的结束符。 SQL 里字符串用一对单引号包裹,'abc' 到第二个引号就结束了。写成 'abc\'' 时,\' 表示「这是一个字面的单引号」,所以 'abc\'' 整体是一个字符串,内容为 abc'。
  2. 保证整条 SQL 语法合法、不被提前截断。 如果不转义,'abc' or '1' = '1' 会被解析成「字符串 abc」+「关键字 or」+「字符串 1」+「=」+「字符串 1」,虽然也能跑,但语义已经变成恒真条件;而 'abc\' or \'1\' = \'1' 在语法上是一个完整的字符串字面量,只是内容里恰好含有 or 和 =。
  3. 这正是 SQL 注入的典型手法。 攻击者输入 abc' or '1' = '1,程序用 '"+name+"' 拼接后得到 where emp_name = 'abc' or '1' = '1','1' = '1' 恒真,or 让整个条件恒真,于是查出全表数据。上面这条带 \' 的写法,是演示「转义后引号不再具备语法意义」的对照,说明靠手工转义防注入并不可靠 ,正确做法是使用 PreparedStatement 的 ? 占位符参数化查询。
java 复制代码
package com.hstc.base;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;

public class JDBCInjection {
    //throws exception
    // JDBC 的核心 API 几乎全部声明了受检异常(checked exception)SQLException,Java 编译器要求调用方必须处理,
    // 处理方式只有两种:try-catch 捕获,或者继续往上 throws 声明。
    public static void main(String[] args) throws Exception {
        //1.注册驱动(可以省略)
        //Class.forName("com.mysql.cj.jdbc.Driver");
        //2.获取连接对象
        String url = "jdbc:mysql://localhost:3306/atguigu?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true";
        String username = "root";
        String password = "1234";
        Connection connection = DriverManager.getConnection(url,username,password);
        //3.执行sql语句的对象,把sql语句发送给数据库执行
        Statement statement = connection.createStatement();

        System.out.println("请输入员工姓名:");
        Scanner scanner = new Scanner(System.in);
        String name = scanner.nextLine();

        //4.编写sql语句并执行,接收返回的结果集
        //'"+name+"'Java 的字符串拼接 "a" + name + "b" 是编译期/运行期把变量的值塞进字符串,不是把变量名塞进去。
        // 所以必须靠字符串拼接把用户输入"嵌"到 SQL 文本里。
        // emp_name 是字符串类型列,SQL 语法规定字符串字面量要用单引号包裹:
        //输入 abc' or '1' = '1,拼出来的实际 SQL 是:
        //where emp_name = 'abc' or '1' = '1'
        //在 Java 或 SQL 中,OR(或)运算的规则是:只要有一边为真,整个结果就为真。
        //因为 '1' = '1' 是永远成立的(恒真),所以不管前面的 emp_name = 'abc' 是真是假,整个 where 条件都会返回 True。
        //于是数据库返回了表中所有的行。
        String sql = "select emp_id,emp_name,emp_salary,emp_age from t_emp where emp_name = '"+name+"'";
        ResultSet resultSet = statement.executeQuery(sql);
        //5.遍历结果集
        while(resultSet.next()){
            int empId = resultSet.getInt("emp_id");
            String empName = resultSet.getString("emp_name");
            double empSalary = resultSet.getDouble("emp_salary");
            int empAge = resultSet.getInt("emp_age");
            System.out.println(empId+"\t"+empName+"\t"+empSalary+"\t"+empAge);
        }
        //6.关闭资源
        resultSet.close();
        statement.close();
        connection.close();
    }
}

PreparedStatement解决注入问题:

java 复制代码
package com.hstc.base;

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

public class JDBCPreparedStatement {
    //throws exception
    // JDBC 的核心 API 几乎全部声明了受检异常(checked exception)SQLException,Java 编译器要求调用方必须处理,
    // 处理方式只有两种:try-catch 捕获,或者继续往上 throws 声明。
    public static void main(String[] args) throws Exception {
        //1.注册驱动(可以省略)
        //Class.forName("com.mysql.cj.jdbc.Driver");
        //2.获取连接对象
        String url = "jdbc:mysql://localhost:3306/atguigu?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true";
        String username = "root";
        String password = "1234";
        Connection connection = DriverManager.getConnection(url,username,password);
        //3.执行sql语句的对象,把sql语句发送给数据库执行
        PreparedStatement statement = connection.prepareStatement("SELECT emp_id,emp_name,emp_salary,emp_age FROM t_emp WHERE emp_name = ?");

        System.out.println("请输入员工姓名:");
        Scanner scanner = new Scanner(System.in);
        String name = scanner.nextLine();

        //4.为?占位符赋值,并执行SQL语句,接受返回的结果
        statement.setString(1,name);
        ResultSet resultSet = statement.executeQuery();

        //5.遍历结果集
        while(resultSet.next()){
            int empId = resultSet.getInt("emp_id");
            String empName = resultSet.getString("emp_name");
            double empSalary = resultSet.getDouble("emp_salary");
            int empAge = resultSet.getInt("emp_age");
            System.out.println(empId+"\t"+empName+"\t"+empSalary+"\t"+empAge);
        }
        //6.关闭资源
        resultSet.close();
        statement.close();
        connection.close();
    }
}

📖今日知识点 ------基于PreparedStatement实现CRUD对象

💻练习代码

练习代码1(查询单行单列):

java 复制代码
package com.hstc.base;

import org.junit.Test;

import java.net.SocketOption;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JDBCOperation {
    @Test
    public void testQuerySingleRowAndCol() throws SQLException {//抛出最大异常
         //1.注解驱动(可以省略)
         //2.获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/atguigu","root","1234");
        //3.预编译sql语句,获取结果集
        PreparedStatement preparedStatement = connection.prepareStatement("select count(*) as count from t_emp");
        //4.执行sql语句,获取结果集
        ResultSet resultSet = preparedStatement.executeQuery();
        //5.处理结果集(如果自己明确一定只有一个结果,要么resultSet最少要做一次next()的判断,才能拿到我们要的列的结果)
        if(resultSet.next()){
            int count = resultSet.getInt("count");//除了能传索引,还能传列名(这里是别名)
            System.out.println(count);
        }
        //6.释放资源
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }
}

运行结果1(查询单行单列):

练习代码2(查询单行多列):

java 复制代码
//查询单行多列结果
    @Test
    public void testQuerySingleRow() throws SQLException {
        //1.注解驱动(可以省略)

        //2.获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/atguigu","root","1234");

        //3.预编译sql语句,获取PreparedStatement对象
        PreparedStatement preparedStatement = connection.prepareStatement("select emp_id,emp_name,emp_salary,emp_age from t_emp where emp_id = ?");

        //4.执行sql语句,获取结果集
        preparedStatement.setInt(1,4);// 把第 1 个 ? 替换成 4
        ResultSet resultSet = preparedStatement.executeQuery();

        //行取决于预编译里面写的查询语句,列取决于while循环里写了多少行
        //5.处理结果集,遍历
        while(resultSet.next()){
            int emp_id = resultSet.getInt("emp_id");
            String emp_name = resultSet.getString("emp_name");
            double emp_salary = resultSet.getDouble("emp_salary");
            int emp_age = resultSet.getInt("emp_age");
            System.out.println(emp_id+"\t"+emp_name+"\t"+emp_salary+"\t"+emp_age);
        }

        //6.释放资源
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }

运行结果2(查询单行多列):

练习代码3(查询多行多列):

java 复制代码
//查询多行多列结果
    @Test
    public void testQueryMoreRow() throws SQLException {
        //1.注解驱动(可以省略)
        //2.获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/atguigu", "root", "1234");
        //3.预编译sql语句,获取PreparedStatement对象
        PreparedStatement preparedStatement = connection.prepareStatement("select emp_id,emp_name,emp_salary,emp_age from t_emp where emp_age > ?");
        //4.执行sql语句,获取结果集
        //为占位符赋值
        //注意:JDBC 的索引都是 从 1 开始,不是从 0。
        preparedStatement.setInt(1,23); // 把第 1 个 ? 替换成 23
        ResultSet resultSet = preparedStatement.executeQuery();
        //5.处理结果集,遍历
        while(resultSet.next()){
            int emp_id = resultSet.getInt("emp_id");
            String emp_name = resultSet.getString("emp_name");
            double emp_salary = resultSet.getDouble("emp_salary");
            int emp_age = resultSet.getInt("emp_age");
            System.out.println(emp_id + "\t" + emp_name + "\t" + emp_salary + "\t" + emp_age);
        }
        //6.释放资源
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }

运行结果3(查询多行多列):

练习代码4(新增数据--插入):

java 复制代码
//新增数据
    @Test
    public void testINsert() throws SQLException {
        //1.注解驱动(可以省略)
        //2.获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/atguigu", "root", "1234");

        //3.获取PreparedStatement对象
        PreparedStatement preparedStatement = connection.prepareStatement("insert into t_emp(emp_name,emp_salary,emp_age) values(?,?,?)");

        //4.执行sql语句,获取结果集
        //为占位符赋值
        preparedStatement.setString(1,"rose");
        preparedStatement.setDouble(2,5000);
        preparedStatement.setInt(3,29);
        int result = preparedStatement.executeUpdate();

        //根据受影响的行数判断是否新增成功
        //5.处理结果
        if(result>0){
            System.out.println("新增成功");
        }else {
            System.out.println("新增失败");
        }
        //6.释放资源
        preparedStatement.close();
        connection.close();
    }

运行结果4(新增数据--插入):

练习代码4(修改数据):

java 复制代码
//修改数据
    @Test
    public void testUpdate() throws SQLException {
        //1.注解驱动(可以省略)
        //2.获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/atguigu", "root", "1234");

        //3.获取PreparedStatement对象
        PreparedStatement preparedStatement = connection.prepareStatement("update t_emp set emp_salary = ? where emp_id = ?");

        //4.执行sql语句,获取结果
        //为占位符赋值
        preparedStatement.setDouble(1,9000);
        preparedStatement.setInt(2,5);
        int result = preparedStatement.executeUpdate();
        int result2 = preparedStatement.executeUpdate();

        //根据受影响的行数判断是否修改成功
        //5.处理结果
        if(result>0){
            System.out.println("修改成功");
        }else {
            System.out.println("修改失败");
        }

        //6.释放资源
        preparedStatement.close();
        connection.close();
    }

运行结果4(修改数据):

练习代码5(删除数据):

java 复制代码
//删除数据
    @Test
    public void testDelete() throws SQLException {
        //1.注解驱动(可以省略)
        //2.获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/atguigu", "root", "1234");

        //3.获取PreparedStatement对象
        PreparedStatement preparedStatement = connection.prepareStatement("delete from t_emp where emp_id = ?");

        //4.执行sql语句,获取结果
        //为占位符赋值
        preparedStatement.setInt(1,5);
        int result3 = preparedStatement.executeUpdate();
        
        //根据受影响的行数判断是否删除成功
        //5.处理结果
        if(result3>0){
            System.out.println("删除成功");
        }else {
            System.out.println("删除失败");
        }
        //6.释放资源
        preparedStatement.close();
        connection.close();
    }

运行结果5(删除数据):

📖今日知识点 ------常见问题

  • 资源管理问题:

    复制代码
      在使用JDBC的相关数据时,比如Connection、PreparedStatement、ResultSet,使用完毕后,要及时关闭这些资源释放数据库服务器资源和避免内存泄漏时很重要的。
  • SQL语句问题:

    复制代码
      java.sql.SQLSyntaxErrorException:SQL语句错误异常,一般有几种可能
              1.SQL语句有错误,检查SQL语句!建议SQL语句在SQL工具中测试后再复制粘贴到Java程序中!
              2.连接数据库的URL中,数据库名称编写错误,也会报该异常!
  • SQL语句未设置参数:

    复制代码
     java.sql.SQLException: No value specified for parameter 1
     在使用预编译SQL语句时,如果有?占位符,要为每一个占位符赋值,否则报该错误!
  • 用户名或密码错误问题:

    复制代码
      连接数据库时,如果用户名或密码输入错误,也会报SQLExecption,容易混淆!所以一定要看清楚异常后面的原因描述
  • 通信异常:

    复制代码
      在连接数据库的URL中,如果IP或端口(hocalhost: 3306)写错了,会报如下异常。
      com.mysql.cj.jdbc.exception.CommunicationException:Communications link failure
相关推荐
这个DBA有点耶33 分钟前
库内语义构建 vs 外挂向量库:业务语义缺失,才是RAG“胡说八道”的根源
数据库·mysql·aigc
迪康coolmu1 小时前
出差终端数据安全落地实战:基于迪康端点安全一体化管理系统的四道防线实践
运维·服务器·经验分享·笔记·其他·安全·php
kobe_OKOK_2 小时前
mysql 开启 binlog 日志
数据库·mysql
鸽芷咕2 小时前
KingbaseES 调参先看执行计划,参数是答案不是起点
数据库
这个DBA有点耶3 小时前
MySQL 8.0直方图:统计信息升级了,执行计划为什么还是选错?
数据库·mysql·代码规范
无咎.lsy4 小时前
《机器学习》笔记
笔记·机器学习
老纪的技术唠嗑局5 小时前
把 seekdb 塞进 iOS App:让混合检索嵌入手机
数据库
坤坤子吖5 小时前
Linux网络编程:TCP服务器的多进程与多线程实现
linux·服务器·开发语言·网络·c++·笔记·学习
ClouGence5 小时前
开源数据库管理工具 CloudDM 新版本发布,支持 OpenSearch、IoTDB 数据源
数据库·开源