一、JDBC 概述
JDBC 即 Java 数据库连接,是 Java 语言中用来规范客户端程序访问数据库的应用程序接口,它提供了查询和更新数据库中数据的方法。JDBC API 由一组用 Java 编程语言编写的接口和类组成,主要接口和类有:DriverManager 类、Connection 接口、Statement 接口等。使用这些标准接口和类,程序员可以编写连接到数据库的应用程序、发送用 SQL 编写的查询并处理结果。而 JDBC 驱动程序为特定的 DBMS 厂商实现了这些接口和类,可以认为,JDBC 驱动程序在 Java 应用程序和所需数据库之间架起一座桥梁。使用 JDBC API 的 Java 应用程序在实际连接到数据库之前会加载特定 DBMS 的指定驱动程序,然后,JDBC DriverManager 类将所有 JDBC API 调用发送到加载的驱动程序来实现数据库的连接和访问。
JDBC 提供了从 java 语言到 X/Open SQL CLI 以及 SQL 标准的映射,这种映射比较自然且易于使用。JDBC API 主要包含在 java.sql 与 javax.sql 这两个包中。
JDBC API 为 java 程序提供了一个访问一个或多个数据源的方式。在大多数情况下,这些数据源都是关系型数据库管理系统,请求是通过 SQL 对数据进行访问。也可以在其他数据源上实现支持 JDBC API 的驱动程序,比如在遗留文件系统和面向对象系统上实现 JDBC API。
JDBC 支持数据库访问的两层和三层客户端 - 服务器模型。两层模型是指 Java 应用程序和目标数据库分别安装在两个层次的组件上。应用程序层包括 Java 应用程序,负责处理表达逻辑、业务逻辑、对包含多条语句的事务或者分布式事务的事务管理以及资源的管理。在此模型中,应用程序直接与 JDBC 驱动程序交互,包括建立和管理物理连接以及处理特定底层数据源实现的详细信息。该应用程序可以利用其对特定实现的了解来利用非标准功能或进行性能调整。但这种模型也有一些缺点,比如将表示和业务逻辑与基础结构和系统级功能相结合,给使用定义良好的体系结构生成可维护代码带来障碍;使应用程序的可移植性降低,因为应用程序被调整到特定的数据库实现;需要连接到多个数据库的应用程序必须了解不同供应商实现之间的差异;限制了可扩展性,通常应用程序会持有一个或多个物理数据库连接直到终止,限制了并发应用程序的数量。在这种模型中,性能、可扩展性和可用性问题由 JDBC 驱动程序和相应的底层数据源处理。如果应用程序处理多个驱动程序,它可能还需要了解每个驱动程序 / 数据源对如何解决这些问题。
二、JDBC 的使用步骤
(一)注册驱动
JDBC 注册驱动有三种方式,各有特点:
- DriverManager.registerDriver(new com.mysql.jdbc.Driver());:这种方式需要在程序中创建一个具体驱动类的实例,并且需要将该类通过import导入,否则会报错。另外,在Driver类中本身已经含有静态块将实例放入驱动列表中,所以这里实际上做了一次无用功。采用这种方式,程序在编译的时候不能脱离驱动类包,为程序切换到其他数据库带来麻烦。
- System.setProperty("jdbc.drivers","com.mysql.jdbc.Driver");:可以脱离驱动包编译,并且可以同时设置多个驱动,多个驱动之间使用冒号分隔。
- Class.forName("com.mysql.jdbc.Driver");:此方式由于参数为字符串,因此很容易修改,移植性强。这是最常见的注册方式,也是推荐的方式。
(二)建立连接
通过Connection建立连接可以使用DriverManager.getConnection()方法。该方法有三个重载方法:
- getConnection(String url)。
- getConnection(String url, Properties prop)。
- getConnection(String url, String user, String password)。
每个重载的方法都需要一个数据库 URL,数据库的 URL 是指向数据库地址。常见的 JDBC 驱动程序名和数据库的 URL 格式如下:
RDBMS | JDBC 驱动程序的名称 | URL 格式 |
MySQL | com.mysql.jdbc.Driver | jdbc:mysql://hostname/databaseName |
ORACLE | oracle.jdbc.driver.OracleDriver | jdbc:oracle:thin:@hostname:portNumber:databaseName |
DB2 | COM.ibm.db2.jdbc.net.DB2Driver | jdbc:db2:hostname:portNumber/databaseName |
Sybase | com.sybase.jdbc.SybDriver | jdbc:sybase:Tds:hostname:portNumber/databaseName |
创建连接对象的三种方式如下:
- 使用数据库 URL 的用户名和密码:
String URL = "jdbc:oracle:thin:@127.0.0.1:1521:orcl"; // 数据库 url
String USER = "mytest"; // 用户名
String PASS = "mytest"; // 密码
Connection conn = DriverManager.getConnection(URL, USER, PASS);
- 只使用一个数据库 URL:
String URL = "jdbc:oracle:thin:mytest/mytest@127.0.0.1:1521:orcl";
Connection conn = DriverManager.getConnection(URL);
- 使用数据库的 URL 和一个Properties对象:
String URL = "jdbc:oracle:thin:@127.0.0.1:1521:orcl";
Properties info = new Properties();
info.put("user", "mytest");
info.put("password", "mytest");
Connection conn = DriverManager.getConnection(URL, info);
(三)创建运行对象
由Connection对象可以产生Statement接口类及其派生接口类。Statement对象主要有两个功能,执行静态 SQL 语句和拿到执行结果集。实际项目中重点是通过Statement对象去执行各种 SQL 语句。
(四)运行 SQL 语句
Statement接口类提供了三个常用方法来运行不同类型的 SQL 语句:
- executeQuery(SQL 语句):用于执行查询语句,返回一个ResultSet对象。例如:
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from teacher");
- executeUpdate(SQL 语句):用于执行 INSERT、UPDATE 或 DELETE 语句以及 SQL DDL(数据定义语言)语句,返回一个整数,表示受影响的行数。例如:
Statement stmt = conn.createStatement();
return stmt.executeUpdate(sql);
- execute(SQL 语句):可用于执行任何 SQL 语句,返回一个boolean值,表明执行该 SQL 语句是否返回了ResultSet。如果执行后第一个结果是ResultSet,则返回true,否则返回false。例如:
boolean hasResultSet = stmt.execute(sql);
if (hasResultSet) {
rs = stmt.getResultSet();
// 处理结果集
} else {
System.out.println("该 SQL 语句影响的记录有" + stmt.getUpdateCount() + "条");
}
(五)处理运行结果
ResultSet对象负责保存查询结果。ResultSet是一个结果集,通过游标操作结果集。可以使用next()方法将光标从当前位置向前移一行,使用getString(int columnIndex)或getString(String columnLabel)等方法以 Java 编程语言中String的形式获取此ResultSet对象的当前行中指定列的值。例如:
while(rs.next()) {
int id = rs.getInt("id");
String bookName = rs.getString("bookName");
float price = rs.getFloat("price");
String author = rs.getString("author");
int bookTypeId = rs.getInt("bookTypeId");
System.out.println(id + " " + bookName + " " + price + " " + author + " " + bookTypeId);
}
(六)释放资源
Connection对象的close方法用于关闭连接并释放相关资源。在使用完数据库连接后,应该及时关闭连接,以释放资源,避免资源泄漏。按照 “先用的后关” 的原则,先关闭ResultSet,再关闭Statement,最后关闭Connection。例如:
if(rs!=null){
rs.close();
}
if(stmt!=null){
stmt.close();
}
if(conn!=null){
conn.close();
}
三、JDBC 驱动的秘密
(一)获取 JDBC 驱动 Jar
在使用 JDBC 连接数据库时,需要获取相应的 JDBC 驱动 Jar 包。以 MySQL 数据库为例,可以通过以下步骤获取驱动 Jar:
- 在百度搜索栏上搜索 “MySQL”。
- 选择 “Downloads”。
- 选择 “Community”。
- 在左侧选择 “MySQL Connectors”。
- 选择对应需要的开发语言的 jar 包,对于 Java 开发选择相应的版本。例如,如果需要 8.0.23 版本的 jar 包,在下载界面选择 “platfrom independent”,然后下载第二个。如果是要下载之前的版本,可以点击 “archives”,然后自行选择版本进行下载。
下载并解压之后,找到对应的 jar 包文件。
一般情况下,将获取到的 JDBC 驱动 Jar 包放置在项目的依赖目录中。对于使用 IDE(如 IntelliJ IDEA)的项目,可以通过以下两种方式导入驱动 Jar 包:
- 右键项目名称,创建一个 “Directory”,然后随便命个名,将复制的 jar 包粘贴到这个目录里。然后再右键该目录,选择 “add as library”。
- 右键 “src”,选择 “open moudle settings”,选择 “dependencies”,然后点击加号,选择 “jars or directories”,找到 jar 包,选择,然后一直 “OK”,最后把相应的勾勾打上。
(二)创建数据库连接代码
以下是创建数据库连接的具体代码示例:
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;
public class ConnectionExample {
public static Connection getConnection() {
// 从 db.properties 文件中获取数据库连接
Properties prop = new Properties();
try (InputStream in = ConnectionExample.class.getResourceAsStream("/db.properties")) {
prop.load(in);
} catch (IOException e) {
e.printStackTrace();
}
Class jdbcDriverClass;
Connection conn = null;
try {
jdbcDriverClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) jdbcDriverClass.newInstance();
DriverManager.registerDriver(driver);
conn = DriverManager.getConnection(prop.getProperty("jdbc.url"), prop.getProperty("jdbc.username"), prop.getProperty("jdbc.password"));
} catch (Exception e) {
e.printStackTrace();
if (conn!= null) {
try {
conn.close();
} catch (SQLException e2) {
e2.printStackTrace();
}
}
}
return conn;
}
}
在上述代码中,首先从配置文件中读取数据库连接参数,然后通过Class.forName加载驱动类,创建驱动实例并注册到DriverManager,最后使用DriverManager.getConnection方法创建数据库连接。各参数含义如下:
- jdbc.url:数据库的连接字符串,包含服务器地址、端口号、数据库名称等信息。
- jdbc.username:连接数据库的用户名。
- jdbc.password:连接数据库的密码。
(三)Class.forName 的作用
Class.forName在 JDBC 中用于加载指定的 JDBC 驱动类。其本质是让 JVM 到指定的路径下找到对应的类并将其加载到内存中,同时初始化这个类。
以 MySQL 为例,当执行Class.forName("com.mysql.jdbc.Driver")时,JVM 会加载名字为 “com.mysql.jdbc.Driver” 对应的驱动类。在 MySQL 的驱动类中,通常会有静态代码块,在类被加载时会执行这个静态代码块,一般会在静态代码块中调用DriverManager.registerDriver方法注册一个 MySQL 的 JDBC 驱动。简而言之,Class.forName的作用就是将数据库驱动注册到DriverManager中去。
(四)数据库与连接字符串
数据库与连接字符串有着紧密的关系。连接字符串是指通过定义一些参数来指定数据库的类型、地址、端口号、服务名、用户名和密码等信息,形成一个完整的连接 URL。通过这个连接字符串,JDBC 程序可以准确地找到目标数据库并建立连接。不同的数据库类型和驱动程序,连接字符串的格式会有所不同,但都包含了定位数据库和验证用户身份所需的关键信息。
(五)DriverManager 设备管理器
DriverManager在 JDBC 中用于注册 / 管理 JDBC 驱动程序及获取Connection对象。
注册驱动程序可以通过多种方式,一般不建议直接使用DriverManager.registerDriver(new Driver()),因为在一些驱动类中已经有静态代码块自动注册驱动,这样会导致驱动被注册两次。实际开发中常使用Class.forName("com.mysql.jdbc.Driver")的方式注册驱动,这种方式由于参数为字符串,容易修改,移植性强。
获取Connection对象可以使用DriverManager.getConnection()方法。该方法有多个重载方法,可以根据不同的需求传入数据库 URL、用户名、密码或Properties对象等参数来获取数据库连接。
(六)Connection 对象
Connection对象在 JDBC 与数据库网络通信中起着关键作用。它代表与特定数据库的连接,在连接上下文中执行 SQL 语句并返回结果。通过Connection对象可以产生Statement接口类及其派生接口类,实际项目中重点是通过Statement对象去执行各种 SQL 语句,从而实现与数据库的数据交互。
(七)MySQL 连接字符串格式
MySQL 连接字符串的格式一般为:jdbc:mysql://hostname/databaseName?param1=value1¶m2=value2...。其中,jdbc:mysql://是固定的前缀,表示使用 JDBC 连接 MySQL 数据库;hostname是数据库服务器的主机名或 IP 地址;databaseName是要连接的数据库名称。后面可以跟一系列的参数,用&连接,每个参数都是param=value的形式。
例如:jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8,表示连接本地的 MySQL 数据库,端口号为 3306,数据库名为test,并设置了字符编码为 UTF-8。
(八)MySQL 连接字符串常用参数
MySQL 连接字符串常用参数及作用如下:
- useUnicode=true:表示使用 Unicode 字符集。
- characterEncoding=UTF-8:设置字符编码为 UTF-8,确保数据的正确存储和读取。
- allowMultiQueries=true:允许执行多条 SQL 语句。
四、JDBC 编程六步曲
(一)第一步:注册驱动
在 JDBC 编程中,注册驱动是第一步。JDBC 注册驱动有三种方式:
- DriverManager.registerDriver(new com.mysql.jdbc.Driver());:这种方式需要在程序中创建一个具体驱动类的实例,并且需要将该类通过import导入,否则会报错。另外,在Driver类中本身已经含有静态块将实例放入驱动列表中,所以这里实际上做了一次无用功。采用这种方式,程序在编译的时候不能脱离驱动类包,为程序切换到其他数据库带来麻烦。
- System.setProperty("jdbc.drivers","com.mysql.jdbc.Driver");:可以脱离驱动包编译,并且可以同时设置多个驱动,多个驱动之间使用冒号分隔。
- Class.forName("com.mysql.jdbc.Driver");:此方式由于参数为字符串,因此很容易修改,移植性强。这是最常见的注册方式,也是推荐的方式。
(二)第二步:获取数据库连接
获取数据库连接是 JDBC 编程的关键步骤之一。在 Java 中,可以通过Connection接口来建立与数据库的连接。Connection连接对象的特点如下:
- 代表与特定数据库的连接,在连接上下文中执行 SQL 语句并返回结果。
- 通过Connection对象可以产生Statement接口类及其派生接口类,实际项目中重点是通过Statement对象去执行各种 SQL 语句,从而实现与数据库的数据交互。
获取数据库连接的方法通常是使用DriverManager.getConnection()方法。该方法有三个重载方法:
- getConnection(String url)。
- getConnection(String url, Properties prop)。
- getConnection(String url, String user, String password)。
每个重载的方法都需要一个数据库 URL,数据库的 URL 是指向数据库地址。常见的 JDBC 驱动程序名和数据库的 URL 格式如下:
RDBMS | JDBC 驱动程序的名称 | URL 格式 |
MySQL | com.mysql.jdbc.Driver | jdbc:mysql://hostname/databaseName |
ORACLE | oracle.jdbc.driver.OracleDriver | jdbc:oracle:thin:@hostname:portNumber:databaseName |
DB2 | COM.ibm.db2.jdbc.net.DB2Driver | jdbc:db2:hostname:portNumber/databaseName |
Sybase | com.sybase.jdbc.SybDriver | jdbc:sybase:Tds:hostname:portNumber/databaseName |
创建连接对象的三种方式如下:
- 使用数据库 URL 的用户名和密码:
String URL = "jdbc:oracle:thin:@127.0.0.1:1521:orcl"; // 数据库 url
String USER = "mytest"; // 用户名
String PASS = "mytest"; // 密码
Connection conn = DriverManager.getConnection(URL, USER, PASS);
- 只使用一个数据库 URL:
String URL = "jdbc:oracle:thin:mytest/mytest@127.0.0.1:1521:orcl";
Connection conn = DriverManager.getConnection(URL);
- 使用数据库的 URL 和一个Properties对象:
String URL = "jdbc:oracle:thin:@127.0.0.1:1521:orcl";
Properties info = new Properties();
info.put("user", "mytest");
info.put("password", "mytest");
Connection conn = DriverManager.getConnection(URL, info);
此外,还有多种获取数据库连接的方式,如在 CSDN 博客中提到的 JDBC 获取数据库连接的 5 种方式:
- 方式一:直接通过new的方式创建Driver对象,然后连接数据库。
- 方式二:使用反射加载Driver类,动态加载,更加灵活,减少依赖性。
- 方式三:使用DriverManager替换Driver。
- 方式四:使用Class.forName自动完成注册驱动,简化代码,推荐使用。
- 方式五:在方式四的基础上增加配置文件,让连接 MySQL 更加灵活。
(三)第三步:获取数据库操作对象
获取数据库操作对象是 JDBC 编程的重要环节。可以通过Connection对象来获取数据库操作对象。不同类型的操作对象有不同的作用:
- Statement接口类:主要有两个功能,执行静态 SQL 语句和拿到执行结果集。实际项目中重点是通过Statement对象去执行各种 SQL 语句。
- PreparedStatement接口类:是 SQL 语句预编译对象,当执行connection.prepareStatement(sql)获取PreparedStatement对象的时候,就会把传进去的 SQL 语句进行编译并且加载进内存中。在接下来的操作中只需要对 SQL 语句中的占位符 “?” 进行赋值,这样 SQL 语句就不需要再次重新编译,与Statement对象相比,提高了执行语句的性能、可读性和可维护性。
(四)第四步:执行 SQL 语句
在 JDBC 中,可以执行不同类型的 SQL 语句,方法及返回结果如下:
- executeQuery(SQL 语句):用于执行查询语句,返回一个ResultSet对象。例如:
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from teacher");
- executeUpdate(SQL 语句):用于执行 INSERT、UPDATE 或 DELETE 语句以及 SQL DDL(数据定义语言)语句,返回一个整数,表示受影响的行数。例如:
Statement stmt = conn.createStatement();
return stmt.executeUpdate(sql);
- execute(SQL 语句):可用于执行任何 SQL 语句,返回一个boolean值,表明执行该 SQL 语句是否返回了ResultSet。如果执行后第一个结果是ResultSet,则返回true,否则返回false。例如:
boolean hasResultSet = stmt.execute(sql);
if (hasResultSet) {
rs = stmt.getResultSet();
// 处理结果集
} else {
System.out.println("该 SQL 语句影响的记录有" + stmt.getUpdateCount() + "条");
}
(五)第五步:处理查询结果集
处理查询结果集是在执行查询语句后进行的操作。可以使用ResultSet对象来保存查询结果。ResultSet是一个结果集,通过游标操作结果集。处理查询结果集的方法如下:
- 使用next()方法将光标从当前位置向前移一行,使用getString(int columnIndex)或getString(String columnLabel)等方法以 Java 编程语言中String的形式获取此ResultSet对象的当前行中指定列的值。例如:
while(rs.next()) {
int id = rs.getInt("id");
String bookName = rs.getString("bookName");
float price = rs.getFloat("price");
String author = rs.getString("author");
int bookTypeId = rs.getInt("bookTypeId");
System.out.println(id + " " + bookName + " " + price + " " + author + " " + bookTypeId);
}
此外,结果集指针可以通过多种方式移动,如next()指向下一个位置,previous()指向前一个位置,relative(n)相对当前位置移动n步,absolute(n)指向结果集中的第n + 1条记录等。还可以通过isFirst()、isLast()、isBeforeFirst()、isAfterLast()等方法判断指针位置。
如果要长期使用结果集中的数据,可以使用离线查询,通过CachedRowSet实现,在本地搞一个结果集的副本,关闭结果集、数据库连接,使用本地副本。可更新的结果集(可增删改结果集中的记录)需要在创建Statement或PreparedStatement对象时传入额外参数,CachedRowSet默认是可滚动的、可更新的,可删除、修改其中的记录。更新结果集时,update、insert需要手动同步,delete却是自动同步,因为delete是一步操作,而update、insert可能是多步操作,JVM 不知道是否完成,需要写resultSet.updateRow()或insertRow()告诉 JVM 操作已完成。
(六)第六步:释放资源
释放资源在 JDBC 编程中非常重要。在使用完数据库连接后,应该及时关闭连接,以释放资源,避免资源泄漏。按照 “先用的后关” 的原则,先关闭ResultSet,再关闭Statement,最后关闭Connection。例如:
if(rs!=null){
rs.close();
}
if(stmt!=null){
stmt.close();
}
if(conn!=null){
conn.close();
}
五、Statement 和 PreparedStatement
(一)SQL 注入现象
- SQL 注入定义解释 SQL 注入的概念。
SQL 注入(SQL Injection)是发生在 Web 程序中数据库层的安全漏洞,是网站存在最多也是最简单的漏洞。主要原因是程序对用户输入数据的合法性没有判断和处理,导致攻击者可以在 Web 应用程序中事先定义好的 SQL 语句中添加额外的 SQL 语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步获取到数据信息。
- SQL 注入实例给出 SQL 注入的具体例子。
- 数字型 SQL 注入:这种注入发生在数字型参数上,它会把参数当做语句继续执行,从而改变 SQL 语句的结果。例如:SELECT * FROM table WHERE id =1 OR 1=1。
- 字符型 SQL 注入:这种注入发生在字符型参数上,它使用特殊字符对 SQL 语句进行改变,从而改变 SQL 语句的结果。例如:SELECT * FROM table WHERE name = 'admin' OR '1'='1'。
- 布尔型 SQL 注入:这种注入发生在布尔型参数上,它使用布尔型参数来改变 SQL 语句的结果。例如:SELECT * FROM table WHERE bool = true OR 1 = 1。
- 时间型 SQL 注入:这种注入发生在时间型参数上,它使用特殊时间参数来改变 SQL 语句的结果。例如:SELECT IF (SUBSTRING (user,1,1)='a', SLEEP (5), 1) FROM users WHERE id = 1。
- SQL 报错注入:是利用程序报错信息来注入恶意 SQL 语句,从而获取数据库数据的一种攻击技术。例如:SELECT * FROM users WHERE id = '1' OR '1' = '1'。
- 盲注:这种注入发生在使用特殊字符的情况下,它使用特殊字符来改变 SQL 语句的结果,无法定位参数,但可以返回正确的结果。例如:SELECT * FROM users WHERE name = (SELECT MID (password,1,1) FROM users WHERE id=1)。
- 联合查询注入:SELECT * FROM table WHERE id IN (SELECT id FROM user WHERE name = 'admin')。
- 联合查询盲注:SELECT * FROM table WHERE id IN (SELECT id FROM user WHERE name = (SELECT MID (user,1,1) FROM user WHERE id=1))。
- 如何解决 SQL 注入呢介绍解决 SQL 注入的方法。
解决 SQL 注入的方法如下:
- 使用参数化查询:参数化查询是一种预编译查询,该查询将参数和 SQL 语句分开,从而允许程序将参数值与 SQL 语句分开执行,可以避免 SQL 注入攻击。
- 对输入数据进行过滤:尽可能过滤所有用户输入的特殊字符,如分号,单引号等,以防止攻击者注入恶意的 SQL 代码。
- 使用存储过程:存储过程可以使 SQL 语句更加安全,可以将 SQL 语句保存在服务器上,而不是在客户端上直接发送,从而有效地防止 SQL 注入攻击。
- 使用安全的接口:使用安全的编程接口可以有效地提高数据库的安全性,避免 SQL 注入攻击。
- 定期审计数据库:定期审计数据库,以发现 SQL 注入攻击行为,可以及时采取措施进行修复。
(二)Statement 和 PreparedStatement 对比
Statement 和 PreparedStatement 有以下不同之处:
与 Statement 相比,
- PreparedStatement 接口代表预编译的语句,它主要的优势在于可以减少 SQL 的编译错误并增加 SQL 的安全性(减少 SQL 注射攻击的可能性)。
- PreparedStatement 中的 SQL 语句是可以带参数的,避免了用字符串连接拼接 SQL 语句的麻烦和不安全。
- 当批量处理 SQL 或频繁执行相同的查询时,PreparedStatement 有明显的性能上的优势,由于数据库可以将编译优化后的 SQL 语句缓存起来,下次执行相同结构的语句时就会很快(不用再次编译和生成执行计划)。
补充:为了提供对存储过程的调用,JDBC API 中还提供了 CallableStatement 接口。存储过程(Stored Procedure)是数据库中一组为了完成特定功能的 SQL 语句的集合,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。虽然调用存储过程会在网络开销、安全性、性能上获得很多好处,但是存在如果底层数据库发生迁移时就会有很多麻烦,因为每种数据库的存储过程在书写上存在不少的差别。
六、JDBC 事务
(一)事务的定义
在数据库中,事务是指一组逻辑操作单元,使数据从一种状态转换到另一种状态。为确保数据库中的数据一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这些单元中一部分操作失败,整个事务全部视为错误,所有从起始点开始以后的操作应全部回退到开始状态。
(二)事务的四个特性
- 原子性(Atomicity):事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency):事务必须使数据库从一个一致性状态转变成另外一个一致性状态。
- 隔离性(Isolation):指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务不能相互干扰。
- 持久性(Durability):指一个事务一旦被提交,它对数据库中的数据的改变就是永久性的,接下来的其他操作和数据库的故障不应该对其有任何影响。
(三)JDBC 中的事务
在 JDBC 中,事务指构成单个逻辑工作单元的操作集合。事务处理需保证所有的事务都作为一个工作单元来执行,即使出现了故障也不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交,要么整个事务回滚到最初的状态。
为了让多个 SQL 语句作为一个事务执行,可以使用以下步骤:
- 调用Connection对象的setAutoCommit(false),以取消自动提交事务。
- 在所有的 SQL 语句都成功执行后,调用commit方法提交事务。
- 在出现异常时,调用rollback方法,事务回滚。
- 若此时Conneciton没有关闭,则需要恢复其自动提交状态。
注意:若使用的数据库引擎不是innodb,则事务无法回滚。而mysql默认的数据库引擎是MyISAM,所以第一次使用事务则需要更改数据表的引擎。
事务的隔离级别对于同时运行的多个事务,当这些事务访问数据库中相同的数据时,如果没有采取必要的隔离机制,就会导致各种并发问题。数据库提供的四种事务隔离级别如下:
- read uncommitted(读未提交数据)。
- read committed(读已提交数据)。
- repeatable read(可重复读)。
- serialized(串行化)。
Oracle支持 2 种事务隔离级别read uncommitted、serialized,默认是read uncommitted。Mysql支持 4 种隔离级别,默认repeatable read。
设置事务隔离级别可以使用 SQL 语句或代码实现。例如,使用 SQL 语句:select @@tx_isolation查看当前隔离级别;设置当前的mysql连接的隔离级别:set transaction isolation level read commited;;设置数据库系统的全局隔离级别:set global transaction isolation level read commited;。使用代码:conn.setTransactionIsolation(conn.TRANSACTION_READ_COMMITTED);。
七、JDBC 常见问题及解决方法
(一)常见错误类型
- ClassNotFoundException 分析该异常出现的原因及解决方法。
-
- 原因:异常信息提示类找不到,原因是 oracle.jdbc.OracleDriver 没有被加载到 JVM 中,可能是因为没有导入 JDBC 的驱动 jar 包,或是加载驱动的参数写错。
-
- 解决方法:查看项目中是否导入 JDBC 的驱动 jar 包,或是查看加载驱动参数是否正确。
- SQLException: No suitable driver found for jdbc:orale:thin:@localhost:1521:xe 解释该异常的原因及解决方案。
-
- 原因:url 写的有错误。
-
- 解决方法:仔细查看自己程序 url。
- SQLException: Listener refused the connection with the following error:xxxxx 说明此异常产生的可能原因。
-
- 原因:可能是数据库监听的配置文件 tnsnames.ora 中的 sid 与数据库的 sid 以及 jdbc 程序中连接数据库的 url 的 sid 不匹配;也可能是连接的 URL 写错;还可能是 URL 中的 IP 地址错,或 oracle 服务有问题。
-
- 解决方案:
-
-
- 查看自己数据库的 sid:用 sysdba 身份登录执行 select name from V$database; 语句,确认 sid。
-
-
-
- 查看数据库监听的配置文件 tnsnames.ora 中的 sid,把其中的 service_name 改成数据库正确的 sid 值。
-
-
-
- 修改 jdbc 程序中连接数据库的 url 的 sid。最后,修改 tnsnames.ora 完成需要重新启动 Oracle。或者检查 URL 是否正确,若 IP 地址错误或 oracle 服务有问题,进行相应调整。
-
- SQLException: IO 错误: The Network Adapter could not establish the connection at 分析该异常的原因及解决思路。
-
- 原因:URL 中的 IP 地址错,或 oracle 服务有问题。
-
- 解决思路:检查网络连接是否正常,确保服务器处于运行状态并且 Java 客户端连接到正确的