JDBC API的那些事,你真的知道吗?


什么是JDBC API?

概念纠正:JDBC(Java Database Connectivity)

以下概念来自官方:
>

The JDBC API is a Java API for accessing virtually any kind of tabular data. (As a point of interest, JDBC is the trademarked name and is not an acronym; nevertheless, JDBC is often thought of as standing for “Java Database Connectivity.”) The JDBC API consists of a set of classes and interfaces written in the Java programming language that provide a standard API for tool/database developers and makes it possible to write industrial strength database applications using an all-Java API

JDBC API是一种Java API,用于访问几乎任何类型的表格数据。(作为一个兴趣点,JDBC是商标名称,并不是首字母缩略词;然而,JDBC通常被认为代表“Java数据库连接”。)JDBC API由一组用Java编写的类和接口组成。为工具/数据库开发人员提供标准API的编程语言,可以使用全Java API编写工业级数据库应用程序。

The JDBC API makes it easy to send SQL statements to relational database systems and supports all dialects of SQL. But the JDBC 2.0 API goes beyond SQL, also making it possible to interact with other kinds of data sources, such as files containing tabular data.

JDBC API可以轻松地将SQL语句发送到关系数据库系统,并支持所有SQL方言。但是JDBC 2.0 API超越了SQL,也使得与其他类型的数据源(例如包含表格数据的文件)进行交互成为可能。

The value of the JDBC API is that an application can access virtually any data source and run on any platform with a Java Virtual Machine. In other words, with the JDBC API, it isn’t necessary to write one program to access a Sybase database, another program to access an Oracle database, another program to access an IBM DB2 database, and so on. One can write a single program using the JDBC API, and the program will be able to send SQL or other statements to the appropriate data source. And, with an application written in the Java programming language, one doesn’t have to worry about writing different applications to run on different platforms. The combination of the Java platform and the JDBC API lets a programmer write once and run anywhere.

JDBC API的价值在于,应用程序几乎可以访问任何数据源,并可以在任何具有Java虚拟机的平台上运行。换句话说,使用JDBC API,不必编写一个程序来访问Sybase数据库,另一个程序访问Oracle数据库,另一个程序访问IBM DB2数据库,等等。可以使用JDBC API编写单个程序,程序将能够将SQL或其他语句发送到适当的数据源。而且,使用Java编程语言编写的应用程序,人们不必担心编写不同的应用程序以在不同的平台上运行。Java平台和JDBC API的结合使程序员可以编写一次并在任何地方运行。

The JDBC API is the industry standard for database-independent connectivity between the Java programming language and a wide range of databases. The JDBC API provides a call-level API for SQL-based database access. JDBC technology allows you to use the Java programming language to exploit “Write Once, Run Anywhere” capabilities for applications that require access to enterprise data.

JDBC API是Java编程语言与各种数据库之间数据库无关连接的行业标准。JDBC API为基于SQL的数据库访问提供了调用级API。JDBC技术允许您使用Java编程语言为需要访问企业数据的应用程序利用“一次编写,随处运行”功能


JDBC API有什么作用?

简单来说,基于JDBC技术的驱动程序(“JDBC驱动程序”)可以做三件事:

  1. 建立与数据源的连接
  2. 将查询和更新语句发送到数据源
  3. 处理结果

JDBC API主要位于JDK中的java.sql包中(之后扩展的内容位于javax.sql包中),主要包括(斜体代表接口,需驱动程序提供者来具体实现):

  • DriverManager:负责加载各种不同驱动程序(Driver),并根据不同的请求,向调用者返回相应的数据库连接(Connection)。
  • Driver:驱动程序,会将自身加载到DriverManager中去,并处理相应的请求并返回相应的数据库连接(Connection)。
  • Connection:数据库连接,负责进行与数据库间的通讯,SQL执行以及事务处理都是在某个特定Connection环境中进行的。可以产生用以执行SQL的Statement。
  • Statement:用以执行SQL查询和更新(针对静态SQL语句和单次执行)。
  • PreparedStatement:用以执行包含动态参数的SQL查询和更新(在服务器端编译,允许重复执行以提高效率)。
  • CallableStatement:用以调用数据库中的存储过程。
  • SQLException:代表在数据库连接的创建和关闭和SQL语句的执行过程中发生了例外情况(即错误)。

从根本上来说,JDBC是一种规范,它提供了一套完整的接口,允许对底层数据库进行可移植的访问,Java可以用于编写不同类型的可执行文件,例如:

  • Java Applications
  • Java Applets
  • Java Servlets
  • Java ServerPages(JSps)
  • Enterp JavaBeans(EJBs)

这些不同的可执行文件都能够使用JDBC驱动程序访问数据库,JDBC提供与ODBC相同的功能,允许Java程序包含与数据库无关的代码


两层和三层模型

JDBC API支持用于数据库访问的两层和三层模型。

  1. 数据库访问的双层体系结构
