JDBC 基础操作

时间:2022-07-26
本文章向大家介绍JDBC 基础操作,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

1.1 简介

1.1.1 概述

  JDBC 的全称是 Java Database Connectivity,即 Java 数据库连接,它是一种可以执行 SQL 语句的 Java API。程序可通过 JDBC API 连接到关系数据库,并使用结构化查询语言(SQL,数据库标准的查询语言)来完成对数据库的查询、更新。   与其他数据库编程环境相比,JDBC 为数据库开发提供了标准的 API,所以使用 JDBC 开发的数据库应用可以跨平台运行,而且可以跨数据库(如果全部使用标准的 SQL)。也就是说,如果使用 JDBC 开发一个数据库应用,则该应用既可以在 Windows 平台上运行,也可以在 UNIX 等其他平台上运行;既可以使用 MySQL 数据库,也可以使用 Oracle 等数据库,而程序无须进行任何修改。   最早的时候,Sun 公司希望自己开发一组 Java API,程序员通过这组 Java API 即可操作所有的数据库系统,但后来 Sun 发现这个目标具有不可实现性,因为数据库系统太多了,而且各数据库系统的内部特性又各不相同。后来 Sun 就制定了一组标准的 API,它们只是接口,没有提供实现类(这些实现类由各数据库厂商提供实现),这些实现类就是驱动程序。而程序员使用 JDBC 时只要面向标准的 JDBC API 编程即可,当需要在数据库之间切换时,只要更换不同的实现类(即更换数据库驱动程序)就行,这是面向接口编程

1.1.2 Driver 接口介绍

java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现。在程序中不需要直接去访问实现了 Driver 接口的类,而是由驱动程序管理器类 java.sql.DriverManager 去调用这些 Driver 实现。常见的 Driver 接口实现   ♞ Oracle 的驱动:oracle.jdbc.driver.OracleDriver   ♞ MySQL 的驱动:com.mysql.jdbc.Driver

1.2 JDBC 的用法

1.2.1 常用接口

DriverManager

  用于管理 JDBC 驱动的服务类。程序中使用该类的主要功能是获取 Connection 对象。提供如下方法:

  ♞ static Connection getConnection(String url, String user, String pass):该方法获得 url 对应数据库的连接。

Connection

  代表数据库连接对象,每个 Connection 代表一个物理连接会话。要想访问数据库,必须先获得数据库连接。提供如下方法:

  ♞ Statement createStatement():该方法返回一个 Statement 对象。   ♞ PreparedStatement prepareStatement(String sql):该方法返回预编译的 Statement 对象,即将 SQL 语句进行预编译。   ♞ CallableStatement prepareCall(String sql):该方法返回 CallableStatement 对象,该对象用于调用存储过程。

  上面三个方法都返回用于执行 SQL 语句的 Statement 对象,PreparedStatement、CallableStatement 是 Statement 的子类,只有获得了 Statement 之后才可执行 SOL 语句。Java7 为 Connection 新增了 setSchema(String schema)、getSchema() 两个方法,这两个方法用于控制该 Connection 访问的数据库 Schema。Java7 还为 Connection 新增了setNetworkTimeout(Executor executor,int milliseconds)、getNetworkTimeout() 两个方法来控制数据库连接的超时行为。除此之外,Connection 还有如下几个用于控制事务的方法。

  ♞ Savepoint setSavepoint():创建一个保存点。   ♞ Savepoint setSavepoint(String name):以指定名字来创建一个保存点。   ♞ void set Transactionlsolation(int level):设置事务的隔离级别。   ♞ void rollback():回滚事务。   ♞ void rollback(Savepoint savepoint):将事务回滚到指定的保存点。   ♞ void setAutoCommit(boolean autoCommit):关闭自动提交,打开事务。   ♞ void commit():提交事务。

