当前位置:   article > 正文

MySQL JDBC详解

mysql jdbc


简介

JDBC 的顶层是开发人员自己编写的 Java 应用程序,如下图所示。Java 应用程序可以通过集成在 JDK 中的 java.sql 及 javax.sql 包中的 JDBC API 来访问数据库。
在这里插入图片描述
下面讲解上图中出现的一些 JDBC 重要组件,思维导图如下。

在这里插入图片描述

JDBC API

JDBC API 存在于 JDK 中,其中包含了 Java 应用程序与各种不同数据库交互的标准接口,如 Connection 是连接数据库的接口、Statement 是操作数据库的接口、ResultSet 是查询结果集接口、PreparedStatement 是预处理操作接口等,开发者可以使用这些 JDBC 接口操作关系型数据库。 JDBC API 中常用接口和类的介绍如下表所示:

接口 /类简介
DriverManager 类根据不同的数据库,管理相应的 JDBC 驱动。可以通过 DriverManager 类的 getConnection()方法获取数据库连接对象(即 Connection 对象)。
Connection 接口由 DriverManager 产生,用于连接数据库并传递数据。
Statement 接口由 Connection 产生,用于执行增、删、改、查等 SQL 语句。
PreparedStatement 接口Statement 的子接口(该接口的定义是:public interface PreparedStatement extends Statement{…})。PreparedStatement 同样由 Connection 产生,同样用于执行增删改查等 SQL 语句。与 Statement 接口相比,Statement 具有更高的安全性(可以防止 SQL 注入等安全隐患)、更高的性能、更高的可读性和可维护性等优点。
CallableStatement 接口PreparedStatement 的子接口(该接口的定义是:public interface CallableStatement extends PreparedStatement {…}),CallableStatement 同样由 Connection 产生,用于调用存储过程或存储函数。
ResultSet 接口接收 Statement 对象(或 PreparedStatement 对象)执行查询操作后,返回的结果集。

从开发的角度讲,JDBC API 主要完成三件事:

  1. 与数据库建立连接
  2. 向数据库发送 SQL 语句
  3. 返回数据库的处理结果

在这里插入图片描述

JDBC Driver Manager

JDBC Driver Manager 也存在于 JDK 中,负责管理各种不同数据库的 JDBC 驱动。

JDBC 驱动

JDBC 驱动由各个数据库厂商或第三方厂商提供,负责针对不同数据库实现 JDBC API。例如,应用程序访问 MySql 和 Oracle 时,就需要不同的 JDBC 驱动。这些 JDBC 驱动都各自实现了 JDBC API 中定义的各种接口。在使用 JDBC 连接数据库时,只要正确加载了 JDBC 驱动,就可以通过调用 JDBC API 来操作数据库。

JDBC 开发步骤

开发一个 JDBC 程序,有以下四个基本步骤:

一,导入 JDBC 驱动包,并加载驱动类

使用 JDBC 访问数据库前,需要先导入相应的驱动包(如 oracle 数据库的驱动包是 ojdbc 版本号.jar)。这里我们以 MySQL 为例介绍在 WebIDE 中导入驱动包的步骤:

  1. 新建一个 java 工程目录,本次实验的所有代码也将以工程的形式运行,所有代码将放在 demo/src 下:
mkdir demo demo/src demo/lib demo/bin
  • 1
  1. 下载驱动包并移动到工程的 lib 目录下:\
wget https://labfile.oss.aliyuncs.com/courses/3232/mysql-connector-java-8.0.22.zip
unzip mysql-connector-java-8.0.22.zip
cp ./mysql-connector-java-8.0.22/mysql-connector-java-8.0.22.jar ./demo/lib
  • 1
  • 2
  • 3

注:也可以到 MySQL 的官网上下载驱动包:Connector/J

驱动包放入 lib 目录下之后,就可以使用 Class.forName() 方法将具体的 JDBC 驱动类加载到 JVM 中,加载的代码如下:

Class.forName("JDBC 驱动类名");
  • 1

如果指定的驱动类名不存在,就会引发 ClassNotFoundException 异常。

之后在代码中,就可以利用连接字符串、用户名和密码等参数来获取数据库连接对象。常见关系型数据库的 JDBC 驱动包包名、驱动类类名及连接字符串如下表所示:

数据库JDBC 驱动包JDBC 驱动类连接字符串
Oracleojdbc 版本号.jaroracle.jdbc.OracleDriverjdbc:oracle:thin:@localhos t:1521:数据库实例名
MySQLmysql-connector-java -版本号-bin.jarcom.mysql.jdbc.Driverjdbc:mysql://localhost:3306/数据库实例名
SqlServersqljdbc 版本号.jarcom.microsoft.sqlserver.jdbc.SQLServerDriverjdbc:microsoft:sqlserver://localhost:1433; databasename=数据库实例名

连接字符串 由协议、服务器地址、端口和数据库实例名构成,示例中 localhost 可被替换成服务器的 ip 地址,1521、3306 和 1433 分别是 Oracle、MySQL 和 SqlServer 三种数据库的默认端口号。

当程序调用 Class.forName(“JDBC 驱动类名”); 在使用 java 命令运行程序时,就需要使用以下命令将其加入到 classpath 中:

javac -d bin/ src/JDBCUpdateByStatement.java
java -cp bin/:lib/mysql-connector-java-8.0.22.jar JDBCUpdateByStatement
  • 1
  • 2

二,建立数据库连接

JDBC 使用 DriverManager 类来管理驱动程序,并通过其 getConnection() 获取连接对象,代码如下:

Connection connection = DriverManager.getConnection("连接字符串","数据库用户名","数据库密码");
  • 1

Connection 接口的常用方法如下表所示:

方 法简 介
Statement createStatement() throws SQLException创建 Statement 对象
PreparedStatement prepareStatement(String sql)创建 PreparedStatement 对象

三,发送 SQL 语句,并获取执行结果

获得了 Connection 对象后,就可以通过 Connection 对象来获得 Statement 或 PreparedStatement 对象,并通过该对象向数据库发送 SQL 语句。

Statement 对象

//创建 Statement 对象
Statement stmt = connection.createStatement();
  • 1
  • 2

发送“增、删、改”类型的 SQL 语句:

int count = stmt.executeUpdate("增、删、改的 SQL 语句")
  • 1

发送“查询”类型的 SQL 语句:

ResultSet rs = stmt.executeQuery("查询的 SQL 语句");
  • 1

如果 SQL 语句是增、删、改操作,会返回一个 int 型结果,表示多少行受到了影响,即增、删、改了几条数据;如果 SQL 语句是查询操作,数据库会返回一个 ResultSet 结果集,该结果集包含了 SQL 查询的所有结果。

Statement 对象的常用方法如下表所示:

方法简介
int executeUpdate()用于执行 INSERT、UPDATE、DELETE 以及 DDL(数据定义语言)语句(如 CREATE TABLE… 和 DROP TABLE…)。 对于 CREATE TABLE 或 DROP TABLE 等 DDL 类型的语句,executeUpdate 的返回值总为零。
ResultSet executeQuery()用于执行 SELECT 查询语句,返回值是一个 ResultSet 类型的结果集。
void close()关闭 Statement 对象。

对于 CREATE TABLE 或 DROP TABLE 等 DDL 类型的语句,executeUpdate 的返回值总为零。

ResultSet executeQuery() 用于执行 SELECT 查询语句,返回值是一个 ResultSet 类型的结果集。

void close() 关闭 Statement 对象。

PreparedStatement 对象

//创建 PreparedStatement 对象
PreparedStatement pstmt = connection.prepareStatement("增、删、改、查的 SQL 语句");
  • 1
  • 2

发送”增、删、改“类型的 SQL 语句:

int count = pstmt.executeUpdate()
  • 1

发送”查询“类型的 SQL 语句:

ResultSet rs = pstmt.executeQuery();
  • 1

PreparedStatement 对象的常用方法如下表所示:

方 法简 介
executeUpdate()用法上,类似于 Statement 接口中的 executeUpdate()。
executeQuery()用法上,类似于 Statement 接口中的 executeQuery ()。
setXxx()有 setInt()、setString()、setDouble()等多个方法,用于给 SQL 中的占位符“?”赋值。setXxx()方法有两个参数,第一个参数表示占位符的位置(从 1 开始),第二个参数表示占位符所代表的具体值。 例如可以将 SQL 写成“select * from student where name=? and age = ? ”,其中两个问号代表两个占位符,之后再使用 setString(1,“张三”)和 setInt(2,23)来分别为两个占位符赋值(即给 name 和 age 赋值)
close()关闭 PreparedStatement 对象

四,处理返回结果集

如果是查询操作,可以通过迭代的方式循环取出结果集中的所有数据:首先通过 rs.next() 判断是否还有下一行数据,如果有,rs 就会移动到下一行,之后再通过 rs.getXxx() 获取行内的每列数据,如下:

while(rs.next()) {
    int stuNo = rs.getInt("stuNo");
    String stuName = rs.getString("stuName");
    …
}
  • 1
  • 2
  • 3
  • 4
  • 5

ResultSet 的常用方法如下表所示:

方法简介
boolean next()将光标从当前位置向下移动一行,指向结果集中的下一行数据。通常用来判断查询到的结果集中是否还有数据。如果有,则返回 true,否则返回 false。
boolean previous()将光标从当前位置向上移动一行。
int getInt(int columnIndex)获取当前一行数据中指定列号的字段值,该列必须是整数类型的字段。例如,学生表中有 number 类型的 stuNo 字段在第一列,就可以使用 getInt(1)来获取值。 除此之外,还有 getFloat()、getString()、getDate()、getBinaryStream()等多个类似方法,用于获取不同类型的字段。
int getInt(String columnLabel)获取当前一行数据中指定列名的字段值,该列必须是整数类型的字段。例如,学生表中有 number 类型的 stuNo 字段,就可以使用 getInt(“stuNo”)来获取值。 除此之外,还有 getFloat()、getString()、getDate()等多个类似方法,用于获取不同类型的字段。
void close()关闭 ResultSet 对象。

JDBC 实现单表增删改查

本节以 MySQL 数据库为例,在实际业务场景中体会 JDBC 细节。 假设数据库中存在一张学生表 student,各字段名称及类型如下表所示:

字 段 名类型含义
stuNoint学号
stuNamevarchar(20)学生姓名
stuAgeint学生年龄

重新打开一个 Terminal ,启动 MySQL:

sudo service mysql start
mysql -u root -p
  • 1
  • 2

实验楼的 MySQL 数据库,root 用户没有设置密码,直接按回车。

之后创建数据库和表:

create database if not exists shiyanlou default character set utf8;    #创建数据库
use shiyanlou;    #选择数据库
create table student
(
    stuNo int not null,
    stuName varchar(20),
    stuAge int,
    primary key (stuNo)
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

一,使用 Statement 访问数据库

之前已经介绍过了 JDBC 的开发步骤,并且知道在使用 JDBC 时需要区分增删改和查询操作,以下是具体的实现细节。

实现“增、删、改”操作

本案例先使用 Statement 提供的的 executeUpdate() 方法,执行插入操作,详见程序清单 JDBCUpdateByStatement.java 。

import java.sql.*;
public class JDBCUpdateByStatement{
    final static String DRIVER = "com.mysql.jdbc.Driver";
    //数据库的实例名是 shiyanlou
    final static String URL = "jdbc:mysql://localhost:3306/shiyanlou?useUnicode=true&characterEncoding=utf8";
    final static String USERNAME = "root";
    final static String PASSWORD = "";
    static Connection connection = null;
    static Statement stmt = null;
    static ResultSet rs = null;
    //执行 `插入` 的方法
    public static boolean executeUpdate() {
        boolean flag = false ;
        try{
            //1 加载数据库驱动
            Class.forName(DRIVER);
            //2 获取数据库连接
            connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            //3 通过连接,获取一个 Statement 的对象,用来操作数据库
            stmt = connection.createStatement();
            //4 通过 executeUpdate()实现插入操作
            String addSql = "insert into student(stuNo,stuName,stuAge) values(5,'王五',25)" ;
            int count = stmt.executeUpdate(addSql);
            System.out.println("受影响的行数是:"+count);
            flag = true ;//如果一切正常,没有发生异常,则将 flag 设置为 true
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            try {
                if(stmt != null)stmt.close();
                if(connection != null)connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return flag ;
    }

    public static void main(String[] args){
        executeUpdate();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

执行 executeUpdate() 方法,即可插入数据。

编译运行:

cd demo
javac -d bin/ src/JDBCUpdateByStatement.java
java -cp bin/:lib/mysql-connector-java-8.0.22.jar JDBCUpdateByStatement
  • 1
  • 2
  • 3

输出结果:
在这里插入图片描述

以上是增加方法的执行细节,如果要执行删除操作,只需要修改上述代码中 executeUpdate() 方法的 SQL 参数,如下:

//通过 executeUpdate()实现对数据库的删除操作
String deleteSql = "delete from student where stuNo = 5" ;
int count = stmt.executeUpdate(deleteSql );
  • 1
  • 2
  • 3

类似的,如果要执行修改操作,也只需要修改 executeUpdate() 方法中的 SQL 参数,如下。

//通过 executeUpdate()实现对数据库的修改操作
String updateSql = "update student set stuName = '李四' where stuName='王五'" ;
int count=stmt.executeUpdate(updateSql);
  • 1
  • 2
  • 3

即增删改操作唯一不同的就是 executeUpdate() 方法中的 SQL 语句。

实现“查询”操作

接下来使用 Statement 对象实现 查询 数据库的操作。此时,笔者的数据库中 student 表中的数据如下图所示:

查询数据库和增、删、改操作的步骤基本相同,详见程序清单 JDBCQueryByStatement.java 。

import java.sql.*;
public class JDBCQueryByStatement{
    final static String DRIVER = "com.mysql.jdbc.Driver";
    //数据库的实例名是 shiyanlou
    final static String URL = "jdbc:mysql://localhost:3306/shiyanlou?useUnicode=true&characterEncoding=utf8";
    final static String USERNAME = "root";
    final static String PASSWORD = "";
    static Connection connection = null;
    static Statement stmt = null;
    static ResultSet rs = null;
    public static void executeQuery() {
        try{
            //1 加载数据库驱动
            Class.forName(DRIVER);
            //2 获取数据库连接
            connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            //3 通过连接,获取一个操作数据库 Statement 的对象
            stmt = connection.createStatement();
            //4 通过 executeQuery()实现对数据库的查询,并返回一个结果集(结果集中包含了所有查询到的数据)
            String querySql = "select stuNo,stuName,stuAge from student";
            rs = stmt.executeQuery(querySql);
            //5 通过循环读取结果集中的数据
            while(rs.next()) {
                //等价于 rs.getInt(1);
                int stuNo = rs.getInt("stuNo");
                // rs.getString(2);
                String stuName = rs.getString("stuName");
                //rs.getInt(3);
                int stuAge = rs.getInt("stuAge");
                System.out.println(stuNo+"\t"+stuName+"\t"+stuAge);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            try {
                //注意 rs、stmt、connection 三个对象的关闭顺序
                if(rs != null)rs.close();
                if(stmt != null)stmt.close();
                if(connection != null)connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args){
        executeQuery();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

执行 executeQuery() 方法,即可查询出 student 表中所有的 stuNo、stuName 和 stuAge 字段值。

编译运行:

cd demo
javac -d bin/ src/JDBCQueryByStatement.java
java -cp bin/:lib/mysql-connector-java-8.0.22.jar JDBCQueryByStatement
  • 1
  • 2
  • 3

输出结果:
在这里插入图片描述

如果是根据 String 类型的 name 变量进行模糊查询,那么查询的 SQL 语句可写为以下形式。

"select stuNo,stuName,stuAge from student where stuName like '%"+name + "%' "
  • 1

请注意 % 两侧的单引号。

二,使用 PreparedStatement 访问数据库

在写代码的时候,PreparedStatement 和 Statement 对象的使用步骤基本相同,只不过在方法的参数、返回值等细节上存在差异。请大家仔细阅读程序清单 JDBCUpdateByPreparedStatement.java 中的代码,并和 Statement 方式的增删改操作进行比较。

实现“增、删、改”操作。

import java.sql.*;
public class JDBCUpdateByPreparedStatement{
    final static String DRIVER = "com.mysql.jdbc.Driver";
    //数据库的实例名是 shiyanlou
    final static String URL = "jdbc:mysql://localhost:3306/shiyanlou?useUnicode=true&characterEncoding=utf8";
    final static String USERNAME = "root";
    final static String PASSWORD = "";
    static Connection connection = null;
    static PreparedStatement pstmt = null;
    public static boolean executeUpdate() {
        boolean flag = false;
        try {
            Class.forName(DRIVER);
            connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            //用占位符来代替参数值
            String deleteSql = "delete from student where stuName = ? and stuAge = ?" ;
            pstmt = connection.prepareStatement(deleteSql);
            //将第一个占位符?的值替换为 `张三` (占位符的位置是从 1 开始的)
            pstmt.setString(1, "张三");
            //将第二个占位符?的值替换为 23
            pstmt.setInt(2, 23);
            int count = pstmt.executeUpdate();
            System.out.println("受影响的行数是:" + count);
            flag = true;
        }
        catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            try {
                if(connection != null)connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return flag;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

可见,与 Statement 相比,本次使用 PreparedStatement 执行增、删、改操作的不同之处如下:

  • SQL 语句提前写在了 prepareStatement() 方法参数中;
  • 先在 SQL 语句中使用了占位符 ? ,然后使用 setXxx() 方法对占位符进行了替换。

实现“查询”操作

请大家仔细阅读程序清单 JDBCQueryByPreparedStatement.java 中的代码,并和使用 Statement 进行查询操作的代码进行对比。

import java.sql.*;
public class JDBCQueryByPreparedStatement{
final static String DRIVER = "com.mysql.jdbc.Driver";
    //数据库的实例名是 shiyanlou
    final static String URL = "jdbc:mysql://localhost:3306/shiyanlou?useUnicode=true&characterEncoding=utf8";
    final static String USERNAME = "root";
    final static String PASSWORD = "";
    static Connection connection = null;
    static Statement stmt = null;
    static ResultSet rs = null;
    public static void executeQuery() {
        Scanner input = new Scanner(System.in);
        try{
            Class.forName(DRIVER);
            connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            System.out.println("请输入用户名:");
            String name = input.nextLine();
            System.out.println("请输入密码:");
            String pwd = input.nextLine();
            //如果用户输入的 username 和 password 在表中有对应的数据(count(1)>0),
            //则说明存在此用户
            String querySql = "select count(1) from login where username = ? and password = ?" ;
            pstmt = connection.preparedStatement(querySql);
            pstmt.setString(1, name);
            pstmt.setString(2, pwd);
            rs = pstmt.executeQuery();
            if (rs.next()){
                //获取 SQL 语句中 count(1)的值
                int count = rs.getInt(1);
                if (count > 0)
                    System.out.println("登录成功");
                else {
                    System.out.println("登录失败");
                }
            }
        }
        catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            try {
                //注意 rs、stmt、connection 三个对象的关闭顺序
                if(rs != null)rs.close();
                if(stmt != null)stmt.close();
                if(connection != null)connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

如果使用 PreparedStatement 进行模糊查询,可以在 setXxx() 方法的第二个参数中加入通配符(如 % )。例如,根据 name 模糊查询代码如下。

PreparedStatement pstmt = ... ;
ResultSet rs = ... ;
...
String querySql = "select \*  from book where name like ?" ;
pstmt.setString(1, "%" +name +"%");
rs = pstmt.executeQuery();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

需要注意的是,如果使用的是 Statement ,当需要给 SQL 语句拼接 String 类型变量时,必须加上单引号,例如 select … from … where stuName like '%"+name + "%'; 但如果使用的是 PreparedStatement ,则不需要加,例如: pstmt.setString(1, "%" +name +"%")

三,JDBC 中的异常处理及资源关闭

在编写 JDBC 代码时,经常会遇到异常处理。 下表列出了一些常见抛出异常的方法:

方法抛出的异常类型
Class.forName()方法ClassNotFoundException
DriverManager.getConnection()方法SQLException
Connection 接口的 createStatement()方法SQLException
Statement 接口的 executeQuery()方法SQLException
Statement 接口的 executeUpdate()方法SQLException
Connection 接口的 preparedStatement()方法SQLException
PreparedStatement 接口的 setXxx()方法SQLException
PreparedStatement 接口的 executeUpdate()方法SQLException
PreparedStatement 接口的 executeQuery()方法SQLException
ResultSet 接口的 next()方法SQLException
ResultSet 接口的 close()方法SQLException
Statement 接口的 close()方法SQLException
Connection 接口的 close()方法SQLException

为了及时地释放不再使用的资源,需要在数据库访问结束时,调用各个对象的 close() 方法,如下表所示:

方法立即释放的资源
ResultSet 接口的 close()方法此 ResultSet 对象的数据库 JDBC 资源
Statement 接口的 close()方法此 Statement 对象的数据库 JDBC 资源(包含 ResultSet 对象)
Connection 接口的 close()方法此 Connection 对象的数据库 JDBC 资源(包含 ResultSet、 Statement 对象)

可以发现,三个 close() 释放的资源存在包含关系,所以在编码时,释放资源的顺序应该写为:ResultSet 对象的 close() 方法(查询操作)→ Statement 的对象 close() 方法 → Connection 对象的 close() 方法。也就是先释放范围小的资源,再释放范围大的资源。

值得注意的是,因为 PreparedStatement 继承自 Statement ,所以 Statement 接口的 close() 方法实际也代表了 PreparedStatement 对象的 close() 方法。

如果不及时的通过 close() 方法释放资源,已创建的 Connection 对象、Statement 对象、ResultSet 对象也会在 GC 执行垃圾回收时自动释放。但自动释放的方式会造成资源的释放不及时(必须等待 GC 主动回收),故不推荐。

综上,JDBC 的代码结构如下:

try{
    ① Class.forName("驱动字符串")
    ② 获取 Connection 对象
    ③ Statement 对象(或 PreparedStatement 对象)相关代码
    ④(如果是查询操作)ResultSet 对象相关代码
} catch (ClassNotFoundException e){
    ...
} catch (SQLException e){
    ...
} catch (Exception e) {
    ...
} finally {
    try{
        (如果是查询操作)关闭 ResultSet 对象
        关闭 Statement 对象
        关闭 Connection 对象
    } catch (SQLException e){
        ...
    } catch (Exception e){
        ...
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

四,Statement 和 PreparedStatement 的比较

Statement 和 PreparedStatement 都可以实现数据库的增删改查等操作。但在实际开发中,一般推荐使用 PreparedStatement 。因为两者相比,PreparedStatement 有如下优势。

1,提高了代码的可读性和可维护性

PreparedStatement 可以避免烦琐的 SQL 语句拼接操作。例如,SQL 语句 insert into student(stuNo,stuName,stuAge,course) values(5,‘王五’,25) ,如果将其中的字段值用变量来表示(int stuNo=5;String stuName=“王五”;int stuAge=23;),用 Statement 方式执行时,需要写成:

stmt.executeUpdate("insert into student(stuNo,stuName,stuAge ) values("+stuNo+",'"+stuName+"',"+stuAge+")");
  • 1

而如果用 PreparedStatement 方式执行时,就可以先用 ? 充当参数值的占位符,然后再用 setXxx() 方法设置 ? 的具体值,从而避免 SQL 语句的拼接操作。

2,提高了 SQL 语句的性能

在使用 Statement 和 PreparedStatement 向数据库发送 SQL 语句时,数据库都会解析并编译该 SQL 语句,并将解析和编译的结果缓存起来。但在使用 Statement 时,这些缓存结果仅仅适用于那些完全相同的 SQL 语句(SQL 主体和拼接的 SQL 参数均相同)。换个角度讲,如果某条 SQL 的 SQL 主体相同,但拼接的参数不同,也仍然不会使用之前缓存起来的结果,这就严重影响了缓存的使用效率。

而 PreparedStatement 就不会像 Statement 那样将 SQL 语句完整的编译起来,而是采用了预编译机制:只编译 SQL 主体,不编译 SQL 参数。因此,在使用 PreparedStatement 时,只要多条 SQL 语句的 SQL 主体相同(与 SQL 语句中的参数无关),就可以复用同一份缓存。这点就类似于 Java 中方法调用的流程:Java 编译器会预先将定义的方法编译好(但不会编译方法的参数值),之后在多次调用这个方法时,即使输入参数值不同,也可以复用同一个方法。因此,如果某个业务需要重复执行主体相同的 SQL 语句(无论 SQL 中的参数是否相同),就可以利用 PreparedStatement 这种预编译 SQL 的特性来提高数据库缓存的利用率,进而提升性能。

但要注意的是,PreparedStatement 虽然在执行重复的 SQL 语句时具有较高的性能,但如果某个 SQL 语句仅仅会被执行一次或者少数几次,Statement 的性能是高于 PreparedStatement 的。

3,提高了安全性,能有效防止 SQL 注入

在使用 Statement 时,可能会用以下代码来进行登录验证。

stmt = connection.createStatement();
String querySql = "select count(_) from login where username = '"+uname+"' and password = '"+upwd+"'" ;
rs = stmt.executeQuery(querySql);if(rs.next()){
    int result = rs.getInt("count(_)");
    if(result>0) { //登录成功}
    else{//登录失败}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上述代码看起来没有问题,但试想如果用户输入的 uname 值是 任意值 or 1=1-- 、upwd 的值是 任意值 ,则 SQL 语句拼接后的结果如下:

select count(\*) from login where username = '任意值' or 1=1-- and password = '任意值';
  • 1

在这条 SQL 语句中,用 or 1=1 使 where 条件永远成立,并且用 – 将后面的 SQL 语句注释掉,这样就造成了安全隐患(SQL 注入),使得并不存在的用户名和密码也能登录成功。

而 PreparedStatement 方式传入的任何数据都不会和已经编译的 SQL 语句进行拼接,因此可以避免 SQL 注入攻击。综上所述,在实际开发中推荐使用 PreparedStatement 操作数据库。

五,使用 JDBC 调用存储过程和存储函数

JDBC 除了能够向数据库发送 SQL 语句以外,还可以通过 CallableStatement 对象调用数据库中的存储过程或存储函数。

CallableStatement 对象可以通过 Connection 对象创建,如下:

CallableStatement cstmt= connection.prepareCall(调用储过程或存储函数);
  • 1

调用存储过程(无返回值)时,prepareCall() 方法的参数(字符串)格式为:

{ call 存储过程名(参数列表) }
  • 1

调用存储函数(有返回值)时,prepareCall() 方法的参数(字符串)格式为:

{ ? = call 存储过程名(参数列表) }
  • 1

对于参数列表,需要注意以下两点:

  • 参数的索引,是从 1 开始编号的。
  • 具体的参数,既可以是输入参数(IN 类型),也可以是输出参数(OUT 类型)。输入参数使用 setXxx() 方法进行赋值;输出参数(或返回值参数)必须先使用 registerOutParameter() 方法设置参数类型,然后调用 execute() 执行存储过程或存储函数,最后再通过 getXxx() 获取结果值。

下面,通过两个数相加的示例,分别演示调用存储过程和存储函数的具体步骤。

1,调用存储过程(无返回值)

先在 MySQL 中,创建存储过程 addTwoNum() ,SQL 脚本如下所示。

delimiter $
create procedure addTwoNum
(
    in num1 int, #输入参数
    in num2 int, #输入参数
    out total int #输出参数
)
begin
set total = num1 + num2;
end$
delimiter ;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

再使用 JDBC 调用刚才创建好的存储过程,详见程序清单 JDBCOperateByCallableStatement.java 。

import java.sql.*;
//package、import
public class JDBCOperateByCallableStatement{
    final static String DRIVER = "com.mysql.jdbc.Driver";
    //数据库的实例名是 shiyanlou
    final static String URL = "jdbc:mysql://localhost:3306/shiyanlou?useUnicode=true&characterEncoding=utf8";
    final static String USERNAME = "root";
    final static String PASSWORD = "";
    static Connection connection = null;
    static Statement stmt = null;
    static ResultSet rs = null;
    static CallableStatement cstmt = null;
    public static void executeByCallableStatement(){
        try {
            Class.forName(DRIVER);
            connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            //创建 CallableStatement 对象,并调用数据库中的存储过程 addTwoNum()
            cstmt = connection.prepareCall("{call addTwoNum(?,?,?)}");
            //将第一个参数值设为 10
            cstmt.setInt(1, 10);
            //将第二个参数值设为 20
            cstmt.setInt(2, 20);
            //将第三个参数(输出参数)类型设置为 int
            cstmt.registerOutParameter(3, Types.INTEGER);
            //执行存储过程
            cstmt.execute() ;
            //执行完毕后,获取第三个参数(输出参数)的值
            int result = cstmt.getInt(3);
            System.out.println("相加结果是:"+result);
        }
        catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            try {
                //注意 rs、stmt、connection 三个对象的关闭顺序
                if(rs != null)rs.close();
                if(stmt != null)stmt.close();
                if(connection != null)connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args){
        executeByCallableStatementWithResult();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

编译运行:

cd demo
javac -d bin/ src/JDBCOperateByCallableStatement.java
java -cp bin/:lib/mysql-connector-java-8.0.22.jar JDBCOperateByCallableStatement
  • 1
  • 2
  • 3

输出结果:
在这里插入图片描述

2,调用存储函数(有返回值)

先在 MySQL 中,创建存储函数 addTwoNumAndReturn() ,SQL 脚本如下程序清单所示。

delimiter $
create function addTwoNumAndReturn
(
    num1 INTEGER, #输入参数
    num2 INTEGER #输入参数
)
returns INTEGER #返回值类型
begin
declare total INTEGER;
set total = num1 + num2;
return total; #返回值
end $
delimiter ;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

再使用 JDBC 调用刚才创建好的存储函数,详见程序清单 JDBCOperateByCallableStatement2.java 的。

import java.sql.*;
//package、import
public class JDBCOperateByCallableStatement2{
    final static String DRIVER = "com.mysql.jdbc.Driver";
    //数据库的实例名是 shiyanlou
    final static String URL = "jdbc:mysql://localhost:3306/shiyanlou?useUnicode=true&characterEncoding=utf8";
    final static String USERNAME = "root";
    final static String PASSWORD = "";
    static Connection connection = null;
    static Statement stmt = null;
    static ResultSet rs = null;
    static CallableStatement cstmt = null;
    public static void executeByCallableStatementWithResult(){
        try {
            Class.forName(DRIVER);
            connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            //创建 CallableStatement 对象,并调用数据库中的存储函数
            cstmt = connection.prepareCall("{? = call addTwoNumAndReturn(?,?)}");
            //将第一个参数(返回值)类型设置为 int
            cstmt.registerOutParameter(1, Types.INTEGER);
            //将第二个参数值设为 10
            cstmt.setInt(2, 10);
            //将第三个参数值设为 20
            cstmt.setInt(3, 20);
            //执行存储函数
            cstmt.execute() ;
            //执行完毕后,获取第三个参数的值(返回值)
            int result = cstmt.getInt(1);
            System.out.println("相加结果是:"+result);
        }
        catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            try {
                //注意 rs、stmt、connection 三个对象的关闭顺序
                if(rs != null)rs.close();
                if(stmt != null)stmt.close();
                if(connection != null)connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args){
        executeByCallableStatementWithResult();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

编译运行:

cd demo
javac -d bin/ src/JDBCOperateByCallableStatement2.java
java -cp bin/:lib/mysql-connector-java-8.0.22.jar JDBCOperateByCallableStatement2
  • 1
  • 2
  • 3

输出结果:
在这里插入图片描述

JDBC 存取大文本和二进制数据

实际开发中,经常会处理一些大文本数据(Oracle 中的 CLOB 类型)或二进制数据(Oracle 中的 BLOB 类型)。要想在数据库中读写 CLOB 或 BLOB 类型的数据,就必须综合使用 PreparedStatement 和 IO 流的相关技术。

一,读写 CLOB 数据

CLOB 用于存放大文本数据。以下是将一篇小说写入 CLOB 类型字段的具体步骤。

在此之前,请同学们在 /home/project 下输入以下命令,下载实验用的 txt 文件:

wget https://labfile.oss.aliyuncs.com/courses/3232/introduce.txt
  • 1

1,创建 myTxt 表,并设置 CLOB 类型的字段 text,SQL 脚本如下程序清单所示

create table myTxt
(
    id int primary key,
    clob text
);
  • 1
  • 2
  • 3
  • 4
  • 5

2,将小说写入 myTxt 表的 clob 字段(text 类型)

先将小说转为字符输入流,然后通过 PreparedStatement 的 setCharacterStream () 方法写入数据库,详见程序清单 WriteText.java 。

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.sql.*;

//package、import
public class WriteText{
    final static String DRIVER = "com.mysql.jdbc.Driver";
    //数据库的实例名是 shiyanlou
    final static String URL = "jdbc:mysql://localhost:3306/shiyanlou?useUnicode=true&characterEncoding=utf8";
    final static String USERNAME = "root";
    final static String PASSWORD = "";
    static Connection connection = null;
    static Statement stmt = null;
    static ResultSet rs = null;
    static PreparedStatement pstmt = null;
    //将小说写入数据库
    public static void writeTextToClob() {
        try {
            Class.forName(DRIVER);
            connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            String sql = "insert into myTxt(id,clob) values(?,?)" ;
            //处理 clob/blob,必须使用 PreparedStatement 对象
            pstmt = connection.prepareStatement(sql) ;
            pstmt.setInt(1, 1); // id=1
            //将小说转为字符输入流,并设置编码格式为中文 GBK 格式
            File file = new File("/home/project/introduce.txt");
            Reader reader = new InputStreamReader(new FileInputStream(file),"utf8");
            //将字符输入流写入 myTxt 表
            pstmt.setCharacterStream(2, reader,(int)file.length());
            int result = pstmt.executeUpdate();
            if(result >0){
                System.out.println("小说写入成功!");
            }else {
                System.out.println("小说写入失败!");
            }
        }
        catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            try {
                //注意 rs、stmt、connection 三个对象的关闭顺序
                if(rs != null) {
                    rs.close();
                }
                if(stmt != null) {
                    stmt.close();
                }
                if(connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args){
        writeTextToClob();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

编译运行:

cd demo
javac -d bin/ src/WriteText.java
java -cp bin/:lib/mysql-connector-java-8.0.22.jar WriteText
  • 1
  • 2
  • 3

输出结果:
在这里插入图片描述

3,读取数据库中的小说

通过 ResultSet 的 getCharacterStream() 方法读取小说,然后通过 IO 流写入硬盘(src 根目录),详见程序清单 ReadText.java 。

import java.io.FileWriter;
import java.io.Reader;
import java.io.Writer;
import java.sql.*;

//package、import
public class ReadText{
    final static String DRIVER = "com.mysql.jdbc.Driver";
    //数据库的实例名是 shiyanlou
    final static String URL = "jdbc:mysql://localhost:3306/shiyanlou?useUnicode=true&characterEncoding=utf8";
    final static String USERNAME = "root";
    final static String PASSWORD = "";
    static Connection connection = null;
    static Statement stmt = null;
    static ResultSet rs = null;
    static PreparedStatement pstmt = null;
    //从数据库读取小说,并放入 src 目录
    public static void readTextToClob(){
        try {
            Class.forName(DRIVER);
            connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            String sql = "select * from myTxt where id = ?" ;
            pstmt = connection.prepareStatement(sql) ;
            pstmt.setInt(1, 1);//id=1
            rs = pstmt.executeQuery() ;
            if(rs.next()){
                //将小说从数据库中读取出,类型为 Reader
                Reader reader = rs.getCharacterStream("clob") ;
                //通过 IO 流将小说写到项目中(硬盘)
                //将小说的输出路径设置为 src(相对路径)
                Writer writer = new FileWriter("src/new_introduce.txt");
                char[] temp = new char[200];
                int len = -1;
                while( (len=reader.read(temp) )!=-1) {
                    writer.write(temp,0,len);
                }
                writer.close();
                reader.close();
                System.out.println("Text 读取成功!");
            }
        }
        catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            try {
                //注意 rs、stmt、connection 三个对象的关闭顺序
                if(rs != null) {
                    rs.close();
                }
                if(stmt != null) {
                    stmt.close();
                }
                if(connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args){
        readTextToClob();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

编译运行:

cd demo
javac -d bin/ src/ReadText.java
java -cp bin/:lib/mysql-connector-java-8.0.22.jar ReadText
  • 1
  • 2
  • 3

输出结果:
在这里插入图片描述

此时可以看到在 ./src 下,生成了一个名为 new_introduce.txt 的文件:
在这里插入图片描述

二,读写 BLOB 数据

BLOB 可用于存放二进制数据(常用于保存图片、视频、音频等格式的数据)。以下是将图片存入 BLOB 类型字段的具体步骤。

在此之前,请同学们在 /home/project 下输入以下命令,下载实验用的 png 文件:

wget https://labfile.oss.aliyuncs.com/courses/3232/myPic.png
  • 1

1,创建 myPicture 表,并设置 BLOB 类型的字段 img,SQL 脚本如下所示,

create table myPicture
(
    id int primary key,
    img Blob
);
  • 1
  • 2
  • 3
  • 4
  • 5

2,将图片写入 myPicture 表的 img 字段(BLOB 类型)

先将图片转为输入流,然后通过 PreparedStatement 对象的 setBinaryStream() 方法写入数据库,详见程序清单 WriteImg.java 。

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.*;

//package、import
public class WriteImg{
    final static String DRIVER = "com.mysql.jdbc.Driver";
    //数据库的实例名是 shiyanlou
    final static String URL = "jdbc:mysql://localhost:3306/shiyanlou?useUnicode=true&characterEncoding=utf8";
    final static String USERNAME = "root";
    final static String PASSWORD = "";
    static Connection connection = null;
    static Statement stmt = null;
    static ResultSet rs = null;
    static PreparedStatement pstmt = null;
    //将图片写入数据库
    public static void writeImgToBlob() {
        try {
            Class.forName(DRIVER);
            connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            String sql = "insert into myPicture(id,img) values(?,?)" ;
            //处理 clob/blob,必须使用 PreparedStatement 对象
            pstmt = connection.prepareStatement(sql) ;
            pstmt.setInt(1, 1);//id=1
            //将图片转为输入流
            File file = new File("/home/project/myPic.png");
            InputStream in = new FileInputStream(file);
            //将输入流写入 myPicture 表
            pstmt.setBinaryStream(2, in,(int)file.length());
            int result = pstmt.executeUpdate();
            if(result >0){
                System.out.println("图片写入成功!");
            }else {
                System.out.println("图片写入失败!");
            }
        }
        catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            try {
                //注意 rs、stmt、connection 三个对象的关闭顺序
                if(rs != null) {
                    rs.close();
                }
                if(stmt != null) {
                    stmt.close();
                }
                if(connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args){
        writeImgToBlob();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

编译运行:

cd demo
javac -d bin/ src/WriteImg.java
java -cp bin/:lib/mysql-connector-java-8.0.22.jar WriteImg
  • 1
  • 2
  • 3

输出结果:
在这里插入图片描述

3,读取数据库中的图片

通过 ResultSet 的 getBinaryStream() 方法读取图片,然后通过 IO 流写入硬盘(src 根目录),详见程序清单 10.16。

import java.io.*;
import java.sql.*;

//package、import
public class ReadImg{
    final static String DRIVER = "com.mysql.jdbc.Driver";
    //数据库的实例名是 shiyanlou
    final static String URL = "jdbc:mysql://localhost:3306/shiyanlou?useUnicode=true&characterEncoding=utf8";
    final static String USERNAME = "root";
    final static String PASSWORD = "";
    static Connection connection = null;
    static Statement stmt = null;
    static ResultSet rs = null;
    static PreparedStatement pstmt = null;
    //从数据库读取图片
    public static void readImgToBlob(){
        try {
            Class.forName(DRIVER);
            connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            String sql = "select * from myPicture where id = ?" ;
            pstmt = connection.prepareStatement(sql) ;
            pstmt.setInt(1, 1);//id=1
            rs = pstmt.executeQuery() ;
            if(rs.next()){
                //将图片从数据库中读取出,类型为 InputStream
                InputStream imgIn = rs.getBinaryStream("img") ;
                //通过 IO 流,将图片写到项目中(硬盘)
                InputStream in = new BufferedInputStream(imgIn) ;
                //将图片的输出路径设置为 src(相对路径),图片名为 myPic.png
                OutputStream imgOut =new FileOutputStream("src/new_myPic.png");
                OutputStream out = new BufferedOutputStream(imgOut) ;
                int len = -1;
                while( (len=in.read() )!=-1) {
                    out.write(len);
                }
                out.close();
                imgOut.close();
                in.close();
                imgIn.close();
                System.out.println("图片读取成功!");
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            try {
                //注意 rs、stmt、connection 三个对象的关闭顺序
                if(rs != null) {
                    rs.close();
                }
                if(stmt != null) {
                    stmt.close();
                }
                if(connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args){
        readImgToBlob();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

编译运行:

cd demo
javac -d bin/ src/ReadImg.java
java -cp bin/:lib/mysql-connector-java-8.0.22.jar  ReadImg
  • 1
  • 2
  • 3

输出结果:
在这里插入图片描述
此时可以看到在 ./src 下,生成了一个名为 new_myPic.png 的图片文件:
在这里插入图片描述

JDBC 总结

本章介绍了如何使用 JDBC 访问关系型数据库 MySQL,具体如下:

  1. JDBC API 包含了 Java 应用程序与各种不同数据库交互的标准接口,如 Connection 连接接口、Statement 操作接口、ResultSet 结果集接口、PreparedStatement 预处理操作接口等,使用这些 JDBC 接口可以操作各种关系型数据库;
  2. 使用 JDBC 访问数据库的基本步骤是:使用 Class.forName(“驱动字符串”)加载驱动类、获取 Connection 对象、使用 Statement 对象(或 PreparedStatement 对象)向数据库发送 SQL 语句,如果是查询操作还需要通过 ResultSet 对象获取结果集;
  3. Statement 相比较,PreparedStatement 有着如下的优势:提高了代码的可读性和可维护性、提高了 SQL 语句的性能、能有效防止 SQL 注入;
  4. 可以使用 CallableStatement 对象的 prepareCall() 方法调用数据库中的存储过程和存储函数,调用存储过程(无返回值)时,该方法参数的格式是 { call 存储过程名(参数列表) } ;调用存储函数(有返回值)时,该方法参数格式是 { ? = call 存储过程名(参数列表) }
  5. 可以使用 PreparedStatementIO 流,在数据库中读写 CLOBBLOB 类型的数据。例如要将图片写入 myPicture 表的 img 字段(BLOB 类型),就可以先将图片转为输入流,然后通过 PreparedStatement 对象的 setBinaryStream() 方法写入数据库。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/452149
推荐阅读
相关标签
  

闽ICP备14008679号