3.PreparedStatement實現CRUD

3.1 操作和訪問資料庫

  • 資料庫連線被用於向資料庫伺服器傳送命令和SQL語句,接受資料庫伺服器返回的結果。(一個數據庫連線就是也給Socket連線)

  • 在 java.sql 包中有 3 個介面分別定義了對資料庫的呼叫的不同方式:

    • Statement:用於執行靜態 SQL 語句並返回它所生成結果的物件。
    • PrepatedStatement:SQL 語句被預編譯並存儲在此物件中,可以使用此物件多次高效!地執行該語句。
    • CallableStatement:用於執行 SQL 儲存過程

3.2 Statement操作資料表的弊端

  1. 存在拼串操作,繁瑣
  2. 存在SQL注入問題 SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1'
    • SQL注入是利用某些系統沒有對使用者輸入的資料進行充分檢查,而在使用者輸入資料中注入非法的SQL語句段或命令,從而利用系統的SQL引擎完成惡意行為的做法。

3.3 PreparedStatement的使用

3.3.1 PreparedStatement介紹

  • 可以通過呼叫 Connection 物件的 preparedStatement(String sql) 方法獲取 PreparedStatement 物件

  • PreparedStatement 介面是 Statement 的子介面,它表示一條預編譯過的 SQL 語句

  • PreparedStatement 物件所代表的 SQL 語句中的引數用問號(?)來表示,呼叫 PreparedStatement 物件的 setXxx() 方法來設定這些引數. setXxx() 方法有兩個引數,第一個引數是要設定的 SQL 語句中的引數的索引(從 1 開始),第二個是設定的 SQL 語句中的引數的值

3.3.2 PreparedStatement vs Statement

  • 程式碼的可讀性和可維護性。

  • PreparedStatement 能最大可能提高效能:

    • DBServer會對預編譯語句提供效能優化。因為預編譯語句有可能被重複呼叫,所以語句在被DBServer的編譯器編譯後的執行程式碼被快取下來,那麼下次呼叫時只要是相同的預編譯語句就不需要編譯,只要將引數直接傳入編譯過的語句執行程式碼中就會得到執行。
    • 在statement語句中,即使是相同操作但因為資料內容不一樣,所以整個語句本身不能匹配,沒有快取語句的意義.事實是沒有資料庫會對普通語句編譯後的執行程式碼快取。這樣每執行一次都要對傳入的語句編譯一次。
    • (語法檢查,語義檢查,翻譯成二進位制命令,快取)
  • PreparedStatement 可以防止 SQL 注入

3.3.3 Java與SQL對應資料型別轉換表

使用步驟:

  1. 建立資料庫連線
  2. 預編譯sql語句,返回PreparedStatement例項
  3. 填充佔位符(注意: 與資料庫互動的API起始值為1)
  4. 執行sql操作
  5. 關閉資源(connection preparedStatement)

3.3.4 增刪改

點選檢視程式碼
    @Test //向t_account中新增一條記錄
public void Update1() throws Exception{
//1.獲取配置檔案基本資訊
InputStream inputStream = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties prop = new Properties();
prop.load(inputStream); String url = prop.getProperty("url");
String driverClass = prop.getProperty("driverClass");
String user = prop.getProperty("user");
String password = prop.getProperty("password"); //2.載入驅動
Class.forName(driverClass); //3,獲取連線
Connection connection = DriverManager.getConnection(url, user, password); //4.update,預編譯sql語句,返回PreparedStatement例項
String sql = "insert into t_account(username, money) values(?,?)";//?為佔位符
PreparedStatement preparedStatement = connection.prepareStatement(sql); //5.填充佔位符(與資料庫互動的API起始值為1)
preparedStatement.setString(1, "Mike");
preparedStatement.setInt(2, 1000000); //6.執行sql操作
preparedStatement.execute(); //7.資源關閉(連線和preparedStatement都要關)
preparedStatement.close();
connection.close();
}

進階:封裝connection與close的實現

點選檢視封裝類JDBCUtils
public class JDBCUtils {
public static Connection getConnection() throws Exception{
//1.獲取配置檔案基本資訊
InputStream inputStream = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties prop = new Properties();
prop.load(inputStream); String url = prop.getProperty("url");
String driverClass = prop.getProperty("driverClass");
String user = prop.getProperty("user");
String password = prop.getProperty("password"); //2.載入驅動
Class.forName(driverClass); //3,獲取連線
Connection connection = DriverManager.getConnection(url, user, password); return connection;
} public static void closeResource(Connection connection, PreparedStatement preparedStatement){
try{
if(connection != null) connection.close();
if(preparedStatement != null) preparedStatement.close();
} catch (SQLException e){
e.printStackTrace();
} }
}
點選檢視實現類
    @Test
