JDBC-2(CRUD)
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中關閉,保證及時其他程式碼出現異常,資源也一定能被關閉。