文章目錄
JDBC
一、連接數據庫
//JDBC:Java爲連接數據庫提供的一套接口(規範)
//1、導入數據庫廠商提供的驅動
// 導入jar包,依賴jar包
//2、加載驅動
//驅動在5.0以上,此步驟可以省略不寫
Class.forName("com.mysql.jdbc.Driver");
//3、建立連接
//URL:統一資源定位符
//格式:"主協議:子協議://ip:端口/資源"
//本地連接:localhost:3306可以省略不寫
// 即:"jdbc:mysql:///mydb"
String url="jdbc:mysql://localhost:3306/mydb";
String username="root";
String password="123456";
Connection conn=DriverManager.getConnection(url,username,password);
//4、獲取操作對象
Statement st=conn.createStatement();
//5、編寫SQL語句
String sql="select * from student"; //SQL語句
6、執行SQL語句
st.executeQuery(sql);//執行SQL語句
7、釋放資源
conn.close();
st.close();
二、JDBC相關類及常用方法
DriverManager //驅動管理類
getConnection(); //試圖建立到給定數據庫 URL 的連接
Connection//接口:與特定數據庫的連接(會話)
createStatement(String sql);//創建一個 Statement 對象來將 SQL 語句發送到數據庫
prepareStatement(String sql);
//創建一個 PreparedStatement 對象來將參數化的 SQL 語句發送到數據庫
prepareStatement(String sql, int autoGeneratedKeys)
//創建一個默認 PreparedStatement 對象,該對象能獲取自動生成的鍵。
Statement//接口:用於執行靜態 SQL 語句並返回它所生成結果的對象
executeUpdate();//執行DML語句,返回值是影響行數
executeQuery();//執行DQL語句,返回值是查詢的結果集
execute();//執行任何語句
addBatch(String sql)//將給定的 SQL 命令添加到此 Statement 對象的當前命令列表中
executeBatch()//將一批命令提交給數據庫來執行,如果全部命令執行成功,則返回更新計數組成的數組
ResultSet//接口
/*表示數據庫結果集的數據表,通常通過執行查詢數據庫的語句生成;
ResultSet 對象具有指向其當前數據行的光標。最初,光標被置於第一行之前。next 方法將光標移動到下一行;
因爲該方法在 ResultSet 對象沒有下一行時返回 false,所以可以在 while 循環中使用它來迭代結果集。
beforeFirst() //將光標移動到此 ResultSet 對象的開頭,正好位於第一行之前。*/
//遍歷取出結果集中對象
ResultSet re=st.executeQuery(sql);
while(re.next()){
int id=re.getInt("id");//參數爲表頭序號或者表頭字段名
String username=re.getString(2);
/*處理這些零碎數據的方法:
把查詢出來的數據封裝到類裏
再把對象存到集合裏*/
}
演示一:模擬登錄
import java.sql.*;
import java.util.Scanner;
public class Example03 {
public static void main(String[] args) throws Exception{
Scanner sc = new Scanner(System.in);
System.out.println("請輸入用戶名:");
String username = sc.nextLine();
System.out.println("請輸入密碼:");
String password=sc.nextLine();
Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql:///mydb";
Connection conn = DriverManager.getConnection(url, "root", "123456");
String sql="select * from users where username=? and password=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1,username);
ps.setString(2,password);
ResultSet resultSet = ps.executeQuery();
//如果查到結果,說明登錄成功
if(resultSet.next()){
System.out.println("登錄成功!");
}else {
System.out.println("登錄失敗!");
}
ps.close();
resultSet.close();
conn.close();
}
}
演示二:批量操作
//部分代碼省略
String sql="insert into demo values(?)";
PreparedStatement ps = connection.prepareStatement(sql);
for (int i = 1; i < 10000; i++) {
ps.setInt(1,i);
ps.addBatch();
}
ps.executeBatch();
演示三:獲取自增長鍵的值
//部分代碼省略
//要獲取自增長鍵的值,需要在獲取操作對象時聲明一個參數 Statement.RETURN_GENERATED_KEYS
PreparedStatement preparedStatement = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
//獲取自增長鍵的結果集
ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
while (generatedKeys.next()){
keyValue = generatedKeys.getInt(1);
}
三、安全問題
SQL注入:通過把SQL命令插入到Web表單提交或輸入域名或頁面請求的查詢字符串,最終達到欺騙服務器執行惡意的SQL命令。
1、SQL注入案例:使用拼串的形式寫SQL語句
//部分代碼省略
Statement st=conn.createStatement();
String name="1'or'1'='1";
"select * from student name='"+name+"'";
st.executeQuery(sql);//執行SQL語句,此語句可以查出student表中所有信息
2、防止SQL注入:使用PrepareStatement 預編譯操作對象
//部分代碼省略
//SQL語句中的參數先用 ? 佔位
String name="張三";
String sql="select * from student where name=?"
PrepareStatement ps=conn.prepareStatement(sql);
//給 ? 賦值
ps.setString(1,name);//參一爲 ? 的索引,注意索引從1開始計算,參二是 ? 的值
ps.executeQuery(); //執行SQL語句,注意不再傳入SQL語句
四、調用存儲過程和函數
1、調用存儲過程
SQL語句格式 {call <procedure-name>[(<arg1>,<arg2>, ...)]}
Java代碼:部分省略
String sql="{call myPro(?,?)}";
CallableStatement callableStatement = connection.prepareCall(sql);
callableStatement.setInt(1,-1);//給第一個問號賦值
//確定第二個問號的數據類型
callableStatement.registerOutParameter(1, Types.VARCHAR);
callableStatement.execute();//執行SQL語句
String r = callableStatement.getString(2);//獲取返回值
System.out.println(r);
myPro 存儲過程代碼
DELIMITER $$
CREATE PROCEDURE pro_testIf(IN num INT,OUT str VARCHAR(2))
BEGIN
IF num>0 THEN
SET str='正數'; -- 注意要用分號結束
ELSEIF num<0 THEN --注意elseif 連寫
SET str='負數';
ELSE
SET str='零';
END IF; --注意要結束if,要寫分號
END $$
DELIMITER;
2、調用函數
SQL語句格式: {?= call <procedure-name>[(<arg1>,<arg2>, ...)]}
Java代碼:部分省略
String sql="{?=call myFun(?)}";
CallableStatement callableStatement = connection.prepareCall(sql);
callableStatement.setInt(2,1);//給第二個 ? 賦值,刪除 id=1 的人
callableStatement.registerOutParameter(1,Types.INTEGER);//確定第一個 ? 數據的類型
callableStatement.execute();//執行sql語句
int r = callableStatement.getInt(1);//獲取函數返回值
System.out.println(r);
myFun函數代碼
create function myFun(num int)
returns int
BEGIN
declare i int default 0;-- 定義i;相當於java中 //int i=0;
delete from mydemo where id=num;-- 刪除id=1的人
select count(*) from mydemo into i;-- 統計剩餘人數,賦給i
return i;-- 返回i
END;
表格
五、事務
概述:事務是指一組最小邏輯操作單元,裏面有多個操作組成。組成事務的每一部分必須要同時提交成功,如果有一個操作失敗,整個操作就回滾。
1、事務四大特性(ACID):
原子性(Atomicity):原子性是指事務是一個不可分割的工作單位,事務中的操作要麼都發生,要麼都不發生。
一致性(Consistency):事務必須使數據庫從一個一致性狀態變換到另外一個一致性狀態。
隔離性(Isolation):
事務的隔離性是多個用戶併發訪問數據庫時,數據庫爲每一個用戶開啓的事務,不能被其他事務的操作數據所幹擾,多個併發事務之間相互隔離。持久性(Durability):持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即使數據庫發生故障也不應該對其有任何影響。
2、事務的提交
默認情況下,Connection 對象處於自動提交模式,這意味着它在執行每個語句後都會自動提交更改,即它的所有 SQL 語句將被執行並作爲單個事務提交。
如果禁用了自動提交模式,那麼它的 SQL 語句將聚集到事務中,要提交更改就必須顯式調用 commit或rollback 方法,否則無法保存數據庫更改。
void setAutoCommit ( boolean autoCommit) //將此連接的自動提交模式設置爲給定狀態。
rollback()//取消在當前事務中進行的所有更改,並釋放此 Connection 對象當前持有的所有數據庫鎖。
commit()//使所有上一次提交/回滾後進行的更改成爲持久更改,並釋放此 Connection 對象當前持有的所有數據庫鎖。
setSavepoint()//在當前事務中創建一個未命名的保存點 (savepoint),並返回表示它的新 Savepoint 對象。
示例:簡易模擬轉賬
import utils.JDBC_Utils;
import java.sql.*;
public class Example00 {
public static void main(String[] args){
Connection conn=null;
PreparedStatement statement1=null;
PreparedStatement statement2=null;
PreparedStatement statement3=null;
PreparedStatement statement4=null;
Savepoint savepoint=null;
try {
conn = JDBC_Utils.getConnection();//獲取Connection對象方法,篇幅有限,不作展示
conn.setAutoCommit(false);//將conn 自動提交設置爲手動提交
//模擬第一次轉賬
String sql1 = "update bank set money=money-1000 where username='zhangsan'";
statement1 = conn.prepareStatement(sql1);
statement1.executeUpdate();
//System.out.println(1/0);
String sql2="update bank set money=money+1000 where username='lisi'";
statement2 = conn.prepareStatement(sql2);
statement2.executeUpdate();
//將以上兩個操作看做一個事務,只有這兩操作都完成纔會提交保存,否則回滾至初始狀態
savepoint = conn.setSavepoint();//創建一個保存點,可以指定回滾至此狀態
//模擬第二次轉賬,假設在此過程出現異常
String sql3 = "update bank set money=money-1000 where username='zhangsan'";
statement3 = conn.prepareStatement(sql3);
statement3.executeUpdate();
System.out.println(1/0);//模擬轉賬過程中的異常
String sql4="update bank set money=money+1000 where username='lisi'";
statement4 = conn.prepareStatement(sql4);
statement4.executeUpdate();
//將以上兩個操作看做第二個事務
} catch (Exception e) {
try {
if (savepoint != null) {
//如果savepoint不爲null且出現異常,說明第一次轉賬成功,第二次失敗,此時只需回滾至指定保存點即可
conn.rollback(savepoint);
}else {
//如果savepoint爲null,說明第一次轉賬異常,則回滾至初始狀態,即第一次之前
conn.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
try {
conn.commit();//手動提交事務,並釋放Conn 對象當前持有的所有數據庫鎖
conn.close();
if (statement1 != null) {
statement1.close();
}
if (statement2 != null) {
statement2.close();
}
if (statement3 != null) {
statement3.close();
}
if (statement4 != null) {
statement4.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
3、事務的隔離級別
1、Read uncommitted 讀未提交
當隔離級別設置爲Read uncommitted時,就可能出現髒讀。
2、Read committed 讀提交 ==>(Oracle默認級別)
當隔離級別設置爲Read committed時,避免了髒讀,但是可能會造成不可重複讀。
3、Repeatable read 重複讀 ==>(MySQL默認級別)
當隔離級別設置爲Repeatable read時,可避免髒讀、不可重複讀的發生,但可能會出現幻讀(錯誤讀取)。爲此級別。
4、Serializable 串行化
最高級別,可以避免所有問題,但效率很低,一般不使用
四種隔離級別的效率:
read uncommitted>read committed>repeatable read>serializable
四種隔離級別的安全性:
read uncommitted<read committed<repeatable read<serializable
設置查看事務的隔離級別
將數據庫的隔離級別設置成 讀未提交
set session transaction isolation level read uncommitted;
查看數據庫的隔離級別
select @@tx_isolation;
java中控制隔離級別: Connection
void setTransactionIsolation(int level) //level是常量