3.PreparedStatement實作CRUD

3.1 操作和通路資料庫
- 資料庫連接配接被用于向資料庫伺服器發送指令和SQL語句,接受資料庫伺服器傳回的結果。(一個資料庫連接配接就是也給Socket連接配接)
- 在 java.sql 包中有 3 個接口分别定義了對資料庫的調用的不同方式:
- Statement:用于執行靜态 SQL 語句并傳回它所生成結果的對象。
- PrepatedStatement:SQL 語句被預編譯并存儲在此對象中,可以使用此對象多次高效!地執行該語句。
- CallableStatement:用于執行 SQL 存儲過程
3.2 Statement操作資料表的弊端
- 存在拼串操作,繁瑣
- 存在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對應資料類型轉換表
使用步驟:
- 建立資料庫連接配接
- 預編譯sql語句,傳回PreparedStatement執行個體
- 填充占位符(注意: 與資料庫互動的API起始值為1)
- 執行sql操作
- 關閉資源(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;
}
- ResultSetMetaData擷取結果集中繼資料 -> 擷取資料庫結果集列名與結果集列數
- 結果集ResultSet對應資料
- 結果集中繼資料ResultSetMetaData對應修飾(列名、列數)
- getColumnName() 擷取結果集列名 (不推薦使用,缺乏普适性)
- getColumnLabel() 擷取列的别名 (沒有起别名時就是類的列名)
- 針對表的字段名與類的屬性名不相同情況時,需使用類的屬性名來命名字段别名
- 使用反射擷取指定屬性、将私有屬性設定為可通路并填充值
- 擷取屬性
- 設定屬性為可通路true
- 填充屬性列值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中關閉,保證及時其他代碼出現異常,資源也一定能被關閉。