image
image

在双层模型中,Java applet或应用程序直接与数据源对话。这需要一个JDBC驱动程序,它可以与正在访问的特定数据源进行通信。用户的命令被传递到数据库或其他数据源,并且这些语句的结果被发送回用户。数据源可以位于用户通过网络连接的另一台机器上。这被称为客户端/服务器配置,用户的机器作为客户端,机器将数据源作为服务器。网络可以是内联网,例如,连接公司内的员工,或者可以是因特网

  1. 数据库访问的三层体系结构
    image
    image

在三层模型中,命令被发送到服务的“中间层”,然后将命令发送到数据源。数据源处理命令并将结果发送回中间层,然后中间层将它们发送给用户。MIS主管发现三层模型非常具有吸引力,因为中间层可以保持对访问的控制以及可以对公司数据进行的更新。另一个优点是它简化了应用程序的部署。最后,在许多情况下,三层架构可以提供性能优势。直到最近,中间层通常使用C或C ++等语言编写,这些语言提供了快速的性能。但是,随着优化编译器的引入,将Java字节码转换为高效的机器特定代码和技术,例如Enterprise JavaBeanstm,Java平台正迅速成为中间层开发的标准平台。这是一个很大的优势,可以利用Java的健壮性,多线程和安全功能。随着企业越来越多地使用Java编程语言编写服务器代码,JDBC API在三层体系结构的中间层中越来越多地被使用。使JDBC成为服务器技术的一些功能是它支持连接池,分布式事务和断开连接的行集。当然,JDBC API允许从Java中间层访问数据源。


SQL 一致性

SQL是访问关系数据库的标准语言。不幸的是,SQL还没有像人们想的那样标准化。

一个难点是不同DBMS(数据库管理系统)使用的数据类型有时会有所不同,并且变化可能很大。JDBC通过在类中定义一组通用SQL类型标识符来处理此问题java.sql.Types。请注意,在本资料中使用的术语“JDBC SQL类型”,“JDBC类型”和“SQL类型”是可互换的,并且是指在中定义的通用SQL类型标识符java.sql.Types。在“映射SQL和Java类型”一章中有关于数据类型一致性的更完整的讨论。

SQL一致性的另一个难点是尽管大多数DBMS使用标准形式的SQL来实现基本功能,但它们不符合最近定义的标准SQL语法或更高级功能的语义。例如,并非所有数据库都支持存储过程或外部联接,并且那些数据库并不总是相互一致。此外,对SQL3功能和数据类型的支持也有很大差异。希望真正标准的SQL部分将扩展为包含越来越多的功能。但是,在此期间,JDBC API必须支持SQL。

JDBC API处理此问题的一种方法是允许将任何查询字符串传递给底层DBMS驱动程序。这意味着应用程序可以根据需要自由使用尽可能多的SQL功能,但是它存在在某些DBMS上收到错误的风险。实际上,应用程序查询可能不是SQL,或者它可能是为特定DBMS设计的SQL的专用衍生物(例如,用于文档或图像查询)。

JDBC处理SQL一致性问题的第二种方法是提供ODBC样式的转义子句。转义语法为SQL分歧的几个更常见的领域提供了标准的JDBC语法。例如,日期文字和存储过程调用都有转义。

对于复杂的应用程序,JDBC以第三种方式处理SQL一致性。它通过接口提供有关DBMS的描述性信息,DatabaseMetaData以便应用程序可以适应每个DBMS的要求和功能。但是,典型的最终用户无需担心元数据。

由于JDBC API用作开发数据库访问工具和其他API的基本API,因此它还必须解决构建在其上的任何内容的一致性问题。JDBC驱动程序必须至少支持ANSI SQL-92 Entry Level。(ANSI SQL-92指的是1992年美国国家标准协会采用的标准。入门级指的是SQL功能的特定列表。)但是,请注意,尽管JDBC 2.0 API包括对SQL3和SQLJ的支持,但JDBC驱动程序不需要支持他们。

鉴于数据库供应商,连接供应商,Internet服务供应商和应用程序编写者广泛接受JDBC API,它已成为Java编程语言数据访问的标准。


常见的JDBC组件

JDBC API提供一下接口和类

  • DriverManager: 该类管理数据库驱动列表.使用通信自协议匹配来自Java应用程序的连接请求和正确的数据库驱动程序.识别JDBC下某个子协议的第一个驱动程序将用于建立数据库连接.
  • Driver: 此接口处理与数据库服务器的通信. 您将很少直接与Driver对象进行交互.而是使用DriverManager对象来管理此类对象.它还抽象了与使用Driver对象相关的细节.
  • Connection:此接口包含用于联系数据库的所有方法.连接对象表示通信上下文.即,与数据库的所有通信仅通过连接的对象.
  • Statement: 您使用此接口创建的对象将SQL语句提交到数据库,除执行存储过程外,某些派生接口还接受参数.
  • ResultSet: 在使用Statement对象执行SQL查询后,这些对象保存从数据库检索的数据,它充当迭代器,允许您遍历其数据.
  • SQLException: 此类处理数据库应用程序中发生的任何错误.

