Bài tập kết nối cơ sở dữ liệu trong java năm 2024

Hình vẽ trên cho thấy cách thức mà một ứng dụng JDBC truyền tin với một hoặc nhiều cơ sở dữ liệu mà không cần biết đến các chi tiết có liên quan đến cài đặt driver cho cơ sở dữ liệu đó. Một ứng dụng sử dụng JDBC như là một giao tiếp, thông qua đó nó truyền tất cả các yêu cầu liên quan đến cơ sở dữ liệu của nó.

Khi ta viết các applet hay ứng dụng cơ sở dữ liệu, ta có thể cung cấp các thông tin cụ thể về trình điều khiển JDBC là URL cơ sở dữ liệu. Thậm chí ta có thể nhập vào URL cơ sở dữ liệu cho ứng dụng và applet vào thời gian chạy dưới dạng các tham số.

JDBC là gói kết nối cơ sở dữ liệu bao gồm giao diện lập trình ứng dụng căn bản Java API. Java cung cấp một interface độc lập với cơ sở dữ liệu để mở một kết nối tới cơ sở dữ liệu, bằng cách phát ra các lời gọi SQL tới cơ sở dữ liệu và nhận về kết quả là một tập hợp các dữ liệu. Ở góc độ kỹ thuật, JDBC đóng vai trò như là một chương trình cài đặt giao tiếp mức lời gọi SQL được định nghĩa bởi X/Open và được hỗ trợ bởi hầu hết các nhà cung cấp cơ sở dữ liệu quan hệ. Để thực hiện giao tác với một kiểu cơ sở dữ liệu cụ thể, ta cần phải có một trình điều khiển JDBC đóng vai trò như là một cầu nối giữa các lời gọi phương thức JDBC và interface cơ sở sữ liệu.

3.1. DriverManager

DriverManager cung cấp phương tiện để nạp các trình điều khiển cơ sở dữ liệu vào một ứng dụng Java hoặc một applet; nó chính là cách để JDBC thiết lập một liên kết với cơ sở dữ liệu. Ứng dụng Java, trước tiên tạo một đối tượng DriverManager, kết nối với cơ sở dữ liệu bằng cách gọi phương thức tĩnh getConnection() của lớp DriverManager, với tham chiếu truyền vào giống như một URL được gọi là URL cơ sở dữ liệu. DriverManager tìm kiếm một driver hỗ trợ việc kết nối trong tập hợp các driver hiện có. Nếu tìm thấy driver nó truyền địa chỉ cơ sở dữ liệu cho driver và yêu cầu driver tạo ra một kết nối. Kết nối tới cơ sở dữ liệu được trả về dưới dạng một đối tượng Connection.

Tất cả các driver JDBC cung cấp một cài đặt giao tiếp java.sql.Driver. Khi một DriverManager được tạo ra, nó tải một tập hợp các driver được xác định bởi thuộc tính của java.sql.Driver. Driver được nạp vào thời gian chạy Java, nó có nhiệm vụ tạo ra một đối tượng và đăng ký đối tượng với DriverManager. Các driver cần cho ứng dụng có thể được nạp bởi phương thức Class.forName()

Driver myDriver=(Driver)Class.forName(“specialdb.Driver”);

3.2. Connection

Mỗi khi các driver cần thiết được nạp bởi DriverManager, sẽ có một liên kết với một cơ sở dữ liệu được tạo ra nhờ phương thức getConnection() của lớp DriverManager. Cơ sở dữ liệu cần làm việc được xác định thông qua một tham số String đóng vai trò như là địa chỉ tham chiếu tới cơ sở dữ liệu. Không có một khuôn dạng chuẩn nào cho địa chỉ xâu cơ sở dữ liệu; DriverManager truyền xâu địa chỉ cho từng driver JDBC đã được nạp và xem nó có hiểu và hỗ trợ kiểu cơ sở dữ liệu đã được xác định.

Jdbc:odbc:financedata

Trong đó financedata là nguồn cơ sở dữ liệu cục bộ. Để truy xuất tới một cơ sở dữ liệu từ xa từ một máy client ta có thể dùng cú pháp sau:

