JDBC (Java Database Connectivity) là một API tiêu chuẩn của Java cho phép các ứng dụng tương tác với cơ sở dữ liệu quan hệ. Nó cung cấp một phương thức nhất quán để truy cập và thao tác dữ liệu, bất kể hệ quản trị cơ sở dữ liệu (DBMS) cụ thể nào đang được sử dụng.
Các bước kết nối cơ sở dữ liệu
Để thực hiện các thao tác với cơ sở dữ liệu bằng JDBC, bạn cần thực hiện các bước sau:
- Nạp Driver: Tải trình điều khiển (driver) của cơ sở dữ liệu vào bộ nhớ JVM.
- Thiết lập kết nối: Sử dụng thông tin kết nối như URL, tên người dùng và mật khẩu để tạo một đối tượng
Connection.
Dưới đây là đoạn mã ví dụ minh họa cho việc thiết lập kết nối:
// Thông tin kết nối cơ sở dữ liệu
private static final String DB_DRIVER = "com.mysql.cj.jdbc.Driver"; // Driver cho MySQL 8+
private static final String DB_URL = "jdbc:mysql://localhost:3306/mydatabase?serverTimezone=UTC"; // URL kết nối
private static final String DB_USER = "myuser"; // Tên người dùng
private static final String DB_PASSWORD = "mypassword"; // Mật khẩu
/**
* Lấy một kết nối tới cơ sở dữ liệu.
* @return Đối tượng Connection nếu thành công, null nếu thất bại.
*/
public static Connection establishConnection() {
Connection connection = null;
try {
// Nạp trình điều khiển cơ sở dữ liệu
Class.forName(DB_DRIVER);
// Mở kết nối tới cơ sở dữ liệu
connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
System.out.println("Kết nối cơ sở dữ liệu thành công.");
} catch (ClassNotFoundException e) {
System.err.println("Lỗi: Không tìm thấy trình điều khiển. Vui lòng kiểm tra file JAR hoặc tên driver.");
throw new RuntimeException("Lỗi nạp driver", e);
} catch (SQLException e) {
System.err.println("Lỗi: Không thể kết nối tới cơ sở dữ liệu. Kiểm tra URL, tên người dùng, mật khẩu.");
throw new RuntimeException("Lỗi kết nối cơ sở dữ liệu", e);
}
return connection;
}
/**
* Đóng kết nối cơ sở dữ liệu.
* @param conn Đối tượng Connection cần đóng.
*/
public static void closeConnection(Connection conn) {
if (conn != null) {
try {
conn.close();
System.out.println("Đã đóng kết nối cơ sở dữ liệu.");
} catch (SQLException e) {
System.err.println("Lỗi khi đóng kết nối: " + e.getMessage());
e.printStackTrace();
}
}
}
Tại sao cần Class.forName()?
Việc gọi Class.forName(driverClassName) sẽ thực thi khối mã tĩnh (static block) của lớp driver. Bên trong khối mã tĩnh này, driver sẽ tự động đăng ký với DriverManager thông qua phương thức DriverManager.registerDriver(new DriverInstance()). Điều này cho phép DriverManager nhận biết và sử dụng driver đó khi cần thiết lập kết nối.
Sử dụng DriverManager
DriverManager là lớp quản lý các trình điều khiển JDBC đã đăng ký. Nó có khả năng lựa chọn driver phù hợp dựa trên URL kết nối được cung cấp.
Thực thi các câu lệnh SQL
1. Sử dụng Statement
Statement được dùng để thực thi các câu lệnh SQL tĩnh.
Phương thức executeUpdate()
Phương thức này được sử dụng cho các câu lệnh SQL DML (Data Manipulation Language) như INSERT, UPDATE, DELETE. Nó trả về số lượng bản ghi bị ảnh hưởng bởi câu lệnh.
Thêm dữ liệu (INSERT)
/**
* Thực hiện thêm mới dữ liệu vào bảng.
* @param sql Câu lệnh SQL INSERT.
*/
public static void executeInsert(String sql) {
Connection conn = establishConnection();
Statement stmt = null;
try {
stmt = conn.createStatement();
int rowsAffected = stmt.executeUpdate(sql);
System.out.println("Thêm dữ liệu thành công. Số bản ghi bị ảnh hưởng: " + rowsAffected);
} catch (SQLException e) {
System.err.println("Lỗi khi thực thi câu lệnh INSERT: " + e.getMessage());
throw new RuntimeException("Lỗi thực thi INSERT", e);
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
closeConnection(conn);
}
}
Cập nhật dữ liệu (UPDATE)
/**
* Thực hiện cập nhật dữ liệu trong bảng.
* @param sql Câu lệnh SQL UPDATE.
*/
public static void executeUpdate(String sql) {
Connection conn = establishConnection();
Statement stmt = null;
try {
stmt = conn.createStatement();
int rowsAffected = stmt.executeUpdate(sql);
System.out.println("Cập nhật dữ liệu thành công. Số bản ghi bị ảnh hưởng: " + rowsAffected);
} catch (SQLException e) {
System.err.println("Lỗi khi thực thi câu lệnh UPDATE: " + e.getMessage());
throw new RuntimeException("Lỗi thực thi UPDATE", e);
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
closeConnection(conn);
}
}
Xóa dữ liệu (DELETE)
/**
* Thực hiện xóa dữ liệu khỏi bảng.
* @param sql Câu lệnh SQL DELETE.
*/
public static void executeDelete(String sql) {
Connection conn = establishConnection();
Statement stmt = null;
try {
stmt = conn.createStatement();
int rowsAffected = stmt.executeUpdate(sql);
System.out.println("Xóa dữ liệu thành công. Số bản ghi bị ảnh hưởng: " + rowsAffected);
} catch (SQLException e) {
System.err.println("Lỗi khi thực thi câu lệnh DELETE: " + e.getMessage());
throw new RuntimeException("Lỗi thực thi DELETE", e);
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
closeConnection(conn);
}
}
2. Sử dụng PreparedStatement
PreparedStatement là một giao diện con của Statement, cho phép thực thi các câu lệnh SQL đã được biên dịch trước với các tham số đầu vào (placeholders). Điều này không chỉ cải thiện hiệu suất mà còn giúp ngăn chặn các cuộc tấn công SQL Injection. Các tham số được đánh số bắt đầu từ 1.
Phương thức executeUpdate()
Thêm dữ liệu với tham số
/**
* Thêm người dùng mới vào bảng user sử dụng PreparedStatement.
* @param userName Tên người dùng cần thêm.
*/
public static void addNewUser(String userName) {
Connection conn = establishConnection();
PreparedStatement pstmt = null;
try {
String sql = "INSERT INTO user (name) VALUES (?)";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, userName); // Thiết lập giá trị cho tham số thứ nhất
int rowsAffected = pstmt.executeUpdate();
System.out.println("Thêm người dùng '" + userName + "' thành công. Số bản ghi bị ảnh hưởng: " + rowsAffected);
} catch (SQLException e) {
System.err.println("Lỗi khi thêm người dùng: " + e.getMessage());
throw new RuntimeException("Lỗi thực thi PreparedInsert", e);
} finally {
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
closeConnection(conn);
}
}
Khi tạo đối tượng PreparedStatement, có một phương thức ghi đè cho phép chỉ định việc trả về các khóa tự động tăng (auto-generated keys):
PreparedStatement prepareStatement(String sql, int autoGeneratedKeys);
// Ví dụ: Statement.RETURN_GENERATED_KEYS hoặc Statement.NO_GENERATED_KEYS
Sau khi thực thi câu lệnh INSERT với cờ RETURN_GENERATED_KEYS, bạn có thể sử dụng pstmt.getGeneratedKeys() để lấy một đối tượng ResultSet chứa các khóa chính vừa được tạo.
Cập nhật và Xóa với PreparedStatement
Tương tự như thêm mới, các thao tác cập nhật và xóa dữ liệu với PreparedStatement cũng tuân theo quy trình thiết lập tham số và thực thi bằng executeUpdate().
Lấy dữ liệu (ResultSet)
Khi thực thi các câu lệnh truy vấn (SELECT), kết quả trả về là một đối tượng ResultSet, đại diện cho một tập hợp các hàng dữ liệu. Bạn có thể duyệt qua ResultSet để truy cập từng bản ghi.
/**
* Truy vấn tất cả người dùng từ bảng user.
*/
public static void queryAllUsers() {
String sql = "SELECT id, name FROM user";
Connection conn = establishConnection();
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
// Phương thức rs.next() di chuyển con trỏ tới hàng tiếp theo.
// Ban đầu, con trỏ nằm trước hàng đầu tiên. Lần gọi đầu tiên sẽ đặt con trỏ vào hàng đầu tiên.
while (rs.next()) {
int userId = rs.getInt("id");
String userName = rs.getString("name");
System.out.println("ID: " + userId + ", Tên: " + userName);
}
} catch (SQLException e) {
System.err.println("Lỗi khi truy vấn dữ liệu: " + e.getMessage());
e.printStackTrace();
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// Cần đóng cả PreparedStatement và Connection
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
closeConnection(conn);
}
}
Thực hiện thao tác theo lô (Batch Operations)
JDBC hỗ trợ thực hiện nhiều câu lệnh SQL cùng lúc trong một lô duy nhất, giúp tối ưu hóa hiệu suất khi cần chèn hoặc cập nhật một lượng lớn dữ liệu.
/**
* Thực hiện thêm hàng loạt người dùng vào bảng user.
* @param sql Câu lệnh SQL INSERT với tham số.
*/
public static void batchInsertUsers(String sql) {
Connection conn = establishConnection();
PreparedStatement pstmt = null;
try {
pstmt = conn.prepareStatement(sql);
// Thêm 1000 bản ghi vào lô
for (int i = 0; i < 1000; i++) {
pstmt.setString(1, "Khách hàng " + i);
pstmt.addBatch(); // Thêm câu lệnh hiện tại vào lô
}
// Thực thi toàn bộ các câu lệnh trong lô
int[] results = pstmt.executeBatch();
System.out.println("Thực hiện thêm hàng loạt thành công. Số bản ghi bị ảnh hưởng bởi từng câu lệnh: ");
for (int count : results) {
System.out.print(count + " ");
}
System.out.println();
// Xóa các câu lệnh đã tích lũy trong lô (nếu cần chuẩn bị lô mới)
pstmt.clearBatch();
} catch (SQLException e) {
System.err.println("Lỗi khi thực hiện thao tác theo lô: " + e.getMessage());
throw new RuntimeException("Lỗi thực thi Batch", e);
} finally {
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
closeConnection(conn);
}
}