Các mẹo nhỏ khi sử dụng JDBC để truy cập cơ sở dữ liệu

Kết nối

Sử dụng try-with-resources để tự động đóng tài nguyên JDBC

Đoạn mã ví dụ sau đây minh họa cách sử dụng try-with-resources để quản lý tài nguyên JDBC:

public List<NguoiDung> layNguoiDung(int maNguoiDung) {
    try (KetNoi ketNoi = DriverManager.getConnection(diaChiKetNoi);
         CauTruyVan ps = taoCauTruyVan(ketNoi, maNguoiDung); 
         TapKetQua rs = ps.thucThiTruyVan()) {

         // Xử lý kết quả ở đây, tất cả tài nguyên sẽ được tự động dọn dẹp

    } catch (SQLException e) {
        e.printStackTrace();
    }
}

private CauTruyVan taoCauTruyVan(KetNoi ketNoi, int maNguoiDung) throws SQLException {
    String sql = "SELECT id, tenDangNhap FROM nguoidung WHERE id = ?";
    CauTruyVan ps = ketNoi.chuanBiCauTruyVan(sql);
    ps.datGiaTriSo(1, maNguoiDung);
    return ps;
}

Cách truyền thống để đóng tài nguyên JDBC:

public static void giaiPhongTatCa(TapKetQua rs, LenhTruyVan stmt, KetNoi conn){
    if(rs!=null){
        try {
            rs.dong();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    if (stmt!=null){
        try {
            stmt.dong();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    if (conn != null) {
        try{
            conn.dong();
        } catch (SQLException e) {
           e.printStackTrace();
        }
   }
}

Hoặc sử dụng AutoCloseable từ JDK 1.7:

/**
* Để đóng resultset, statement và connection cùng lúc, hãy đóng theo thứ tự r->s->c
* @param obj
*/
public static void dongTatCa(AutoCloseable... obj) {
   for (AutoCloseable item : obj) {
       try {
           if(item!=null){
               item.close();
           }
       } catch (Exception e) {
            e.printStackTrace();
       }
   }
}

Trong JDK 1.7, vì tất cả tài nguyên đều implements AutoCloseable, ta có thể dùng nó để đóng. Tuy nhiên, cú pháp try-with-resources vẫn là cách tốt nhất.

Sử dụng C3P0 kết nối cơ sở dữ liệu gặp lỗi "Apparent Dead Lock"

Thường là do tên người dùng hoặc mật khẩu sai (cần chú ý đến khoảng trắng). Nếu không phải nguyên nhân này thì kiểm tra các vấn đề khác.

Lớp tiện ích kết nối cơ sở dữ liệu với C3P0

public class LopKetNoiC3P0 {
    private static ComboPooledDataSource nguonDuLieu;
    
    static{
        
        nguonDuLieu = new ComboPooledDataSource();//Đọc tệp c3p0.properties trong classpath mặc định
        /*Properties prop = new Properties();//Cách khác để đọc tệp cấu hình
        try {
            InputStream in = LopKetNoiC3P0.class.getClassLoader().getResourceAsStream("jdbc.properties");
            prop.load(in);
            nguonDuLieu.setDriverClass(prop.getProperty("jdbc.driverClass"));
            nguonDuLieu.setJdbcUrl(prop.getProperty("jdbc.jdbcUrl"));
            nguonDuLieu.setUser(prop.getProperty("jdbc.user"));
            nguonDuLieu.setPassword(prop.getProperty("jdbc.password"));
            nguonDuLieu.setInitialPoolSize(Integer.parseInt(prop.getProperty("jdbc.initialPoolSize")));
            nguonDuLieu.setMaxPoolSize(Integer.parseInt(prop.getProperty("jdbc.maxPoolSize")));
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        } */
    }
    
    public static DataSource layNguonDuLieu() {
        return nguonDuLieu;
    }
    
    public static KetNoi layKetNoi() throws SQLException{
        KetNoi conn = nguonDuLieu.getConnection();
        return conn;
    }
    
    //Các mã khác

}

Xử lý ngày tháng

Chuyển chuỗi ngày tháng có định dạng thành LocalDateTime

String thoiGian = "20170818103605"
DateTimeFormatter boDinhDang = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
LocalDateTime dateTime = LocalDateTime.parse(thoiGian, boDinhDang);

Ghi dữ liệu

Phương thức chèn dữ liệu chung

Sử dụng insert để chèn dữ liệu, cú pháp chung như sau: `insert bang1(cot1, cot2) values(?, ?)`. Các nghiệp vụ chèn khác nhau ở kiểu dữ liệu của từng cột và số lượng cột. Có thể dùng phương thức sau để chèn chung:

public static int chen(KetNoi conn, String sql, Object[] thamSo){
        CauTruyVan pstmt = null;
        int ketQua = 0;
        try{
            pstmt = conn.chuanBiCauTruyVan(sql);
            for(int i =0; i < thamSo.length; i++){
                pstmt.datChuoi(i+1, thamSo[i]!=null?thamSo[i].toString():null);
            }
            ketQua = pstmt.thucThiCapNhat();
        }catch (Exception e) {
            e.printStackTrace();
        }finally{
            //Mã để đóng tài nguyên cơ sở dữ liệu
        }
        return ketQua;
    }

Lưu ý: giá trị trả về của phương thức `toString` của đối tượng trong `thamSo` có phải là giá trị bạn cần chèn không.

Có thể sử dụng phương thức `setObject(int parameterIndex, Object x)` để đơn giản hơn. Tuy nhiên, hai phương thức này có vấn đề tương thích khi xử lý giá trị null, có thể dùng `setNull` hoặc `setObject(int parameterIndex, Object x, int sqlType)`.

Đọc dữ liệu

Khi giá trị cột trong cơ sở dữ liệu là NULL, ResultSet.getDouble() trả về 0.0

public class DocDuLieu {
    
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/test1";
        String tenDangNhap = "root";
        String matKhau = "123456";
        String sql = "select id, mssv, tuoi, ngaySinh, chuyenNganh from sinhVien";

        try (KetNoi con = DriverManager.getConnection(url, tenDangNhap, matKhau);
                CauTruyVan pStatement = con.chuanBiCauTruyVan(sql);
                TapKetQua rs = pStatement.thucThiTruyVan()) {
            while (rs.tiepTuc()) {
                /*Lưu ý: giá trị trả về của getInt*/
                System.out.printf("id=%s mssv=%s tuoi=%s ns=%s cn=%s%n", rs.getInt("id"), rs.getChuoi("mssv"), rs.getInt("tuoi"),
                        rs.getNgay("ngaySinh"), rs.getChuoi("chuyenNganh"));
                /*Có thể dùng toàn bộ getString để lấy dữ liệu*/
                System.out.printf("id=%s mssv=%s tuoi=%s ns=%s cn=%s%n", rs.getChuoi("id"), rs.getChuoi("mssv"), rs.getChuoi("tuoi"),
                        rs.getChuoi("ngaySinh"), rs.getChuoi("chuyenNganh"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }
}

Đối với bản ghi: `2 20150112 Vu Kinh m null null null` kết quả trả về:

id=2 mssv=20150112 tuoi=0 ns=null cn=null
id=2 mssv=20150112 tuoi=null ns=null cn=null

Có thể thấy tuổi mặc dù là Null nhưng trả về 0. Làm thế nào để trả về null? Xem mã tham khảo:

Integer tuoi = rs.getInt("tuoi");
Object oTuoi = rs.getDoiTuong("tuoi");
System.out.printf("tuoi=%s, oTuoi=%s%n",tuoi, oTuoi);

Kết quả in ra: `tuoi=0, oTuoi=null`. Còn có thể dùng cách:

Integer tuoi = rs.getInt("tuoi");
if (rs.daNull())
    tuoi = null;

Sử dụng getString để trả về dữ liệu dưới dạng chuỗi

Dữ liệu như trên, dùng mã:

/*Có thể dùng toàn bộ getString để lấy dữ liệu dưới dạng chuỗi*/
System.out.printf("id=%s mssv=%s tuoi=%s ns=%s cn=%s%n", rs.getChuoi("id"), rs.getChuoi("mssv"), rs.getChuoi("tuoi"), rs.getChuoi("ngaySinh"), rs.getChuoi("chuyenNganh"));

Kết quả: `id=2 mssv=20150112 tuoi=null ns=null cn=null`. Lưu ý: giá trị null trong cơ sở dữ liệu được chuyển thành null.

Thẻ: jdbc Java Database Connection Pooling preparedstatement

Đăng vào ngày 2 tháng 6 lúc 18:58