JDBC 数据库连接

JDBC 简介和开发步骤

什么是jdbc

如何通过 Java 程序来访问数据库呢?可以使用 JDBC(Java DataBase Connectivity)。JDBC 由一组使用 Java 语言编写的类和接口组成,可以为 Oracle、MySQL 等多种关系数据库提供统一的访问方式,从而实现用 Java 代码来操作数据库。

JDBC 的顶层是开发人员自己编写的 Java 应用程序,如下图所示。Java 应用程序可以通过集成在 JDK 中的 java.sql 及 javax.sql 包中的 JDBC API 来访问数据库。

图片描述

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驱动

JDBC 驱动分为管理和驱动两个组件。JDBC Driver Manager 存在于 JDK 中,负责管理各种不同数据库的 JDBC 驱动。

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

导入驱动加载类

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

  1. 新建一个 java 工程目录,本次实验的所有代码也将以工程的形式运行,所有代码将放在 demo/src 下:

    mkdir demo demo/src demo/lib demo/bin
    复制代码
  2. 下载驱动包并移动到工程的 lib 目录下:

    wget https://labfile.oss.aliyuncs.com/courses/3488/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
    复制代码

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

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

Class.forName("JDBC 驱动类名");
复制代码

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

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

数据库 JDBC 驱动包 JDBC 驱动类 连接字符串
Oracle ojdbc 版本号.jar oracle.jdbc.OracleDriver jdbc:oracle:thin:@localhos t:1521:数据库实例名
MySQL mysql-connector-java -版本号-bin.jar com.mysql.cj.jdbc.Driver jdbc:mysql://localhost:3306/数据库实例名
SqlServer sqljdbc 版本号.jar com.microsoft.sqlserver.jdbc.SQLServerDriver jdbc:microsoft:sqlserver://localhost:1433; databasename=数据库实例名

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

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

cd /home/project/demo
javac -d bin/ src/JDBCUpdateByStatement.java
java -cp bin/:lib/mysql-connector-java-8.0.22.jar demo.src.JDBCUpdateByStatement
复制代码

建立数据库连接

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

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

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

方 法 简 介
Statement createStatement() throws SQLException 创建 Statement 对象
PreparedStatement prepareStatement(String sql) 创建 PreparedStatement 对象4
获得了 Connection 对象后,就可以通过 Connection 对象来获得 Statement 或 PreparedStatement 对象,并通过该对象向数据库发送 SQL 语句。

Statement 对象

// 创建 Statement 对象
Statement stmt = connection.createStatement();
复制代码

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

int count = stmt.executeUpdate("增、删、改的 SQL 语句")
复制代码

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

ResultSet rs = stmt.executeQuery("查询的 SQL 语句");
复制代码

如果 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 对象。

实现单表增删查改

使用 Statement 实现增删改

首先启动 MySQL

之后创建数据库和表,各字段名称及类型

CREATE DATABASE IF NOT EXISTS shiyan DEFAULT 
CHARACTER SET UTF8; # 创建数据库 
USE shiyanlou; # 选择数据库
CREATE TABLE student ( 
stuNo INT NOT NULL, 
stuName VARCHAR(20),
stuAge INT,
PRIMARY KEY (stuNo) 
);
复制代码

使用 Statement 提供的的 executeUpdate() 方法

import java.sql.*;
public class JDBCUpdateByStatement{ 
final static String DRIVER = "com.mysql.cj.jdbc.Driver";
// 数据库的实例名是 shiyan
final static String URL = "jdbc:mysql://localhost:3306/shiyan?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();
} 
}
复制代码

执行 executeUpdate() 方法

编译运行

cd demo
javac -d bin/ src/JDBCUpdateByStatement.java
java -cp bin/:lib/mysql-connector-java-8.0.22.jar JDBCUpdateByStatement
复制代码

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

// 通过 executeUpdate()实现对数据库的删除操作
String deleteSql = "DELETE FROM student WHERE stuNo = 5" ;
INT count = stmt.executeUpdate(deleteSql );
复制代码

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

// 通过 executeUpdate()实现对数据库的修改操作
String updateSql = "UPDATE student SET stuName = '李四' WHERE stuName = '王五'" ;
INT count=stmt.executeUpdate(updateSql);
复制代码

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