Jdbc:odbc:drvr://dataserver.foobar.com:500/financedata.

Đặc tả JDBC API khuyến cáo một URL cơ sở dữ liệu nên có dạng như sau:

Jdbc::

Trong đó xác định dịch vụ kết nối cơ sở dữ liệu và cung cấp tất cả các thông tin cần thiết để dịch vụ tìm cơ sở dữ liệu và kết nối tới nó.

Phương thức getConnection() trên DriverManager hoặc là trả về một đối tượng Connection biểu diễn liên kết tới cơ sở dữ liệu đã được chỉ ra, hoặc là đưa ra ngoại lệ nếu liên kết không được thiết lập.

3.3. Statement

Giao tiếp Connection cho phép người sử dụng tạo ra một câu lệnh truy vấn tới cơ sở dữ liệu. Các lệnh truy vấn được biểu diễn dưới dạng các đối tượng Statement hoặc các lớp con của nó. Giao tiếp Connection cung cấp ba phương thức để tạo ra các lệnh truy vấn cơ sở dữ liệu là: createStatement(), prepareStatement(), và precpareCall(). createStatement() được sử dụng cho các lệnh SQL đơn giản không liên quan đến các tham số. Phương thức này trả về một đối tượng Statement được sử dụng để phát tra các truy vấn SQL tới cơ sở dữ liệu, bằng cách sử dụng phương thức executeQuery(). Phương thức này chấp nhận một lệnh SQL như là một xâu và các kết quả trả về là ở dưới dạng một đối tượng ResultSet. Các phương thức khác có trong giao tiếp Statement để phát ra các lệnh SQL tới các cơ sở dữ liệu là phương thức execute(), phương thức này được sử dụng cho các truy vấn SQL và trả về nhiều resultset và phương thức executeUpdate() được sử dụng để phát ra các lệnh INSERT, UPDATE, hoặc DELETE.

Ngoài giao tiếp Statement cơ bản, một đối tượng Connection có thể được sử dụng để tạo ra một đối tượng PreparedStatement và các CallableStatement biểu diễn các thủ tục stored procedure trong cơ sở dữ liệu. Một lệnh SQL có thể liên quan đến nhiều tham số đầu vào, hoặc một lệnh mà ta muốn xử lý nhiều lần, có thể được tạo ra bằng cách sử dụng lệnh prepareStatement() trên đối tượng Connection, phương thức này trả về đối tượng PreparedStatement. Lệnh SQL được truyền cho phương thức prepareStatement() là một lệnh được biên dịch trước vì vậy việc xử lý nhiều lần một lệnh sẽ hiệu quả hơn. Lớp con của lớp Statement hỗ trợ việc thiết lập các giá trị của các tham số đầu vào được biên dịch trước thông qua các phương thức setXXX(). Đối tượng PreparedStatement có phương thức executeQuery() không cần tham số, thay vào đó nó xử lý các lệnh SQL được biên dịch trước trên cơ sở dữ liệu. Chú ý rằng không phải tất cả các nhà sản xuất cơ sở dữ iệu hoặc các driver JDBC đều hỗ trợ các lệnh được biên dịch trước.

3.4. ResultSet

Các dòng dữ liệu được trả về từ việc xử lý một lệnh được biểu diễn bằng một ResultSet trong JDBC. Ví dụ, phương thức executeQuery() của Statement trả về một đối tượng ResultSet. Đối tượng ResultSet cung cấp các cách để duyệt qua các dòng dữ liệu được trả về từ việc xử lý câu lệnh truy vấn SQL thông qua phương thức next() của nó; các trường dữ liệu trong mỗi hàng có thể được tìm kiếm thông qua các tên hoặc chỉ mục cột bằng cách sử dụng phương thức getXXX(). Người dùng cần phải biết kiểu dữ liệu trong mỗi cột dữ liệu được trả về, vì mỗi mục dữ liệu được tìm kiếm thông qua các phương thức getXXX() có kiểu cụ thể.