Statement

  用于执行 SOL 语句的工具接口。该对象既可用于执行 DDL、DCL 语句,也可用于执行 DML 语句,还可用于执行 SQL 查询。当执行 SQL 查询时,返回查询到的结果集。它的常用方法如下:

  ♞ ResultSet executeQuery(String sql):该方法用于执行查询语句,并返回查询结果对应的 ResultSet 对象。该方法只能用于执行查询语句。   ♞ int executeUpdate(String sql):该方法用于执行 DML 语句,并返回受影响的行数;该方法也可用于执行 DDL 语句,执行 DDL 语句将返回 0。   ♞ boolean execute(String sql):该方法可执行任何 SQL 语句。如果执行后第一个结果为 ResultSet 对象,则返回 true;如果执行后第一个结果为受影响的行数或没有任何结果,则返回 false。

  Java 7为 Statement 新增了closeOnCompletion() 方法,如果 Statement 执行了该方法,则当所有依赖于该 Statement 的 ResultSet 关闭时,该 Statement 会自动关闭。Java7 还为 Statement 提供了一个 isCloseOnCompletion() 方法,该方法用于判断该 Statement 是否打开了“closeOnCompletion”。   Java 8 为 Statement 新增了多个重载的 executeLargeUpdate() 方法,这些方法相当于增强版的 executeUpdate() 方法,返回值类型为 long,也就是说,当 DML 语句影响的记录条数超过 Integer.MAX_VALUE 时,就应该使用 executeLargeUpdate() 方法。

PreparedStatement

  预编译的 Statement 对象。PreparedStatement 是 Statement 的子接口,它允许数据库预编译 SQL 语句(这些 SQL 语句通常带有参数),以后每次只改变 SQL 命令的参数,避免数据库每次都需要编译 SQL 语句,因此性能更好。相对于 Statement 而言,使用 PreparedStatement 执行 SQL 语句时,无须再传入 SQL 语句,只要为预编译的 SQL 语句传入参数值即可。所以它比 Statement 多了如下方法:

  ♞ void setXxx(int parameterIndex,Xxx value):该方法根据传入参数值的类型不同,需要使用不同的方法。传入的值根据索引传给 SQL 语句中指定位置的参数。

  PreparedStatement 同样有 executeUpdate()、executeQuery() 和 execute() 三个方法,只是这三个方法无须接收 SQL 字符串,因为 PreparedStatement 对象已经预编译了 SQL 命令,只要为这些命令传入参数即可。Java8 还为 PreparedStatement 增加了不带参数的 executeLargeUpdate() 方法,执行 DML 语句影响的记录条数可能超过 Integer.MAX_VALUE 时,就应该使用 executeLargeUpdate() 方法。

ResultSet

  结果集对象。该对象包含访问查询结果的方法,ResultSet 可以通过列索引或列名获得列数据。它包含了如下常用方法来移动记录指针。

  ♞ void close():释放 ResultSet 对象。   ♞ boolean absolute(int row):将结果集的记录指针移动到第 row 行,如果 row 是负数,则移动到倒数第 row 行。如果移动后的记录指针指向一条有效记录,则该方法返回 true。   ♞ void beforeFirst():将 ResultSet 的记录指针定位到首行之前,这是 ResultSet 结果集记录指针的初始状态,记录指针的起始位置位于第一行之前。   ♞ boolean first():将 ResultSet 的记录指针定位到首行。如果移动后的记录指针指向一条有效记录,则该方法返回 true。   ♞ boolean previous():将 ResultSet 的记录指针定位到上一行。如果移动后的记录指针指向一条有效记录,则该方法返回 true。   ♞ boolean next():将 ResultSet 的记录指针定位到下一行,如果移动后的记录指针指向一条有效记录,则该方法返回 true。   ♞ boolean last():将 ResultSet 的记录指针定位到最后一行,如果移动后的记录指针指向一条有效记录,则该方法返回 true。   ♞ void afterLast():将 ResultSet 的记录指针定位到最后一行之后。

  当把记录指针移动到指定行之后,ResultSet 可通过 getxxx(int columnlndex) 或 getXxx(String columnLabel) 方法来获取当前行、指定列的值,前者根据列索引获取值,后者根据列名获取值。Java7 新增了 T getObject(int columnIndex,Class type)和 T getObject(String columnLabel,Class type) 两个泛型方法,它们可以获取任意类型的值。