JDBC驱动程序类型

JDBC驱动程序实现了在JDBC API中定义的接口,用于于数据库服务器交互。例如:使用JDBC驱动程序可以通过发送SQL或数据库命令,然后使用Java接收结果,让你打开数据库连接并与数据库进行交互。JDK附带的 Java.sql包中包含了各种类,其行为已定义,其实际实现在第三方驱动程序中完成。第三方供应商在其数据库驱动程序中实现java.sql.Driver接口。下图显示了各种驱动程序实现的可能性:

image
image

在开发JDBC API之前,Microsoft的ODBC(开放数据库连接)API是用于访问关系数据库的最广泛使用的编程接口。

JDBC驱动程序实现因Java运行的各种操作系统和硬件平台而异,Sun将实现类型分为四种类型:

Type 1:JDBC-ODBC Bridge Driver

该类型驱动程序中,JDBC桥接器用于访问安装在每台客户机上的ODBC驱动程序。驱动把所有JDBC的调用传递给ODBC,再让后者调用数据库本地驱动代码(也就是数据库厂商提供的数据库操作二进制代码库,例如Oracle中的oci.dll),使用ODBC需要在系统上配置表示目标数据库的数据源名称(DSN)
当Java第一次出现时,这是一个有用的驱动程序,因为大多数的数据库只支持ODBC访问,但现在推荐这种类型的驱动程序仅用于实验用途或没有其他代替方案可用

image
image

优点:

  • 只要有对应的ODBC驱动(大部分数据库厂商都会提供),几乎可以访问所有的数据库。

缺点:

  • 执行效率比较低,不适合大数据量访问的应用
  • 由于需要客户端预装对应的ODBC驱动,不适合Internet/Intranet应用

Type 2:JDBC-Native API

在该类型驱动程序中,驱动通过客户端加载数据库厂商提供的本地代码库(C/C++等)来访问数据库,JDBC API调用将转换为本机C/C++ API调用,这些调用对于数据库是唯一的。与JDBC-ODBC Bridge的使用方式相同,必须在每台客户端计算机上安装供应商特定的驱动程序。
如果我们更改数据库,我们必须更改本机API,因为它是特定于数据库的而且现在几乎已经过时,但是你可能会通过该类型的驱动程序实现一些速度提升,因为它消除了ODBC的开销

image
image

优点:

  • 速度快于第一类驱动(但仍比不上第3、第4类驱动)。

缺点:

  • 由于需要客户端预装对应的数据库厂商代码库,仍不适合Internet/Intranet应用。

Type 3:JDBC-Net pure Java (网络协议)

此驱动程序给客户端提供了一个网络API,客户端上的JDBC使用套接字(Socket)来调用服务器上的中间件程序,该程序将JDBC调用转换为独立于DBMS的网络协议,然后由服务器将其转换为DBMS协议并转发到数据库服务器。这个网络服务器中间件能够将其纯Java客户端连接到许多不同的数据库。使用的具体协议取决于供应商。通常,这是最灵活的JDBC替代方案,它不需要在客户端上安装代码。该解决方案的所有供应商都可能提供适合Intranet使用的产品。为了使这些产品也支持Internet访问,它们必须处理Web强加的安全性,通过防火墙访问等的附加要求。

image
image

可以将应用程序服务器视为JDBC“代理”,这意味着它会调用客户端应用程序。因此,需要了解应用程序服务器的配置,以便有效地使用此驱动程序。

优点:

  • 不需要在客户端加载数据库厂商提供的代码库,单个驱动程序可以对多个数据库进行访问,可扩展性较好。

缺点:

  • 在中间件层仍需对最终数据进行配置
  • 由于多出一个中间件层,速度不如第四类驱动程序

Type 4:Pure Java

这种类型的驱动程序将JDBC调用直接转换为DBMS使用的网络协议。这允许从客户端计算机直接调用DBMS服务器,是Intranet访问的绝佳解决方案。由于许多协议都是专有的,因此数据库供应商本身就是主要来源。

在Type 4驱动程序中,基于纯Java的驱动程序通过套接字连接与供应商的数据库直接通信。这是数据库可用的最高性能驱动程序,通常由供应商自己提供。
这种驱动是非常灵活的,不需要在客户端或服务器上安装特殊的软件。此外,这些驱动程序可以动态下载。

image
image

优点:

  • 访问速度最快
  • 这是最直接、最纯粹的Java实现

缺点:

  • 几乎只有数据库厂商自己才能提供这种类型的JDBC驱动
  • 需要针对不同的数据库使用不同的驱动程序

MySql的 Connector/J 就是Type 4驱动程序。由于其网络协议的专有性质,数据库供应商通常提供Type 4驱动程序。

