JDBC 基础操作

2020-09-28 17:42:41 浏览数 (1)

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)

代码语言:javascript复制
Class.forName(driverClass)

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

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

获取数据库连接

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

代码语言:javascript复制
// 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 对象

代码语言:javascript复制
// 执行的 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。

释放资源

代码语言:javascript复制
// 采用倒序关闭
resultSet.close();
statement.close();
connection.close();

1.2.3 示例

使用 Statement

代码语言:javascript复制
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 代码失效

代码语言:javascript复制
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)

代码语言:javascript复制
driver = com.mysql.jdbc.Driver
url = jdbc:mysql://47.103.4.*:3306/mydatabase
user = work
password = mymima

1.3.3 JDBC 工具类

代码语言:javascript复制
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();
            }
        }
    }
}

0 人点赞