寫在前面的話:
(1)使用JDBC,必須要使用對應的jar包,該筆記中使用jar包:
mysql-connector-java-5.1 .6-bin.jar
(2)使用連接池,一定要導入對象的數據庫連接池包,該筆記中使用jar包:
c3p0-0.9.1.2.jar
常用連接池:dbcp連接池、c3p0連接池、druid連接池
第一版:不用數據庫連接池:
1 package cn; 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.ResultSetMetaData; 8 import java.sql.SQLException; 9 import java.util.ArrayList; 10 import java.util.HashMap; 11 import java.util.List; 12 import java.util.Map; 13 14 public class SqlService { 15 16 // 四大金剛 17 String driver = "com.mysql.jdbc.Driver" ;// 驅動名稱 18 String url = "jdbc:mysql:///mao" ;// 連接字符串 19 String username = "root" ;// 用戶名 20 String password = "123456" ;// 密碼 21 22 // 三劍客 23 Connection con = null ;// 連接對象 24 PreparedStatement pstmt = null ;// 語句對象 25 ResultSet rs = null ;// 結果集對象 26 27 /** 28 * 獲得連接對象 29 * 30 * @return 連接對象 31 * @throws ClassNotFoundException 32 * @throws SQLException 33 */ 34 public Connection getConnection() throws ClassNotFoundException, 35 SQLException { 36 Class. forName( driver); 37 con = DriverManager.getConnection( url , username , password ); 38 return con ; 39 } 40 41 /** 42 * 關閉三劍客 43 * 44 * @throws SQLException 45 */ 46 public void close(ResultSet rs, PreparedStatement pstmt, Connection con) { 47 48 try { 49 if (rs != null) 50 rs.close(); 51 if (pstmt != null) 52 pstmt.close(); 53 if (con != null) 54 con.close(); 55 } catch (SQLException e) { 56 // TODO: handle exception 57 e.printStackTrace(); 58 } 59 } 60 61 /** 62 * 執行更新 63 * 64 * @param sql 65 * 傳入的預設的 sql語句 66 * @param params 67 * 問號參數列表 68 * @return 影響行數 69 */ 70 public int execUpdate(String sql, Object[] params) { 71 try { 72 this .getConnection();// 獲得連接對象 73 this .pstmt = this. con .prepareStatement(sql);// 獲得預設語句對象 74 75 if (params != null) { 76 // 設置參數列表 77 for (int i = 0; i < params. length; i++) { 78 // 因為問號參數的索引是從1開始,所以是i+1,將所有值都轉為字符串形式,好讓setObject成功運行 79 this .pstmt .setObject(i + 1, params[i] + "" ); 80 } 81 } 82 83 return this .pstmt .executeUpdate(); // 執行更新,並返回影響行數 84 85 } catch (ClassNotFoundException | SQLException e) { 86 // TODO Auto-generated catch block 87 e.printStackTrace(); 88 } finally { 89 this .close( this. rs, this. pstmt , this .con ); 90 } 91 return 0; 92 } 93 94 /** 95 * 執行查詢 96 * 97 * @param sql 98 * 傳入的預設的 sql語句 99 * @param params 100 * 問號參數列表 101 * @return 查詢后的結果 102 */ 103 public List<Map<String, Object>> execQuery(String sql, Object[] params) { 104 105 try { 106 this .getConnection();// 獲得連接對象 107 this .pstmt = this. con .prepareStatement(sql);// 獲得預設語句對象 108 109 if (params != null) { 110 // 設置參數列表 111 for (int i = 0; i < params. length; i++) { 112 // 因為問號參數的索引是從1開始,所以是i+1,將所有值都轉為字符串形式,好讓setObject成功運行 113 this .pstmt .setObject(i + 1, params[i] + "" ); 114 } 115 } 116 117 // 執行查詢 118 ResultSet rs = pstmt .executeQuery(); 119 120 List<Map<String, Object>> al = new ArrayList<Map<String, Object>>(); 121 122 // 獲得結果集元數據(元數據就是描述數據的數據,比如把表的列類型列名等作為數據) 123 ResultSetMetaData rsmd = rs.getMetaData(); 124 125 // 獲得列的總數 126 int columnCount = rsmd.getColumnCount(); 127 128 // 遍歷結果集 129 while (rs.next()) { 130 Map<String, Object> hm = new HashMap<String, Object>(); 131 for (int i = 0; i < columnCount; i++) { 132 // 根據列索引取得每一列的列名,索引從1開始 133 String columnName = rsmd.getColumnName(i + 1); 134 // 根據列名獲得列值 135 Object columnValue = rs.getObject(columnName); 136 // 將列名作為key,列值作為值,放入 hm中,每個 hm相當於一條記錄 137 hm.put(columnName, columnValue); 138 } 139 // 將每個 hm添加到al中, al相當於是整個表,每個 hm是里面的一條記錄 140 al.add(hm); 141 } 142 143 return al; 144 145 } catch (ClassNotFoundException | SQLException e) { 146 // TODO Auto-generated catch block 147 e.printStackTrace(); 148 } finally { 149 this .close( this. rs, this. pstmt , this .con ); 150 } 151 return null ; 152 } 153 }
代碼解釋:
一:為什么直接使用 “Class. forName( driver); ”注冊驅動
①原始注冊驅動寫法:
1 // 注冊驅動:告訴 java 去連接哪種數據庫 2 //DriverManager :驅動管理類,負責管理驅動,創建連接對象 3 DriverManager. registerDriver( new com.mysql.jdbc.Driver());
static void
|
向
DriverManager
注冊給定驅動程序。
|
參數:Driver=====來哦子欲你要連接的數據庫(如果連接orcale,來自於oracle,如果連接mysql,來自於mysql)
備注:查詢
Drive源碼
1 public class Driver extends NonRegisteringDriver implements java.sql.Driver { 2 // ~ Static fields/initializers 3 // --------------------------------------------- 4 5 // 6 // Register ourselves with the DriverManager 7 // 8 static { 9 try { 10 java.sql.DriverManager. registerDriver( new Driver()); 11 } catch (SQLException E) { 12 throw new RuntimeException( "Can't register driver!" ); 13 } 14 }
可以看到:
java.sql.DriverManager. registerDriver( new Driver());是寫在靜態代碼塊中,只要調用Driver這個方法,就會自動運行這段代碼,這樣會造成“注冊兩次驅動”
因此,我們可以改成如下版本:
②改進版
1 // 注冊驅動:告訴 java去連接哪種數據庫 2 //DriverManager.registerDriver(new com.mysql.jdbc.Driver()); 3 new com.mysql.jdbc.Driver();
但是這樣寫:
1、靜態初始化已經new了一個Driver對象,注冊到DriverManager中去,在此再創建一個Driver對象則是完全沒有必要的,浪費空間;
2、不夠靈活,如果需要換數據庫的話,需要改動代碼,此時最好把要變得內容改成字符串的形式,可以由變量讀取外部配置文件進行傳入(由此可以解釋,為什么源代碼中要直接寫成靜態代碼塊)
③最終版
1 // 注冊驅動:告訴 java 去連接哪種數據庫 2 //DriverManager.registerDriver(new com.mysql.jdbc.Driver()); 3 //new com.mysql.jdbc.Driver(); 4 Class. forName( "com.mysql.jdbc.Driver");
最終版的好處:
1、Class.forName()的作用是要求JVM查找並加載指定的類,也就是說JVM會執行該類的靜態代碼段。
2、既然在靜態初始化器中已經進行了注冊,所以我們在使用JDBC時只需要Class.forName(XXX.XXX)就可以了
二:獲得連接對象
con = DriverManager.getConnection( url , username , password );
url:連接到某一個具體的數據庫
user:數據庫的用戶名
password:數據庫用戶名對應的密碼
url = "jdbc:mysql://localhost:3306/mao"
jdbc: jdbc協議
mysql: jdbc子協議(表示mysql協議)
備注:
url其他寫法一:
url = jdbc:mysql://localhost:3306/mao?userUnicode=true&characterEncoding=utf8 //?userUnicode=true&characterEncoding=utf8 //加上這個可以解決數據庫的亂碼問題
/*
*1、存數據時:
*數據庫在存放項目數據的時候會先用UTF-8格式將數據解碼成字節碼,然后再將解碼后的字節碼重新使用GBK編碼存*放到數據庫中。
*
*2.取數據時:
* 在從數據庫中取數據的時候,數據庫會先將數據庫中的數據按GBK格式解碼成字節碼,然后再將解碼后的字節*碼重新按UTF-8格式編碼數據,最后再將數據返回給客戶端。
*
*/
url其他寫法二:
//如果連接的mysql是在本機,並且mysql的端口是3306,比如: url:"jdbc:mysql://localhost:3306/mao" //那么url可以簡寫為: url:"jdbc:mysql:///mao"
三、語句對象
1 // 創建語句對象Statement 2 Statement stmt = con.createStatement(); 3 4 //創建預設語句對象PreparedStatement 5 String sql = "update class set classDesc = ? where id = ?"; 6 PreparedStatement pstmt = con.prepareStatement(sql);
接下來是重點:
1:Statement的缺陷:
①容易被sql注入
sql注入:在頁面表單中輸入sql的片段。達到串改程序中sql語句。
正常情況:
select * from user where username='zhangsan' and password = '123456';
當sql被注入之后的語句:
select * from user where username='zhangsan' and password = '' or '1'='1'
②效率不高
Statement每執行一次,sql進行一次編譯。即使每次的sql語句格式都一樣
2:PreparedStatement的優勢
①防止sql注入
PreparedStatement會對特殊字符進行轉譯,並通過參數形式設置到語句中,而不再是變量拼湊成sql語句
②預編譯功能
相同格式的sql語句只被編譯一次,后一條格式相同的sql語句運行時,只需改變參數的值即可
第二版:使用數據庫連接池
代碼前備注:
本工具類代碼使用c3p0連接池
c3p0-config.xml
1 <? xml version ="1.0" encoding= "UTF-8" ?> 2 < c3p0-config> 3 <!-- 默認配置,c3p0框架默認加載這段默認配置 --> 4 < default-config> 5 <!-- 配置JDBC 四個基本屬性 --> 6 < property name ="driverClass" > com.mysql.jdbc.Driver</ property > 7 < property name ="jdbcUrl" > jdbc:mysql:///數據庫名</ property > 8 < property name ="user" > 數據庫用戶名</ property > 9 < property name ="password" > 數據庫密碼</ property > 10 </ default-config> <!-- This app is massive! --> 11 </ c3p0-config>
1 package cn.service; 2 3 import java.sql.Connection; 4 import java.sql.PreparedStatement; 5 import java.sql.ResultSet; 6 import java.sql.ResultSetMetaData; 7 import java.sql.SQLException; 8 import java.util.ArrayList; 9 import java.util.HashMap; 10 import java.util.List; 11 import java.util.Map; 12 13 import org.junit.Test; 14 15 import com.mchange.v2.c3p0.ComboPooledDataSource; 16 import com.mchange.v2.c3p0.util.TestUtils; 17 18 public class DBService { 19 20 // 三劍客 21 Connection con = null;// 連接對象 22 PreparedStatement pstmt = null;// 語句對象 23 ResultSet rs = null;// 結果集對象 24 25 /** 26 * 獲得連接對象 27 * 28 * @return 連接對象 29 * @throws ClassNotFoundException 30 * @throws SQLException 31 */ 32 public Connection getConnection() throws ClassNotFoundException, 33 SQLException { 34 35 // 創建c3p0連接池 36 ComboPooledDataSource ds = new ComboPooledDataSource("itcast"); 37 // 通過連接池對象創建連接 38 con = ds.getConnection(); 39 return con; 40 } 41 42 /** 43 * 關閉三劍客 44 * 45 * @throws SQLException 46 */ 47 public void close(ResultSet rs, PreparedStatement pstmt, Connection con) { 48 49 try { 50 if (rs != null) 51 rs.close(); 52 if (pstmt != null) 53 pstmt.close(); 54 if (con != null) 55 con.close(); 56 } catch (SQLException e) { 57 // TODO: handle exception 58 e.printStackTrace(); 59 } 60 } 61 62 /** 63 * 執行更新 64 * 65 * @param sql 66 * 傳入的預設的sql語句 67 * @param params 68 * 問號參數列表 69 * @return 影響行數 70 */ 71 public int execUpdate(String sql, Object[] params) { 72 try { 73 this.getConnection();// 獲得連接對象 74 this.pstmt = this.con.prepareStatement(sql);// 獲得預設語句對象 75 76 if (params != null) { 77 // 設置參數列表 78 for (int i = 0; i < params.length; i++) { 79 // 因為問號參數的索引是從1開始,所以是i+1,將所有值都轉為字符串形式,好讓setObject成功運行 80 this.pstmt.setObject(i + 1, params[i] + ""); 81 } 82 } 83 84 return this.pstmt.executeUpdate();// 執行更新,並返回影響行數 85 86 } catch (ClassNotFoundException | SQLException e) { 87 // TODO Auto-generated catch block 88 e.printStackTrace(); 89 } finally { 90 this.close(this.rs, this.pstmt, this.con); 91 } 92 return 0; 93 } 94 95 /** 96 * 執行查詢 97 * 98 * @param sql 99 * 傳入的預設的sql語句 100 * @param params 101 * 問號參數列表 102 * @return 查詢后的結果 103 */ 104 public List<Map<String, Object>> execQuery(String sql, Object[] params) { 105 106 try { 107 this.getConnection();// 獲得連接對象 108 this.pstmt = this.con.prepareStatement(sql);// 獲得預設語句對象 109 110 if (params != null) { 111 // 設置參數列表 112 for (int i = 0; i < params.length; i++) { 113 // 因為問號參數的索引是從1開始,所以是i+1,將所有值都轉為字符串形式,好讓setObject成功運行 114 this.pstmt.setObject(i + 1, params[i] + ""); 115 } 116 } 117 118 // 執行查詢 119 ResultSet rs = pstmt.executeQuery(); 120 121 List<Map<String, Object>> al = new ArrayList<Map<String, Object>>(); 122 123 // 獲得結果集元數據(元數據就是描述數據的數據,比如把表的列類型列名等作為數據) 124 ResultSetMetaData rsmd = rs.getMetaData(); 125 126 // 獲得列的總數 127 int columnCount = rsmd.getColumnCount(); 128 129 // 遍歷結果集 130 while (rs.next()) { 131 Map<String, Object> hm = new HashMap<String, Object>(); 132 for (int i = 0; i < columnCount; i++) { 133 // 根據列索引取得每一列的列名,索引從1開始 134 String columnName = rsmd.getColumnName(i + 1); 135 // 根據列名獲得列值 136 Object columnValue = rs.getObject(columnName); 137 // 將列名作為key,列值作為值,放入hm中,每個hm相當於一條記錄 138 hm.put(columnName, columnValue); 139 } 140 // 將每個hm添加到al中,al相當於是整個表,每個hm是里面的一條記錄 141 al.add(hm); 142 } 143 144 return al; 145 146 } catch (ClassNotFoundException | SQLException e) { 147 // TODO Auto-generated catch block 148 e.printStackTrace(); 149 } finally { 150 this.close(this.rs, this.pstmt, this.con); 151 } 152 153 return null; 154 } 155 156 } 157