public void Update2() throws Exception {
Connection connection = null;
PreparedStatement preparedStatement = null;
try { //1.獲取連線
connection = JDBCUtils.getConnection(); //2.預編譯sql語句,返回preparedStatement例項
String sql = "UPDATE t_account\n" +
"SET money = ?\n" +
"WHERE username = ?;";
preparedStatement = connection.prepareStatement(sql); //可選 填充佔位符
preparedStatement.setInt(1, 200000000);
preparedStatement.setString(2, "Jingd"); //3執行sql操作
preparedStatement.execute();
} catch (Exception e){
e.printStackTrace();
} finally {
//4.資源關閉
JDBCUtils.closeResource(connection, preparedStatement);
}
}

進階:通用增刪改操作(函式傳參為sql語句與佔位符)

點選檢視程式碼
    public void allUpdate(String sql, Object ...args){
Connection connection = null;
PreparedStatement preparedStatement = null; try {
//1.建立連線
connection = JDBCUtils.getConnection(); //2.預編譯sql語句,返回preparedStatement例項
preparedStatement = connection.prepareStatement(sql); //3.(可選) 填充佔位符
for (int i = 0; i << args.length; i++) {
//注意! 易錯!
preparedStatement.setObject(i + 1, args[i]);
} //4.執行
preparedStatement.execute();
} catch (Exception e){
e.printStackTrace();
} finally {
//5.關閉資源
JDBCUtils.closeResource(connection, preparedStatement);
}
}

注:若表中有關鍵詞名與sql語句名重名,可用著重號``表示獨特性。

3.3.5 查

1-ResultSet概述

  • 查詢需要呼叫PreparedStatement 的 executeQuery() 方法,查詢結果是一個ResultSet 物件

  • ResultSet 物件以邏輯表格的形式封裝了執行資料庫操作的結果集,ResultSet 介面由資料庫廠商提供實現

  • ResultSet 返回的實際上就是一張資料表。有一個指標指向資料表的第一條記錄的前面。

  • ResultSet 物件維護了一個指向當前資料行的遊標,初始的時候,遊標在第一行之前,可以通過 ResultSet 物件的 next() 方法移動到下一行。呼叫 next()方法檢測下一行是否有效。若有效,該方法返回 true,且指標下移。相當於Iterator物件的 hasNext() 和 next() 方法的結合體。

  • 當指標指向一行時, 可以通過呼叫 getXxx(int index) 或 getXxx(int columnName) 獲取每一列的值。

    • 例如: getInt(1), getString("name")
    • 注意:Java與資料庫互動涉及到的相關Java API中的索引都從1開始。
  • ResultSet 介面的常用方法:

    • boolean next()

    • getString()

2-ResultSetMetData

  • 可用於獲取關於 ResultSet 物件中列的型別和屬性資訊的物件

  • ResultSetMetaData meta = rs.getMetaData();

    • getColumnName(int column):獲取指定列的名稱

    • getColumnLabel(int column):獲取指定列的別名

    • getColumnCount():返回當前 ResultSet 物件中的列數。

    • getColumnTypeName(int column):檢索指定列的資料庫特定的型別名稱。

    • getColumnDisplaySize(int column):指示指定列的最大標準寬度,以字元為單位。

    • isNullable(int column):指示指定列中的值是否可以為 null。

    • isAutoIncrement(int column):指示是否自動為指定列進行編號,這樣這些列仍然是隻讀的。


查詢操作

  • R與CUD的主要區別在於執行與處理結果,查詢結果用FormatClass類
點選檢視程式碼
    @Test
public void selectTest1(){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null; try {
//1.建立資料庫連線
connection = JDBCUtils.getConnection(); //2.預編譯sql語句,返回preparedStatement例項
String sql = "SELECT *\n" +
"FROM t_account\n" +
"WHERE id IN(?,?);";
preparedStatement = connection.prepareStatement(sql); //3.填充佔位符
preparedStatement.setObject(1, 1);
preparedStatement.setObject(2, 2); //4.執行(區別於CRD的主要),並返回結果集
resultSet = preparedStatement.executeQuery(); //5.處理結果集****
//next方法:判斷結果集下一條是否有資料,若有返回true且指標下移;否則返回false
while(resultSet.next()){
//獲取當前資料的各個欄位值
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
int money = resultSet.getInt(3);
Date date = resultSet.getDate(4); //1.sout 2.Object[]
//3.將資料封裝為一個物件(推薦)
FormatClass formatClass = new FormatClass(id, name, money, date);
System.out.println(formatClass); }
} catch (Exception e){
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection, preparedStatement, resultSet);
}
}
點選檢視ORM類
package Jing.bean;

import java.sql.Date;