JDBC 体系结构

JDBC API包含两组主要的接口:第一组是应用程序编写者的JDBC API,第二组是驱动程序编写者的低级JDBC驱动程序API。应用程序和applet可以使用基于纯Java JDBC技术的驱动程序通过JDBC API访问数据库,如图所示:

image
image

左侧Type 4,右侧Type 3

下图说明了使用ODBC驱动程序和现有数据库客户端库的JDBC连接:

image
image

左侧Type 1,右侧Type 2

应该使用哪种驱动程序?

驱动类别|全Java|网络连接
-|-|-
JDBC-ODBC Bridge|No|Direct
Native API as basis|No|Direct
JDBC-Net|client Yes/server Maybe|Indirect
Native protocol as basis|Yes|Direct

  • Direct:JDBC客户端直接与DBMS服务器建立的连接,该服务器可能是远程的
  • Indirect:JDBC客户端与中间件进程建立的连接,充当DBMS服务器的桥梁

驱动程序Type 3和4是使用JDBC API访问数据库的首选方法。驱动程序Type 1和2是临时解决方案,其中尚未提供直接纯Java驱动程序。对于需要中间件的Type 1和Type 2(下表中未显示)可能存在差异,但这些通常是不太理想的解决方案。Type 3和4提供了Java技术的所有优点

  • 如果要访问某种类型的数据库(如Oracle,Sybase或IBM),则首选Type 4驱动程序
  • 如股票Java应用程序同时访问多种类型的数据库,则Type 3是首选驱动程序
  • 当Type 3或Type 4驱动程序不可用的情况下采用Type 2驱动程序
  • Type 1驱动程序不被视为部署及驱动程序,通常仅用于开发和测试

JDBC-Datebase Connections

安装了适当的驱动程序后,就可以使用JDBC简历数据库连接了.以下是四个基本步骤:

  • 导入JDBC包:需要包含数据库变成所需的JDBC类的包.大多数情况下,
    使用 import sql.* 就够了
  • 注册JDBC驱动程序:需要初始化驱动程序,将JVM所需的驱动程序实现加载到内存中,以便可以打开与数据库的通信通道.
  • 打开一个连接: 需要使用DriverManager.getConneection()方法创建一个Conection对象,它表示与数据库的物理连接.
  • 执行查询:需要使用类型为Statement的对象来构建和提交SQL语句到数据库.
  • 从结果集中提取数据:需要使用相应的ResultSet.getXXX()方法从结果集中检索数据.
  • 清理环境: 需要明确的关闭所有数据库资源,而不依赖于JVM的垃圾回收.
导入JDBC包

import语句告诉Java编辑器在哪里找到你在代码中引用的类的源代码位置.要使用标准JDBC包(允许在SQL表中选择,插入,更新和删除数据),请将以下导入添加到源代码中:

impor java.sql.*;
注册JDBC驱动程序

在实用程序之前,必须先注册该驱动程序,将驱动程序的类文件加载到内存中,因此可以将其用作JDBC接口的实现。

Class.forName();

注册驱动程序最常用的方法是使用Java的Class.forName()将驱动程序的类文件动态加载到内存中,自动注册他。此方法是首选,因为它使驱动程序的注册可配置和可移植。

try{
    Class.forName("com.mysql.jdbc.Driver");
}catch(ClassNotFoundException e){
    //
}

注册驱动实际执行下面代码:

static {
    try {
        java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!");
    }
}
数据库URL配置

可以使用DriverManager.getConnection() 方法建立连接。为了便于参考,这里列出三个重载的方法:

  • getConnection(String url)
  • getConnection(String url,Properties prop)
  • getConnection(String url,String user,String password)

每个方法都需要一个数据库URL,下表列出了常用的JDBC、驱动程序名称和数据库URL。

RDBMS|JDBC驱动程序名称|网址格式
-|-|-
MySQL|com.mysql.jdbc.Driver|jdbc:myql://hostname/databaseName
ORACLE|oracle.jdbc.driver.OracleDriver|jdbc:oracle:thin:@hostname:port:databaseName
DB2|com.ibm.db2.jdbc.net.DB2Driver|jdbc:db2: hostname:port/databaseName
Sysbase|com.sybase.jdbc.SybDriver|jdbc:sybase:Tds: hostname:port/databaseName

URl中突出显示的部分都是静态的,只需要根据数据库设置更改剩余部分。


创建连接对象

数据库服务安装在本机,端口号为3306,用户名为root,密码为root,数据库名是test。

  • 使用带用户名和密码的数据库URL
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url,user,password);
  • 仅使用数据库URL
String url = "jdbc:mysql://localhost:3306/test?user=root&password=root";
Connection connection = DriverManager.getConnection(url);
  • 使用数据库URL和Properties对象
String url = "jdbc:mysql://localhost:3306/test";
Properties info = new Properties();
info.put("user","root");
info.put("password","root");
Connection connection = DriverManager.getConnection(url,info);