1.2.2 JDBC 编程步骤

加载驱动

  MySQL5 以后的 JDBC 驱动已经可以通过 SPI 自动注册驱动类了,在 JDBC 驱动 JAR 包的 META-INFservices 路径下会包含一个 java.sql.Driver 文件,该文件指定了 JDBC 驱动类。因此,如果使用这种新的驱动 JAR 包,这一步其实可以省略,但不推荐省略加载驱动。(需要导入 mysql-connector-java.jar)

Class.forName(driverClass)

//加载 MySql 驱动
Class.forName("com.mysql.jdbc.Driver")

//加载 Oracle 驱动
Class.forName("oracle.jdbc.driver.OracleDriver")

获取数据库连接

  当使用 DriverManager 获取数据库连接时,通常需要传入三个参数:数据库 URL、登录数据库的用户名和密码。这三个参数中用户名和密码通常由 DBA(数据库管理员)分配,而且该用户还应该具有相应的权限,才可以执行相应的 SQL 语句。

// url ☞ jdbc:mysql://主机名称:mysql服务端口号/数据库名称?参数=值&参数=值
Connection connection = DriverManager.getConnection(url, username, password);

// 获取 MySQL 连接
// 如果JDBC程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集
// jdbc:mysql://localhost:3306/mydatabase?useUnicode=true&characterEncoding=utf8
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "root", "root");

创建 Statement 对象

// 执行的 sql 语句
String sql = "insert into ···";

// 创建 Statement 对象
Statement statement = connection.createStatement();

执行 SQL

ResultSet executeQuery(String sql):该方法只能用于执行查询语句。 int executeUpdate(String sql):该方法用于执行 DML 语句,也可用于执行 DDL 语句。 boolean execute(String sql):该方法可执行任何 SQL 语句,比较麻烦。

操作结果集

  如果执行的 SQL 语句是查询语句,则执行结果将返回一个 ResultSet 对象,该对象里保存了 SQL 语句查询的结果。程序可以通过操作该 ResultSet 对象来取出查询结果。执行的 SQL 语句是增、删、改语句,则执行结果返回的是受影响的行数。行的 SQL 语句是权限操作语句,则执行结果返回的是 0。

释放资源

// 采用倒序关闭
resultSet.close();
statement.close();
connection.close();

1.2.3 示例

使用 Statement

import java.sql.*;

/**
 * Created with IntelliJ IDEA.
 *
 * @author Demo_Null
 * @date 2020/7/9
 * @description JDBC 演示类
 */
public class JDBCTest {

    private static String url = "jdbc:mysql://47.103.4.*:3306/mydatabase";
    private static String username = "root";
    private static String password = "root";
    private static String drive = "com.mysql.jdbc.Driver";
    private static Connection connection = null;

