Java JDBC编程基础

JDBC简介

JDBC (Java Database Connectivity) 是Java语言中用于执行SQL语句的标准API,它提供了一套与数据库交互的类和接口,使开发者能够用统一的方式访问各种关系型数据库。JDBC位于java.sqljavax.sql包中,是Java企业级应用的基础组件之一。

JDBC的主要优势在于它的”编写一次,到处运行”的特性,通过不同的驱动程序,同一套JDBC代码可以连接不同的数据库系统,如MySQL、Oracle、SQL Server等。

JDBC架构

JDBC架构主要包括四个组件:

  1. JDBC API:提供了程序对数据库的访问能力
  2. JDBC驱动管理器:用于管理和选择合适的驱动程序
  3. JDBC驱动程序接口:连接数据库的标准接口
  4. JDBC驱动程序:特定数据库的实现

JDBC驱动类型

JDBC驱动程序分为四种类型:

  1. JDBC-ODBC桥驱动:通过ODBC连接数据库(已过时)
  2. 本地API驱动:部分Java代码,部分本地代码
  3. 网络协议驱动:纯Java驱动,通过中间服务器
  4. 本地协议驱动:纯Java驱动,直接与数据库交互

现在大多数数据库厂商提供的都是第四种类型的驱动程序。

JDBC编程步骤

使用JDBC访问数据库通常包括以下步骤:

  1. 导入JDBC包
  2. 注册JDBC驱动
  3. 建立数据库连接
  4. 创建Statement对象
  5. 执行SQL查询
  6. 处理结果集
  7. 关闭连接和释放资源

导入JDBC包

1
2
3
4
5
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

MySQL数据库示例

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
public class JDBCExample {
// JDBC驱动名和数据库URL
static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC";

// 数据库的用户名和密码
static final String USER = "root";
static final String PASS = "password";

public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
// 注册JDBC驱动
Class.forName(JDBC_DRIVER);

// 打开链接
System.out.println("连接数据库...");
conn = DriverManager.getConnection(DB_URL, USER, PASS);

// 执行查询
System.out.println("实例化Statement对象...");
stmt = conn.createStatement();
String sql = "SELECT id, name, age FROM employees";
ResultSet rs = stmt.executeQuery(sql);

// 展开结果集
while (rs.next()) {
// 通过字段检索
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");

// 输出数据
System.out.print("ID: " + id);
System.out.print(", 姓名: " + name);
System.out.println(", 年龄: " + age);
}

// 完成后关闭
rs.close();
stmt.close();
conn.close();
} catch (SQLException se) {
// 处理JDBC错误
se.printStackTrace();
} catch (Exception e) {
// 处理Class.forName错误
e.printStackTrace();
} finally {
// 关闭资源
try {
if (stmt != null) stmt.close();
} catch (SQLException se2) {
// 什么都不做
}
try {
if (conn != null) conn.close();
} catch (SQLException se) {
se.printStackTrace();
}
}
System.out.println("再见!");
}
}

PreparedStatement的使用

PreparedStatementStatement的子接口,它表示预编译的SQL语句。使用PreparedStatement有以下优势:

  1. 提高安全性,防止SQL注入攻击
  2. 提高性能,因为SQL语句只编译一次
  3. 处理特殊字符更方便
  4. 可以使用参数化查询
1
2
3
4
5
6
7
8
9
10
11
// 使用PreparedStatement执行插入操作
String sql = "INSERT INTO employees(name, age) VALUES(?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);

// 设置参数值
pstmt.setString(1, "张三");
pstmt.setInt(2, 30);

// 执行SQL语句
int rowsAffected = pstmt.executeUpdate();
System.out.println("插入了 " + rowsAffected + " 条记录");

事务管理