关闭JDBC连接

在JDBC程序结束时,需要明确关闭所有的数据库连接。如果在程序中忘了关闭,Java垃圾回收器将在清理果实对象时关闭该连接。
但是依靠垃圾回收,特别是数据库编程,是一个很不好的编程实践。所以应该要养成使用与连接对象关联的close()方法关闭连接的习惯。
要确保连接已关闭,可以将关闭连接的代码编写在“finally”块中,不管是否发生异常“finally”块最后总是会执行。

connection.close();

Statements、PreparedStatement和CallableStatement

一旦获得链接,我们就可以与数据库进行交互。JDBC Statement,PreparedStatement和CallableStatement接口定义了能够发送SQL或PL/SQL命令以及从数据库接收数据的方法和属性。
他们还定义了有助于桥接数据库中使用的Java和SQL数据类型之间的数据类型差异的方法。
下表提供了每个接口确定要使用的接口的目的的摘要。

接口|推荐用途
-|-
Statement|用于对数据库的通用访问。在运行时使用静态SQL语句时很有用。Statement接口不能接收参数。
PreparedStatement|当你计划多次使用SQL语句时使用。PreparedStatement接口在运行时可以接收输入参数。
CallableStatement|在想要访问数据库存储过程时使用。CallableStatement接口也可以接受运行时输入的参数。

  1. Statement
Statement stmt = null;
try{
    stmt = connection.createStatement();
}catch(SQLException e){
    ...
}finally{
    stmt.close();
}

在创建Statement对象后,可以使用它来执行一个SQL语句,它有三个方法可以执行。分别是:

  • boolean execute(String sql):如果可以检索到ResultSet对象,则返回一个布尔值true,否则返回false。使用此方法执行SQL DDL语句或者需要使用真正的动态SQL,可以使用执行创建数据库,创建表的SQL语句等等。
  • int executeUpdate(String sql):返回受执行SQL语句所影响的行数。使用次方法执行预期会影响多行的SQL语句,例如:INSERT、UPDATE或DELETE语句。
  • ResultSet executeQuery(String sql):返回一个ResultSet对象。当您西往获得结果集时,请使用此方法,就像使用SELECT语句一样。

关闭Statement对象

就像关闭Connection对象以保存数据库资源一样,出于同样的原因,也应该关闭Statement对象。如果先关闭Connection对象,它也会关闭Statement对象。但是应该明确关闭Statement对象以确保正确的清理顺序。

  1. PreparedStatement对象

    PreparedStatement扩展了Statement接口,它提供了一些通用Statement额外的功能。这个语句具有动态提供参数的灵活性。

PreparedStatement pstmt = null;
try{
    String sql = "UPDATE Employees SET age = ? WHERE id = ?";
    pstmt = connection.prepareStatement(sql);
    pstmt.setInt(1,18);
    ...
}catch(SQLException e){
    ...
}finally{
    pstmt.close();
}
  • JDBC中的所有参数都有‘?’符号作为占位符,这被称为参数标记。在执行SQL语句之前,必须为每个参数(占位符)提供值。
    setXXX(int parameterIndex, XXX x) 方法将值绑定到参数,其中XXX表示绑定到输入参数的值的Java数据类型。如果忘记提供值,将抛出一个SQLException。
  • 每个参数标记是它其位置序号的引用。第一个标记表示位置1,下一个位置2等等。该方法与Java数组下标不同(从0开始)
  • 所有Statement对象与数据库交互的方法:execute(),executeQuery()和executeUpdate()也可以用于PreparedStatement对象。但是,这些方法被修改为可以使用输入参数的SQL语句。
关闭PreparedStatement对象

如果先关闭Connection对象,也会关闭PreparedStatement对象。但是,应该明确的关闭PreparedStatement对象以确保正确顺序清理资源。

  1. CallableStatement对象
    类似Connection对象创建Statement和PreparedStatement对象一样,它还可以使用桐言的方式创建CallableStatement对象,该对象将用于执行对数据库存储过程的调用。

假如需要执行以下MySQL存储过程:

DELIMITER $$

