Java JDBC数据库操作
2022-03-141.4k 阅读
Java JDBC 简介
Java 数据库连接(Java Database Connectivity,简称 JDBC)是 Java 编程语言中用于执行 SQL 语句的 API。它提供了一个通用的抽象层,使得 Java 程序能够与各种不同类型的数据库进行交互,如 MySQL、Oracle、SQL Server 等,而无需为每种数据库编写特定的代码。JDBC 是 Java 企业级应用开发中不可或缺的一部分,广泛应用于 Web 应用程序、企业级软件等领域,用于实现数据的持久化存储和查询。
JDBC 架构
JDBC 架构主要分为两层:应用层和驱动层。
- 应用层:这是开发人员编写的 Java 代码所在的层次,通过 JDBC API 来执行数据库操作。应用层使用 JDBC 接口来与数据库进行交互,这些接口包括
DriverManager
、Connection
、Statement
、ResultSet
等。 - 驱动层:由各个数据库厂商提供的 JDBC 驱动组成,负责与具体的数据库进行通信。每个数据库都有其特定的 JDBC 驱动,例如 MySQL 有
mysql - connector - java
驱动,Oracle 有ojdbc
驱动等。这些驱动实现了 JDBC 接口,将应用层的请求转换为数据库能够理解的命令,并将数据库的响应转换回 Java 可以处理的形式。
JDBC 核心接口
- DriverManager
- 作用:
DriverManager
类是 JDBC 的管理层,作用于用户和驱动程序之间。它跟踪可用的驱动程序,并在数据库和相应驱动之间建立连接。 - 常用方法:
static Connection getConnection(String url, String user, String password)
:尝试建立到给定数据库 URL 的连接。url
是数据库的地址,user
和password
是数据库的登录凭证。例如,对于 MySQL 数据库,url
格式通常为jdbc:mysql://localhost:3306/mydb
,其中localhost
是数据库服务器地址,3306
是端口号,mydb
是数据库名称。static void registerDriver(Driver driver)
:向DriverManager
注册给定的驱动程序。在 JDBC 4.0 及更高版本中,驱动程序可以通过服务提供商接口(SPI)自动注册,无需手动调用此方法。
- 作用:
- Connection
- 作用:
Connection
接口代表与数据库的连接。一个Connection
对象包含了数据库的特定配置信息,并提供了创建Statement
对象的方法,用于执行 SQL 语句。 - 常用方法:
Statement createStatement()
:创建一个Statement
对象,用于执行不带参数的 SQL 语句。PreparedStatement prepareStatement(String sql)
:创建一个PreparedStatement
对象,用于执行预编译的 SQL 语句。预编译语句可以防止 SQL 注入攻击,并且在多次执行相同结构的 SQL 语句时性能更好。CallableStatement prepareCall(String sql)
:创建一个CallableStatement
对象,用于执行数据库存储过程。void close()
:关闭此数据库连接。
- 作用:
- Statement
- 作用:
Statement
接口用于执行静态 SQL 语句并返回其生成的结果。它提供了执行 SQL 查询(如SELECT
)和更新(如INSERT
、UPDATE
、DELETE
)的方法。 - 常用方法:
ResultSet executeQuery(String sql)
:执行给定的 SQL 查询语句,返回一个ResultSet
对象,该对象包含查询结果集。此方法适用于SELECT
语句。int executeUpdate(String sql)
:执行给定的 SQL 更新语句(INSERT
、UPDATE
、DELETE
),返回受影响的行数。void close()
:关闭此Statement
对象。
- 作用:
- ResultSet
- 作用:
ResultSet
接口表示数据库查询的结果集,它通过迭代的方式提供对查询结果的访问。ResultSet
对象维护一个指向当前数据行的光标,最初光标位于第一行之前。 - 常用方法:
boolean next()
:将光标从当前位置向前移动一行。如果新的当前行有效,则返回true
;否则返回false
,表示结果集已遍历完毕。getXXX(int columnIndex)
和getXXX(String columnName)
:获取当前行中指定列的值,XXX
代表数据类型,如getString
、getInt
、getDate
等。columnIndex
是从 1 开始的列索引,columnName
是列的名称。
- 作用:
JDBC 操作步骤
- 加载 JDBC 驱动 在 JDBC 4.0 之前,需要显式加载驱动程序,例如对于 MySQL 驱动:
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
在 JDBC 4.0 及更高版本中,驱动程序会自动加载,无需手动调用 Class.forName
。
2. 建立数据库连接
String url = "jdbc:mysql://localhost:3306/mydb";
String user = "root";
String password = "password";
try (Connection conn = DriverManager.getConnection(url, user, password)) {
// 在这里进行后续数据库操作
} catch (SQLException e) {
e.printStackTrace();
}
- 创建
Statement
对象- 创建普通
Statement
:
- 创建普通
Statement stmt = conn.createStatement();
- 创建
PreparedStatement
:
String sql = "INSERT INTO users (name, age) VALUES (?,?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
- 执行 SQL 语句
- 执行查询语句(
SELECT
):
- 执行查询语句(
String query = "SELECT * FROM users";
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
String name = rs.getString("name");
int age = rs.getInt("age");
System.out.println("Name: " + name + ", Age: " + age);
}
- 执行更新语句(
INSERT
、UPDATE
、DELETE
):
String insertSql = "INSERT INTO users (name, age) VALUES ('John', 25)";
int rowsInserted = stmt.executeUpdate(insertSql);
System.out.println("Rows inserted: " + rowsInserted);
对于 PreparedStatement
执行更新:
pstmt.setString(1, "Jane");
pstmt.setInt(2, 30);
int rowsUpdated = pstmt.executeUpdate();
System.out.println("Rows updated: " + rowsUpdated);
- 处理结果集(如果有)
如上述查询示例中,通过
ResultSet
的next
方法和getXXX
方法来遍历和获取结果集的数据。 - 关闭资源
在操作完成后,需要关闭
ResultSet
、Statement
和Connection
,以释放资源。在 Java 7 及更高版本中,可以使用try - with - resources
语句自动关闭资源:
try (Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(query)) {
// 处理结果集
} catch (SQLException e) {
e.printStackTrace();
}
JDBC 事务处理
- 事务概念 事务是一组数据库操作的逻辑单元,这些操作要么全部成功执行,要么全部回滚(撤销)。例如,在银行转账操作中,从一个账户扣款和向另一个账户存款必须作为一个事务处理,以确保数据的一致性。
- JDBC 中的事务处理
- 自动提交模式:默认情况下,JDBC 连接处于自动提交模式,即每执行一条 SQL 语句就会立即提交到数据库。可以通过
Connection
对象的setAutoCommit(false)
方法关闭自动提交。 - 提交事务:调用
Connection
对象的commit()
方法来提交事务,将所有在事务中的操作永久保存到数据库。 - 回滚事务:调用
Connection
对象的rollback()
方法来回滚事务,撤销在事务中执行的所有操作。 示例代码:
- 自动提交模式:默认情况下,JDBC 连接处于自动提交模式,即每执行一条 SQL 语句就会立即提交到数据库。可以通过
try (Connection conn = DriverManager.getConnection(url, user, password)) {
conn.setAutoCommit(false);
try {
String sql1 = "UPDATE accounts SET balance = balance - 100 WHERE account_id = 1";
stmt.executeUpdate(sql1);
String sql2 = "UPDATE accounts SET balance = balance + 100 WHERE account_id = 2";
stmt.executeUpdate(sql2);
conn.commit();
} catch (SQLException e) {
conn.rollback();
e.printStackTrace();
}
} catch (SQLException e) {
e.printStackTrace();
}
JDBC 连接池
- 连接池概念 在传统的 JDBC 操作中,每次建立数据库连接都需要消耗一定的资源和时间。连接池是一种缓存数据库连接的技术,它在应用程序启动时预先创建一定数量的数据库连接,并将这些连接保存在池中。当应用程序需要与数据库交互时,从连接池中获取一个连接,使用完毕后再将其归还到连接池中,而不是每次都创建和销毁连接,从而提高了应用程序的性能和资源利用率。
- 常见的 JDBC 连接池
- HikariCP:是一个高性能的 JDBC 连接池,具有快速的连接获取速度和低资源消耗的特点。它被广泛应用于各种 Java 项目中。
- C3P0:是一个开源的 JDBC 连接池库,提供了丰富的配置选项,易于使用和集成到项目中。
- Tomcat JDBC Pool:是 Apache Tomcat 服务器自带的 JDBC 连接池,与 Tomcat 服务器紧密集成,也可以在其他 Java 应用中使用。
- 使用 HikariCP 连接池示例
- 添加依赖:如果使用 Maven,在
pom.xml
文件中添加以下依赖:
- 添加依赖:如果使用 Maven,在
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
- 配置和使用连接池:
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class HikariCPExample {
private static HikariDataSource dataSource;
static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("root");
config.setPassword("password");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
dataSource = new HikariDataSource(config);
}
public static void main(String[] args) {
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM users");
ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
String name = rs.getString("name");
int age = rs.getInt("age");
System.out.println("Name: " + name + ", Age: " + age);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
JDBC 与数据库元数据
- 数据库元数据概念 数据库元数据是关于数据库结构和对象的信息,如数据库中有哪些表、表中有哪些列、列的数据类型是什么、数据库支持哪些特性等。JDBC 提供了访问数据库元数据的接口,使得开发人员可以在运行时获取这些信息,从而编写更灵活和通用的数据库操作代码。
- 获取数据库元数据
通过
Connection
对象的getMetaData()
方法可以获取DatabaseMetaData
对象,该对象提供了一系列方法来查询数据库元数据。 示例代码:
try (Connection conn = DriverManager.getConnection(url, user, password)) {
DatabaseMetaData metaData = conn.getMetaData();
System.out.println("Database Product Name: " + metaData.getDatabaseProductName());
System.out.println("Database Product Version: " + metaData.getDatabaseProductVersion());
ResultSet tables = metaData.getTables(null, null, null, new String[]{"TABLE"});
while (tables.next()) {
String tableName = tables.getString("TABLE_NAME");
System.out.println("Table Name: " + tableName);
}
} catch (SQLException e) {
e.printStackTrace();
}
- 获取结果集元数据
通过
ResultSet
对象的getMetaData()
方法可以获取ResultSetMetaData
对象,该对象提供了关于结果集列的信息,如列名、列的数据类型等。 示例代码:
try (Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
System.out.println("Column Name: " + metaData.getColumnName(i) + ", Data Type: " + metaData.getColumnTypeName(i));
}
while (rs.next()) {
// 处理结果集数据
}
} catch (SQLException e) {
e.printStackTrace();
}
JDBC 中的批处理
- 批处理概念 批处理是指将多个 SQL 语句组合成一个批处理,然后一次性发送到数据库执行。这样可以减少应用程序与数据库之间的通信次数,提高数据库操作的效率,特别是在需要执行大量相同结构的 SQL 语句时。
- 使用
Statement
进行批处理
try (Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement()) {
for (int i = 0; i < 100; i++) {
String insertSql = "INSERT INTO users (name, age) VALUES ('User" + i + "', " + (20 + i) + ")";
stmt.addBatch(insertSql);
}
stmt.executeBatch();
} catch (SQLException e) {
e.printStackTrace();
}
- 使用
PreparedStatement
进行批处理
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO users (name, age) VALUES (?,?)")) {
for (int i = 0; i < 100; i++) {
pstmt.setString(1, "User" + i);
pstmt.setInt(2, 20 + i);
pstmt.addBatch();
}
pstmt.executeBatch();
} catch (SQLException e) {
e.printStackTrace();
}
JDBC 高级特性
- LOB(Large Object)处理
- 概念:LOB 用于存储大型数据对象,如二进制大对象(BLOB)和字符大对象(CLOB)。BLOB 通常用于存储图片、音频、视频等二进制数据,CLOB 用于存储大量的文本数据。
- 处理方法:
- 插入 LOB 数据:使用
PreparedStatement
的setBlob
或setClob
方法。例如,插入 BLOB 数据:
- 插入 LOB 数据:使用
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO files (file_name, file_data) VALUES (?,?)")) {
File file = new File("image.jpg");
FileInputStream fis = new FileInputStream(file);
pstmt.setString(1, "image.jpg");
pstmt.setBlob(2, fis);
pstmt.executeUpdate();
} catch (SQLException | FileNotFoundException e) {
e.printStackTrace();
}
- **读取 LOB 数据**:使用 `ResultSet` 的 `getBlob` 或 `getClob` 方法。例如,读取 BLOB 数据:
try (Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT file_data FROM files WHERE file_name = 'image.jpg'")) {
if (rs.next()) {
Blob blob = rs.getBlob("file_data");
InputStream is = blob.getBinaryStream();
// 处理输入流,例如保存为文件
}
} catch (SQLException e) {
e.printStackTrace();
}
- 存储过程调用
- 概念:存储过程是存储在数据库中的预编译 SQL 代码块,它可以接受输入参数、返回输出参数,并执行一系列数据库操作。调用存储过程可以提高数据库操作的安全性和效率,因为存储过程在数据库服务器端执行,减少了网络传输和代码重复。
- 调用方法:使用
CallableStatement
接口。例如,假设数据库中有一个存储过程get_user_by_id
,接受一个用户 ID 作为输入参数,并返回用户的姓名和年龄:
try (Connection conn = DriverManager.getConnection(url, user, password);
CallableStatement cstmt = conn.prepareCall("{call get_user_by_id(?,?,?)}")) {
cstmt.setInt(1, 1);
cstmt.registerOutParameter(2, Types.VARCHAR);
cstmt.registerOutParameter(3, Types.INTEGER);
cstmt.execute();
String name = cstmt.getString(2);
int age = cstmt.getInt(3);
System.out.println("Name: " + name + ", Age: " + age);
} catch (SQLException e) {
e.printStackTrace();
}
JDBC 常见问题及解决方法
- SQL 注入问题
- 问题描述:SQL 注入是一种常见的安全漏洞,攻击者通过在输入字段中插入恶意的 SQL 语句,从而改变原 SQL 语句的逻辑,达到获取敏感数据或执行非法操作的目的。例如,在登录验证的 SQL 语句中,如果用户名和密码的输入没有进行正确的处理,攻击者可以通过输入特殊字符来绕过验证。
- 解决方法:使用
PreparedStatement
代替Statement
。PreparedStatement
会对输入参数进行预编译,将参数值与 SQL 语句的结构分离,从而防止 SQL 注入攻击。
- 连接泄漏问题
- 问题描述:连接泄漏是指应用程序在使用完数据库连接后,没有正确关闭连接,导致连接资源无法释放,最终可能耗尽连接池中的所有连接,使应用程序无法再获取新的连接,从而影响系统的正常运行。
- 解决方法:使用
try - with - resources
语句自动关闭连接、Statement
和ResultSet
等资源,确保在代码块结束时资源被正确释放。另外,在使用连接池时,连接池通常也会提供一些机制来检测和回收泄漏的连接。
- 性能问题
- 问题描述:在处理大量数据或高并发请求时,JDBC 操作可能会出现性能瓶颈,如频繁的连接创建和销毁、低效的 SQL 语句等。
- 解决方法:使用连接池技术减少连接创建和销毁的开销;对 SQL 语句进行优化,例如使用索引、避免全表扫描;使用批处理操作减少数据库通信次数;合理设置连接池的参数,如最大连接数、最小连接数等,以适应应用程序的负载。
通过以上对 Java JDBC 数据库操作的详细介绍,希望能帮助开发者深入理解 JDBC 的原理和使用方法,在实际项目中能够高效、安全地进行数据库交互。