JDBC是一組可以執行SQL語句的API
1. 鏈接數據庫的步驟:
1) 註冊驅動
JDBC類庫向DriverManager註冊數據庫驅動·
2) 創建鏈接java
使用DriverManager提供的getConnection()方法鏈接到數據庫
3) 建立語句mysql
經過數據庫的鏈接對象的createStatement方法創建SQL語句對象
4) 執行語句sql
執行SQL語句,並將結果集合返回到ResultSet中
5) 處理結果數據庫
使用while循環讀取結果
6) 釋放資源(注意關閉的順序)服務器
demo框架
DriverManager.registerDriver(newcom.mysql.jdbc.Driver());性能
System.setProperty("jdbc.drivers", "com.mysql.jdbc.Driver");優化
//加載驅動方式url
Class.forName("com.mysql.jdbc.Driver");//推薦方式spa
//2.創建鏈接
String url = "jdbc:mysql:localhost:3306/jdbc";
String user = "root";
String password = "mysql";
Connection conn = DriverManager.getConnection(url,user,password);
//3.建立語句
Statement st = conn.createStatement();
//4.執行語句
ResultSet rs = st.executeQuery("select * from user");
//5.處理結果
while(rs.next()){//按行來遍歷
System.out.println(rs.getObject(1) + "\t" + rs.getObject(2) + "\t" +rs.getObject(3) + "\t" + rs.getObject(4) );
}
//6.釋放資源(注意關閉的順序)
rs.close();
st.close();
conn.close();
1.創建鏈接:
創建鏈接分爲兩個關鍵步驟:①加載驅動程序;②創建鏈接。此處所涉及到的Java類有:Class、Driver、DriverManager。
創建鏈接,需要提供3個必不可少的參數:①數據庫名稱;②數據庫帳號;③數據庫密碼。
關鍵代碼:
① 加載驅動程序:Class.forName(「com.mysql.jdbc.Driver」);
② 創建鏈接:
Connection conn=DriverManager.getConnection(url,name,pwd);
此處,String url=」jdbc:mysql://localhost:3306/」+數據庫名稱
String name=數據庫帳號
String pwd=數據庫密碼
須要說明的是,這兩句關鍵代碼所用的方法都會拋出異常,故應將這兩句放在try-catch塊裏。
import java.sql.*; public class TestConnection{ static { try{ //加載驅動程序 Class.forName(「com.mysql.jdbc.Driver」); } catch(Exeception e){ e.printStackTrace(); } }
public static void main(String[] args){ String url=」jdbc:mysql://localhost:3306/lt_atm」; String name=」admin」; String pwd=」lt11235」; Connection conn=null; try{ //創建數據庫鏈接 conn=DriverManager.getConnection(url,name,pwd); } catch(SQLExeception e){ e.printStackTrace(); } finally{ try{ conn.close(); } catch(SQLException e){ e.printStackTrace(); }
} } } |
通常,咱們在靜態代碼塊里加載驅動程序,不須要每次調用時都從新加載一次驅動。
創建鏈接過程——流程圖總結:
2.執行SQL命令:
①在創建起來的鏈接線上建立Statement對象;②用新建的Statement對象傳送SQL命令;③接收Statement對象返回過來的結果。
管理系統將所接受的SQL命令分爲兩類:①寫,具體來講就是增刪改;②讀,即查。對於寫性質的SQL命令,管理系統會將受到影響的記錄條數交給信使Statement;對於讀性質的SQL命令,管理系統會將知足條件的全部記錄封裝在一個對象裏交給信使Statement,而這個對象就是ResultSet類的對象。如今咱們來看一些這些動做的關鍵代碼:
增刪改:stmt.executeUpdate(sql),返回修改的記錄條數
查:stmt.executeQuery,返回結果對象
關鍵代碼:
建立信使
Statement stmt=conn.createStatement();
傳送SQL命令&接收返回結果
l 添加記錄:
String sql=」INSERT INTO account VALUES (‘1132’,’金龍’,’男’,10000) 」;
int count=stmt.executeUpdate(sql);
修改記錄:
String sql=」UPDATE account SET name=’無花’ WHERE accId=’1132’」;
int count=stmt.executeUpdate(sql);
刪除記錄:
String sql=」DELETE FROM account WHERE accId=’1132’」;
int count=stmt.executeUpdate(sql);
l 讀性質的命令:
查詢記錄:
String sql=」SELECT * FROM account」;
ResultSet rs=stmt.executeQuery(sql);
接下來,咱們以具體的程序例子來講明傳送SQL命令的過程;再以流程圖來總結該過程:
傳送SQL命令過程——代碼示例:
import java.sql.*;
public class TestExecute{
static {
try{
//加載驅動程序
Class.forName(「com.mysql.jdbc.Driver」);
System.out.println(「Success loading Driver!」);
}
catch(Exeception e){
e.printStackTrace();
}
}
public static void main(String[] args){
String url=」jdbc:mysql://localhost:3306/lt_atm」;
String name=」admin」;
String pwd=」lt11235」;
Connection conn=null;
try{
//創建數據庫鏈接
conn=DriverManager.getConnection(url,name,pwd);
Statement stmt=conn.createStatement();
//添加記錄的SQL命令
String sql_1=」INSERT INTO account VALUES
(‘1132’,’金龍’,’男’,10000) 」;
//修改記錄的SQL命令
String sql_2=」UPDATE account SET name=’無花’
WHERE accId=’1132’」;
//刪除記錄的SQL命令
String sql_3=」DELETE FROM account WHERE accId=’1132’」;
//查詢記錄的SQL命令
String sql_4=」SELECT * FROM account」;
//傳送SQL命令
int count_1=stmt.executeUpdate(sql_1);
int count_2= stmt.executeUpdate(sql_2);
int count_3= stmt.executeUpdate(sql_3);
ResultSet rs=stmt.executeQuery(sql_4);
}
catch(SQLExeception e){
e.printStackTrace();
}
finally{
try{
conn.close();
}
catch(SQLException e){
e.printStackTrace();
}
}
}
}
傳送SQL命令過程——流程圖總結:
3.從返回的對象(結果集)中讀取數據:
當是查找數據時,返回的是結果對象集合。
如何從返回的ResultSet對象中讀取數據呢?
ResultSet對象中,除了每一條記錄各佔用一個位置,還有一些特殊做用的位置,其中有兩個分別是:beforeFirst、afterLast。他們分別是第一條記錄的前一個位置、最後一條記錄的下一個位置。當咱們用ResultSet對象封裝了查詢到的記錄,遊標初始位置置爲beforeFirst。
ResultSet類中有一個next()方法,其做用就是移動遊標,使其指向下一個位置。該方法的返回值是一個boolean類型變量,當移動遊標後能指向一個有效記錄,返回true,不然(好比說移動到了afterLast位置)返回false。ResultSet類還有一系列方法,能夠統一寫成getXXX()。其做用是從遊標指向的記錄中讀取數據。其中XXX是String、Int、Double類的變量類型,取決於所要讀取的字段的數據類型。
思路:①用獲得的ResultSet對象調用next()方法,若返回true轉向②,不然轉向③;②調用getXXX方法,從遊標當前指向的記錄中讀取各個字段的數值;而後調用next方法,若返回true,轉向②,不然轉向③。③讀取過程完成,正常結束。
讀取數據的過程——代碼示例:
import java.sql.*;
public class TestExecute{
static {
try{
//加載驅動程序
Class.forName(「com.mysql.jdbc.Driver」);
System.out.println(「Success loading Driver!」);
}
catch(Exeception e){
e.printStackTrace();
}
}
public static void main(String[] args){
String url=」jdbc:mysql://localhost:3306/lt_atm」;
String name=」admin」;
String pwd=」lt11235」;
Connection conn=null;
try{
//創建數據庫鏈接
conn=DriverManager.getConnection(url,name,pwd);
System.out.println(「Success eastablishing the Connection…」);
Statement stmt=conn.createStatement();
//查詢記錄的SQL命令
String sql=」SELECT * FROM account」;
//傳送SQL命令
ResultSet rs=stmt.executeQuery(sql);
//讀取數據
while(rs.next()){
String accId=rs.getString(1); //或者 rs.getString(「accId」);
String name=rs.getString(2);//或者 rs.getString(「name」);
Char sex=rs.getChar(3);//或者 rs.getChar(「sex」);
int count=rs.getInt(4);//或者 rs.getInt(「count」);
System.out.println(accId+」/t」+name+」/t」+sex+」/t」+count);
}
}
catch(SQLExeception e){
e.printStackTrace();
}
finally{
try{
conn.close();
}
catch(SQLException e){
e.printStackTrace();
}
}
}
}
讀取數據的過程——流程圖總結:
動態SQL:程序運行時根據條件執行具體的內容。
1、條件動態化:
編寫一個方法,他從外界接收一個參數,並用這個參數使方法內部的SQL條件具體化,而後return一個結果。具體代碼以下:
public static ResultSet sqlQuery(String accName){
String sql=」SELECT * FROM account WHERE name=’」+ accName +」’」;
.............................
return ResultSet;
}
2、字段動態化:
承上所述,此處所謂的字段動態化,就是咱們不只能按姓名來查詢記錄,還能根據其餘字段來查詢:好比說根據編號,根據身份,甚至是根據性別。在上文sqlQuery方法的基礎上繼續擴展,其代碼以下:
public static ResultSet sqlQuery(String column,String value){
String sql=」SELECT * FROM account WHERE
’」+ column +」’=’」+ value +」’」;
.............................
return ResultSet;
}
3、數據表動態化:
目標是使他能不受具體數據表的限制(傳入一個數據表參數),而是根據咱們不一樣的須要,訪問不一樣的數據表。代碼:
public static ResultSet sqlQuery(String table,String column,String value){
String sql=」SELECT * FROM ’」+ table +」’ WHERE
’」+ column +」’=’」+ value +」’」;
.............................
.............................
return ResultSet;
}
4、PreparedStatement類的介紹:
PreparedStatement是繼承自Statement的類
PreparedStatement擁有Statement全部的特性和方法。可是與Statement不一樣在於PreparedStatement能夠將SQL命令事先編譯,並存儲在PreparedStatement對象中,當須要執行屢次類似的SQL命令時,可以比較高效地執行。
另外,PreparedStatement最主要的功能就是可以輸入條件式的SQL命令
import java.sql.*;
public class VisitDB{
static {
try{
//加載驅動程序
Class.forName(「com.mysql.jdbc.Driver」);
}
catch(Exeception e){
e.printStackTrace();
}
}
//負責創建與數據庫管理系統之間的鏈接
public static Connection getCon(){
String url=」jdbc:mysql://localhost:3306/lt_atm」;
String name=」admin」;
String pwd=」lt11235」;
Connection conn=null;
try{
//創建數據庫鏈接
conn=DriverManager.getConnection(url,name,pwd);
}
catch(SQLExeception e){
conn=null;
e.printStackTrace();
}
return conn;
}
//負責動態執行查詢語句
public static ResultSet sqlQuery(String table,String column,String value){
Connection conn= VisitDB.getCon();
//條件式SQL命令,?表示待定參數
String sql=」SELECT * FROM ? WHERE ?=?」;
try{
//建立pstmt時就將sql命令傳過去,使其預先編譯
PreparedStatement pstmt=conn.prepareStatement(sql);
//表示第1個?的值爲String類型的table
pstmt.setString(1,table);
//表示第2個?的值爲String類型的column
pstmt.setString(2, column);
//表示第3個?的值爲String類型的value
pstmt.setString(3, value);
//讓pstmt對象執行將編譯好的SQL命令傳過去,並接收結果
ResultSet rs=pstmt.executeQuery();
return rs;
}
catch(Exception e){
return null;
e.printStackTrace();
}
}
//負責關閉再也不用的對象,釋放系統資源
public static void close(ReaultSet rs,Statement stmt,Connection conn){
try{
rs.close();
stmt.close();
conn.close();
}
catch(Exception e){
e.printStackTrace();
}
}
}
好了,此時咱們的三個動態擴展也用PreparedStatement實現了。不得不提的是PreparedStatement類的setXXX系列的方法並非只有setString一種,只是上例中咱們只用到了這一種而已
1、Statement的批處理功能
Statement提供了批處理的功能,批處理多用在執行寫操做的SQL命令時,若是一次要增刪改不少條記錄,使用批處理會減小對象在程序與數據庫系統之間的交互,從而提升其性能。批處理的流程以下所示:
方法說明:
1、public void clearBatch() throws SQLException
做用:將Statement對象的Batch中的全部SQL命令清空
2、public void addBatch(String sql) throws SQLException
做用:插入一個SQL命令到Statemen對象中的Batch中
3、public int[] executeBatch() throws SQLException
做用:將Statement對象的Batch中的全部SQL命令傳給數據庫系統
代碼示例:
Import java.sql.*;
public class TestBatch{
String[] sqls={「INSERT INTO account VALUES(‘13579’ , ’雲飛’ , ‘男’ , 100)」,
」DELETE FROM account WHERE name=’無花’」,
」UPDATE account SET name=’鐵手’ WHERE accId=’2468’」,}
public void testBatch(){
Connection conn=null;
try{
conn= VisitDB.getCon();
Statement stmt=conn.createStatement();
stmt.clearBatch();
for(int i=0;i<sqls.length;i++){
stmt.addBatch(sqls[i]);
}
stmt.executeBatch();
}
catch(Exception e){
e.printStackTrace();
}
}
}
2、PreparedStatement的批處理功能:
PreparedStatement是Statement的子類,因此他也有批處理功能,其流程以下所示:
圖中所涉及到的方法都已說過,就再也不贅述了。如今咱們直接來看一下代碼示例:
public void testPreBatch(){
try{
Connection conn= VisitDB.getCon();
PreparedStatement pstmt=conn.prepareStatement(
「DELETE FROM account WHERE name=?」);
pstmt.clearBatch();
pstmt.setString(「金龍」);
pstmt.addBatch();
pstmt.setString(「無花」);
pstmt.addBatch();
pstmt.setString(「楚香帥」);
pstmt.addBatch();
pstmt.executeBatch();
}
catch(Exception e){
e.printStachTrace();
}
}
事務最主要的功能是用來確保多個連續的數據庫操做,能做爲一個總體被對待。即在執行時,要麼所有執行成功,不然所有執行失敗——回到最初狀態。
流程說明:
1、Connection對象調用setAutoCommit()方法,將AutoCommit設置爲false,以取消自動提交事務機制(自動提交事務機制將每一條SQL命令看作是一個事務,每作一次數據庫操做,會自動提交一次)。
2、當一個邏輯事務所包含的SQL命令都被執行後,調用commit()方法,向數據庫系統提交事務;這條語句一般是try塊裏最後一句。
3、中途若是發生任何錯誤或不明緣由致使操做中斷,調用rollback()方法,要求數據庫系統執行Rollback操做。
方法說明:
public void setAutoCommit(boolean autoCommit) throws SQLException
說明:設置是否自動提交事務。當true時,每執行一條SQL命令,數據庫系統自動提交一次事務,即對數據庫裏的數據進行操做;當爲false時,數據庫系統不自動提交事務,知道遇到調用的commit()方法。
public boolean getAutoCommit() throws SQLException
說明:得到當前的Commit方式
public void commit() throws SQLException
說明:指示數據庫系統提交事務,將結果寫進數據庫
public void rollback() throws SQLException
說明:指示數據庫執行恢復操做,將數據恢復到最初狀態
流程圖:
代碼示例:
public void testTransaction(){
try{
Connection conn= VisitDB.getCon();
conn.setAutoCommit(false);
Statement stmt=con.createStatement();
String sql_1=」UPDATE account SET balance=balance-1000
WHERE accId=’A’」;
String sql_1=」UPDATE account SET balance=balance+1000
WHERE accId=’B’」;
conn.commit();
}
catch(SQLException ex){
ex.printStackTrace();
try{
conn.rollback();
}
catch(SQLException e){
e.printStackTrace();
}
}
}
PreparedStatement(從Statement擴展而來)相對Statement的優勢:
1、沒有SQL注入的問題。
2、Statement會使數據庫頻繁編譯SQL,可能會形成數據庫緩衝區溢出。
3、數據庫和驅動能夠對PreparedStatement進行優化(只有在相關聯的數據庫鏈接沒有關閉的狀況下有效)。
爲何要始終使用PreparedStatement代替Statement?
1 代碼可維護性
(辨別: Statement:使用 + PreparedStatement:使用?)
stmt.executeUpdate("insert into tb_name (col1,col2,col2,col4) values ('"+var1+"','"+var2+"',"+var3+",'"+var4+"')");
注意Statement和PreparedStatement語句上的區別:Statement是在執行的時候才傳入SQL語句,而PreparedStatement在建立的時候就已經傳入了SQL語句。
perstmt = con.prepareStatement("insert into tb_name (col1,col2,col2,col4) values (?,?,?,?)");
perstmt.setString(1,var1);
perstmt.setString(2,var2);
perstmt.setString(3,var3);
perstmt.setString(4,var4);
perstmt.executeUpdate();
2 防止SQL注入
對於JDBC而言,SQL注入攻擊只對Statement有效,對PreparedStatement是無效的,這是由於PreparedStatement不容許在插入時改變查詢的邏輯結構.
SQL注射原理
SQL 注射能使攻擊者繞過認證機制,徹底控制遠程服務器上的數據庫
登錄驗證
假如後臺的sql語句時這樣拼接的
select id from test where username='"+myname+"' and password='"+mypasswd+"' ";
表面上看,若是用戶名和口令對匹配,那麼該用戶經過認證;不然,該用戶不會經過認證——可是,事實果然如此嗎?這裏並無對SQL命令進行設防,因此攻擊者徹底可以在用戶名或者口令字段中注入SQL語句,從而改變SQL查詢 。爲此,咱們仔細研究一下上面的SQL查詢字符串:
上述代碼認爲字符串username和password都是數據,不過,攻擊者卻能夠爲所欲爲地輸入任何字符 。若是一位攻擊者輸入的用戶名爲
‘’OR 1=1—
而口令爲
x
雙劃符號--告訴SQL解析器,右邊的東西所有是註釋,因此沒必要理會。這樣,查詢字符串至關於:
select id from test where username='' or 1=1;
由於如今只要用戶名爲長度爲零的字符串''或1=1這兩個條件中一個爲真,就返回用戶標識符ID——咱們知 道,1=1是恆爲真的。因此這個語句將返回user_table中的全部ID。在此種狀況下,攻擊者在username字段放入的是SQL指令 'OR1=1--而非數據。
更爲嚴重的狀況是當username對應的是'OR1=1;DROPTABLEuser_table;--
數據庫中執行的sql語句就變成了:
select id from test where username='' or 1=1;drop table test
這個語句將執行句法上徹底正確的SELECT語句,並利用drop命令清空test表。
應對策略
問題的關鍵就是不要用string構造sql語句,這樣就不會利用輸入的參數構造sql語句了。因此要用PreparedStatement替換Statement,即用佔位符做爲實參定義sql語句,從而避免sql注入攻擊。
無論什麼框架,仍是純JDBC,只用Preparedstatement,必定要用佔位符做爲實參來構造sql(或hql)語句。
String sql= "select * from test where usernmae=? and password=? " ;
PreparedStatement psm=conn.preparedStatement(sql);
psm.setString(1,myname);
psm.setString(2,mypasswd);
Result rs=psm.executeQuery();
if (rs.next){
rs.close();
con.close();
return false ;
}
else {
rs.close();
con.close();
return true ;
}