使用 Statement 实现查操作

查询数据库和增、删、改操作的步骤基本相同

import java.sql.*;
public class JDBCQueryByStatement{
    final static String DRIVER = "com.mysql.cj.jdbc.Driver";
    // 数据库的实例名是 shiyan
    final static String URL = "jdbc:mysql://localhost:3306/shiyan?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.getString(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();
    }
}
复制代码

执行 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
复制代码

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

"SELECT stuNo,stuName,stuAge FROM student WHERE stuName LIKE '%"+name + "%' "
复制代码

请注意 % 两侧的单引号。

使用 PreparedStatement 实现

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

import java.sql.*;
public class JDBCUpdateByPreparedStatement{
    final static String DRIVER = "com.mysql.cj.jdbc.Driver";
    //数据库的实例名是 shiyan
    final static String URL = "jdbc:mysql://localhost:3306/shiyan?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 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 {
                // 注意 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();
            }
        }
        return flag;
    }
    public static void main(String[] args){
        executeUpdate();
    }
}
复制代码

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

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

使用 PreparedStatement 实现查操作。

import java.sql.*;
import java.util.Scanner;
public class JDBCQueryByPreparedStatement{
final static String DRIVER = "com.mysql.cj.jdbc.Driver";
    // 数据库的实例名是 shiyan
    final static String URL = "jdbc:mysql://localhost:3306/shiyan?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();
            }
        }
    }
    public static void main(String[] args){
        executeQuery();
    }
}
复制代码

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

PreparedStatement pstmt = ... ;
ResultSet rs = ... ;
...
String querySql = "SELECT *  FROM book WHERE name LIKE ?" ;
pstmt.setString(1, "%" +name +"%");
rs = pstmt.executeQuery();
复制代码

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

Statement 和 PreparedStatement 的比较

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

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

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+")");
复制代码

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

提高了 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 的。

提高了安全性,能有效防止 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{//登录失败}
}
复制代码

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

select count(*) from login where username = '任意值' or 1=1-- and password = '任意值';
复制代码

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

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

调用存储过程和存储函数

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

先在 MySQL 中,切换数据库到上一节创建的 shiyan,创建存储过程 addTwoNum() ,SQL 脚本如下所示。

DELIMITER $
CREATE PROCEDURE addTwoNum
(
    IN num1 INT,  # 输入参数
    IN num2 INT,  # 输入参数
    OUT total INT # 输出参数
)
BEGIN
SET total = num1 + num2;
END $
DELIMITER ;
复制代码

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

import java.sql.*;
// package、import
public class JDBCOperateByCallableStatement{
    final static String DRIVER = "com.mysql.cj.jdbc.Driver";
    // 数据库的实例名是 shiyan
    final static String URL = "jdbc:mysql://localhost:3306/shiyan?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){
        executeByCallableStatement();
    }
}
复制代码

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

先在 MySQL 中切换数据库到上一节使用的 shiyan,创建存储函数 addTwoNumAndReturn() ,SQL 脚本如下程序清单所示。

DELIMITER $
CREATE function addTwoNumAndReturn
(
    num1 INTEGER, # 输入参数
    num2 INTEGER  # 输入参数
)
returns INTEGER # 返回值类型
BEGIN
DECLARE TOTAL INTEGER;
SET total = num1 + num2;
RETURN TOTAL; # 返回值
END $
DELIMITER ;
复制代码

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

import java.sql.*;
// package、import
public class JDBCOperateByCallableStatement2{
    final static String DRIVER = "com.mysql.cj.jdbc.Driver";
    // 数据库的实例名是 shiyan
    final static String URL = "jdbc:mysql://localhost:3306/shiyan?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();
    }
}
复制代码

总结:

jdbc连接数据库:关键、
1 加载数据库驱动
2 获取数据库连接
3 通过连接,获取一个操作数据库 Statement 的对象
4 通过 executeQuery()实现对数据库的查询,并返回一个结果集(结果集中包含了所有查询到的数据)
String querySql = "sql语句";
rs = stmt.executeQuery(querySql);

里面代码可以当作模板,当自己使用时候,修改数据库名,修改sql语句就可以了

猜你喜欢

转载自juejin.im/post/7097415034912899086