Tùy thuộc vào kiểu trình điều khiển JDBC được cài đặt, việc duyệt qua các hàng dữ liệu trong đối tượng ResultSet có thể tạo ra hiệu ứng lấy dữ liệu từ cơ sở dữ liệu, hoặc đơng giản là trả về từng hàng dữ liệu từ cache. Nếu hiệu năng của các giao dịch là vấn đề đối với ứng dụng, ta cần xác định dữ liệu trả về được quản lý như thế nào bởi các trình điều khiển của nhà sản xuất.

Lưu ý: Giá trị trả lại của hàm getXXX(args) là dữ liệu của trường có tên là args của các dòng dữ liệu đã được chọn ra. Ngoài ra cũng cần phân biệt các kiểu của Java với các kiểu dữ liệu của SQL. Bảng dưới đây mô tả các kiểu dữ liệu tương ứng của Java, SQL và các hàm getXXX().

Kiểu của SQL

Kiểu của Java

Hàm getXXX()

CHAR

String

getString()

VARCHAR

String

getString()

LONGVARCHAR

String

getString()

NUMBERIC

java.math.BigDecimal

getBigDecimal()

DECIMAL

java.math.BigDecimal

getBigDecimal()

BIT

Boolean (boolean)

getBoolean()

TINYINT

Integer (byte)

getByte()

SMALLINT

Integer (short)

getShort()

INTEGER

Integer (int)

getInt()

BIGINT

Long (long)

getLong()

REAL

Float (float)

getFloat()

FLOAT

Double (double)

getDouble()

DOUBLE

Double (double)

getDouble()

BINARY

byte[]

getBytes()

VARBINARY

byte[]

getBytes()

LONGVARBINARY

byte[]

getBytes()

DATE

java.sql.Date

getDate()

TIME

java.sql.Time

getTime()

TIMESTAMP

java.sql.Timestamp

getTimestamp()

Bảng 10.1

4. Lớp DatabaseMetaData

Muốn xử lý tốt các dữ liệu của một CSDL thì chúng ta phải biết được những thông tin chung về cấu trúc của CSDL đó như: hệ QTCSDL, tên của các bảng dữ liệu, tên gọi của các trường dữ liệu, v.v .

Để biết được những thông tin chung về cấu trúc của một hệ CSDL, chúng ta có thể sử dụng giao diện java.sql.DatabaseMetaData thông qua hàm getMetaData().

DatabaseMetaData dbmeta = con.getMetaData();

trong đó, con là đối tượng kết nối đã được tạo ra bởi lớp Connection.

Lớp DatabaseMetaData cung cấp một số hàm được nạp chồng để xác định được những thông tin về cấu hình của một CSDL. Một số hàm cho lại đối tượng của String (getURL()), một số trả lại giá trị logic (nullsAreSortedHigh()) hay trả lại giá trị nguyên như hàm getMaxConnection()). Những hàm khác cho lại kết quả là các đối tượng của ResultSet như: getColumns(), getTableType(), getPrivileges(), v.v.

5. Lớp ResultSetMetaData

Giao diện ResultSetMetaData cung cấp các thông tin về cấu trúc cụ thể của ResultSet, bao gồm cả số cột, tên và giá trị của chúng. Ví dụ sau là một chương trình hiển thị các kiểu và giá trị của từng trường của một bảng dữ liệu.

Ví dụ 9.3 Chương trình hiển thị một bảng dữ liệu.

import java.sql.*;

import java.util.StringTokenizer;

public class TableViewer {

final static String jdbcURL = "jdbc:odbc:StudentDB";

final static String jdbcDriver =

"sun.jdbc:odbc:JdbcOdbcDriver";

final static String table = "STUDENT";

public static void main(java.lang.String[]args) {

System.out.println("-Table Viewer -");

try {

Class.forName(jdbcDriver);

Connection con =

DriverManager.getConnection(jdbcURL, "", "");

Statement stmt = con.createStatement();

// Đọc ra cả bảng Student và đưa vào đối tượng rs

ResultSet rs = stmt.executeQuery("SELECT * FROM " + table);

// Đọc ra các thông tin về rs

ResultSetMetaData rsmd = rs.getMetaData();

// Xác định số cột của rsmd

int colCount = rsmd.getColumnCount();

for(int col = 1; col <= colCount; col++)

{

// In ra tên và kiểu của từng trường dữ liệu trong rsmd

System.out.print(rsmd.getColumnLabel(col));

System.out.print(" (" + rsmd.getColumnTypeName(col) + ")");

if(col < colCount)

System.out.print(", ");

}

System.out.println();

while(rs.next()){

// In ra dòng dữ liệu trong rsmd

for(int col = 1; col <= colCount; col++)

{

System.out.print(rs.getString(col));

if(col < colCount)

System.out.print(" ");

}

System.out.println();

}

rs.close();

stmt.close();

con.close();

}

catch (ClassNotFoundException e) {

System.out.println("Unable to load database driver class");

}

catch (SQLException se) {

System.out.println("SQL Exception: " + se.getMessage());

}

}

}