    static {
        try {
            // 加载驱动程序
            Class.forName(drive);
            // 获得数据库连接
            connection = DriverManager.getConnection(url, username, password);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void sql(String id) {
        try {
            // sql 语句
            String sql = "select * from dept where id = " + id;

            // 获取执行器
            Statement statement = connection.createStatement();

            // 执行 sql
            ResultSet resultSet = statement.executeQuery(sql);

            // 处理结果集
            while (resultSet.next()) {
                String dname = resultSet.getString("dname");
                System.out.println(dname);
            }

            // 释放资源
            resultSet.close();
            statement.close();
            connection.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }

    }

    public static void main(String[] args) {
        sql(" 1 ");
    }
}

  运行上述代码,成功的从数据库中获取到了 id = 1 的 dname,但是我们使用的是 Statement,Statement 存在着一些弊端,他需要我们将参数与 SQL 拼接起来,十分繁琐,而且由于拼接会导致 SQL 注入的问题。SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令,从而利用系统的 SQL 引擎完成恶意行为的做法。例如:我们只想返回 id 为 1 的 dname,但是用户传入了1 or 1 = 1,结果 SQL 语句就变为了 select * from dept where id = 1 or 1 = 1 甚至修改为 select * from dept where id = 1; drop table dept;

使用 PreparedStatement

  使用 PreparedStatement 执行 SQL,可以在 SQL 语句中,对值所在的位置使用 ? 占位符,实际的值,可以通过另外的方法传入。此时 PreparedStatement 会对值做特殊的处理,处理后,会导致恶意注入的 SQL 代码失效

import java.sql.*;

/**
 * Created with IntelliJ IDEA.
 *
 * @author Demo_Null
 * @date 2020/7/9
 * @description JDBC 演示类
 */
public class JDBCTest {

    private static String url = "jdbc:mysql://47.103.4.*:3306/mydatabase";
    private static String username = "root";
    private static String password = "root";
    private static String drive = "com.mysql.jdbc.Driver";
    private static Connection connection = null;

    static {
        try {
            // 加载驱动程序
            Class.forName(drive);
            // 获得数据库连接
            connection = DriverManager.getConnection(url, username, password);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void sql(String id) {
        try {
            // sql 语句
            String sql = "select * from dept where id = ?";

            // 获取执行器
            PreparedStatement preparedStatement = connection.prepareStatement(sql);

            // 设置参数
            preparedStatement.setString(1, id);

            // 执行 sql
            ResultSet resultSet = preparedStatement.executeQuery();

            // 打印执行语句
            System.out.println(preparedStatement.toString());

            // 处理结果集
            while (resultSet.next()) {
                String dname = resultSet.getString("dname");
                System.out.println(dname);
            }

            // 释放资源
            resultSet.close();
            preparedStatement.close();
            connection.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }

    }

    public static void main(String[] args) {
        sql(" 1 or 1 = 1 ");
    }
}

1.3 JDBC 优化

1.3.1 概述

  每次使用 JDBC 都需要写冗长的代码,而且代码大部分都是相同的,我们可以将其封装为一个工具类,提高代码的复用性。其次,我们的 MySQL 参数都是写死在代码中,不利于维护,在集合中有一个 Properties 集合,它可以从文本中读取数据。根据该思路对现有 JDBC 操作进行优化。

1.3.2 配置文件(jdbc.properties)

driver = com.mysql.jdbc.Driver
url = jdbc:mysql://47.103.4.*:3306/mydatabase
user = work
password = mymima

1.3.3 JDBC 工具类

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
 
public class jdbc_utils {
    static String driver;
    static String url;
    static String user;
    static String password;
 
    static {
       /**  【需要获取配置文件路径是使用这种方式】
        *    // 加载本类字节码文件进内存,获取统一资源定位符(URL)
        *    URL url = jdbc_utils.class.getClassLoader().getResource("jdbc.properties");    
        *    // 获取 URL 路径
        *    String s = url.getParth();
        *    // 使用 File
        *    pro.load(new File(s));
        */
        
        // 加载字节码文件获取配置文件流
        InputStream is = jdbc_utils.class.getClassLoader().getResourceAsStream("jdbc.properties");
        
        try {
            // 创建 properties 集合
            Properties pro = new Properties();
            // 从文件中加载 key-value 对
            pro.load(is);
 
            // 获取属性并复制
            driver = pro.getProperty("driver");
            url = pro.getProperty("url");
            user = pro.getProperty("user");
            password = pro.getProperty("password");
 
            // 加载驱动
            Class.forName(driver);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    // 获取数据库连接
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url,user,password);
    }
 
    // 释放资源
    // DML 释放两个资源
    public static void close(PreparedStatement preparedStatement, Connection connection) {
        if (preparedStatement != null) {
            try {
                preparedStatement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
 
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
 
    // DQL 释放三个资源
    public static void close(ResultSet resultSet, PreparedStatement preparedStatement, Connection connection) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
 
        if (preparedStatement != null) {
            try {
                preparedStatement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
 
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}