Backend

99 Ngày Java — Ngày 97: JDBC

SSite Admin
1 tháng 11, 2026 3 phút đọc 0 lượt xem
99 Ngày Java — Ngày 97: JDBC

JPA, Spring Data hay jOOQ đều chạy trên JDBC, API chuẩn của Java để nói chuyện với database quan hệ. Hiểu JDBC giúp bạn đọc được stack trace, hiểu transaction thật sự là gì và biết framework đang làm thay mình những việc nào. Bài này dùng JDBC thuần với bốn khái niệm: kết nối, câu lệnh có tham số, transaction và pool.

JDBC

1. Connection, Statement, ResultSet

Ba đối tượng chính đều giữ tài nguyên ở phía database, nên phải đóng. try-with-resources đóng chúng theo thứ tự ngược lại kể cả khi có ngoại lệ. ResultSet là một con trỏ: next() chuyển sang hàng kế tiếp và trả false khi hết. Cột nên đọc theo tên để code không vỡ khi câu SELECT đổi thứ tự cột. Driver được tìm tự động từ classpath, không cần Class.forName như code rất cũ.

String sql = "SELECT id, total, status FROM orders WHERE customer_id = ?";
try (Connection connection = dataSource.getConnection();
     PreparedStatement statement = connection.prepareStatement(sql)) {
    statement.setString(1, customerId);
    try (ResultSet rows = statement.executeQuery()) {
        var orders = new ArrayList<Order>();
        while (rows.next()) {
            orders.add(new Order(rows.getLong("id"),
                    rows.getBigDecimal("total"), rows.getString("status")));
        }
        return orders;
    }
}

2. PreparedStatement và SQL injection

Ghép dữ liệu người dùng vào chuỗi SQL cho phép họ đổi nghĩa câu lệnh: nhập ' OR '1'='1 là lấy được mọi hàng. PreparedStatement gửi câu lệnh và giá trị riêng rẽ; database coi giá trị thuần là dữ liệu, không bao giờ là cú pháp. Đây là cách phòng thủ duy nhất đáng tin, không phải việc tự lọc ký tự. Lưu ý dấu hỏi chỉ thay được giá trị; tên bảng hay tên cột động phải lấy từ một danh sách cho phép trong code.

// vulnerable: user input becomes part of the SQL
String bad = "SELECT * FROM users WHERE email = '" + email + "'";

// safe: the value is bound, never parsed as SQL
var statement = connection.prepareStatement(
        "SELECT * FROM users WHERE email = ?");
statement.setString(1, email);

3. Transaction thủ công

Mặc định mỗi câu lệnh tự commit. Để nhiều câu lệnh thành một đơn vị hoặc tất cả hoặc không gì cả, tắt auto-commit, chạy các câu lệnh rồi commit; có lỗi thì rollback. Transaction gắn với một Connection, nên mọi câu lệnh trong đó phải dùng cùng một kết nối. Đây chính là điều @Transactional của Spring làm thay bạn: mượn kết nối, tắt auto-commit, commit hoặc rollback, rồi trả kết nối.

try (Connection connection = dataSource.getConnection()) {
    connection.setAutoCommit(false);
    try {
        debit(connection, fromAccount, amount);
        credit(connection, toAccount, amount);
        connection.commit();
    } catch (SQLException | RuntimeException e) {
        connection.rollback();
        throw e;
    }
}

4. Connection pool

Mở một kết nối database tốn bắt tay mạng, xác thực và cấp phát bộ nhớ phía server, thường hàng chục mili giây. Pool giữ sẵn một số kết nối để tái sử dụng: getConnection() mượn một cái, close() trả nó về pool chứ không đóng thật. HikariCP là pool mặc định của Spring Boot. Quên đóng kết nối là rò rỉ: pool cạn dần và mọi request sau treo cho tới khi hết thời gian chờ.

  • Luôn dùng DataSource có pool; DriverManager chỉ hợp cho script nhỏ.

  • Ghi nhiều hàng thì dùng addBatch và executeBatch.

  • Đặt query timeout để một truy vấn treo không giữ kết nối mãi.

  • Bắt SQLException ở ranh giới và chuyển thành ngoại lệ có nghĩa với nghiệp vụ.

var config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/shop");
config.setUsername("shop");
config.setPassword(System.getenv("DB_PASSWORD"));
config.setMaximumPoolSize(10);
DataSource dataSource = new HikariDataSource(config);

JDBC là tầng mà mọi công cụ truy cập dữ liệu của Java đứng lên trên; bốn khái niệm hôm nay xuất hiện lại ở mọi framework. Ngày 98 gọi ra thế giới bên ngoài bằng HTTP Client có sẵn trong JDK.

Tài liệu đối chiếu

  • https://docs.oracle.com/javase/tutorial/jdbc/basics/index.html

  • https://github.com/brettwooldridge/HikariCP

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Spring — Ngày 97: Security Hardening cho Production

Security header, không lộ thông tin qua lỗi và Actuator, quét dependency và nguyên tắc quyền tối thiểu từ database tới container.

1 thg 11, 20263 phút0
Kafka trong dự án thực tế: Saga đặt hàng — Order, Inventory, Payment không cần distributed transaction

Thiết kế saga đặt hàng trên Kafka: orchestration hay choreography, command/reply topic, state machine với outbox và idempotent consumer, giữ hàng an toàn, timeout khi chưa biết kết quả thanh toán — kèm code Spring Kafka, SQL và sơ đồ.

31 thg 10, 202624 phút8
99 Ngày Spring — Ngày 96: Performance Tuning

Đo trước khi chỉnh, kích thước pool HikariCP, các nút thắt thường gặp ở tầng dữ liệu, tùy chọn JVM và load test sơ lược.

30 thg 10, 20263 phút6