6. Các bước cơ bản để kết nối với cơ sở dữ liệu từ một ứng dụng Java

· Bước 1: Nạp trình điều khiển

try{

Class.forName(“oracle.jdbc.driver.OracleDriver”);

}

catch(ClassNotFoundException e)

{

System.out.println(“Loi nap trinh dieu khien:”+e);

}

· Bước 2: Xác định URL cơ sở dữ liệu

String host=”dbhost.yourcompany.com”;

String dbName=”someName”;

int port=1234;

String oracaleURL=”jdbc:oracle:thin:@”+host+”:”+port+dbName;

· Bước 3: Thiết lập liên kết

String username=”hoan_td2001”;

String password=”topsecret”;

Connection con=DriverManager.getConnecton(oracleURL,username,password);

· Bước 4: Tạo ra một đối tượng Statement

Statement s=con.createStatement();

· Bước 5: Xử lý truy vấn

String q=”Select col1, col2, col3 from sometable”;

ResultSet rs=s.executeQuery(q);

· Bước 6: Xử lý kết quả

while(rs.next())

{

System.out.println(rs.getString(1)+” “+

rs.getString(2)+” “+

rs.getString(3));

}

Cột đầu tiên có chỉ mục là 1 chứ không phải là 0.

· Bước 7: Đóng liên kết

con.close();

Các ví dụ về kết nối cơ sở dữ liệu từ ứng dụng Java.

Ví dụ về kết nối kiểu 1:

import java.sql.*;

class DBOracle1

{

public static void main(String args[])throws ClassNotFoundException, SQLException

{

try{

//Co the dung lenh nay de tai driver Class.forName("oracle.jdbc.OracleDriver");

DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());

//Lien ket toi co so du lieu

Connection conn = DriverManager.getConnection("jdbc:oracle:oci8:@HOAN", "scott", "tiger");

Statement stmt = conn.createStatement( );

ResultSet rset = stmt.executeQuery("select empno, ename from emp");

ResultSetMetaData rst=rset.getMetaData();

int numcol=rst.getColumnCount();

System.out.println("So cot cua bang la:"+numcol);

System.out.println("Schema Name:" + rst.getTableName(1));

for(int i=1;i

System.out.println(rst.getColumnName(i)+" "+rst.getColumnTypeName(i));

while(rset.next( ))

{

System.out.println(rset.getString("empno"));

System.out.println(rset.getString("ename"));

}

rset.close( );

stmt.close( );

conn.close( );

}

catch(Exception e)

{

System.err.println("Ex : "+e);

}

}

}

Ví dụ về kết nối kiểu 2:

import java.io.*;

import java.sql.*;

import java.text.*;

public class DBOracle2 {

Connection conn;

public DBOracle2( )

{

try

{

DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());

Connection conn = DriverManager.getConnection("jdbc:oracle:oci8:@HOAN", "scott", "tiger");

}

catch (SQLException e)

{

System.err.println(e.getMessage( ));

e.printStackTrace( );

}

}

public static void main(String[] args)throws Exception, IOException

{

new DBOracle2().process( );

}

public void process( ) throws IOException, SQLException