在JDBC中,默认情况下事务是自动提交的。如果需要手动控制事务,可以按照以下方式进行:

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
Connection conn = null;
try {
conn = DriverManager.getConnection(DB_URL, USER, PASS);

// 禁用自动提交
conn.setAutoCommit(false);

// 创建Statement对象
Statement stmt = conn.createStatement();

// 执行第一个更新
String sql1 = "UPDATE accounts SET balance = balance - 100 WHERE id = 1";
stmt.executeUpdate(sql1);

// 执行第二个更新
String sql2 = "UPDATE accounts SET balance = balance + 100 WHERE id = 2";
stmt.executeUpdate(sql2);

// 提交事务
conn.commit();

System.out.println("事务执行成功");
} catch (SQLException e) {
// 发生错误时回滚事务
if (conn != null) {
try {
conn.rollback();
System.out.println("事务已回滚");
} catch (SQLException se) {
se.printStackTrace();
}
}
e.printStackTrace();
} finally {
// 恢复自动提交
if (conn != null) {
try {
conn.setAutoCommit(true);
} catch (SQLException se) {
se.printStackTrace();
}
}
}

批处理操作

当需要执行多条SQL语句时,可以使用批处理来提高性能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
conn.setAutoCommit(false);  // 禁用自动提交

PreparedStatement pstmt = conn.prepareStatement(
"INSERT INTO employees(name, age) VALUES(?, ?)");

// 添加批处理
for (int i = 0; i < 10; i++) {
pstmt.setString(1, "员工" + i);
pstmt.setInt(2, 20 + i);
pstmt.addBatch();

// 每500条执行一次
if (i % 500 == 0) {
pstmt.executeBatch();
pstmt.clearBatch();
}
}

// 执行剩余的批处理
pstmt.executeBatch();

// 提交事务
conn.commit();

使用连接池

在实际应用中,为了提高性能并有效管理数据库连接,通常会使用连接池。以下是使用Apache DBCP连接池的示例:

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
import org.apache.commons.dbcp2.BasicDataSource;

public class DbcpExample {
private static BasicDataSource dataSource;

static {
dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/testdb");
dataSource.setUsername("root");
dataSource.setPassword("password");

// 连接池配置
dataSource.setInitialSize(5); // 初始连接数
dataSource.setMaxTotal(20); // 最大连接数
dataSource.setMaxIdle(10); // 最大空闲连接数
dataSource.setMinIdle(5); // 最小空闲连接数
}

public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}

public static void main(String[] args) {
try (Connection conn = getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM employees")) {

while (rs.next()) {
System.out.println(rs.getString("name"));
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}

使用Java 7的try-with-resources

Java 7引入的try-with-resources语句可以自动关闭实现了AutoCloseable接口的资源,包括JDBC中的ConnectionStatementResultSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void printEmployees() {
String sql = "SELECT id, name, age FROM employees";

try (
Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery()
) {
while (rs.next()) {
System.out.println("ID: " + rs.getInt("id") +
", 姓名: " + rs.getString("name") +
", 年龄: " + rs.getInt("age"));
}
} catch (SQLException e) {
e.printStackTrace();
}
}

处理大型对象(LOB)

JDBC支持处理大型对象,如BLOB(二进制大型对象)和CLOB(字符大型对象):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 读取BLOB(例如图片)
String sql = "SELECT id, name, photo FROM employees WHERE id=?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 1001);
ResultSet rs = pstmt.executeQuery();

if (rs.next()) {
Blob blob = rs.getBlob("photo");
InputStream in = blob.getBinaryStream();
// 处理输入流...
}

// 写入BLOB
sql = "INSERT INTO employees(name, photo) VALUES(?, ?)";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "李四");

// 读取文件并设置BLOB
File file = new File("photo.jpg");
FileInputStream fis = new FileInputStream(file);
pstmt.setBinaryStream(2, fis, (int)file.length());
pstmt.executeUpdate();

总结

JDBC是Java连接数据库的标准API,它提供了一套完整的解决方案,使Java应用能够与各种关系型数据库交互。虽然现在有许多更高级的ORM框架(如Hibernate和MyBatis),但JDBC仍然是这些框架的基础,了解JDBC对于深入理解Java数据库编程非常重要。

在实际应用中,建议使用连接池管理数据库连接,使用PreparedStatement而不是Statement来防止SQL注入,并利用事务确保数据的一致性。同时,合理使用批处理和处理大型对象的技术能够提高应用程序的性能。