DROP PROCEDURE IF EXISTS 'EMP'.'getName' $$
CREATE PROCEDURE 'EMP'.'getName'
    (IN EMP_ID INT,OUT EMP_FIRST VARCHAR(255)
BEGIN 
    SELECT first INTO EMP_FIRST
    FROM Employees
    WHERE ID = EMP_ID;
END $$

DELIMITER ;

存在三种类型的参数:IN,OUT和INOUT。PreparedStatement对象仅使用IN参数。CallableStatement对象可以使用这三个。
以下是上面三种类型参数的定义:

参数|描述
-|-
IN|创建SQL语句是其参数值是未知的。使用setXXX()方法将值绑定到IN参数。
OUT|有SQL语句返回的参数值。可以使用getXXX()方法从OUT参数中检索值。
INOUT|提供输入和输出值的参数。使用setXXX()方法绑定变量并使用getXXX()方法检索值。

CallableStatement cstmt = null;
try{
    String sql = "{call getEmpName (?,?)}";
    cstmt = connection.prepareCall(sql);
    ...
}catch(SQLException e){
    ...
}finally{
    cstmt.close();
}

String变量sql表示存储过程,带有两个参数占位符。
使用CallableStatement对象就像使用PreparedStatement对象一样。在执行语句之前,必须将值绑定到所有参数。否则将抛出一个SQLException。
如果有IN参数,只需遵循适用于PreparedStatement对象的相同规则和技术。
使用OUt和INOUT参数时,必须使用一个额外的CallableStatement对象方法registerOutParameter()。registerOutParamter()方法将JDBC数据类型绑定到存储过程并返回预期数据类型。
当调用存储过程,可以使用适当的getXXX()方法从OUT参数中检索该值,此方法将检索到的SQL类型的值转换为对应的Java数据类型。

关闭CallableStatement对象

就像关闭其他Statement对象一样,由于同样的原因(节省数据库系统资源),还应该关闭CallableStatement对象。

简单的调用close()方法将执行关闭CallableStatement对象。 如果先关闭Connection对象,它也会关闭CallableStatement对象。 但是,应该始终显式关闭CallableStatement对象,以确保按正确顺序的清理资源。

JDBC-Result Sets

SQL语句执行后从数据库,返回的数据放在结果集中。SELECT语句用于从数据库中选择行并在结果集中查看它们的标准方法。java.sql.ResultSet接口表示数据库查询的结果集。

ResultSet对象维护指向结果集中当前行的游标。术语”结果集“指的是ResultSet对象中包含的行和列数据。

ResultSet接口的方法可以分为三类:

  • 导航方法:用于移动游标。
  • 获取方法:用于查看游标指向的当前行的列中的数据
  • 更新方法:用于更新当前行的列中的数据。然后,也可以在底层数据库中更新。

游标可根据ResultSet的属性移动。在创建生成ResultSet的对应Statement时,将指定这些属性。

JDBC提供以下链接方法来创建具有所需ResultSet的语句:

  • createStatement(int resultSetType,int resultSetConcurrency)
  • prepareStatement(String sql,int resultSetType,int resultSetConcurrency)
  • prepareCall(String sql,int resultSetType,int resultSetConcurrency)

resultSetType参数表示ResultSet对象的类型,resultSetConcurrency参数指定结果集是只读还是可更新。

Type of ResultSet

可以指定的的resultSetType类型如下:(如果不指定任何resultSetType类型,将自动分配一个TYPE_FORWARD_ONLY值)

类型|描述
-|-
ResultSet.TYPE_FORWARD_ONLY|游标只能在结果集中向前移动
ResultSet.TYPE_SCROLL_INSENSITIVE|游标可以向前和向后滚动,结果集对创建结果集后发生的数据库所做的更改不敏感
ResultSet.TYPE_SCROLL_SENSITIVE|游标可以向前和向后滚动,结果集对创建结果集之后的其他数据库的更改敏感

Concurrency of ResultSet

可以制定的resultSetConcurrency类型如下:(如果不指定任何resultSetConcurrency类型,将自动分配一个CONCUR_READ_ONLY值)

并发性|描述
-|-
ResultSet.CONCUR_READ_ONLY|创建只读结果集,这是默认值。

ResultSet.CONCUR_UPDATABLE|创建可更新的结果集。

浏览结果集
方法|描述
-|-
public void beforeFirst() throw SQLException|将游标移动到第一行之前
public void afterLast() throw SQLException|将游标移动到最后一行之后
public boolean first() throw SQLException|将游标移动到第一行
public void last() throw SQLException|将游标移动到最后一行
public boolean absolute (int row) throw SQLException|将游标移动到指定行
public boolean relative (int row) throw SQLException|将游标从当前位置向前或向后移动给定的行数
public boolean previous () throw SQLException|将游标移动到上一行
public boolean next() throw SQLException|将游标移动到下一行
public int getRow() throw SQLException|返回游标指向的行号
public void moveToInsertRow () throw SQLException|将游标移动到结果集中的一个特殊行,该行可用于将新航插入数据库

public void moveToCurrentRow () throw SQLException|如果游标位于插入行,将光标移动到记住的光标位置,通常为当前行。如果光标不位于插入行上,则此方法无效。

查看结果集

每个可能的数据类型都有get方法,每个get方法都有两个版本:

  • 采用列名称
  • 采用列索引

方法|描述
-|-
public xxx getXXX(String columnName) throws SQLException|返回当前行名为columnName的列中值

public xxx getXXX(int columnIndex) throw SQLException|返回当前行中指定列索引的值。列索引从1开始,意味着行的第一列为1

更新结果集

ResultSet接口包含一组用于更新结果集的数据的更新方法。与get方法一样,每种类型都有两种:

  • 采用列名称
  • 采用列索引

例如,要更新结果集当前行的String例:
方法|描述
-|-
public void updateString(int columnIndex,String s) throw SQLException|将指定列中的String值更改为s的值
public void updateString(String columnName,String s) throw SQLException|将列名为columnName的String值更改为s的值

更新结果集中的行会更改ResultSet对象中当前行的列,但不会更改基础数据库中的列。要想对数据库中的行进行更改,需要调用以下方法:

方法|描述
-|-
public void updateRow()|通过更新数据库中相对应行来更新当前行
public void deleteRow()|从数据库中删除当前行
public void refreshRow()|刷新结果集中的数据以反映数据库中的任何最新更改
public void cancelRowUpdates()|取消对当前行进行的任何更新

public void insertRow()|在数据库中插入一行。只有在游标指向插入行时才能调用此方法。

数据类型

JDBC驱动程序将Java数据类型转换为适当的JDBC类型。然后将其发送到数据库。它为大多数数据类型提供并使用默认映射。例如:Java int类型会被转换成SQL INTERGER。创建默认映射以提供驱动程序之间的一致性

下表总结了在嗲用PreparedStatement或CallableStatement对象的setXXX()方法或ResultSet.updateXXX()方法时,Java数据类型转换为JDBC的默认数据类型:
SQL类型|JDBC/Java类型|setXXX|updateXXX
-|-|-|-
VARCHAR|java.lang.String|setString|updateString
CHAR|java.lang.String|setString|updateString
LONGVARCHAR|java.lang.String|setString|updateString
BIT|boolean|setBoolean|updateBoolean
NUMERIC|java.math.BigDecimal|setBigDecimal|updateBigDecimal
TINYINT|byte|setByte|updateByte
SMALLINT|short|setShort|updateShort
INTEGER|int|setInt|updateInt
BIGINT|long|setLong|updateLong
REAL|float|setFloat|updateFloat
FLOAT|float|setFloat|updateFloat
DOUBLE|double|setDouble|updateDouble
VARBINARY|byte[ ]|setBytes|updateBytes
BINARY|byte[ ]|setBytes|updateBytes
DATE|java.sql.Date|setDate|updateDate
TIME|java.sql.Time|setTime|updateTime
TIMESTAMP|java.sql.Timestamp|setTimestamp|updateTimestamp
CLOB|java.sql.Clob|setClob|updateClob
BLOB|java.sql.Blob|setBlob|updateBlob
ARRAY|java.sql.Array|setARRAY|updateARRAY
REF|java.sql.Ref|setRef|updateRef
STRUCT|java.sql.Struct|setStruct|updateStruct

JDBC 3.0增强了对BLOB,CLOB,ARRAY和REF数据类型的支持。 ResultSet对象现在具有updateBLOB(),updateCLOB(),updateArray()和updateRef()方法,使您能够直接操作数据库服务器上的相应数据。

setXXX()和updateXXX()方法可以将特定的Java类型转换为特定的JDBC数据类型。方法setObject()和updateObject()可以将几乎任何Java类型映射到JDBC数据类型。


ResultSet对象为每个数据类型提供了相应的get方法来检索列值。每个方法都可以使用列明或其序号位置来检索值。

SQL|JDBC/Java|getXXX
-|-|-
VARCHAR|java.lang.String|getString
CHAR|java.lang.String|getString
LONGVARCHAR|java.lang.String|getString
BIT|boolean|getBoolean
NUMERIC|java.math.BigDecimal|getBigDecimal
TINYINT|byte|getByte
SMALLINT|short|getShort
INTEGER|int|getInt
BIGINT|long|getLong
REAL|float|getFloat
FLOAT|float|getFloat
DOUBLE|double|getDouble
VARBINARY|byte[ ]|getBytes
BINARY|byte[ ]|getBytes
DATE|java.sql.Date|getDate
TIME|java.sql.Time|getTime
TIMESTAMP|java.sql.Timestamp|getTimestamp
CLOB|java.sql.Clob|getClob
BLOB|java.sql.Blob|getBlob
ARRAY|java.sql.Array|getARRAY
REF|java.sql.Ref|getRef

STRUCT|java.sql.Struct|getStruct

事务

如果JDBC连接处于自动提交模式(默认情况下),那么每个SQL语句在完成后都会提交到数据库
对于简单的应用程序可能没问题,但是有三个原因需要考虑是否关闭自动提交并管理自己的事务:

  • 提高性能
  • 保证业务流程的完整性
  • 使用分布式事务

事务能够控制何时将更改提交并应用于数据库。它将单个SQL语句或一组SQL语句视为一个逻辑单元,如果任何语句失败,则整个事务失败
要启动手动事务支持,需调用Connection对象的setAutoCommit()方法。如果将布尔值false传给setAutoCommit(),则关闭自动提交,也可以传递一个true来重新打开它

connection.setAutoCommit(false);

提交和回滚

完成更改后,若要提交更改,那么可调用connection对象的commit()方法

connection.commit();

如果要回滚数据库更新,使用以下代码:

connection.rollback();

以下示例展示了如果使用提交和回滚:

try{
    connection.setAutoCommit(false);
    Statement stmt = connection.createStatement();
    String sql = "INSERT INTO Employees VALUES (123,'java');
    stmt.executeUpdate(sql);
    connection.commit();
}catch(SQLException e){
    connection.rollback();
}finally{
    //release resource
}

Savepoint

新的JDBC 3.0添加了Savepoint接口提供了额外的事务控制能力。大多数现代DBMS支持其环境中的保存点,
设置Savepoint时,可以在事务中定义逻辑回滚点。如果在Savepoint之后发生错误,则可以使用回滚方法来撤销所有更改或仅保存保存点之后所做的更改。
Connection对象由两个新方法管理保存点:

  • Savepoint setSavepoint(String name):定义新的保存点并返回该对象
  • void releaseSavepoint(Savepoint savepoint):删除保存点。
    有一个void rollback(Savepoint savepoint)方法,它将使用事务回滚到制定的保存点。
Savepoint savepoint = null;
try{
    connection.setAutoCommit(false);
    Statement stmt = connection.createStatement();
    String sql = "INSERT INTO Employees VALUES (123,'Java')";
    stmt.executeUpdate(sql);
    savepoint = connection.setSavepoint("Savepoint");
    sql = "INSERT INTO Employees VALUES (456,'C++')";
    stmt.executeUpdate(sql);
    sql = "INSERT INTO Employees VALUES ('789','Python')";//error sql
    stmt.executeUpdate(sql);
    connection.commit();
}catch(SQLException e){
    if (savepoint == null){
        connection.rollback();
    }else{
        connection.rollback(savepoint);
        connection.commit();    
    }
}finally{
    //release resource
}

在这种情况下,第二条INSERT不会成功。

批量处理

批量处理允许将相关的SQL语句分组到批处理中,并通过对数据路的一次调用来提交它们。一次向数据库发送多个SQL语句,可以减少通信开销,从而提高性能

  • 应使用DatabaseMetaData的supportsBatchUpdates()方法来确定目标数据库是否支持批量更新。如果JDBC驱动程序支持此功能,该方法将返回true
  • Statement、PreparedStatement和CallableStatement的addBatch()方法用于将单个语句添加到批处理。
  • executeBatch()用于执行分在一组的所有语句并返回一个int数组,数组的每个元素表示相应的更新语句的更新计数
  • clearBatch()方法可以删除使用addBatch()方法添加的所有语句。但是无法选择要删除某个语句

使用Statement对象进行批处理

  • 使用connection.createStatement()方法创建Statement对象
  • 使用connection.setAutoCommit(false)取消自动提交
  • 使用statement.addBatch(String sql)方法将SQL语句添加到批处理中
  • 使用statement.executeBatch()方法执行所有SQL语句
  • 使用connection.commit()方法提交所有更改
//create Statement object
Statement stmt = connection.createStatement();

//set auto-commit to false
connection.setAutoCommit(false);

//create some SQL statement
String sql1 = "INSERT INTO Employees VALUES (123,'Java')";
String sql2 = "INSERT INTO Employees VALUES (456,'C++')";

//add above SQL statement in the batch
stmt.addBatch(sql1);
stmt.addBatch(sql2);

//execute batch tasks
int[] result = stmt.executeBatch();

//commit transaction
connection.commit();

使用PreparedStatement对象进行批处理

  • 创建带有占位符的SQL语句
  • 使用connection.prepareStatement()方法创建PreparedStatement对象
  • 使用connection.setAutoCommit(false)方法取消自动提交
  • 使用preparedStatement.addBatch(String sql)方法将SQL语句添加到批处理中
  • 使用preparedStatement.executeBatch()方法执行所有SQL语句
  • 使用connection.commit()方法提交所有更改
//create SQL Statement
String sql = "INSERT INTO Employees VALUES (?,?)";

//create PreparedStatement object
PreparedStatement pstmt = connection.prepareStatement(sql);

//set auto-commit to false
connection.setAutoCommit(false);

//set the variables
pstmt.setInt(123);
pstmt.setString("Java");
pstmt.addBatch();

 //set the variables
pstmt.setInt(456);
pstmt.setString("C++");
pstmt.addBatch();

//execute batch tasks
int[] result = pstmt.executeBatch();

//commit transaction
connection.commit();

参考文献

Getting Started with the JDBC API:https://docs.oracle.com/javase/1.5.0/docs/guide/jdbc/getstart/GettingStartedTOC.fm.html

JDBC Tutorial:https://www.tutorialspoint.com/jdbc/index.htm

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » JDBC API的那些事,你真的知道吗?

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址