編寫工具類簡化JDBC(CURD)的操作
JDBC
全稱是JAVA DATE BASE Connectity(java資料庫連線),可以為多種資料庫提供統一的訪問。即定義一個規範的介面,讓各資料庫廠商提供介面的具體實現類,這種實現類同時也被稱作驅動。
JDBC規範
(4個核心物件) - DriverManager:用於註冊驅動。 - Connection:與資料庫建立連線。 - Statement:操作資料庫sql語句的物件。 - ResultSet:結果集(存放由select語句查詢到的資料資訊)
JDBC連線資料庫的5個主要步驟
(即根據4個核心類物件推匯出的步驟並封裝)
1.載入驅動
方式一: 直接註冊驅動
DriverManager.registerDriver(new Driver());
方式二:(推薦使用) 通過反射的方式,實現驅動的載入
Class.forName("com.mysql.cj.jdbc.Driver");
注意:
- 在實際開發的過程中,不推薦使用方式一來進行驅動的註冊,檢視Driver原始碼(如下)可以看到,它本身就載入了一次驅動,這樣一來就會導致驅動程式註冊兩次,也就表示了在記憶體中會有兩個Driver物件
- 程式依賴mysql的jar包,一旦脫離,程式將無法編譯,將來程式切換底層資料庫將會非常麻煩。
Driver原始碼
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
- 採用方式二不會導致驅動物件在記憶體中重複出現,程式僅需要一個字串,不需要依賴具體的驅動,使程式的靈活性更高。
2.建立連線
通過註冊的JDBC驅動來建立資料庫連線物件從而獲取資料庫的URL,使用者名稱,密碼來連線資料庫
String url = "jdbc:mysql://localhost:3306/day04?serverTimezone=UTC&characterEnconding=utf-8";
Connection con = DriverManager.getConnection(url,"root","123456");
解析
String url = "jdbc:mysql://localhost:3306/day04?serverTimezone=UTC&characterEnconding=utf-8";
jdbc:是JDBC連線協議 mysq:// 是mysql資料庫連線協議,即JDBC子協議 Localhost:3306 是主機和埠 day04 是指需要連線的資料庫 serverTimezone=UTC:國際通用時區 characterEncoding=utf-8:資料庫編碼格式 (主要是解決資料庫亂碼的問題)
3.操作資料
方式一:(該方式有弊端,會導致sql注入問題) 通過資料庫連線物件獲取操作資料庫sql語句的物件Statement
String sql = "select * from user where username = '"+name+"' and password = '"+passwd+"';";
Statement stmt = con.createStatement();
模擬使用者登入 需求:通過鍵盤錄入來獲取使用者名稱和密碼,同時在資料庫中判斷該使用者是否存在 存在即登入成功,反之則失敗。
程式碼一:
import java.sql.*;
import java.util.Scanner;
public class JDBCTest3 {
public static void main(String[] args) {
System.out.println("請輸入使用者名稱");
String name = new Scanner(System.in).nextLine();
System.out.println("請輸入密碼");
String passwd = new Scanner(System.in).nextLine();
//載入驅動
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
try {
//建立連線
String url = "jdbc:mysql://localhost:3306/day04?serverTimezone=UTC&characterEnconding=utf-8";
con = DriverManager.getConnection(url,"root","123456");
//操作資料
String sql = "select * from user where username = '"+name+"' and password = '"+passwd+"';";
stmt = con.createStatement();
rs = stmt.executeQuery(sql);
// 判斷返回的結果
if (rs.next()) {
// 登入成功
int id = rs.getInt("id");
String u_name = rs.getString("username");
String u_pwd = rs.getString("password");
String email = rs.getString("email");
// System.out.println(id + " : " + u_name + " : " + u_pwd + " : " + email);
System.out.println("登入成功!");
} else {
// 登入失敗
System.out.println("登入失敗! 使用者名稱或密碼錯誤!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//釋放資源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if (con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
con = null;
}
}
}
}
結果:
請輸入使用者名稱
lisi
請輸入密碼
123
登入成功!
由於對使用者的輸入沒有進行充分的檢查而SQL又是拼接而成,在使用者輸入引數時,在引數中新增一些SQL 關鍵字,達到改變SQL執行結果的目的。
案例一: 輸入使用者時: zhangsan’ or ‘1’=’1 password 隨意 程式碼中sql語句進行拼接
select * from user where username ='zhangsan' or '1'='1' and password ='';
由於 and 優先順序 執行 高於 or,導致不需要任何密碼都能登入成功
請輸入使用者名稱
lisi' or '1'='1
請輸入密碼
00000000
登入成功!
案例二: 使用者輸入 username: zhangsan’ – password 隨意 程式碼中sql語句進行拼接
select * from user where username ='zhangsan' -- ' and password ='' ;
在SQL新增 – 是mysql的註釋
請輸入使用者名稱
lisi' --
請輸入密碼
111111
登入成功!
方式二:(推薦使用) 通過資料庫連線物件獲取操作資料庫sql語句的物件PreparedStatement
String sql = "select * from user where username = ? and password = ?;";
PreparedStatement pstmt = con.prepareStatement(sql);
pstmt.setString(1,name);
pstmt.setString(2,passwd);
解析:
- ?表示佔位符
- 將帶有?的SQL 傳送給資料庫完成預編譯 ,在SQL編譯後發現缺少兩個引數。PreparedStatement 可以將? 代替引數傳送給資料庫伺服器,因為SQL已經編譯過,引數中特殊字元不會當做特殊字元編譯,無法達到SQL注入的目的 。
- setString(int parameterIndex, String x)
//表示給第一個?設定一個字串型別的值
pstmt.setString(1,name);
- PreparedStatement是Statement的子介面,它的例項物件可以通過呼叫Connection.preparedStatement(sql)方法獲得,相對於Statement物件而言: PreperedStatement可以避免SQL注入的問題。 Statement會使資料庫頻繁編譯SQL,可能造成資料庫緩衝區溢位。PreparedStatement可對SQL進行預編譯,從而提高資料庫的執行效率。並且PreperedStatement對於sql中的引數,允許使用佔位符的形式進行替換,簡化sql語句的編寫。
程式碼二:
import java.sql.*;
import java.util.Scanner;
public class JDBCTest3 {
public static void main(String[] args) {
System.out.println("請輸入使用者名稱");
String name = new Scanner(System.in).nextLine();
System.out.println("請輸入密碼");
String passwd = new Scanner(System.in).nextLine();
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
String url = "jdbc:mysql://localhost:3306/day04?serverTimezone=UTC&characterEnconding=utf-8";
con = DriverManager.getConnection(url,"root","123456");
String sql = "select * from user where username = ? and password = ?;";
pstmt = con.prepareStatement(sql);
pstmt.setString(1,name);
pstmt.setString(2,passwd);
rs = pstmt.executeQuery();
// 判斷返回的結果
if (rs.next()) {
// 登入成功
int id = rs.getInt("id");
String u_name = rs.getString("username");
String u_pwd = rs.getString("password");
String email = rs.getString("email");
// System.out.println(id + " : " + u_name + " : " + u_pwd + " : " + email);
System.out.println("登入成功!");
} else {
// 登入失敗
System.out.println("登入失敗! 使用者名稱或密碼錯誤!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
pstmt = null;
}
if (con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
con = null;
}
}
}
}
結果
請輸入使用者名稱
lisi
請輸入密碼
123
登入成功!
4.獲取操作資料
- executeUpdate 無結果集 用於向資料庫傳送 insert/update/delete 語句,返回int 型別引數,代表影響記錄行數.
int count = pstmt.executeUpdate();
- executeQuery 通過結果集獲取操作資料庫後的結果 用於向資料庫傳送 select 語句,返回ResultSet 結果集物件.。
ResultSet rs = pstmt.executeQuery();
- execute 用於判斷資料庫傳送任何SQL語句(包括 select/insert/update/delete) 返回boolean型別 ,SQL執行結果是select返回true,否則 false。
boolean flag = pstmt.execute();
5.釋放資源
Jdbc程式執行完後,切記要釋放程式在執行過程中,建立的那些與資料庫進行互動的物件,這些物件通常是ResultSet, Statement和Connection物件。 特別是Connection物件,它是非常稀有的資源,用完後必須馬上釋放,如果Connection不能及時、正確的關閉,極易導致系統宕機。Connection的使用原則是儘量晚建立,儘量早的釋放。為確保資源釋放程式碼能執行,資源釋放程式碼也一定要放在finally語句中。
無結果集釋放資源
public static void release(Connection con, Statement stmt) {
if(con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
con = null;
}
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
}
有結果集釋放資源
public static void release(Connection con,Statement stmt,ResultSet rs) {
if(con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
con = null;
}
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
release(rs);
}
public static void release(ResultSet rs) {
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
}
對上述JDBC連線資料庫並操作資料的步驟進行封裝
抽取資料庫資訊
- 抽取註冊驅動依賴的mysql的jar包
- 抽取註冊的JDBC驅動來建立資料庫連線物件從而獲取資料庫的URL,使用者名稱,密碼
- 把以上抽取的物件放入配置檔案中
driverClass=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/day04?serverTimezone=UTC&characterEncoding=utf-8
username=root
passwd=123456
解析配置檔案並對上述JDBC連線資料庫的步驟進行封裝
import java.sql.*;
public class JDBCUtils {
private static final String DRIVERCLASS="com.mysql.cj.jdbc.Driver";
private static final String URL="jdbc:mysql://localhost:3306/day04?serverTimezone=UTC" +
"&characterEncoding=utf-8";
private static final String USERNAME="root";
private static final String PASSWD="123456";
//載入驅動
public static void loadDriver() {
try {
Class.forName(DRIVERCLASS);
} catch (ClassNotFoundException e) {
throw new RuntimeException("驅動載入失敗");
}
}
//獲取連線
public static Connection getConnection() throws SQLException {
String url = URL;
Connection con = DriverManager.getConnection(url,USERNAME,PASSWD);
return con;
}
//無結果集釋放資源
public static void realease(Connection con, Statement stmt) {
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
con = null;
}
}
//有結果集釋放資源
public static void realease(Connection con, Statement stmt,ResultSet rs) {
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
con = null;
}
realease(rs);
}
public static void realease(ResultSet rs) {
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
}
}
測試:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCTest5 {
public static void main(String[] args) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = JDBCUTils2.getConnection();
String sql = "update user set username = ?,password = ?,email = ? where id = ?;";
pstmt = con.prepareStatement(sql);
pstmt.setString(1,"jiuba");
pstmt.setString(2,"888");
pstmt.setString(3,"[email protected]");
pstmt.setString(4,"2");
int count = pstmt.executeUpdate();
System.out.println(count);
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUTils2.realease(con,pstmt);
}
}
}
結果
1