Các tính năng nâng cao của JDBC API
- 08-12-2023
- Toanngo92
- 0 Comments
Đối tượng ResultSet trong JDBC API đại diện cho một bảng kết quả SQL trong ứng dụng JDBC. Một bảng kết quả SQL là bảng kết quả được trả về bởi cơ sở dữ liệu trong quá trình thực thi truy vấn. Nó bao gồm một tập hợp các hàng từ cơ sở dữ liệu cũng như các thông tin về truy vấn như tên cột và loại, kích thước của mỗi cột.
Mục lục
Đối tượng ResultSet
Một tập kết quả trong JDBC API có thể được coi là một bảng dữ liệu đại diện cho một tập kết quả cơ sở dữ liệu, thường được tạo ra bằng cách thực thi một câu lệnh truy vấn cơ sở dữ liệu. Một đối tượng kết quả mặc định không thể được cập nhật hoặc cuộn lên xuống. Theo mặc định, con trỏ chỉ di chuyển về phía trước. Vì vậy, nó chỉ có thể được lặp qua một lần và chỉ từ hàng đầu tiên đến hàng cuối cùng. Ngoài việc di chuyển về phía trước, từng hàng một, qua một ResultSet, Driver JDBC cũng cung cấp khả năng di chuyển về phía trước hoặc đi trực tiếp đến một hàng cụ thể. Ngoài ra, cũng có khả năng cập nhật và xóa các hàng của kết quả. ResultSet cũng có thể được giữ mở sau một câu lệnh COMMIT.
Các đặc điểm của ResultSet như sau:
Có thể cuộn (Scrollable)
Điều này liên quan đến khả năng di chuyển lên xuống cũng như về phía trước qua một tập kết quả.
Có thể cập nhật (Updatable)
Điều này liên quan đến khả năng cập nhật dữ liệu trong một tập kết quả và sau đó sao chép các thay đổi vào cơ sở dữ liệu. Điều này bao gồm việc chèn các hàng mới vào tập kết quả hoặc xóa các hàng hiện tại.
Có thể giữ (Holdable)
Điều này liên quan đến khả năng kiểm tra xem con trỏ có giữ mở sau một câu lệnh COMMIT không.
Scrollable ResultSet
Một ResultSet có khả năng cuộn (scrollable) cho phép con trỏ được di chuyển đến bất kỳ hàng nào trong tập kết quả. Khả năng này hữu ích cho các công cụ GUI để duyệt qua các tập kết quả. Do ResultSet có khả năng cuộn đòi hỏi chi phí thêm, chúng nên được sử dụng chỉ khi ứng dụng yêu cầu tính năng cuộn. Bạn có thể tạo một ResultSet có khả năng cuộn thông qua các phương thức của giao diện Connection.
Một số phương thức Scrollable ResultSet:
createStatement()
Cú pháp:
Statement statement = connection.createStatement();
Ví dụ:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class StatementExample {
public static void main(String[] args) {
// JDBC connection parameters
String jdbcUrl = "jdbc:mysql://localhost:3306/your_database";
String username = "your_username";
String password = "your_password";
// SQL query
String sqlQuery = "SELECT * FROM your_table";
try (Connection connection = DriverManager.getConnection(jdbcUrl, username, password)) {
// Creating a Statement
Statement statement = connection.createStatement();
// Executing the query
ResultSet resultSet = statement.executeQuery(sqlQuery);
// Processing the result set
while (resultSet.next()) {
// Accessing columns by name or index
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
// Process the retrieved data as needed
System.out.println("ID: " + id + ", Name: " + name);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
prepareCall()
Cú pháp:
CallableStatement callableStatement = connection.prepareCall("{CALL your_stored_procedure(?, ?)}");
Ví dụ Sql Server:
Có procedure sau:
CREATE PROCEDURE getEmployeeDetails(IN employeeId INT, OUT employeeName VARCHAR(255))
BEGIN
SELECT name INTO employeeName FROM employees WHERE id = employeeId;
END;
Ví dụ MySQL
Tạo bảng:
CREATE TABLE employees (
id INT AUTO_INCREMENT PRIMARY KEY, -- Employee ID (Primary Key)
name VARCHAR(255) NOT NULL -- Employee Name
);
Tạo Procedure
DELIMITER $$
CREATE PROCEDURE getEmployeeDetails(IN employeeId INT, OUT employeeName VARCHAR(255))
BEGIN
SELECT name INTO employeeName
FROM employees
WHERE id = employeeId;
END$$
DELIMITER ;
Call Procedure:
SET @employeeId = 1; -- Replace 1 with the desired employee ID
CALL getEmployeeDetails(@employeeId, @employeeName);
-- Check the result
SELECT @employeeName AS EmployeeName;
Sử dụng prepareCall để gọi procedure:
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Types;
public class CallableStatementExample {
public static void main(String[] args) {
// JDBC connection parameters
String jdbcUrl = "jdbc:mysql://localhost:3306/your_database";
String username = "your_username";
String password = "your_password";
// Example parameters
int employeeId = 1;
String employeeName = null;
try (Connection connection = DriverManager.getConnection(jdbcUrl, username, password)) {
// Creating a CallableStatement
CallableStatement callableStatement = connection.prepareCall("{CALL getEmployeeDetails(?, ?)}");
// Setting the input parameter
callableStatement.setInt(1, employeeId);
// Registering the output parameter
callableStatement.registerOutParameter(2, Types.VARCHAR);
// Executing the stored procedure
callableStatement.execute();
// Retrieving the output parameter value
employeeName = callableStatement.getString(2);
// Process the retrieved data as needed
System.out.println("Employee Name: " + employeeName);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Các kiểu giá trị ResultSet
Một đối tượng ResultSet thường được tạo ra bằng cách thực thi một câu lệnh truy vấn cơ sở dữ liệu.
Các giá trị hằng số tĩnh khác nhau có thể được chỉ định cho loại kết quả như sau:
TYPE_FORWARD_ONLY
Một con trỏ chỉ có thể được sử dụng để xử lý từ đầu của một ResultSet đến cuối của nó. Con trỏ chỉ di chuyển về phía trước. Đây là loại mặc định. Bạn không thể cuộn qua loại kết quả này và nó cũng không thể được đặt vị trí, cuối cùng, nó không nhạy cảm với các thay đổi được thực hiện trong cơ sở dữ liệu.
Đoạn mã dưới thể hiện con trỏ TYPE_FORWARD_ONLY. Giả sử rằng một bảng Employees đã được tạo trong cơ sở dữ liệu BankDB trên SQL Server. Giả sử rằng bảng này có bốn trường là EMP_NO, NAME, SALARY và RATING có kiểu dữ liệu lần lượt là int, varchar, int và float. Một số bản ghi phải tồn tại với một trong nhân viên có Emp_No là 28959.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class DataDemo {
public static void main(String[] args) {
try {
Connection cn = DriverManager.getConnection("jdbc:sqlserver://127.0.0.1:1433;InstanceName=FUJI\\SQLEXPRESS;databaseName=BankDS", "ea", "playware");
// ResultSet block
PreparedStatement pst = cn.prepareStatement("SELECT EMP_NO, SALARY FROM EMPLOYEES WHERE EMP_NO = ?");
pst.setString(1, "28959");
ResultSet rs = pst.executeQuery();
// end of ResultSet block
int empnum;
int sal;
Statement st = cn.createStatement();
st.executeUpdate("UPDATE employees SET SALARY = 990 WHERE EMP_NO = 28959");
while (rs.next()) {
empnum = rs.getInt("EMP_NO");
sal = rs.getInt("SALARY");
System.out.println("EMP_NO: " + empnum);
System.out.println("SALARY: " + sal);
}
} catch (SQLException ce) {
System.out.println(ce.getMessage());
}
}
}
Trong ví dụ này, setQueryTimeout(ResultSet.CLOSE_CURSORS_AT_COMMIT)
được sử dụng để chỉ định rằng các trường EMP_NO và SALARY là không nhạy cảm với các thay đổi được thực hiện trong cơ sở dữ liệu trong khi ResultSet đang mở. Phương thức next()
sau đó được sử dụng để lặp qua ResultSet và hiển thị giá trị. Bất kỳ thay đổi nào về SALARY trong quá trình thực thi chương trình sẽ không ảnh hưởng đến kết quả của chương trình, và lương cũ trước khi cập nhật sẽ được hiển thị.
TYPE_SCROLL_INSENSITIVE
Một con trỏ có thể được sử dụng để cuộn qua một ResultSet theo nhiều cách khác nhau. Loại con trỏ này là không nhạy cảm đối với những thay đổi được thực hiện trên cơ sở dữ liệu trong khi nó đang mở. Nó chứa các hàng thỏa mãn truy vấn khi truy vấn được xử lý hoặc khi dữ liệu được lấy.
Ví dụ con trỏ TYPE_SCROLL_INSENSITIVE:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class ScrollInsensitiveResultSetExample {
public static void main(String[] args) {
// JDBC connection parameters
String jdbcUrl = "jdbc:mysql://localhost:3306/your_database";
String username = "your_username";
String password = "your_password";
try (Connection connection = DriverManager.getConnection(jdbcUrl, username, password)) {
// Creating a Statement with TYPE_SCROLL_INSENSITIVE cursor
Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
// SQL query
String sqlQuery = "SELECT * FROM your_table";
// Executing the query
ResultSet resultSet = statement.executeQuery(sqlQuery);
// Move to the last row
if (resultSet.last()) {
// Process the last row
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
System.out.println("Last Row - ID: " + id + ", Name: " + name);
// Move to the first row
resultSet.first();
// Process the first row
id = resultSet.getInt("id");
name = resultSet.getString("name");
System.out.println("First Row - ID: " + id + ", Name: " + name);
} else {
System.out.println("No data in the result set.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
TYPE_SCROLL_SENTITIVE
“Một con trỏ có thể được sử dụng để cuộn qua một Resultset theo nhiều cách khác nhau. Loại con trỏ này nhạy cảm đối với các thay đổi được thực hiện trên cơ sở dữ liệu trong khi nó đang mở. Các thay đổi vào cơ sở dữ liệu có ảnh hưởng trực tiếp đến dữ liệu Resultset.
Ví dụ con trỏ TYPE_SCROLL_SENSITIVE:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class ScrollSensitiveResultSetExample {
public static void main(String[] args) {
// JDBC connection parameters
String jdbcUrl = "jdbc:mysql://localhost:3306/your_database";
String username = "your_username";
String password = "your_password";
try (Connection connection = DriverManager.getConnection(jdbcUrl, username, password)) {
// Creating a Statement with TYPE_SCROLL_SENSITIVE cursor
Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
// SQL query
String sqlQuery = "SELECT * FROM your_table";
// Executing the query
ResultSet resultSet = statement.executeQuery(sqlQuery);
// Move to the last row
if (resultSet.last()) {
// Process the last row
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
System.out.println("Last Row - ID: " + id + ", Name: " + name);
// Move to the first row
resultSet.first();
// Process the first row
id = resultSet.getInt("id");
name = resultSet.getString("name");
System.out.println("First Row - ID: " + id + ", Name: " + name);
} else {
System.out.println("No data in the result set.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Các phương thức xác định vị trí hàng
Mặc định, Resultset luôn cho phép di chuyển chỉ về phía trước, có nghĩa là phương thức định vị con trỏ duy nhất hợp lệ để gọi là next(). Bạn phải yêu cầu một Resultset có thể cuộn theo chiều ngược lại.
Bảng mô tả các phương thức:
- next(): Phương thức này di chuyển con trỏ một dòng về phía trước trong Resultset từ vị trí hiện tại. Phương thức trả về true nếu con trỏ được đặt trên một dòng hợp lệ và false nếu ngược lại.
- previous(): Phương thức di chuyển con trỏ một dòng về phía sau trong Resultset. Phương thức trả về true nếu con trỏ được đặt trên một dòng hợp lệ và false nếu ngược lại.
- first(): Phương thức di chuyển con trỏ đến dòng đầu tiên trong Resultset. Phương thức trả về true nếu con trỏ được đặt trên dòng đầu tiên và false nếu Resultset trống.
- last(): Phương thức di chuyển con trỏ đến dòng cuối cùng trong Resultset. Phương thức trả về true nếu con trỏ được đặt trên dòng cuối cùng và false nếu Resultset trống.
- beforeFirst(): Phương thức di chuyển con trỏ ngay trước dòng đầu tiên trong Resultset. Phương thức này không có giá trị trả về.
- afterLast(): Phương thức di chuyển con trỏ ngay sau dòng cuối cùng trong Resultset. Phương thức này không có giá trị trả về.
- relative(int rows): Phương thức di chuyển con trỏ tương đối với vị trí hiện tại. Nếu giá trị hàng là 0, phương thức này không có tác động. Nếu giá trị hàng là dương, con trỏ di chuyển về phía trước một số hàng đó. Nếu có ít hơn số hàng giữa vị trí hiện tại và cuối Resultset so với tham số đầu vào, phương thức này hoạt động giống như phương thức afterLast(). Nếu giá trị hàng là âm, con trỏ di chuyển về phía sau một số hàng đó. Phương thức trả về true nếu con trỏ được đặt trên một dòng hợp lệ và false nếu ngược lại.
- absolute(int row): Phương thức di chuyển con trỏ đến dòng được chỉ định bởi giá trị hàng. Nếu giá trị hàng là dương, con trỏ được đặt vào số hàng đó từ đầu của Resultset. Dòng đầu tiên được đánh số 1, dòng thứ hai là 2, và cứ tiếp tục. Nếu giá trị hàng là âm, con trỏ được đặt vào số hàng đó từ cuối Resultset. Dòng cuối cùng được đánh số -1, dòng thứ hai từ cuối là -2, và cứ tiếp tục. Nếu giá trị hàng là 0, phương thức này hoạt động giống như beforeFirst(). Phương thức trả về true nếu con trỏ được đặt trên một dòng hợp lệ và false nếu ngược lại.
Lưu ý: Gọi absolute(1) tương đương với việc gọi first() và gọi absolute(-1) tương đương với việc gọi last().
ResultSet Có Thể Cập Nhật
ResultSet có thể cập nhật là khả năng cập nhật các hàng trong một result set bằng cách sử dụng các phương thức của ngôn ngữ lập trình Java thay vì các lệnh SQL.
Một ResultSet có thể cập nhật sẽ cho phép người lập trình thay đổi dữ liệu trong hàng hiện tại, chèn một hàng mới hoặc xóa một hàng hiện tại. Các phương thức newUpdateXXX() của giao diện ResultSet có thể được sử dụng để thay đổi dữ liệu trong một hàng hiện tại.
Khi sử dụng một result set có thể cập nhật, nên làm cho nó có thể cuộn để có thể đặt vị trí đến bất kỳ hàng nào muốn thay đổi.
Tính Đồng Thời trong ResultSet
Khả năng cập nhật có nghĩa là cập nhật dữ liệu trong một result set và sau đó, tích hợp hoặc sao chép các thay đổi vào cơ sở dữ liệu như chèn các hàng mới vào result set hoặc xóa các hàng hiện tại.
Việc cập nhật cũng có thể đòi hỏi khóa ghi cơ sở dữ liệu để cung cấp quyền truy cập vào cơ sở dữ liệu cơ bản. Vì bạn không thể có nhiều khóa ghi đồng thời, tính đồng thời trong một result set liên quan đến tính đồng thời trong việc truy cập cơ sở dữ liệu. Đồng thời là một quá trình mà hai sự kiện diễn ra song song.
Loại đồng thời của một result set xác định liệu nó có thể cập nhật hay không. Các giá trị hằng mà có thể được gán để chỉ định các loại đồng thời là như sau:
CONCUR_READ_ONLY
: Result set không thể được sửa đổi và do đó, không thể cập nhật bằng bất kỳ cách nào.
CONCUR_UPDATABLE
: Các hoạt động cập nhật, chèn, và xóa có thể được thực hiện trên result set và các thay đổi sẽ được sao chép vào cơ sở dữ liệu.
Cập Nhật Một Hàng
Các hàng có thể được cập nhật trong bảng cơ sở dữ liệu bằng cách sử dụng một đối tượng của giao diện ResultSet. Quá trình này có hai bước. Bước đầu tiên là thay đổi các giá trị cho một hàng cụ thể bằng cách sử dụng các phương thức update, trong đó là một loại dữ liệu Java. Các phương thức update này tương ứng với các phương thức get có sẵn để truy xuất giá trị. Bước thứ hai là áp dụng các thay đổi vào các hàng của cơ sở dữ liệu cơ bản. Cơ sở dữ liệu chính nó không được cập nhật cho đến bước thứ hai. Việc cập nhật cột trong một ResultSet mà không gọi phương thức updateRow() không tạo ra bất kỳ thay đổi nào trong cơ sở dữ liệu. Một khi phương thức updateRow() được gọi, các thay đổi vào cơ sở dữ liệu là cuối cùng và không thể hoàn tác.
Tuần tự các bước cập nhật:
Bước 1: Đặt Con Trỏ
Đoạn mã dưới mô tả cách đặt con trỏ,
Đoạn mã số 4:
// Tạo một result set có thể cập nhật
Statement st = cn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
// Lấy result set có thể cuộn và có thể cập nhật
ResultSet rs = st.executeQuery("SELECT NAME, EMP_NO FROM EMPLOYEES");
// Di chuyển đến dòng đầu tiên trong result set
rs.first();
Phương thức first() di chuyển con trỏ đến dòng đầu tiên trong result set. Sau khi gọi phương thức này, mọi lời gọi đến các phương thức cập nhật ảnh hưởng đến giá trị của dòng đầu tiên cho đến khi con trỏ được di chuyển đến một dòng khác.
Tương tự, có các phương thức khác như last(), next(), previous(), beforeFirst(), afterLast(), absolute(int), và relative(int) có thể được sử dụng để di chuyển con trỏ đến vị trí cần trong result set.
Bước 2: Cập Nhật Các Cột
Sau khi sử dụng phương thức first() để điều hướng đến dòng đầu tiên của result set, gọi updatent() để thay đổi giá trị của cột EMP_No trong result set.
Đoạn mã dưới:
// Cập nhật cột thứ hai trong result set
rs.updateInt(2, 34523);
Phương thức updateInt() được sử dụng vì cột cần cập nhật có giá trị số nguyên. Phương thức updateInt() có hai tham số. Tham số đầu tiên chỉ định số thứ tự của cột cần cập nhật. Tham số thứ hai chỉ định giá trị mới sẽ thay thế giá trị hiện tại của cột đối với bản ghi cụ thể đó.
Tương tự, có các phương thức cập nhật khác như updateString(), updateFloat(), và updateBoolean() có thể được sử dụng để cập nhật một cột cụ thể chứa giá trị chuỗi, float, và boolean tương ứng.
Bước 3: Xác Nhận Việc Cập Nhật
Sau khi thay đổi, gọi phương thức updateRow() của giao diện ResultSet để thực sự phản ánh thay đổi vào cơ sở dữ liệu cơ bản như thể hiện trong đoạn mã dưới:
// Xác nhận cập nhật dòng
rs.updateRow();
Nếu phương thức updateRow() không được gọi trước khi di chuyển đến một dòng khác trong result set, bất kỳ thay đổi nào được thực hiện sẽ bị mất.
Để thực hiện nhiều thay đổi trong một dòng duy nhất, thực hiện nhiều lời gọi đến các phương thức updatexxx() và sau đó, một lời gọi đến updateRow(). Hãy chắc chắn gọi updateRow() trước khi chuyển đến một dòng khác.
Bước Để Xóa Một Hàng
Việc xóa một hàng từ một result set có thể cập nhật là dễ dàng. Hai bước để xóa hàng là đặt con trỏ và xóa hàng.
Việc xóa một hàng từ result set đã được mô tả trong các bước.
Bước 1: Đặt Con Trỏ
Bước đầu tiên là đặt con trỏ bằng cách di chuyển con trỏ đến dòng mong muốn mà sẽ bị xóa, như thể hiện trong đoạn mã dưới:
// Di chuyển con trỏ đến dòng cuối cùng của result set
rs.last();
Ở đây, con trỏ được di chuyển đến bản ghi cuối cùng của result set mà sẽ bị xóa. Bạn có thể di chuyển đến dòng mong muốn bằng cách sử dụng bất kỳ phương thức điều hướng nào của giao diện ResultSet để xóa một dòng cụ thể.
Bước 2: Xóa Hàng
Bước thứ hai là xóa hàng. Sau khi di chuyển đến dòng cuối cùng của result set, gọi phương thức deleteRow() để xác nhận việc xóa hàng.
Đoạn mã số dưới hiển thị mã để xóa hàng.
// Xóa hàng khỏi result set
rs.deleteRow();
Gọi phương thức deleteRow() của giao diện Resultset cũng xóa hàng từ cơ sở dữ liệu cơ bản.
Một số driver JDBC sẽ loại bỏ hàng và hàng sẽ không hiển thị trong result set. Các driver JDBC khác sẽ đặt một hàng trống vào vị trí của hàng đã bị xóa. Sau đó, phương thức absolute() có thể được sử dụng với các vị trí hàng gốc để di chuyển con trỏ vì số thứ tự hàng trong result set không thay đổi sau khi bị xóa.
Store Procedure
Một thủ tục lưu trữ có thể được định nghĩa như một nhóm các câu lệnh SQL thực hiện một nhiệm vụ cụ thể. Thủ tục lưu trữ được sử dụng để nhóm hoặc gộp một tập hợp các hoạt động hoặc truy vấn để thực thi trên một máy chủ cơ sở dữ liệu. Thủ tục lưu trữ có thể được biên dịch và thực thi với bất kỳ sự kết hợp nào của các tham số đầu vào, đầu ra hoặc đầu vào/đầu ra. Do thủ tục lưu trữ được biên dịch trước, chúng nhanh chóng và hiệu quả hơn so với việc sử dụng các câu lệnh truy vấn SQL cá nhân.
Thủ Tục Lưu Trữ được Hỗ Trợ bởi Hệ Thống Cơ Sở Dữ Liệu như SQL Server, Oracle, hoặc Sybase.
Ghi chú: Xem xét một cơ sở dữ liệu nhân viên có các chức năng như tính tổng lương, tìm kiếm một nhân viên hoặc bộ phận cụ thể và tính lương dựa trên bộ phận. Các chức năng này có thể được viết dưới dạng thủ tục lưu trữ và được thực thi bởi mã ứng dụng.
Một thủ tục lưu trữ là một nhóm có tên của các câu lệnh SQL đã được tạo trước và lưu trữ trong cơ sở dữ liệu máy chủ. Một thủ tục lưu trữ giống như một chương trình được lưu giữ và thực thi trong máy chủ cơ sở dữ liệu. Thủ tục lưu trữ chấp nhận các tham số đầu vào để một thủ tục duy nhất có thể được sử dụng qua mạng bởi nhiều khách hàng với dữ liệu đầu vào khác nhau. Thủ tục lưu trữ được gọi từ một lớp Java bằng cách sử dụng cú pháp đặc biệt. Khi thủ tục được gọi, tên của thủ tục và các tham số bạn chỉ định được gửi qua kết nối JDBC đến DBMS, nơi thực thi thủ tục và trả kết quả lại qua kết nối.
Thủ tục lưu trữ giảm lưu lượng mạng cơ sở dữ liệu, cải thiện hiệu suất và giúp đảm bảo tính toàn vẹn của cơ sở dữ liệu. Một thủ tục lưu trữ được viết bằng một ngôn ngữ kim loại, được xác định bởi nhà cung cấp. Nó được sử dụng để nhóm hoặc gộp nhiều câu lệnh SQL được lưu trữ dưới dạng có thể thực thi tại cơ sở dữ liệu. Thông thường, nó có thể được viết bằng PL/SQL hoặc Sybase Transact SQL. Những ngôn ngữ này không có thể chuyển giao giữa các DBMS khác nhau.
Các yếu tố cơ bản mà một thủ tục lưu trữ bao gồm là các câu lệnh SQL, biến và một hoặc nhiều tham số. Các yếu tố này có thể được sử dụng để thực hiện một loạt các nhiệm vụ khác nhau từ câu lệnh SELECT đơn giản đến kiểm tra quy tắc kinh doanh phức tạp.
Các câu lệnh định hình thành phần chính của thủ tục lưu trữ. Các câu lệnh bao gồm các câu lệnh SQL tiêu chuẩn, các câu lệnh điều khiển luồng như if, if…else, while và các câu lệnh cụ thể của SQL Server cho phép điều khiển cài đặt máy chủ hoặc tạo hoặc sửa đổi các đối tượng cơ sở dữ liệu và nhiều hơn nữa. Biến giống như bất kỳ biến nào khác được tìm thấy trong một ngôn ngữ lập trình. Biến lưu trữ giá trị tạm thời trong bộ nhớ trong thời gian thực hiện thủ tục lưu trữ. Tham số được sử dụng để chuyển và lấy giá trị từ một thủ tục lưu trữ.
Một thủ tục lưu trữ có thể được định nghĩa bởi người dùng, hệ thống đã được định nghĩa trong SQL Server, hoặc mở rộng. Thủ tục lưu trữ do người dùng tạo được tạo bởi người dùng. Thủ tục lưu trữ hệ thống bắt đầu bằng sp_ và được lưu trữ trong cơ sở dữ liệu Master. Thủ tục lưu trữ mở rộng là DLL biên dịch và có mặt trong cơ sở dữ liệu master.
Thủ tục lưu trữ trong SQL Server tương tự như các thủ tục trong các ngôn ngữ lập trình khác. Có nhiều lợi ích khi sử dụng thủ tục lưu trữ. Những lợi ích mà một ứng dụng có khi sử dụng một thủ tục lưu trữ là như sau: giảm việc sử dụng mạng giữa khách hàng và máy chủ, nâng cao khả năng của phần cứng và phần mềm, tăng cường bảo mật, giảm chi phí phát triển và tăng độ tin cậy và an toàn tập trung, quản lý và bảo trì cho các quy trình phổ biến.
Thủ tục lưu trữ cũng có nhiều ưu điểm khác nhau như sau:
- Chấp nhận tham số đầu vào và trả giá trị qua tham số đầu ra cho thủ tục gọi.
- Chứa lời gọi đến các thủ tục khác và cũng chứa các câu lệnh lập trình thực hiện các hoạt động trong cơ sở dữ liệu.
- Cho biết thành công hoặc thất bại bằng cách trả giá trị trạng thái cho thủ tục hoặc lô lệnh gọi.
- Biên dịch mỗi thủ tục lưu trữ một lần và sau đó, tái sử dụng kết quả. Do đó, có sự cải thiện về hiệu suất khi thủ tục lưu trữ được gọi lặp đi lặp lại.
- Có thể được thực thi độc lập với quyền truy cập bảng cơ sở dữ liệu cơ bản sau khi được cấp quyền.
- Thực thi trên cả SQL Server cục bộ và từ xa. Điều này cho phép chạy quy trình trên các máy khác và làm việc với thông tin trên nhiều máy chủ.
- Cung cấp giải pháp tối ưu giữa phần mềm ở phía máy khách và SQL Server vì một chương trình ứng dụng viết bằng C hoặc Visual Basic cũng có thể thực thi thủ tục lưu trữ.
- Cho phép thực thi nhanh hơn vì đường dẫn thực thi được tối ưu hóa được tính toán trước và lưu trữ.
- Tách hoặc trừu tượng hóa các chức năng phía máy chủ khỏi phía máy khách. Việc lập trình một ứng dụng GUI để gọi một thủ tục dễ dàng hơn so với việc xây dựng một truy vấn thông qua mã GUI.
Nhược điểm của việc sử dụng thủ tục lưu trữ là tính di động không cao. Đối với mỗi hệ quản trị cơ sở dữ liệu (DBMS) cụ thể, mã thủ tục lưu trữ phải được viết lại.
Đặc Điểm của Thủ Tục Lưu Trữ
Thủ tục lưu trữ có các đặc điểm sau:
- Chúng chứa các câu lệnh SQL sử dụng cấu trúc và các cấu trúc điều khiển.
- Chúng có thể được gọi theo tên trong một ứng dụng sử dụng SQL.
- Chúng cho phép một chương trình ứng dụng chạy ở hai phần như ứng dụng trên máy khách và
thủ tục lưu trữ trên máy chủ.
Một ứng dụng khách không sử dụng thủ tục lưu trữ sẽ làm tăng lưu lượng mạng, trong khi một ứng dụng
sử dụng thủ tục lưu trữ giảm lưu lượng mạng. Một ứng dụng sử dụng thủ tục lưu trữ cũng giảm số lần truy cập
đến cơ sở dữ liệu.
Lợi Ích của Thủ Tục Lưu Trữ
Các lợi ích mà một ứng dụng có khi sử dụng một thủ tục lưu trữ như sau:
Giảm lưu lượng mạng
Khi gọi một thủ tục lưu trữ, quyền kiểm soát được chuyển đến máy chủ cơ sở dữ liệu bởi ứng dụng sử dụng thủ tục lưu trữ. Xử lý trung gian của dữ liệu được thực hiện bởi thủ tục lưu trữ và các bản ghi cần thiết cho ứng dụng khách được chuyển. Do đó, lưu lượng mạng giảm và hiệu suất được tăng cường.
Nâng cao khả năng của phần cứng và phần mềm
Một ứng dụng sử dụng thủ tục lưu trữ truy cập vào bộ nhớ, không gian đĩa và phần mềm đã cài đặt của máy chủ cơ sở dữ liệu. Do đó, logic kinh doanh có thể được phân phối trên các máy có khả năng phần cứng và phần mềm cần thiết.
Tăng cường bảo mật
Bảo mật được tăng cường nếu thủ tục lưu trữ được cấp các quyền đặc quyền cơ sở dữ liệu.
Người quản trị cơ sở dữ liệu (DBA) hoặc người phát triển thủ tục lưu trữ sẽ có các đặc quyền cần thiết theo yêu cầu của thủ tục lưu trữ. Ngược lại, người dùng ứng dụng khách không cần phải có cùng đặc quyền và điều này sẽ tăng cường bảo mật.
Giảm chi phí phát triển
Trong ứng dụng cơ sở dữ liệu, các nhiệm vụ được lặp lại có thể trả về một bộ dữ liệu cố định hoặc thực hiện cùng một bộ yêu cầu đến cơ sở dữ liệu. Việc tái sử dụng một thủ tục lưu trữ chung sẽ dẫn đến giảm chi phí phát triển và giải quyết những tình huống lặp đi lặp lại này.
Kiểm soát tập trung
Bảo mật, quản trị và bảo trì của các quy trình chung trở nên dễ dàng nếu logic được chia sẻ được đặt ở một nơi trên máy chủ. Ứng dụng khách có thể gọi các thủ tục lưu trữ chạy các truy vấn SQL với ít hoặc không có xử lý bổ sung.
Ghi chú: Nhược điểm của việc sử dụng một thủ tục lưu trữ là thiếu tính di động. Đối với mỗi hệ quản trị cơ sở dữ liệu đích, mã thủ tục lưu trữ có thể phải được viết lại.
Tạo Một Thủ Tục Lưu Trữ Bằng Đối Tượng Lệnh (Statement Object)
Thủ tục lưu trữ có thể được tạo bằng đối tượng lệnh (Statement object). Việc tạo một thủ tục lưu trữ với đối tượng lệnh bao gồm hai bước.
Tạo định nghĩa thủ tục lưu trữ và lưu trữ nó trong một biến String
Đoạn mã dưới hiển thị mã để khai báo biến chuỗi chứa định nghĩa của một thủ tục lưu trữ. Giả sử rằng các bảng PRODUCTS và COFFEES đã tồn tại:
String createProcedure = "Create Procedure DISPLAY_PRODUCTS " + "as " +
"select PRODUCTS.PRD_NAME, COFFEES.COF_NAME " +
"from PRODUCTS, COFFEES " +
"where PRODUCTS.PRD_ID = COFFEES.PRD_ID " +
"order by PRD_NAME";
Đoạn mã tạo 2 câu lệnh SQL cho một thủ tục lưu trữ và lưu trữ chúng trong biến chuỗi createProcedure
.
Sử dụng đối tượng statement
Đoạn mã dưới mô tả cách sử dụng đối tượng statematent thực thi procedure.
// Biến kết nối active cn được sử dụng để tạo một đối tượng Statement
Statement st = cn.createStatement();
// Thực thi thủ tục lưu trữ
st.executeUpdate(createProcedure);
Trong đoạn mã trên:
Đối tượng kết nối cn
được sử dụng để tạo một đối tượng Lệnh (Statement
). Đối tượng Lệnh được sử dụng để gửi câu lệnh SQL tạo thủ tục lưu trữ đến cơ sở dữ liệu. Thủ tục DISPLAY_PRODUCTS
được biên dịch và lưu trữ trong cơ sở dữ liệu dưới dạng một đối tượng cơ sở dữ liệu và có thể được gọi. Sau khi executeUpdate()
hoàn thành thành công, một thủ tục có tên là DISPLAY_PRODUCTS
được tạo ra. Giả sử rằng mã này đã được chạy trên một kết nối SQL Server đến một cơ sở dữ liệu có tên là Traders. Bây giờ, sau khi thực thi đoạn mã dưới, mở SQL Server Management, điều hướng đến cơ sở dữ liệu và xem trong Programmability -> Stored Procedures như được hiển thị trong hình dưới. Bạn sẽ tìm thấy DISPLAY_PRODUCTS
.
Tham Số của Thủ Tục Lưu Trữ
Thủ tục lưu trữ có thể chấp nhận dữ liệu dưới dạng các tham số đầu vào được chỉ định khi thực thi. Có thể có không hoặc nhiều tham số cho mỗi thủ tục lưu trữ.
Các loại tham số khác nhau được sử dụng trong thủ tục lưu trữ như sau:
IN
Tham số IN được sử dụng để truyền giá trị vào một thủ tục lưu trữ. Giá trị của một tham số IN không thể thay đổi hoặc gán lại trong mô-đun và do đó là hằng số.
OUT
Giá trị của tham số OUT được truyền ra khỏi mô-đun thủ tục lưu trữ, trở lại khối gọi. Một giá trị có thể được gán cho tham số OUT trong thân mô-đun. Giá trị được lưu trữ trong tham số OUT là một biến và không phải là một hằng số.
INOUT
Tham số IN/OUT là một tham số có thể hoạt động như một tham số IN hoặc một tham số OUT hoặc cả hai. Giá trị của tham số IN/OUT được truyền vào thủ tục lưu trữ và một giá trị mới có thể được gán cho tham số và truyền ra khỏi mô-đun. Một tham số IN/OUT hoạt động như một biến được khởi tạo.
Ghi chú: Cả hai tham số IN và IN/OUT phải là biến, không thể là hằng số.
Tạo Một Đối Tượng CallableStatement
Một thủ tục lưu trữ có thể được gọi từ một ứng dụng Java với sự giúp đỡ của một đối tượng CallableStatement
. Đối tượng CallableStatement
không chứa chính thủ tục lưu trữ mà chỉ chứa một cuộc gọi đến thủ tục lưu trữ.
Cuộc gọi đến một thủ tục lưu trữ được viết trong cú pháp escape, tức là cuộc gọi được bao quanh bởi dấu ngoặc nhọn. Cuộc gọi có thể có hai dạng, một là với một tham số kết quả và một là không có tham số kết quả.
Tham số kết quả là giá trị được trả về bởi một thủ tục lưu trữ, tương tự như một tham số OUT. Cả hai dạng đều có một số lượng tham số khác nhau được sử dụng như là tham số đầu vào (tham số IN), tham số đầu ra (tham số OUT), hoặc cả hai (tham số IN/OUT). Dấu chấm hỏi (?) được sử dụng để đại diện cho một địa điểm giữ chỗ cho một tham số.
Cú pháp cho việc gọi một thủ tục lưu trữ không có tham số trong JDBC như sau:
{call procedure_name}
Cú pháp cho việc gọi một thủ tục lưu trữ có tham số trong JDBC như sau:
{call procedure_name[(?, ?, ...)]}
Dấu ngoặc vuông được bao quanh bởi dấu ngoặc vuông cho biết rằng chúng là tùy chọn.
Cú pháp cho một thủ tục lưu trữ trả về một tham số kết quả như sau:
{?= call tên_thủ_tục[(?, ?, ...)]}
Đối tượng CallableStatement
kế thừa các phương thức từ các giao diện Statement
và PreparedStatement
. Tất cả các phương thức được định nghĩa trong giao diện CallableStatement
đều liên quan đến các tham số OUT. Các phương thức như getxx()
như getInt()
và getString()
trong Resultset
sẽ truy xuất giá trị từ một result set
, trong khi trong CallableStatement
, chúng sẽ truy xuất giá trị từ các tham số OUT hoặc giá trị trả về của một thủ tục lưu trữ.
Đối tượng CallableStatement
được tạo bằng cách sử dụng phương thức prepareCall()
của giao diện Connection
. Phần được bao quanh bởi dấu ngoặc nhọn là cú pháp escape cho các thủ tục lưu trữ. Driver chuyển đổi cú pháp escape thành SQL native được sử dụng bởi cơ sở dữ liệu.
Cú pháp
CallableStatement cst = cn.prepareCall("{call functionname(?, ?, ...)}");
Trong đó,
cst
là tên của đối tượngCallableStatement
.functionname
là tên của hàm/thủ tục sẽ được gọi.
Xem xét câu lệnh sau:
CallableStatement cs = cn.prepareCall("{call sa1(?)}");
Câu lệnh này tạo một phiên bản của CallableStatement
. Nó chứa một cuộc gọi đến thủ tục lưu trữ sa1()
, có một đối số và không có tham số kết quả. Loại của các tham số vị trí (?2) – có phải là tham số IN hay OUT – hoàn toàn phụ thuộc vào cách thủ tục lưu trữ sa1()
đã được định nghĩa.
Tham số Đầu Vào (IN Parameters)
Các phương thức set<Type>()
được sử dụng để truyền giá trị của bất kỳ tham số IN nào vào một đối tượng CallableStatement
. Các phương thức set<Type>()
này được kế thừa từ đối tượng PreparedStatement
. Loại giá trị đang được truyền vào sẽ xác định phương thức set<Type>()
nào được sử dụng. Ví dụ, setFloat()
được sử dụng để truyền vào một giá trị kiểu float
, và cứ như vậy.
Tham số Đầu Ra (OUT Parameters)
Trong trường hợp thủ tục lưu trữ trả về một số giá trị (OUT parameters), kiểu JDBC của mỗi tham số OUT phải được đăng ký trước khi thực thi đối tượng CallableStatement
. Phương thức registerOutParameter()
được sử dụng để đăng ký kiểu JDBC. Sau khi đã đăng ký xong, câu lệnh phải được thực thi. Các phương thức get<Type>()
của CallableStatement
được sử dụng để truy xuất giá trị của tham số OUT. Phương thức registerOutParameter()
sử dụng một kiểu JDBC (để nó phù hợp với kiểu JDBC mà cơ sở dữ liệu sẽ trả về), và get<Type>()
chuyển đổi nó thành kiểu Java.
Một số kiểu JDBC phổ biến như sau:
- Char: Được sử dụng để biểu diễn chuỗi kí tự có độ dài cố định.
- Varchar: Được sử dụng để biểu diễn chuỗi kí tự có độ dài biến đổi.
- Bit: Được sử dụng để biểu diễn giá trị bit đơn có thể là 0 hoặc 1.
- Integer: Được sử dụng để biểu diễn giá trị số nguyên có dấu 32-bit.
- Double: Được sử dụng để biểu diễn số thực độ chính xác kép (double-precision) với 15 chữ số của phần thập phân.
- Date: Được sử dụng để biểu diễn một ngày gồm ngày, tháng và năm.
Ví dụ:
Có một procedure như sau:
CREATE PROCEDURE GetEmployee
@EmployeeID INT,
@EmployeeName NVARCHAR(255) OUTPUT,
@EmployeeSalary DECIMAL(18, 2) OUTPUT
AS
BEGIN
SELECT @EmployeeName = EmployeeName, @EmployeeSalary = Salary
FROM Employees
WHERE EmployeeID = @EmployeeID;
END;
Code Java tương ứng:
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Types;
public class StoredProcedureExample {
public static void main(String[] args) {
// JDBC connection parameters for SQL Server
String jdbcUrl = "jdbc:sqlserver://localhost:1433;databaseName=your_database";
String username = "your_username";
String password = "your_password";
try (Connection connection = DriverManager.getConnection(jdbcUrl, username, password)) {
// Prepare the SQL call
String sqlCall = "{CALL GetEmployee(?, ?, ?)}";
try (CallableStatement callableStatement = connection.prepareCall(sqlCall)) {
// Set the input parameter
callableStatement.setInt(1, 101); // Employee ID
// Register the output parameters
callableStatement.registerOutParameter(2, Types.NVARCHAR); // Employee Name
callableStatement.registerOutParameter(3, Types.DECIMAL); // Employee Salary
// Execute the stored procedure
callableStatement.execute();
// Retrieve the output parameters
String employeeName = callableStatement.getString(2);
double employeeSalary = callableStatement.getDouble(3);
// Process the retrieved data as needed
System.out.println("Employee Name: " + employeeName);
System.out.println("Employee Salary: " + employeeSalary);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
9.4 Cập Nhật Hàng Loạt
Cập nhật hàng loạt có thể được định nghĩa như một tập hợp các câu lệnh cập nhật đa dạng được gửi đến cơ sở dữ liệu để được xử lý như một loạt. Trong Java, các đối tượng Statement, PreparedStatement và CallableStatement có thể được sử dụng để gửi các cập nhật hàng loạt.
Lợi ích của cập nhật hàng loạt là nó cho phép bạn yêu cầu các bản ghi, đưa chúng đến máy khách, thực hiện các thay đổi trong bản ghi ở phía máy khách, và sau đó, gửi bản ghi đã cập nhật trở lại nguồn dữ liệu vào một thời điểm khác. Gửi nhiều cập nhật cùng một lúc, thay vì từng cái một, có thể cải thiện đáng kể hiệu suất. Ngoài ra, cập nhật hàng loạt được sử dụng khi không cần duy trì một kết nối không đổi với cơ sở dữ liệu.
Cập Nhật Hàng Loạt Bằng Giao Diện Statement
Cơ chế cập nhật hàng loạt cho phép một đối tượng Statement gửi nhiều lệnh cập nhật cùng một lúc dưới dạng một đơn vị đơn, hoặc hàng loạt, đến hệ quản trị cơ sở dữ liệu (DBMS) cơ bản.
Các bước để thực hiện cập nhật hàng loạt bằng giao diện Statement như sau:
Tắt chế độ tự động commit:
- Chế độ tự động commit của đối tượng Connection được đặt thành false để cho phép gửi nhiều câu lệnh cùng một lúc như một giao dịch. Để xử lý lỗi một cách đúng đắn, chế độ tự động commit nên luôn bị tắt trước khi bắt đầu một cập nhật hàng loạt.
// Tắt tự động commit
con.setAutoCommit(false);
- Để bắt đầu gửi một cập nhật hàng loạt đến cơ sở dữ liệu, trước hết chế độ tự động commit của đối tượng kết nối,
con
, được đặt thành false. Khi chế độ tự động commit của kết nối đã bị tắt, ứng dụng có thể tự quyết định liệu có commit giao dịch hay không nếu có lỗi xảy ra hoặc nếu một số lệnh trong hàng loạt không thể thực hiện.
Tạo một thể hiện của đối tượng Statement:
- Một thể hiện của giao diện Statement được tạo ra bằng cách gọi phương thức
createStatement()
trên đối tượng Connection.
// Tạo một thể hiện của đối tượng Statement
Statement stmt = con.createStatement();
- Một thể hiện của đối tượng Statement
stmt
được tạo ra, ban đầu có một danh sách trống các lệnh liên quan.
Thêm các lệnh SQL vào hàng loạt:
- Các lệnh được thêm vào danh sách các lệnh liên quan đến đối tượng Statement bằng phương thức
addBatch()
của giao diện Statement. Một đối tượng Statement có khả năng theo dõi một danh sách các lệnh hoặc hàng loạt có thể được gửi cùng nhau để thực hiện.
// Thêm các lệnh SQL vào hàng loạt
stmt.addBatch("INSERT INTO EMPLOYEES VALUES (1000, 'William John', 8000)");
stmt.addBatch("INSERT INTO EMPLOYEES VALUES (1001, 'Jacky Lawrence', 9000)");
stmt.addBatch("INSERT INTO EMPLOYEES VALUES (1002, 'Valentina Watson', 4000)");
- Khi một đối tượng Statement được tạo, danh sách các lệnh liên quan đến nó là trống rỗng. Mỗi lệnh
addBatch()
thêm một lệnh vào danh sách các lệnh của đối tượng gọi. Các lệnh này đều là các lệnh INSERT INTO, mỗi lệnh thêm một hàng gồm hai giá trị cột.
Thực hiện các lệnh hàng loạt:
- Phương thức
executeBatch()
được gọi để gửi danh sách các lệnh của đối tượng Statement đến DBMS cơ bản để thực hiện. Lệnh được thực hiện theo thứ tự mà nó được thêm vào hàng loạt và trả về một số lượng cập nhật cho mỗi lệnh trong hàng loạt, cũng theo thứ tự.
// Thực hiện hàng loạt các lệnh cập nhật
int[] updateCounts = stmt.executeBatch();
- Các lệnh được thực hiện bởi DBMS theo thứ tự mà chúng được thêm vào danh sách lệnh. Trước hết, nó sẽ thêm hàng giá trị cho 1000; tiếp theo, nó sẽ thêm hàng giá trị cho 1001, và cuối cùng là 1002. DBMS sẽ trả về một số lượng cập nhật cho mỗi lệnh theo thứ tự nó được thực hiện, miễn là tất cả ba lệnh đều được thực hiện thành công. Các giá trị số nguyên chỉ định có bao nhiêu hàng bị ảnh hưởng bởi mỗi lệnh được lưu trữ trong mảng
updateCounts
. - Nếu tất cả ba lệnh trong hàng loạt được thực hiện thành công,
updateCounts
sẽ chứa ba giá trị, tất cả đều là 1 vì mỗi lệnh chèn ảnh hưởng đến một hàng.
Commit các thay đổi trong cơ sở dữ liệu:
- Phương thức
commit()
làm cho hàng loạt các cập nhật trên bảng trở nên vĩnh viễn. Phương thức này phải được gọi một cách rõ ràng vì chế độ tự động commit cho kết nối này đã được vô hiệu hóa trước đó.
// Commit các thay đổi trong cơ sở dữ liệu
conn.commit();
// Tự động bật chế độ tự động commit
conn.setAutoCommit(true);
- Dòng mã đầu tiên sẽ commit các thay đổi và làm cho hàng loạt các cập nhật trở nên vĩnh viễn. Dòng mã thứ hai sẽ tự động bật chế độ tự động commit của đối tượng Connection. Câu lệnh sẽ được commit sau khi nó được thực thi, và ứng dụng không còn phải gọi phương thức
commit()
.
Xóa các lệnh khỏi hàng loạt:
- Phương thức
clearBatch()
làm rỗng danh sách lệnh hiện tại của đối tượng Statement. Phương thức này được chỉ định bởiclearBatch()
trong giao diệnjava.sql.Statement
.
// Xóa danh sách lệnh SQL hiện tại
stmt.clearBatch();
- Phương thức
stmt.clearBatch()
có thể được gọi để đặt lại một hàng loạt nếu ứng dụng muốn gửi một hàng loạt các lệnh đã được xây dựng cho một câu lệnh.
Cập Nhật Hàng Loạt Bằng Giao Diện PreparedStatement
- Tính năng cập nhật hàng loạt được sử dụng với PreparedStatement để liên kết nhiều bộ giá trị tham số đầu vào với một đối tượng PreparedStatement duy nhất.
- Phương thức
addBatch()
của giao diện Statement nhận một câu lệnh cập nhật SQL làm tham số, và câu lệnh SQL được thêm vào danh sách các lệnh của đối tượng Statement để được thực thi trong hàng loạt tiếp theo. Đây là một ví dụ về cập nhật hàng loạt tĩnh. - Giao diện PreparedStatement cho phép tạo hàng loạt cập nhật tham số. Các phương thức
setxxx()
của giao diện PreparedStatement được sử dụng để tạo mỗi bộ tham số, trong khi phương thứcaddBatch()
thêm một bộ tham số vào hàng loạt hiện tại. Cuối cùng, phương thứcexecuteBatch()
của giao diện PreparedStatement được gọi để gửi các cập nhật đến DBMS, đồng thời xóa danh sách các thành phần hàng loạt của câu lệnh.
Ví dụ:
// Thực hiện cập nhật hàng loạt với PreparedStatement
String sql = "INSERT INTO EMPLOYEES VALUES (?, ?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
// Tạo bộ giá trị tham số thứ nhất
pstmt.setInt(1, 1000);
pstmt.setString(2, "William John");
pstmt.setInt(3, 8000);
// Thêm bộ giá trị tham số thứ nhất vào hàng loạt
pstmt.addBatch();
// Tạo bộ giá trị tham số thứ hai
pstmt.setInt(1, 1001);
pstmt.setString(2, "Jacky Lawrence");
pstmt.setInt(3, 9000);
// Thêm bộ giá trị tham số thứ hai vào hàng loạt
pstmt.addBatch();
// Tạo bộ giá trị tham số thứ ba
pstmt.setInt(1,
1002);
pstmt.setString(2, "Valentina Watson");
pstmt.setInt(3, 4000);
// Thêm bộ giá trị tham số thứ ba vào hàng loạt
pstmt.addBatch();
// Thực hiện cập nhật hàng loạt
int[] updateCounts = pstmt.executeBatch();
Các phương thức setxxx()
của giao diện PreparedStatement được sử dụng để tạo mỗi bộ tham số, trong khi phương thức addBatch()
thêm một bộ tham số vào hàng loạt hiện tại. Cuối cùng, phương thức executeBatch()
của giao diện PreparedStatement được gọi để gửi các cập nhật đến DBMS, đồng thời xóa danh sách các thành phần hàng loạt của câu lệnh.
Cập Nhật Hàng Loạt Bằng Giao Diện CallableStatement
- Chức năng của một đối tượng CallableStatement và một đối tượng PreparedStatement là giống nhau.
- Các đối tượng CallableStatement chỉ có thể gọi các thủ tục lưu trữ có tham số đầu vào hoặc không có tham số nào cả. Hơn nữa, thủ tục lưu trữ phải trả về một số bản ghi được cập nhật. Phương thức
executeBatch()
của giao diện CallableStatement được kế thừa từ giao diện PreparedStatement và sẽ ném một ngoại lệBatchUpdateException
nếu giá trị trả về của thủ tục lưu trữ không phải là một số bản ghi được cập nhật hoặc nó chưa các tham số OUT hoặc IN/OUT. - Ví dụ sau mô tả cập nhật hàng loạt bằng giao diện CallableStatement. Trong trường hợp này, giả sử có một bảng khác là ProductDetails.
// Thực hiện cập nhật hàng loạt bằng CallableStatement
CallableStatement cst = conn.prepareCall("{call updateProductDetails(?, ?)}");
// Thêm bộ giá trị tham số thứ nhất vào hàng loạt
cst.setString(1, "Cheese");
cst.setFloat(2, 70.99f);
cst.addBatch();
// Thêm bộ giá trị tham số thứ hai vào hàng loạt
cst.setString(1, "Almonds");
cst.setFloat(2, 80.99f);
cst.addBatch();
// Thực hiện cập nhật hàng loạt
int[] updateCounts = cst.executeBatch();
// Bật chế độ tự động commit
conn.commit();
Đối tượng CallableStatement cst
chứa một lời gọi đến thủ tục lưu trữ có tên là updateProductDetails
với hai bộ tham số được liên kết với nó. Khi cst
được thực thi, hai lệnh gọi thủ tục lưu trữ sẽ được thực thi cùng nhau như một hàng loạt. Bản ghi đầu tiên sẽ có giá trị “Cheese” và 70.99F tương ứng, và bản ghi thứ hai là “Almond” và 80.99F tương ứng. Ký tự F theo sau là một số, như trong 70.99F, cho biết cho trình biên dịch Java biết rằng giá trị là một số dấu chấm động.
Giao dịch (Transaction)
Trong thế giới trực tuyến, việc sử dụng cơ sở dữ liệu phân tán là phổ biến, nơi dữ liệu liên quan có thể được lưu trữ trên các máy tính khác nhau tại cùng một địa điểm hoặc trên các máy tính phân tán toàn cầu.
Tính toàn vẹn cơ sở dữ liệu là một đặc điểm quan trọng của hệ quản trị cơ sở dữ liệu quan hệ. Có những lúc khi người ta không muốn một câu lệnh hoặc hành động nào đó trên cơ sở dữ liệu được thực hiện trừ khi câu lệnh hoặc hành động khác đã thành công. Sử dụng ví dụ về một cơ sở dữ liệu điểm của một tổ chức giáo dục, khi một bản ghi sinh viên được xóa khỏi bảng Sinh viên, nó cũng phải được xóa khỏi bảng Kết quả; nếu không, dữ liệu sẽ không nhất quán. Để đảm bảo rằng cả hai hành động này diễn ra hoặc không có hành động nào diễn ra, giao dịch được sử dụng.
Các Thuộc tính của Giao dịch
Các thuộc tính Atom, Consistency, Isolation và Durability (ACID) đảm bảo rằng các giao dịch cơ sở dữ liệu được xử lý một cách đáng tin cậy.
Atom (Tính nguyên tử):
Nó đề cập đến khả năng của hệ thống quản lý cơ sở dữ liệu để đảm bảo rằng hoặc tất cả các nhiệm vụ của một giao dịch được thực hiện hoặc không có nhiệm vụ nào được thực hiện.
Consistency (Nhất quán):
Nó đề cập đến cơ sở dữ liệu đang ở trong một trạng thái hợp lệ sao cho một giao dịch không thể vi phạm các quy tắc như ràng buộc tính toàn vẹn. Ví dụ, nếu một ràng buộc tính toàn vẹn nói rằng người giữ tài khoản không thể có số dư âm, thì một giao dịch không tuân theo quy tắc này sẽ bị hủy.
Isolation (Isolation):
Nó đề cập đến khả năng của Hệ quản trị Cơ sở dữ liệu để đảm bảo không có xung đột giữa các giao dịch đồng thời. Ví dụ, nếu hai người cùng cập nhật một mục, thì các thay đổi của một người không nên bị ghi đè khi người thứ hai lưu một bộ thay đổi khác. Hai người dùng nên có thể làm việc cách ly.
Durability (Bền vững):
Nó đề cập đến khả năng của Hệ quản trị Cơ sở dữ liệu để khôi phục lại các giao dịch đã cam kết ngay cả khi hệ thống hoặc phương tiện lưu trữ gặp sự cố.
Thực hiện Giao dịch bằng JDBC
Có bốn bước để thực hiện giao dịch bằng JDBC, như Bắt đầu giao dịch, Thực hiện giao dịch, Sử dụng Savepoint và Đóng hoặc Kết thúc giao dịch.
Bước 1: Bắt đầu Giao dịch
Bước đầu tiên là bắt đầu giao dịch. Khi một kết nối được tạo, theo mặc định, nó ở chế độ tự động commit. Do đó, mỗi câu lệnh SQL riêng lẻ được xem xét như là một giao dịch và tự động được commit ngay sau khi được thực hiện. Khi bạn tắt tự động commit, bắt đầu và kết thúc một giao dịch được định nghĩa, cho phép bạn xác định liệu có nên commit hay rollback toàn bộ giao dịch hay không. Để tắt chế độ tự động commit của kết nối, bạn gọi phương thức setAutoCommit()
, như sau:
cn.setAutoCommit(false);
Để bắt đầu giao dịch, chế độ tự động commit của kết nối đang hoạt động sẽ bị tắt. Khi chế độ tự động commit bị tắt, không có câu lệnh SQL nào sẽ được commit cho đến khi phương thức commit
được gọi một cách rõ ràng. Tất cả các câu lệnh được thực hiện sau cuộc gọi phương thức commit trước đó sẽ được bao gồm trong giao dịch hiện tại và sẽ được commit cùng nhau như một đơn vị.
Bước 2: Thực hiện Giao dịch
Bước thứ hai là thực hiện giao dịch như được thể hiện trong đoạn mã dưới. Trong đoạn mã này, giả sử rằng một bảng có tên là COFFEES đã được tạo và được điền với các bản ghi.
Ví dụ:
PreparedStatement modifySales = null;
PreparedStatement modifyTotal = null;
String updateString = "update " + ".COFFEES " + "set SALES = ? where COF_NAME = 2";
try {
cn.setAutoCommit(false);
modifySales = cn.prepareStatement(updateString);
modifySales.setInt(1, 104);
modifySales.setString(2, "Colombian");
modifySales.executeUpdate();
String updateStatement = "update " + ".COFFEES " + "set TOTAL = TOTAL + 2 where COF_NAME";
modifyTotal = cn.prepareStatement(updateStatement);
modifyTotal.setInt(1, 103);
modifyTotal.setString(2, "French Roast_Decaf");
modifyTotal.executeUpdate();
modifySales.setInt(1, 100);
modifySales.setString(2, "French Roast");
modifySales.executeUpdate();
modifySales.setString(2, "Espresso");
modifySales.executeUpdate();
modifyTotal.executeUpdate();
cn.commit();
} catch (SQLException e) {
// Xử lý ngoại lệ
e.printStackTrace();
// Nếu có lỗi, rollback giao dịch
try {
if (cn != null) {
cn.rollback();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
// Đóng các tài nguyên
try {
if (modifySales != null) {
modifySales.close();
}
if (modifyTotal != null) {
modifyTotal.close();
}
cn.setAutoCommit(true); // Trả lại chế độ auto-commit mặc định
} catch (SQLException ex) {
ex.printStackTrace();
}
}
Trong đoạn mã trên, chúng ta sử dụng PreparedStatement
để thực hiện các câu lệnh SQL đã chuẩn bị trước. Giao dịch bắt đầu bằng cách tắt chế độ tự động commit (setAutoCommit(false)
), và sau đó, các câu lệnh SQL được thực hiện theo thứ tự mong muốn. Nếu một ngoại lệ xảy ra, giao dịch sẽ bị rollback để đảm bảo tính nhất quán của dữ liệu, ngược lại, nếu mọi thứ diễn ra suôn sẻ, giao dịch sẽ được commit. Cuối cùng, tất cả các tài nguyên sẽ được đóng trong phần finally
.
Bước 3: Sử dụng Savepoint
Bước thứ ba là sử dụng Savepoint trong giao dịch, như được thể hiện trong đoạn mã dưới. Lần này, một bảng khác được sử dụng để mô tả cách sử dụng Savepoint.
Ví dụ:
// Tạo một thể hiện của đối tượng Statement
Statement st = cn.createStatement();
int rows = st.executeUpdate("INSERT INTO COFFEES (COF_NAME) VALUES ('Modern Espresso')");
// Tạo một thể hiện của đối tượng Statement
Statement st = cn.createStatement();
int rows = st.executeUpdate("INSERT INTO COFFEES (COF_NAME) VALUES ('Modern Espresso')");
// Thiết lập Savepoint
Savepoint svpt = cn.setSavepoint("SAVEPOINT_1");
rows = st.executeUpdate("INSERT INTO COFFEES (NAME) VALUES ('arabica')");
cn.rollback(svpt);
Mã này chèn một dòng vào một bảng, thiết lập Savepoint svpt
, và sau đó, chèn một dòng thứ hai. Khi giao dịch được rollback sau svpt
dựa trên một số điều kiện, việc chèn thứ hai sẽ bị hủy, nhưng việc chèn đầu tiên vẫn được giữ nguyên. Do đó, khi giao dịch được commit, chỉ có dòng chứa “Modern Espresso” sẽ được thêm vào COFFEES.
Phương thức cn.releaseSavepoint()
nhận một đối tượng Savepoint làm tham số và loại bỏ nó khỏi giao dịch hiện tại. Bất kỳ tham chiếu nào đến savepoint sau khi nó bị loại bỏ sẽ gây ra một SQLException.
Bước 4: Đóng Giao dịch
Bước cuối cùng là đóng hoặc kết thúc giao dịch. Một giao dịch có thể kết thúc bằng cách commit hoặc rollback. Khi một giao dịch commit, các sửa đổi dữ liệu được thực hiện bởi các câu lệnh của nó được lưu lại. Nếu một câu lệnh trong một giao dịch gặp sự cố, giao dịch sẽ rollback, làm hoàn tác tác động của tất cả các câu lệnh trong giao dịch.
Ví dụ:
// Kết thúc giao dịch
// Sử dụng rollback hoặc commit tùy thuộc vào điều kiện
cn.rollback(svpt);
// hoặc
cn.commit();
Trong đoạn mã này, cn.rollback(svpt)
được sử dụng để rollback giao dịch đến Savepoint svpt
, hoặc cn.commit()
được sử dụng để commit giao dịch. Lựa chọn giữa rollback và commit phụ thuộc vào điều kiện và logic của ứng dụng.
Các Tính năng của JDBC 4.3
Cập nhật JDBC 4.3 đã được phát hành với Java 9 vào năm 2017. Một trong những tính năng mới trong phiên bản này là việc thông báo thông qua các phương thức beginRequest()
và endRequest()
trên đối tượng Connection
. Nói một cách đơn giản, những phương thức hoặc API này được bể các pool kết nối sử dụng để báo cho trình điều khiển rằng một kết nối đã được kiểm tra ra khỏi pool (beginRequest
) hoặc kiểm tra lại vào pool (endRequest
).
Trình điều khiển yêu cầu là một đơn vị làm việc độc lập và nằm ở điểm bắt đầu trên kết nối này. Thông qua những API/phương thức này, trình điều khiển nhận thông tin về ranh giới của yêu cầu. Trình điều khiển có thể ping cơ sở dữ liệu, thay thế kết nối mà không cần trình điều khiển khác để cân bằng tải, và xóa các trạng thái bẩn.
Các tính năng mới khác được giới thiệu trong JDBC 4.3 bao gồm:
- Hỗ trợ thêm cho Sharding (chia nhỏ dữ liệu thành các phần nhỏ và lưu trữ trên nhiều server).
- Thêm các giao diện:
java.sql.ConnectionBuilder
java.sql.ShardingKey
java.sql.ShardingKeyBuilder
javax.sql.XAConnectionBuilder
javax.sql.PooledConnectionBuilder
RowSet
RowSet
là một giao diện trong gói mở rộng tiêu chuẩn mới javax.sql.rowset
và được tạo ra từ giao diện ResultSet
. Thông thường, nó chứa một tập hợp các dòng từ một nguồn dữ liệu bảng như một ResultSet
. Nó có thể được cấu hình để kết nối đến và đọc/giữ liệu từ một nguồn dữ liệu JDBC. Một đối tượng JDBC RowSet
dễ sử dụng hơn một ResultSet
.
Vì các đối tượng RowSet
là một thành phần JavaBeans, nó chia sẻ một số đặc điểm chung của JavaBeans, bao gồm:
- Thuộc tính (Properties):
Một trường trong mộtRowSet
được gọi là thuộc tính của mộtRowSet
. Mọi thuộc tính đều có các phương thức getter và setter tương ứng do việc triển khai giao diện cung cấp. - Cơ chế Thông báo của JavaBeans:
Một sự di chuyển con trỏ, cập nhật, chèn, xóa một dòng, hoặc thay đổi toàn bộ nội dung củaRowSet
tạo ra một sự kiện thông báo. Mô hình sự kiện củaRowSet
thông báo cho tất cả các thành phần triển khai giao diệnRowSetListener
và đã đăng ký làm người nghe của các sự kiện của đối tượngRowSet
.
Hình dưới mô tả mô hình RowSet
.
Lợi ích khi Sử dụng RowSet thay vì ResultSet
Một đối tượng RowSet
mang lại một số lợi ích bổ sung so với một đối tượng ResultSet
đơn giản. Dưới đây là một số điểm mạnh của RowSet
:
Hệ thống Quản lý Cơ sở dữ liệu hoặc các trình điều khiển được cung cấp bởi một số nhà cung cấp cơ sở dữ liệu không hỗ trợ result set có khả năng cuộn và/hoặc có thể cập nhật.
Một đối tượng RowSet
cung cấp khả năng cuộn và có thể cập nhật cho bất kỳ loại Hệ thống Quản lý Cơ sở dữ liệu hoặc trình điều khiển nào.
Sự di động (Scrollability) được sử dụng trong ứng dụng GUI và để cập nhật chương trình.
Khả năng di chuyển con trỏ của một tập kết quả đến một dòng cụ thể được biết đến là Di chuyển. Khả năng sử dụng ngôn ngữ lập trình Java thay vì SQL được biết đến là Khả năng Cập nhật.
Một đối tượng RowSet
, vì là một thành phần JavaBeans, có thể được sử dụng để thông báo cho các thành phần GUI đã đăng ký về sự thay đổi.
Các Loại Khác nhau của RowSets
RowSets
được phân loại tùy thuộc vào thời gian kết nối của chúng với cơ sở dữ liệu. Do đó, một đối tượng RowSet
có thể là connected (đã kết nối) hoặc disconnected (đã ngắt kết nối).
- Connected RowSet:
Một đối tượngRowSet
đã kết nối sử dụng một trình điều khiển JDBC để kết nối đến một cơ sở dữ liệu quan hệ. Kết nối này được duy trì suốt thời gian sống của đối tượngRowSet
. - Disconnected RowSet:
Một đối tượngRowSet
đã ngắt kết nối kết nối đến nguồn dữ liệu chỉ để đọc dữ liệu từ mộtResultSet
hoặc ghi dữ liệu trở lại nguồn dữ liệu. Sau khi hoàn thành thao tác đọc/ghi, đối tượngRowSet
ngắt kết nối từ nguồn dữ liệu.
Hình dưới mô tả các đối tượng RowSet
đã kết nối và đã ngắt kết nối.
Các Giao diện Mở rộng từ Giao diện RowSet
Một số giao diện mở rộng từ giao diện RowSet
như sau:
JdbcRowSet
: Giao diện này mở rộngRowSet
và thêm hỗ trợ cho các tính năng của JDBC, bao gồm cả khả năng chuyển động và cập nhật.CachedRowSet
: Giao diện này cung cấp mộtRowSet
có khả năng chuyển động và có thể cập nhật được duyệt bằng cách giữ một bản sao của dữ liệu trong bộ nhớ.WebRowSet
: Giao diện này mở rộngCachedRowSet
và thêm hỗ trợ cho lưu trữ XML, cho phép dễ dàng truyền tải dữ liệu qua mạng.JoinRowSet
: Giao diện này mở rộngWebRowSet
và thêm hỗ trợ cho việc thực hiện các phép nối giữa cácRowSet
khác nhau.FilteredRowSet
: Giao diện này mở rộngWebRowSet
và thêm khả năng lọc dữ liệu dựa trên các quy tắc được xác định.
Hình dưới thể hiện các mối quan hệ phân tầng trong cấu trúc của RowSet
:
API RowSet 1.1 bao gồm các lớp sau để tạo các thể hiện của RowSet:
javax.sql.rowset.RowSetProvider
:
Lớp này được sử dụng để tạo một đối tượngRowSetFactory
, như được thể hiện trong đoạn mã sau:
RowSetFactory rowsetFactory = RowSetProvider.newFactory();
Giao diện RowSetFactory
bao gồm các phương thức sau để tạo các thể hiện khác nhau của RowSet
:
createCachedRowSet()
: Tạo một đối tượngCachedRowSet
.createFilteredRowSet()
: Tạo một đối tượngFilteredRowSet
.createJdbcRowSet()
: Tạo một đối tượngJdbcRowSet
.createJoinRowSet()
: Tạo một đối tượngJoinRowSet
.createWebRowSet()
: Tạo một đối tượngWebRowSet
.
Lớp javax.sql.rowset.RowSetFactory
và các phương thức này cung cấp cách thuận tiện để tạo các thể hiện của RowSet
dựa trên nhu cầu và yêu cầu cụ thể của ứng dụng.
Thực hiện của Connected RowSet
JdbcRowSet
là duy nhất hiện thực của một RowSet
đã kết nối. Nó tương tự như một đối tượng ResultSet
và thường được sử dụng như một lớp bao để chuyển đổi một đối tượng ResultSet
chỉ đọc không cuộn thành một đối tượng có thể cuộn và có thể cập nhật. Để thiết lập kết nối và điền dữ liệu vào đối tượng RowSet
, các thuộc tính như tên người dùng, mật khẩu, URL của cơ sở dữ liệu và tên nguồn dữ liệu phải được đặt. Thuộc tính Command
là truy vấn xác định dữ liệu nào đối tượng RowSet
sẽ giữ.
Giao diện JdbcRowSet
:
JdbcRowSet
là một giao diện trong gói javax.sql.rowset.
JdbcRowSetImpl
là lớp hiện thực tham chiếu của giao diện JdbcRowSet
. Nếu một đối tượng JdbcRowSet
đã được xây dựng bằng cách sử dụng hàm tạo mặc định, thì đối tượng mới không thể sử dụng cho đến khi phương thức execute()
của nó được gọi. Phương thức execute()
chỉ có thể được gọi trên một đối tượng như vậy nếu tất cả các thuộc tính khác như command
, username
, password
, và URL
đã được đặt. Những thuộc tính này có thể được đặt bằng cách sử dụng các phương thức của cả RowSet
và JdbcRowSet
interface.
Các phương thức được mô tả trong Bảng:
Phương thức | Mô tả |
---|---|
setUsername(String user) | Đặt tên người dùng của đối tượng RowSet . |
setPassword(String pass) | Đặt mật khẩu của cơ sở dữ liệu của đối tượng RowSet thành pass . |
setURL(String url) | Đặt URL của đối tượng RowSet thành url sẽ được sử dụng để tạo kết nối. |
setCommand(String sql) | Đặt thuộc tính Command của đối tượng RowSet thành truy vấn SQL cung cấp. |
execute() | Thực hiện kết nối đến cơ sở dữ liệu, thực hiện truy vấn được đặt trong thuộc tính Command và đọc dữ liệu từ tập kết quả vào đối tượng RowSet . |
commit() | Thực hiện tất cả các thay đổi đã được thực hiện kể từ lần commit cuối cùng. Sau khi gọi phương thức này, tất cả các khóa cơ sở dữ liệu hiện tại được giải phóng bởi đối tượng RowSet . |
rollback() | Lăn ngược tất cả các thay đổi đã được thực hiện trong giao dịch hiện tại và giải phóng tất cả các khóa cơ sở dữ liệu. |
Phương thức commit()
và rollback()
làm cho các thay đổi trở nên vĩnh viễn hoặc hủy bỏ trong giao dịch hiện tại và giải phóng khóa cơ sở dữ liệu.
Lớp JdbcRowSetImpl
Lớp JdbcRowSetImpl
là lớp thực hiện tiêu chuẩn của giao diện JdbcRowSet
. Một thể hiện của lớp này có thể được đạt được bằng cách sử dụng bất kỳ hàm tạo nào trong bảng dưới.
Hàm tạo | Mô tả |
---|---|
JdbcRowSetImpl() | Hàm tạo này tạo một đối tượng JdbcRowSet mặc định. |
JdbcRowSetImpl(Connection con) | Hàm tạo này tạo một đối tượng JdbcRowSet mặc định nếu đối tượng kết nối được cung cấp là hợp lệ. Đối tượng RowSet hoạt động như một proxy cho đối tượng ResultSet được tạo. |
JdbcRowSetImpl(String url, String user, String password) | Hàm tạo này tạo một đối tượng JdbcRowSet với URL, tên người dùng và mật khẩu cụ thể. |
JdbcRowSetImpl(java.sql.ResultSet res) | Hàm tạo này tạo một đối tượng JdbcRowSet hoạt động như một bao bọc mỏng quanh đối tượng ResultSet . |
Lưu ý: JdbcRowSetImpl
có thể được loại bỏ trong các bản phát hành tương lai.
Tất cả các hàm tạo đều ném java.sql.SQLException
nếu các thuộc tính trình điều khiển JDBC không hợp lệ hoặc nếu có lỗi truy cập cơ sở dữ liệu. Một đối tượng JdbcRowSet
mặc định không hiển thị các dòng đã bị xóa trong dữ liệu của nó. Nó không áp đặt bất kỳ hạn chế thời gian nào cho thời gian mà một trình điều khiển mất để thực hiện truy vấn RowSet
. Không có giới hạn cho số dòng mà một RowSet
có thể chứa.
Ngoài ra, không có giới hạn về số byte mà một cột của RowSet
có thể chứa. RowSet
có một con trỏ có thể cuộn và không hiển thị các thay đổi được thực hiện bởi người dùng khác đối với cơ sở dữ liệu. RowSet
không hiển thị dữ liệu chưa được xác nhận, do đó, vấn đề đọc bẩn được loại bỏ. Mỗi đối tượng RowSet
chứa một đối tượng HashTable
trống để lưu trữ bất kỳ tham số nào được đặt.
Tạo đối tượng JdbcRowSet
bằng cách sử dụng một đối tượng ResultSet
Một cách để tạo đối tượng JdbcRowSet
là chuyển một đối tượng ResultSet
cho hàm tạo của JdbcRowSetImpl
. Điều này tạo ra một đối tượng JdbcRowSet
và điền dữ liệu từ đối tượng ResultSet
vào đó.
Ví dụ:
Statement st = con.createStatement();
ResultSet rs = st.executeQuery("SELECT * FROM Employees");
JdbcRowSet jRs = new JdbcRowSetImpl(rs);
Trong đoạn mã này, đối tượng Statement
được tạo bởi con.createStatement()
để thực hiện truy vấn. Đối tượng trả về từ truy vấn (rs
) sau đó được chuyển vào hàm tạo của JdbcRowSetImpl
để tạo một đối tượng JdbcRowSet
.
Tạo đối tượng JdbcRowSet
bằng cách sử dụng hàm khởi tạo mặc định
Hàm tạo mặc định tạo ra một đối tượng JdbcRowSet
trống rỗng. Phương thức execute()
có thể được gọi khi tất cả các thuộc tính đã được thiết lập để thiết lập kết nối đến cơ sở dữ liệu. Điều này thực hiện thuộc tính Command
và kết quả là đối tượng JdbcRowSet
được điền dữ liệu từ đối tượng ResultSet
kết quả.
Ví dụ:
import com.sun.rowset.JdbcRowSetImpl;
import java.sql.SQLException;
import javax.sql.rowset.JdbcRowSet;
public class JdbcRowSetDemo {
public static void main(String[] args) {
try {
JdbcRowSet jrs2 = new JdbcRowSetImpl();
jrs2.setUsername("root");
jrs2.setPassword("password");
jrs2.setUrl("jdbc:mysql://127.0.0.1:3306/test");
jrs2.setCommand("SELECT Emp_no FROM Employees");
jrs2.execute();
while (jrs2.next()) {
System.out.println(jrs2.getInt(1));
}
} catch (SQLException se) {
System.out.println("Error: " + se.getMessage());
}
}
}
Trong đoạn mã này, đối tượng JdbcRowSet
(jrs2
) được tạo bằng cách sử dụng hàm tạo mặc định và sau đó được thiết lập các thuộc tính như tên người dùng, mật khẩu, URL và truy vấn. Cuối cùng, phương thức execute()
được gọi để lấy dữ liệu từ cơ sở dữ liệu và sau đó dùng vòng lặp để in ra kết quả.
Sử dụng giao diện RowSetFactory
Một thể hiện của giao diện RowSetFactory
có thể được sử dụng để tạo một đối tượng JdbcRowSet
. Đoạn dưới dưới đây mô tả cách tạo đối tượng JdbcRowSet
bằng cách sử dụng phương pháp này.
Ví dụ:
import javax.sql.rowset.RowSetFactory;
import javax.sql.rowset.RowSetProvider;
import javax.sql.rowset.JdbcRowSet;
import java.sql.SQLException;
import java.sql.ResultSet;
import java.sql.Statement;
public class JdbcRowSetDemo {
public void createJdbcRowSetWithRowSetFactory(String username, String password) throws SQLException {
RowSetFactory myRowSetFactory = null;
JdbcRowSet jdbcRs = null;
ResultSet rs = null;
Statement stmt = null;
try {
myRowSetFactory = RowSetProvider.newFactory();
jdbcRs = myRowSetFactory.createJdbcRowSet();
jdbcRs.setUrl("jdbc:mysql://127.0.0.1:3306/test");
jdbcRs.setUsername(username);
jdbcRs.setPassword(password);
jdbcRs.setCommand("SELECT * FROM Employees");
jdbcRs.execute();
// Rest of the code to process the JdbcRowSet
} catch (SQLException se) {
System.out.println("Error: " + se.getMessage());
} finally {
// Close resources if needed
}
}
}
Dòng myRowSetFactory = RowSetProvider.newFactory();
tạo đối tượng RowSetFactory
(myRowSetFactory
) với triển khai mặc định của RowSetFactory
, có thể là com.sun.rowset.RowSetFactoryImpl
. Sau đó, chúng ta sử dụng myRowSetFactory.createJdbcRowSet();
để tạo một đối tượng JdbcRowSet
.
Lưu ý: Các tài nguyên như kết nối và các đối tượng có thể được đóng trong phần finally
để đảm bảo giải phóng tài nguyên.
Sử dụng Đối tượng JdbcRowSet
Dưới đây là một số phương thức thường được sử dụng của lớp JdbcRowSetImpl
(được triển khai từ giao diện JdbcRowSet
), được liệt kê trong Bảng 9.5.
Phương thức | Mô tả |
---|---|
absolute(int row) | Di chuyển con trỏ đến dòng được chỉ định trong RowSet . |
afterLast() | Di chuyển con trỏ đến cuối của đối tượng RowSet ngay sau dòng cuối cùng. |
beforeFirst() | Di chuyển con trỏ đến đầu của đối tượng RowSet ngay trước dòng đầu tiên. |
first() | Di chuyển con trỏ đến dòng đầu tiên của đối tượng RowSet . |
deleteRow() | Xóa dòng hiện tại khỏi đối tượng RowSet và cơ sở dữ liệu cơ bản. Gửi thông báo sự kiện cho tất cả các người nghe rằng một dòng đã thay đổi. |
insertRow() | Chèn nội dung của dòng chèn vào RowSet và cơ sở dữ liệu. Gửi thông báo sự kiện cho tất cả các người nghe rằng một dòng đã thay đổi. |
last() | Di chuyển con trỏ đến dòng cuối cùng trong RowSet . |
isLast() | Trả về true nếu con trỏ đang ở dòng cuối cùng của RowSet . |
isAfterLast() | Trả về true nếu con trỏ đang sau dòng cuối cùng của RowSet . |
isBeforeFirst() | Trả về true nếu con trỏ đang trước dòng đầu tiên của RowSet . |
next() | Di chuyển con trỏ đến dòng tiếp theo dưới dòng hiện tại trong RowSet . |
previous() | Di chuyển con trỏ đến dòng trước đó trên dòng hiện tại trong RowSet . |
moveToCurrentRow() | Di chuyển con trỏ đến vị trí được ghi nhớ. Phương thức này thường được sử dụng khi chèn một dòng mới vào đối tượng RowSet . |
moveToInsertRow() | Di chuyển con trỏ đến dòng chèn của đối tượng RowSet . |
updateDate(int column, java.sql.Date date) | Cập nhật cột được chỉ định với giá trị ngày được đưa ra trong tham số. |
updateInt(int column, int value) | Cập nhật cột được chỉ định với giá trị số nguyên được đưa ra trong tham số. |
updateString(int column, String str) | Cập nhật cột được chỉ định với giá trị chuỗi được đưa ra trong tham số. |
updateRow() | Cập nhật cơ sở dữ liệu với nội dung mới của dòng hiện tại của RowSet . Gửi thông báo sự kiện cho tất cả các người nghe rằng một dòng đã thay đổi. |
Cập Nhật, Chèn và Xóa Dữ Liệu Trong JdbcRowSet
Một dòng dữ liệu có thể được cập nhật, chèn và xóa một cách tương tự như một đối tượng ResultSet
có thể cập nhật. Mọi thay đổi được thực hiện trên đối tượng JdbcRowSet
đều được phản ánh lên cơ sở dữ liệu.
Cập Nhật (Update)
Để cập nhật một bản ghi từ đối tượng RowSet
, chúng ta di chuyển con trỏ đến dòng đó, cập nhật dữ liệu từ dòng đó và cuối cùng là cập nhật cơ sở dữ liệu, như thể thấy trong đoạn mã dưới.
Ví dụ:
jdbcRs.absolute(1);
jdbcRs.updateFloat("Rating", 15.75);
jdbcRs.updateRow();
Trong đoạn mã, phương thức absolute(int rowNumber)
di chuyển con trỏ đến dòng được chỉ định. Các phương thức cập nhật khác được sử dụng để thay đổi giá trị của các ô (cột) cá nhân như thể hiện trong đoạn mã. Phương thức updateRow()
được gọi để cập nhật cơ sở dữ liệu. Lưu ý rằng đoạn mã này sẽ hoạt động chỉ khi bảng có một khóa chính được thiết lập trước đó.
Chèn (Insert)
Để chèn một bản ghi vào đối tượng JdbcRowSet
, chúng ta gọi phương thức moveToInsertRow()
. Vị trí con trỏ hiện tại được nhớ và sau đó con trỏ được đặt ở dòng chèn. Dòng chèn là một dòng đặc biệt được cung cấp bởi một tập kết quả có thể cập nhật để xây dựng một dòng mới. Khi con trỏ ở dòng này, chỉ có thể gọi các phương thức update
, get
và insertRow()
. Tất cả các cột phải được gán giá trị trước khi gọi phương thức insertRow()
. Phương thức insertRow()
chèn dòng mới được tạo vào tập kết quả như thể hiện trong Đoạn mã dưới.
Ví dụ:
jdbcRs.moveToInsertRow();
jdbcRs.updateInt("EMP_NO", 118);
jdbcRs.updateString("NAME", "Homer Smith");
jdbcRs.updateInt("SALARY", 4900);
jdbcRs.updateFloat("RATING", 7.994);
jdbcRs.insertRow();
jdbcRs.moveToCurrentRow();
Phương thức moveToCurrentRow()
di chuyển con trỏ đến vị trí được nhớ.
Xóa (Delete)
Xóa một dòng từ đối tượng JdbcRowSet
đơn giản như thể hiện trong Đoạn mã dưới.
Ví dụ:
jdbcRs.setCommand("SELECT * FROM Employee");
jdbcRs.execute();
jdbcRs.last();
jdbcRs.deleteRow();
Đoạn mã xóa dòng cuối cùng từ RowSet
cũng như từ cơ sở dữ liệu cơ bản. Con trỏ được di chuyển đến dòng cuối cùng và phương thức deleteRow()
xóa dòng khỏi RowSet
và cơ sở dữ liệu.
Lấy (Retrieve)
Một đối tượng JdbcRowSet
là có thể cuộn, điều này có nghĩa là con trỏ có thể được di chuyển xuôi và ngược bằng cách sử dụng các phương thức next()
, previous()
, last()
, absolute()
, và first()
. (Một khi con trỏ ở trên dòng mong muốn, các phương thức getter có thể được gọi trên RowSet
để lấy giá trị mong muốn từ các cột.)
Triển Khai Của Disconnected RowSet
Sử dụng mạng có băng thông lớn vẫn không đảm bảo một kết nối không gián đoạn đến cơ sở dữ liệu mọi lúc. Kết nối cơ sở dữ liệu đáng tin cậy là nguồn tài nguyên có hạn và phải được sử dụng một cách khôn ngoan. Do đó, một RowSet
không kết nối là hữu ích vì nó không yêu cầu một kết nối liên tục với cơ sở dữ liệu. Một RowSet
không kết nối lưu trữ dữ liệu của nó trong bộ nhớ và hoạt động trên dữ liệu đó thay vì trực tiếp thao tác trên dữ liệu trong cơ sở dữ liệu.
Một đối tượng CachedRowSet
là một ví dụ về đối tượng RowSet
không kết nối. Những đối tượng này lưu trữ hoặc đệm dữ liệu trong bộ nhớ và hoạt động trên dữ liệu này thay vì thao tác dữ liệu lưu trữ trong cơ sở dữ liệu.
CachedRowSet
là giao diện cha cho tất cả các đối tượng RowSet
không kết nối. Một đối tượng CachedRowSet
thường xuất dữ liệu từ cơ sở dữ liệu quan hệ nhưng cũng có khả năng lấy và lưu trữ dữ liệu từ một nguồn dữ liệu lưu trữ dữ liệu theo dạng bảng.
Một đối tượng CachedRowSet
bao gồm tất cả các khả năng của một đối tượng JdbcRowSet
. Ngoài ra, nó có thể thực hiện các chức năng sau:
- Kết nối đến nguồn dữ liệu và thực hiện một truy vấn.
- Đọc dữ liệu từ đối tượng
ResultSet
kết quả và lấy dữ liệu được điền. - Thao tác dữ liệu khi không kết nối.
- Kết nối lại với nguồn dữ liệu để ghi các thay đổi trở lại.
- Giải quyết xung đột với nguồn dữ liệu, nếu có.
Một đối tượng CachedRowSet
có thể lấy dữ liệu từ cơ sở dữ liệu quan hệ hoặc từ bất kỳ nguồn dữ liệu nào lưu trữ dữ liệu của mình theo định dạng bảng. Cột chính phải được đặt trước khi dữ liệu có thể được lưu vào nguồn dữ liệu.
Một đối tượng CachedRowSet
có thể được tạo ra bằng một trong những cách sau:
- Sử dụng Constructor Mặc Định:
CachedRowSetImpl
là lớp triển khai tiêu chuẩn của giao diệnCachedRowSet
. Một đối tượngCachedRowSet
được tạo ra bằng cách sử dụng constructor mặc định. Cú pháp:
CachedRowSet crs = new CachedRowSetImpl();
- Thiết Lập Thuộc Tính Trước Khi Thực Hiện Truy Vấn:
CachedRowSet
sẽ được khởi tạo với các thuộc tính nhưusername
,password
,url
, vàcommand
trước khi phương thứcexecute()
được gọi. Ví dụ:
CachedRowSet crs = new CachedRowSetImpl();
crs.setUsername("root");
crs.setPassword("");
crs.setUrl("jdbc:mysql://127.0.0.1:3306/test");
crs.setCommand("SELECT * FROM Employees");
crs.execute();
while (crs.next())
System.out.println("Emp No: " + crs.getInt(1));
Trong đoạn mã này, các thuộc tính username
, password
, url
, và command
được đặt trước khi gọi phương thức execute()
để kết nối đến nguồn dữ liệu và lấy dữ liệu.
Sử Dụng SyncProvider Implementation
Tên đầy đủ của một SyncProvider
implementation được truyền vào constructor của CachedRowSetImpl
. Đối tượng CachedRowSet
có thể được điền dữ liệu bằng cách thiết lập các thuộc tính tương tự như đối tượng JdbcRowSet
.
Ví dụ:
CachedRowSet crs = new CachedRowSetImpl(com.myJava.providers.HighAvailabilityProvider);
Trong đoạn mã này, một đối tượng CachedRowSet
được tạo ra bằng cách sử dụng lớp triển khai của SyncProvider
, là HighAvailabilityProvider
.
Một đối tượng CachedRowSet
không thể được điền dữ liệu cho đến khi các thuộc tính username
, password
, url
, và datasourceName
được thiết lập cho đối tượng. Các thuộc tính này có thể được thiết lập bằng cách sử dụng các phương thức setter tương ứng của giao diện CachedRowSet
. Tuy nhiên, RowSet
vẫn chưa thể sử dụng bởi ứng dụng.
Đối với đối tượng CachedRowSet
có thể lấy dữ liệu, thuộc tính command
phải được thiết lập. Điều này có thể thực hiện bằng cách gọi phương thức setCommand()
và truyền truy vấn SQL vào nó như một tham số.
Đoạn mã dưới thể hiện việc setCommand để lấy ra dữ liệu ở bảng STUDENT
crs.setCommand("SELECT * FROM STUDENT");
Trong đoạn mã này, thuộc tính command
được thiết lập với một truy vấn SQL sản xuất một đối tượng Resultset
chứa toàn bộ dữ liệu trong bảng STUDENT
.
Đoạn mã 2:
CachedRowSet crsStudent = new CachedRowSetImpl();
int[] keyColumns = {1, 2};
crsStudent.setKeyColumns(keyColumns);
Trong đoạn mã này, đối tượng CachedRowSet
có tên crsStudent
được tạo và sau đó, cột khóa (keyColumns
) được thiết lập bằng cách sử dụng phương thức setKeyColumns()
. Các cột khóa này giúp xác định duy nhất một hàng, giống như một khóa chính trong cơ sở dữ liệu.
Phương thức execute()
được gọi để điền đối tượng RowSet
. Dữ liệu trong đối tượng RowSet
được lấy bằng cách thực hiện truy vấn trong thuộc tính command
. Phương thức execute()
của một đối tượng Disconnected RowSet
thực hiện nhiều chức năng hơn so với phương thức execute()
của một đối tượng Connected RowSet
.
Một đối tượng CachedRowSet
có một đối tượng SyncProvider
đi kèm. Đối tượng SyncProvider
này cung cấp một đối tượng RowSetReader
mà khi gọi phương thức execute()
, thiết lập một kết nối với cơ sở dữ liệu bằng cách sử dụng các thuộc tính username
, password
, và URL
hoặc datasourceName
được thiết lập trước đó. Đối tượng đọc sau đó thực thi truy vấn được thiết lập trong thuộc tính command
và đối tượng RowSet
được điền với dữ liệu. Đối tượng đọc sau đó đóng kết nối với nguồn dữ liệu và đối tượng CachedRowSet
có thể được ứng dụng sử dụng.
Cập nhật, chèn, và xóa các hàng tương tự như trong một đối tượng JdbcRowSet
. Tuy nhiên, các thay đổi mà ứng dụng thực hiện trên đối tượng Disconnected RowSet
cần phải được phản ánh lại nguồn dữ liệu. Điều này được thực hiện bằng cách gọi phương thức acceptChanges()
trên đối tượng CachedRowSet
. Như phương thức execute()
, phương thức này cũng thực hiện các hoạt động của mình đằng sau màn hình. Đối tượng SyncProvider
cũng có một đối tượng RowSetWriter
mở kết nối với cơ sở dữ liệu, ghi lại các thay đổi vào cơ sở dữ liệu và sau đó cuối cùng, đóng kết nối với cơ sở dữ liệu.
Sử Dụng Đối Tượng CachedRowSet
Một dòng dữ liệu có thể được cập nhật, chèn, và xóa trong đối tượng CachedRowSet. Những thay đổi trong dữ liệu được phản ánh trên cơ sở dữ liệu bằng cách gọi phương thức acceptChanges().
Cập Nhật
Cập nhật một bản ghi từ một đối tượng RowSet liên quan đến việc duyệt đến dòng đó, cập nhật dữ liệu từ dòng đó, và cuối cùng là cập nhật cơ sở dữ liệu. Đoạn mã dưới minh họa cách cập nhật dòng. Giả sử có một bảng có tên là BmpLeave tồn tại cho đoạn mã này. Hãy đảm bảo rằng đoạn mã này được bao bọc trong một khối try-catch phù hợp.
CachedRowSet crs = new CachedRowSetImpl();
crs.setUsername("root");
crs.setPassword("");
crs.setUrl("jdbc:mysql://127.0.0.1:3306/test");
crs.setCommand("SELECT * FROM EmpLeave");
crs.execute();
crs.next();
if (crs.getInt("EMP_NO") != 0) {
int currentQuantity = crs.getInt("BAL_LEAVE") + 1;
System.out.println("Updating balance leave to " + currentQuantity);
crs.updateInt("BAL_LEAVE", currentQuantity + 1);
crs.updateRow();
// Đồng bộ hóa dòng trở lại cơ sở dữ liệu
Connection con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "");
con.setAutoCommit(false);
crs.acceptChanges(con);
}
Trong đoạn mã trên, cột BAL_LEAVE được cập nhật. Phương thức updateInt()
được sử dụng để thay đổi giá trị của ô (cột) cá nhân như được thể hiện trong mã. Phương thức updateRow()
được gọi để cập nhật bộ nhớ. Phương thức acceptChanges()
lưu các thay đổi vào dữ liệu.
Chèn
Để chèn một bản ghi vào đối tượng CachedRowSet, phương thức moveToInsertRow()
được gọi. Vị trí con trỏ hiện tại được ghi nhớ và sau đó con trỏ được đặt vào một hàng chèn. Hàng chèn là một hàng đặc biệt được cung cấp bởi một bộ kết quả có thể cập nhật để xây dựng một hàng mới. Khi con trỏ ở trong hàng này, chỉ có thể gọi các phương thức update
, get
, và insertRow()
. Tất cả các cột phải được gán giá trị trước khi gọi phương thức insertRow()
. Phương thức insertRow()
chèn hàng mới tạo vào bộ kết quả. Phương thức moveToCurrentRow()
di chuyển con trỏ đến vị trí đã ghi nhớ.
crs.moveToInsertRow();
// Gán giá trị cho các cột
// ...
crs.insertRow();
crs.moveToCurrentRow();
Xóa
Xóa một hàng từ đối tượng CachedRowSet là đơn giản. Đoạn mã dưới mô tả điều này.
while (crs.next()) {
if (crs.getInt("EMP_ID") == 12345) {
crs.deleteRow();
break;
}
}
Đoạn mã trên xóa hàng chứa số nhân viên là 12345 từ bộ kết quả. Con trỏ được di chuyển đến hàng phù hợp và phương thức deleteRow()
xóa hàng khỏi bộ kết quả. Để đảm bảo mã chạy thành công, hãy đảm bảo rằng autocommit được đặt thành false và bảng có một khóa chính được thiết lập.
Truy xuất
Một đối tượng CachedRowSet có thể cuộn, có nghĩa là con trỏ có thể di chuyển về phía trước và phía sau bằng cách sử dụng các phương thức next()
, previous()
, last()
, absolute()
, và first()
. Khi con trỏ ở dòng mong muốn, các phương thức getter có thể được gọi trên RowSet để truy xuất giá trị mong muốn từ các cột.
Bài tập
Bài 1:
Mục tiêu: Sử dụng Java JDBC để thực hiện các thao tác cơ bản trên cơ sở dữ liệu MySQL.
Yêu cầu
-
Tạo cơ sở dữ liệu và bảng trong MySQL:
- Tạo một cơ sở dữ liệu tên là
EmployeeDB
. - Tạo bảng
Employees
với các cột sau:id
(int, auto-increment, primary key)name
(varchar(50))age
(int)department
(varchar(50))salary
(double)
- Tạo một cơ sở dữ liệu tên là
-
Viết chương trình Java thực hiện các chức năng sau:
- Kết nối tới cơ sở dữ liệu
EmployeeDB
. - Thêm một nhân viên mới vào bảng
Employees
. - Hiển thị danh sách tất cả nhân viên.
- Cập nhật thông tin nhân viên theo
id
. - Xóa một nhân viên theo
id
. - Tìm kiếm nhân viên theo tên hoặc phòng ban.
- Kết nối tới cơ sở dữ liệu
Bài 2:
Yêu cầu
Tạo cơ sở dữ liệu và bảng trong MySQL
- Tạo một cơ sở dữ liệu tên là
ProductDB
. - Tạo bảng
Products
với các cột sau:id
(int, auto-increment, primary key)name
(varchar(100))category
(varchar(50))price
(double)quantity
(int)
Viết chương trình Java thực hiện các chức năng sau:
- Kết nối tới cơ sở dữ liệu
ProductDB
. - Thêm một sản phẩm mới vào bảng
Products
. - Hiển thị danh sách tất cả các sản phẩm.
- Cập nhật thông tin sản phẩm theo
id
. - Xóa một sản phẩm theo
id
. - Tìm kiếm sản phẩm theo tên hoặc danh mục
Yêu cầu:
- Cấu trúc dữ liệu khi in sản phẩm ra phải sử dụng lớp HashTable với key lưu id của sản phẩm, value lưu đối tượng sản phẩm.
- Nâng cao: Sử dụng mô hình DAO để triển khai (có model, repository, implement)