【聲明】
歡迎轉載,但請保留文章原始出處→_→
生命壹號:http://www.cnblogs.com/smyhvae/
文章來源:http://www.cnblogs.com/smyhvae/p/4050825.html
【正文】
首先需要回顧一下上一篇文章中的內容:MySQL數據庫學習筆記(八)----JDBC入門及簡單增刪改數據庫的操作
一、ResultSet接口的介紹:
對數據庫的查詢操作,一般需要返回查詢結果,在程序中,JDBC為我們提供了ResultSet接口來專門處理查詢結果集。
Statement通過以下方法執行一個查詢操作:
ResultSet executeQuery(String sql) throws SQLException
單詞Query就是查詢的意思。函數的返回類型是ResultSet,實際上查詢的數據並不在ResultSet里面,依然是在數據庫里,ResultSet中的next()方法類似於一個指針,指向查詢的結果,然后不斷遍歷。所以這就要求連接不能斷開。
ResultSet接口常用方法:
- boolean next() 遍歷時,判斷是否有下一個結果
- int getInt(String columnLabel)
- int getInt(int columnIndex)
- Date getDate(String columnLabel)
- Date getDate(int columnIndex)
- String getString(String columnLabel)
- String getString(int columnIndex)
二、ResultSet接口實現查詢操作:
步驟如下:(和上一篇博文中的增刪改的步驟類似哦)
- 1、加載數據庫驅動程序:Class.forName(驅動程序類)
- 2、通過用戶名密碼和連接地址獲取數據庫連接對象:DriverManager.getConnection(連接地址,用戶名,密碼)
- 3、構造查詢SQL語句
- 4、創建Statement實例:Statement stmt = conn.createStatement()
- 5、執行查詢SQL語句,並返回結果:ResultSet rs = stmt.executeQuery(sql)
- 6、處理結果
- 7、關閉連接:rs.close()、stmt.close()、conn.close()
我們來舉個例子吧,來查詢下面的這個表:
新建工程JDBC02,依舊先導入jar包。然后新建類,完整版代碼如下:
1 package com.vae.jdbc; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.ResultSet; 6 import java.sql.SQLException; 7 import java.sql.Statement; 8 9 public class JdbcQuey { 10 11 12 //數據庫連接地址 13 private final static String URL = "jdbc:mysql://localhost:3306/JDBCdb"; 14 //用戶名 15 public final static String USERNAME = "root"; 16 //密碼 17 public final static String PASSWORD = "smyh"; 18 //加載的驅動程序類(這個類就在我們導入的jar包中) 19 public final static String DRIVER = "com.mysql.jdbc.Driver"; 20 21 public static void main(String[] args) { 22 // TODO Auto-generated method stub 23 query(); 24 25 } 26 27 28 //方法:查詢操作 29 public static void query(){ 30 try { 31 Class.forName(DRIVER); 32 Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); 33 String sql = "select id,name,age,description from person"; 34 Statement state = conn.createStatement(); 35 //執行查詢並返回結果集 36 ResultSet rs = state.executeQuery(sql); 37 while(rs.next()){ //通過next來索引:判斷是否有下一個記錄 38 //rs.getInt("id"); //方法:int java.sql.ResultSet.getInt(String columnLabel) throws SQLException 39 int id = rs.getInt(1); //方法:int java.sql.ResultSet.getInt(int columnIndex) throws SQLException 40 41 String name = rs.getString(2); 42 int age = rs.getInt(3); 43 String description = rs.getString(4); 44 System.out.println("id="+id+",name="+name+",age="+age+",description="+description); 45 } 46 rs.close(); 47 state.close(); 48 conn.close(); 49 50 } catch (ClassNotFoundException e) { 51 e.printStackTrace(); 52 } catch (SQLException e) { 53 e.printStackTrace(); 54 } 55 } 56 }
關於代碼的解釋,可以看上一篇博客。上方代碼的核心部分是37至45行。
37行:next()函數:通過next來索引,判斷是否有下一個記錄。一開始就指向內存的首地址,即第一條記錄,如果返回值為true,指針會自動指向下一條記錄。
38、39行:getInt(String columnLabel)或者getInt(int columnIndex)代表的是列的索引,參數可以是列的名字,也可以用編號來表示,我們一般采用后者。編號的順序是按照33行sql語句中列的順序來定的。
程序運行后,后台輸出如下:
上一篇博客+以上部分,實現了對數據庫的簡單增刪改查的操作。其實這種拼接的方式很不好:既麻煩又不安全。我們接下來進行改進。
三、使用PreparedStatement重構增刪改查(推薦)
概念:表示預編譯的SQL語句的對象。SQL語句被預編譯並存儲在PreparedStatement對象中。然后可以使用此對象多次高效地執行該語句。PreparedStatement是Statement的一個接口。
作用:靈活處理sql語句中的變量。
舉例:
以下面的這張數據庫表為例:
新建Java工程文件JDBC3。新建一個Person類,方便在主方法里進行操作。Person類的代碼如下:
package com.vae.jdbc; public class Person { private int id; private String name; private int age; private String description; 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 getAge() { return age; } public void setAge(int age) { this.age = age; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Person(int id, String name, int age, String description) { super(); this.id = id; this.name = name; this.age = age; this.description = description; } public Person(String name, int age, String description) { super(); this.name = name; this.age = age; this.description = description; } public Person() { super(); } @Override public String toString() { return "Person [id=" + id + ", name=" + name + ", age=" + age + ", description=" + description + "]"; } }
上方是一個簡單的Person類,並添加set和get方法以及構造方法,無需多解釋。
插入操作:
現在在主類JDBCtest中實現插入操作,完整代碼如下:
1 package com.vae.jdbc; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.PreparedStatement; 6 import java.sql.SQLException; 7 8 public class JDBCtest { 9 10 11 //數據庫連接地址 12 public final static String URL = "jdbc:mysql://localhost:3306/JDBCdb"; 13 //用戶名 14 public final static String USERNAME = "root"; 15 //密碼 16 public final static String PASSWORD = "smyh"; 17 //驅動類 18 public final static String DRIVER = "com.mysql.jdbc.Driver"; 19 20 21 public static void main(String[] args) { 22 // TODO Auto-generated method stub 23 Person p = new Person("smyhvae",22,"我是在Java代碼中插入的數據"); 24 insert(p); 25 } 26 27 28 29 //方法:使用PreparedStatement插入數據 30 public static void insert(Person p){ 31 32 try { 33 Class.forName(DRIVER); 34 Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); 35 String sql = "insert into person(name,age,description)values(?,?,?)"; 36 PreparedStatement ps = conn.prepareStatement(sql); 37 //設置占位符對應的值 38 ps.setString(1, p.getName()); 39 ps.setInt(2, p.getAge()); 40 ps.setString(3, p.getDescription()); 41 42 ps.executeUpdate(); 43 44 ps.close(); 45 conn.close(); 46 47 48 } catch (ClassNotFoundException e) { 49 e.printStackTrace(); 50 } catch (SQLException e) { 51 e.printStackTrace(); 52 } 53 } 54 }
我們來看一下上面的代碼是怎么實現代碼的優化的:
30行:將整個person對象進去,代表的是數據庫中的一條記錄。
35行:問號可以理解為占位符,有幾個問號就代表要插入幾個列,這樣看來sql代碼就比較簡潔。
38至40行:給35行的問號設值,參數1代表第一個問號的位置,以此類推。
然后我們在main主方法中給Person設具體的值(23行),通過insert()方法就插入到數據庫中去了。數據庫中就多了一條記錄:
更新操作:
代碼和上方類似,修改操作的方法如下:
1 //方法:使用PreparedStatement更新數據 2 public static void update(Person p){ 3 try { 4 Class.forName(DRIVER); 5 Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); 6 String sql = "update person set name=?,age=?,description=? where id=?"; 7 PreparedStatement ps = conn.prepareStatement(sql); 8 //設置占位符對應的值 9 ps.setString(1, p.getName()); 10 ps.setInt(2, p.getAge()); 11 ps.setString(3, p.getDescription()); 12 ps.setInt(4, p.getId()); 13 14 ps.executeUpdate(); 15 16 ps.close(); 17 conn.close(); 18 19 20 } catch (ClassNotFoundException e) { 21 e.printStackTrace(); 22 } catch (SQLException e) { 23 e.printStackTrace(); 24 } 25 }
因為在這里有四個問號的占位符,所以稍后再main方法中記得使用四個參數的Person構造方法,傳遞四個參數。
刪除操作:
代碼和上方類似,方法如下:
1 //方法:使用PreparedStatement刪除數據 2 public static void delete(int id){ 3 try { 4 Class.forName(DRIVER); 5 Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); 6 String sql = "delete from person where id=?"; 7 PreparedStatement ps = conn.prepareStatement(sql); 8 //設置占位符對應的值 9 ps.setInt(1, id); 10 11 ps.executeUpdate(); 12 13 ps.close(); 14 conn.close(); 15 16 17 } catch (ClassNotFoundException e) { 18 e.printStackTrace(); 19 } catch (SQLException e) { 20 e.printStackTrace(); 21 } 22 }
這里的方法中,傳入的參數是是一個id。
查詢操作:
1 // 使用PreparedStatement查詢數據 2 public static Person findById(int id){ 3 Person p = null; 4 try { 5 Class.forName(DRIVER); 6 Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); 7 String sql = "select name,age,description from person where id=?"; 8 PreparedStatement ps = conn.prepareStatement(sql); 9 //設置占位符對應的值 10 ps.setInt(1, id); 11 12 ResultSet rs = ps.executeQuery(); 13 if(rs.next()){ 14 p = new Person(); 15 p.setId(id); 16 p.setName(rs.getString(1)); 17 p.setAge(rs.getInt(2)); 18 p.setDescription(rs.getString(3)); 19 //把 java.sql.Date 與 java.util.Date之間的轉換 20 // java.util.Date date = rs.getDate(4); 21 // ps.setDate(4, new java.sql.Date(date.getTime())); 22 23 } 24 rs.close(); 25 ps.close(); 26 conn.close(); 27 28 29 } catch (ClassNotFoundException e) { 30 e.printStackTrace(); 31 } catch (SQLException e) { 32 e.printStackTrace(); 33 } 34 return p; 35 }
查詢操作稍微麻煩一點,在方法中傳入的參數是id,方法的返回值是查詢的結果,即Person類。
四、PreparedStatement小結:
在JDBC應用中,如果你已經是稍有水平開發者,你就應該始終以PreparedStatement代替Statement。也就是說,在任何時候都不要使用Statement。
基於以下的原因:
- 一、代碼的可讀性和可維護性
- 二、PreparedStatement可以盡最大可能提高性能
- 三、最重要的一點是極大地提高了安全性
如果使用Statement而不使用PreparedStatement,則會造成一個安全性問題:SQL注入
來看一下SQL注入是怎么回事。現在有如下的一張用戶名密碼表user:
我們在執行如下sql語句進行查詢:
select id,name,pwd from user where name='xxx' and pwd = 'x' or '1'='1'
竟能出奇地查到所有的用戶名、密碼信息:
因為1=1永遠是成立的,所以這句話永遠都成立。所以在Java代碼中,可以利用這個漏洞,將上方的藍框部分內容當做pwd的變量的內容。來舉個反例:使用Statement寫一個登陸的操作:
1 //登 錄(Statement:會造成SQL注入的安全性問題) 2 public static void login(String name,String pwd){ 3 Person p = null; 4 try { 5 Class.forName(DRIVER); 6 Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); 7 // String sql = "select id,name,pwd from user where name='' and pwd=''"; 8 9 StringBuffer sql = new StringBuffer("select id,name,pwd from user where name='"); 10 sql.append(name).append("' and pwd='").append(pwd).append("'"); 11 Statement ps = conn.createStatement(); 12 13 ResultSet rs = ps.executeQuery(sql.toString()); 14 if(rs.next()){ 15 } 16 rs.close(); 17 ps.close(); 18 conn.close(); 19 20 21 } catch (ClassNotFoundException e) { 22 e.printStackTrace(); 23 } catch (SQLException e) { 24 e.printStackTrace(); 25 } 26 }
上方代碼中的第10行就是采用字符串拼接的方式,就會造成SQL注入的安全性問題。
而如果使用PreparedStatement中包含問號的sql語句,程序就會先對這句sql語句進行判斷,就不會出現字符串拼接的現象了。
五、完整版代碼:
最后附上本文中,PreparedStatement接口重構增刪改查的完整版代碼:
1 package com.vae.jdbc; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.PreparedStatement; 6 import java.sql.ResultSet; 7 import java.sql.SQLException; 8 9 public class JDBCtest { 10 11 12 //數據庫連接地址 13 public final static String URL = "jdbc:mysql://localhost:3306/JDBCdb"; 14 //用戶名 15 public final static String USERNAME = "root"; 16 //密碼 17 public final static String PASSWORD = "smyh"; 18 //驅動類 19 public final static String DRIVER = "com.mysql.jdbc.Driver"; 20 21 22 public static void main(String[] args) { 23 // TODO Auto-generated method stub 24 Person p = new Person(); 25 //insert(p); 26 //update(p); 27 //delete(3); 28 p = findById(2); 29 System.out.println(p); 30 } 31 32 33 //方法:使用PreparedStatement插入數據 34 public static void insert(Person p){ 35 36 try { 37 Class.forName(DRIVER); 38 Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); 39 String sql = "insert into person(name,age,description)values(?,?,?)"; 40 PreparedStatement ps = conn.prepareStatement(sql); 41 //設置占位符對應的值 42 ps.setString(1, p.getName()); 43 ps.setInt(2, p.getAge()); 44 ps.setString(3, p.getDescription()); 45 46 ps.executeUpdate(); 47 48 ps.close(); 49 conn.close(); 50 51 52 } catch (ClassNotFoundException e) { 53 e.printStackTrace(); 54 } catch (SQLException e) { 55 e.printStackTrace(); 56 } 57 } 58 59 60 //方法:使用PreparedStatement更新數據 61 public static void update(Person p){ 62 try { 63 Class.forName(DRIVER); 64 Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); 65 String sql = "update person set name=?,age=?,description=? where id=?"; 66 PreparedStatement ps = conn.prepareStatement(sql); 67 //設置占位符對應的值 68 ps.setString(1, p.getName()); 69 ps.setInt(2, p.getAge()); 70 ps.setString(3, p.getDescription()); 71 ps.setInt(4, p.getId()); 72 73 ps.executeUpdate(); 74 75 ps.close(); 76 conn.close(); 77 78 79 } catch (ClassNotFoundException e) { 80 e.printStackTrace(); 81 } catch (SQLException e) { 82 e.printStackTrace(); 83 } 84 } 85 86 87 //方法:使用PreparedStatement刪除數據 88 public static void delete(int id){ 89 try { 90 Class.forName(DRIVER); 91 Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); 92 String sql = "delete from person where id=?"; 93 PreparedStatement ps = conn.prepareStatement(sql); 94 //設置占位符對應的值 95 ps.setInt(1, id); 96 97 ps.executeUpdate(); 98 99 ps.close(); 100 conn.close(); 101 102 103 } catch (ClassNotFoundException e) { 104 e.printStackTrace(); 105 } catch (SQLException e) { 106 e.printStackTrace(); 107 } 108 } 109 110 111 // 使用PreparedStatement查詢數據 112 public static Person findById(int id){ 113 Person p = null; 114 try { 115 Class.forName(DRIVER); 116 Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); 117 String sql = "select name,age,description from person where id=?"; 118 PreparedStatement ps = conn.prepareStatement(sql); 119 //設置占位符對應的值 120 ps.setInt(1, id); 121 122 ResultSet rs = ps.executeQuery(); 123 if(rs.next()){ 124 p = new Person(); 125 p.setId(id); 126 p.setName(rs.getString(1)); 127 p.setAge(rs.getInt(2)); 128 p.setDescription(rs.getString(3)); 129 //把 java.sql.Date 與 java.util.Date之間的轉換 130 // java.util.Date date = rs.getDate(4); 131 // ps.setDate(4, new java.sql.Date(date.getTime())); 132 133 } 134 rs.close(); 135 ps.close(); 136 conn.close(); 137 138 139 } catch (ClassNotFoundException e) { 140 e.printStackTrace(); 141 } catch (SQLException e) { 142 e.printStackTrace(); 143 } 144 return p; 145 } 146 147 148 }