//ORM程式設計思想(object relational mapping)
//一個數據對應一個java類
//表中的一條記錄對應java類的一個物件
//表中的一個欄位對應java類的一個屬性
public class FormatClass { private int id;
private String name;
private int money;
private Date birth; public FormatClass(int id, String name, int money, Date birth) {
this.id = id;
this.name = name;
this.money = money;
this.birth = birth;
} public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getMoney() {
return money;
} public void setMoney(int money) {
this.money = money;
} public Date getBirth() {
return birth;
} public void setBirth(Date birth) {
this.birth = birth;
} @Override
public String toString() {
return "FormatClass{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
", birth=" + birth +
'}';
}
}

進階-通用查詢操作(反射實現)

點選檢視程式碼
    //針對查詢的通用操作
public List<FormatClass> selectAll(String sql, Object ...args){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null; try {
//1.獲取資料庫連結
connection = JDBCUtils.getConnection(); //2.預編譯sql語句,返回preparedStatement例項
preparedStatement = connection.prepareStatement(sql); //3.填充佔位符
for(int i = 0; i < args.length; i++){ //>
preparedStatement.setObject(i + 1, args[i]);
} //4.執行-返回結果集
resultSet = preparedStatement.executeQuery(); //獲取結果集的元資料(修飾現有資料的資料)
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
int colCount = resultSetMetaData.getColumnCount(); //獲取結果集的列數 //5.處理結果集
List<FormatClass> list = new ArrayList<>();
while(resultSet.next()){
FormatClass formatClass = new FormatClass();
//處理一行結果集中資料的每一列
for(int i = 1; i <= colCount; i++){ //>
//列值
Object colVal = resultSet.getObject(i);
//列名
//String colName = resultSetMetaData.getColumnName(i); String colName = resultSetMetaData.getColumnLabel(i);
//給format物件指定的colName屬性賦值為colValue - 反射
Field field = FormatClass.class.getDeclaredField(colName);//獲取指定屬性名
field.setAccessible(true);//設定為可訪問
field.set(formatClass, colVal);
}
// int id = resultSet.getInt(1);
// String username = resultSet.getString(2);
// int money = resultSet.getInt(3);
// Date date = resultSet.getDate(4);
list.add(formatClass);
}
return list; } catch (Exception e){
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection, preparedStatement, resultSet);
}
return null;
}
  1. ResultSetMetaData獲取結果集元資料 -> 獲取資料庫結果集列名與結果集列數

    • 結果集ResultSet對應資料
    • 結果集元資料ResultSetMetaData對應修飾(列名、列數)
      • getColumnName() 獲取結果集列名 (不推薦使用,缺乏普適性)
      • getColumnLabel() 獲取列的別名 (沒有起別名時就是類的列名)
      • 針對表的欄位名與類的屬性名不相同情況時,需使用類的屬性名來命名欄位別名
  2. 使用反射獲取指定屬性、將私有屬性設定為可訪問並填充值
    1. 獲取屬性
    2. 設定屬性為可訪問true
    3. 填充屬性列值set

查詢操作總結:


進階-針對不同表的通用查詢操作

點選檢視程式碼
    //泛型方法,clazz為對應返回類
public <T> List<T> getInstance(Class<T> clazz, String sql, Object ...args){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null; try {
//1.建立連線
connection = JDBCUtils.getConnection(); //2.預編譯sql語句獲取preparedStatement物件
preparedStatement = connection.prepareStatement(sql); //3.填充佔位符
for(int i = 0; i < args.length; i++){
preparedStatement.setObject(i + 1, args[i]);
} //4.獲取結果集與結果集元資料
resultSet = preparedStatement.executeQuery();
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
int countVal = resultSetMetaData.getColumnCount(); //5.資料處理
List<T> list = new ArrayList<>();
while(resultSet.next()){
T t = clazz.newInstance();
for(int i = 0; i < countVal; i++){
//獲取查詢結果
Object Val = resultSet.getObject(i + 1); //獲取列名
String valName = resultSetMetaData.getColumnLabel(i + 1); //反射注入
Field field = t.getClass().getDeclaredField(valName);
field.setAccessible(true); //設定私有屬性可訪問
field.set(t, Val);
}
list.add(t);
} return list;
} catch (Exception e){
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection, preparedStatement, resultSet);
}
return null;
}
  • 使用泛型方法處理泛型類並返回對應泛型集合(傳參時將類的型別傳入xxx.class)

3.3.6 資源釋放

  • 釋放ResultSet, Statement,Connection。
  • 資料庫連線(Connection)是非常稀有的資源,用完後必須馬上釋放,如果Connection不能及時正確的關閉將導致系統宕機。Connection的使用原則是儘量晚建立,儘量早的釋放。
  • 可以在finally中關閉,保證及時其他程式碼出現異常,資源也一定能被關閉。