{

int rows = 0;

ResultSet rslt = null;

PreparedStatement pstmt = null;

String insert ="insert into EMP " +"( EMPNO, ENAME,JOB) " +"values " +"( ?, ?, ?)";

try {

System.out.println(insert);

pstmt = conn.prepareStatement(insert);

pstmt.setString( 1, "EMPNO" );

pstmt.setString( 2, "ENAME" );

pstmt.setString( 3,"JOB" );

rows = pstmt.executeUpdate( );

pstmt.close( );

pstmt = null;

System.out.println(rows + " rows inserted");

System.out.println("");

}

catch (SQLException e) {

System.err.println(e.getMessage( ));

}

finally {

if (pstmt != null)

try {

pstmt.close( );

}

catch(SQLException ignore)

{

}

}

}

protected void finalize( )throws Throwable {

if (conn != null)

try { conn.close( ); } catch (SQLException ignore) { }

super.finalize( );

}

}

Ví dụ về kết nối kiểu 4:

//Type 4 Driver

import java.sql.*;

import java.util.*;

class DBOracle4

{

public static void main(String args[])throws ClassNotFoundException, SQLException

{

try{

//Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());

Enumeration drivers = DriverManager.getDrivers();

while(drivers.hasMoreElements())

{

Driver driver = (Driver)drivers.nextElement();

System.out.println("Registered Driver:"+driver.getClass().getName());

}

//Lien ket toi co so du lieu

Connection conn = DriverManager.getConnection("jdbc:Oracle:thin:@neworacle02:1521:HOAN", "scott", "tiger");

DatabaseMetaData dbmd=conn.getMetaData();

System.out.println(dbmd.getDatabaseProductName());

System.out.println(dbmd.getDatabaseProductVersion());

Statement stmt = conn.createStatement( );

ResultSet rset = stmt.executeQuery("select empno, ename from emp");

ResultSetMetaData rst=rset.getMetaData();

int numcol=rst.getColumnCount();

System.out.println("So cot cua bang la:"+numcol);

System.out.println(rst.getTableName(1));

for(int i=1;i

System.out.println(rst.getColumnName(i)+" "+rst.getColumnTypeName(i));

while(rset.next( ))

{

System.out.println(rset.getString("empno")+" "+rset.getString("ename"));

}

rset.close( );

stmt.close( );

conn.close( );

}

catch(Exception e)

{

System.err.println("Ex : "+e);

}

}

}

7. Sử dụng PreparedStatement

Đôi khi việc sử dụng một đối tượng PreparedStatent hiệu quả và tiện lợi hơn nhiều so với việc sử dụng đối tượng Statement. Kiểu lệnh đặc biệt này là lớp con của lớp Statement.

Khi nào cần sử dụng đối tượng PreparedStatement

Nếu ta muốn xử lý một đối tượng Statement nhiều lần, ta có thể sử dụng đối tượng PreparedStatement để giảm thời gian xử lý.

Đặc trưng chính của một đối tượng PreparedStatement là nó được cung cấp trước một lệnh SQL trước khi tạo ra đối tượng. Đối tượng PreparedStatement là một lệnh SQL đã được biên dịch trước. Điều này nghĩa là khi đối tượng PreparedStatement được xử lý, hệ quản trị cơ sở dữ liệu chỉ cần xử lý lệnh SQL của PreparedStatement mà không phải biên dịch nó.

Mặc dù PreparedStatement có thể được sử dụng với các lệnh SQL không có tham số nhưng ta thường hay sử dụng các lệnh SQL có tham số. Ưu điểm của việc sử dụng lệnh SQL có tham số là ta có thể sử dụng cùng một lệnh và cung cấp cho nó các giá trị khác nhau mỗi khi xử lý. Ta sẽ thấy điều này trong ví dụ ở phần sau.

Tạo một đối tượng PreparedStatement

Giống như các đối tượng Statement, bạn đọc có thể tạo ra các đối tượng PrepraredStatement với một phương thức Connection. Sử dụng một kết nối mở trong ví dụ trước là con, có thể tạo ra đối tượng PreparedStatement nhận hai tham số đầu vào như sau:

PreparedStatement updateSales = con.prepareStatement(

"UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ?");

Cung cấp các giá trị cho các tham số của đối tượng PreparedStatement

Ta cần cung cấp các giá trị được sử dụng thay cho vị trí của các dấu hỏi nếu có trước khi xử lý một đối tượng PreparedStatement. Ta có thể thực hiện điều này bằng cách gọi một trong các phương thức setXXX đã được định nghĩa trong lớp PreparedStatement. Nếu giá trị ta muốn thay thế cho dấu hỏi (?) là kiểu int trong Java, ta có thể gọi phương thức setInt. Nếu giá trị ta muốn thay thế cho dấu (?) là kiểu String trong Java, ta có thể gọi phương thức setString,…Một cách tổng quát, ứng với mỗi kiểu trong ngôn ngữ lập trình Java sẽ có một phương thức setXXX tương ứng.

Ví dụ:

import java.sql.*;

public class PreparedStmt{

public static void main(String args[]){

int empid;

String LastName;

String FirstName;

String query = "SELECT * FROM Employees where EmployeeID=?;";

try {

Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

Connection con =DriverManager.getConnection ("jdbc:odbc:MyData");

PreparedStatement pstmt = con.prepareStatement(query);

pstmt.setInt(1,2);

ResultSet rs = pstmt.executeQuery();

while (rs.next()) {

empid = rs.getInt("EmployeeID");

LastName = rs.getString("LastName");

FirstName = rs.getString("FirstName");

System.out.println(empid+", "+LastName+"\t"+FirstName+"\t");

}

}

catch(ClassNotFoundException e){

e.printStackTrace();

}

catch(SQLException e){

e.printStackTrace();

}

}

}

· Sử dụng một vòng lặp để thiết lập các giá trị

Ta có thể sử dụng vòng lặp để thiết lập các giá trị cho các tham số đầu vào.

PreparedStatement updateSales;

String updateString = "update COFFEES " +

"set SALES = ? where COF_NAME like ?";

updateSales = con.prepareStatement(updateString);

int [] salesForWeek = {175, 150, 60, 155, 90};

String [] coffees = {"Colombian", "French_Roast", "Espresso",

"Colombian_Decaf", "French_Roast_Decaf"};

int len = coffees.length;

for(int i = 0; i < len; i++) {

updateSales.setInt(1, salesForWeek[i]);

updateSales.setString(2, coffees[i]);

updateSales.executeUpdate();

}

Các giá trị trả về của phương thức executeUpdate

Phương thức executeQuery trả về một đối tượng ResultSet chứa các kết quả của truy vấn được gửi tới hệ quản trị cơ sở dữ liệu, giá trị trả về khi xử lý phương thức executeUpdate là một số nguyên int chỉ ra số hàng trong bảng đã được cập nhật.

updateSales.setInt(1, 50);

updateSales.setString(2, "Espresso");

int n = updateSales.executeUpdate();

// n = 1 because one row had a change in it

8. Sử dụng các giao tác

Quản lý giao tác

Một giao tác là một tập hợp một hoặc nhiều lệnh được xử lý cùng với nhau như một chỉnh thể thống nhất (đơn vị). Khi xử lý một giao tác hoặc tất cả các lệnh được xử lý hoặc không lệnh nào được xử lý. Nhiều trường hợp ta không muốn một lệnh có hiệu lực ngay nếu lệnh khác không thành công.

Điều này có thể được thực hiện nhờ phương thức setAutoCommit() của đối tượng Connection. Phương thức này nhận một giá trị boolean làm tham số..

Ngăn chế độ Auto-commit

Khi một liên kết được tạo ra, thì liên kết đó ở chế độ auto-commit.

Mỗi lệnh SQL được xem như là một giao tác và sẽ được tự động hoàn thành ngay khi nó được xử lý.

Cách để cho phép hai hoặc nhiều lệnh được nhóm cùng với nhau thành một giao tác là cấm chế độ auto-commit.

Ví dụ:

con.setAutoCommit(false);

Xác nhận hoàn thành một giao tác

Mỗi khi chế độ auto-commit bị cấm, không có lệnh SQL nào sẽ được xác nhận hoàn thành cho tới khi ta gọi phương thức commit().

Ta có thể thực hiện điều này bằng cách gọi phương thức commit() của các đối tượng liên kết.

Nếu ta cố gắng xử lý một hay nhiều lệnh trong một giao tác và nhận được một ngoại lệ SQLException, ta cần gọi phương thức rollback() để hủy bỏ giao tác và khởi động lại toàn bộ giao tác.