Java JDBC->Mybatis
1 什麽是JDBC
Java程序都是通過JDBC(Java Data Base Connectivity)連接數據庫的,通過SQL對數據庫編程。JDBC是由SUN公司(SUN公司已被Oracle公司收購)提出的一系列規範,只定義了接口規範,具體的實現是由各個數據庫廠商去完成的。因為每個數據庫都有其特殊性,這些是Java規範沒有辦法確定的,所以JDBC就是一種典型的橋接模式。
2 常用接口
2.1 Driver接口
要連接數據庫,必須先加載特定廠商的數據庫驅動程序,不同的數據庫有不同的加載方法。共有2種方式。
2.1.1 Class.forName("com.mysql.jdbc.Driver");
推薦這種方式,不會對具體的驅動類產生依賴。
例如:
1 // 加載Oracle驅動 2 Class.forName("oracle.jdbc.driver.OracleDriver");
2.1.2 DriverManager.registerDriver("com.mysql.jdbc.Driver");
會造成DriverManager中產生2個一樣的驅動,並會對具體的驅動類產生依賴。
2.2 Connection接口
與特定數據庫連接後,Connection執行SQL語句並返回結果。DriverManager.getConnection(url, user, password)方法建立在JDBC URL中定義的數據庫Connection連接上。
1 // 連接MySQL數據庫 2 Connection conn = DriverManager.getConnection("jdbc:mysql://host:port/database", "user", "password"); 3 4 // 連接Oracle數據庫 5 Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@host:port:database", "user", "password");
常用方法:
createStatement():創建向數據庫發送sql的statement對象。
prepareStatement(sql) :創建向數據庫發送預編譯sql的PreparedSatement對象。
prepareCall(sql):創建執行存儲過程的callableStatement對象。
setAutoCommit(boolean autoCommit):設置事務是否自動提交。
commit() :在此連接上提交事務。
rollback() :在此連接上回滾事務。
2.3 Statement接口
用於執行靜態SQL語句並返回它所生成結果的對象。
3個Statement類:
Statement:由createStatement方法創建,用於發送簡單的SQL語句(不帶參數)。
PreparedStatement :繼承Statement類,由prepareStatement方法創建,用於發送含有1個或多個參數的SQL語句。與父類Statement相比,有以下幾個優點:
—可以防止SQL註入。
DBUtils輔助類
1 package com.test.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 * @author Administrator 10 * 模板類DBUtils 11 */ 12 public final class DBUtils { 13 // 參數定義 14 private static String url = "jdbc:mysql://localhost:3306/mytest"; // 數據庫地址 15 private static String username = "root"; // 數據庫用戶名 16 private static String password = "root"; // 數據庫密碼 17 18 private DBUtils() { 19 20 } 21 // 加載驅動 22 static { 23 try { 24 Class.forName("com.mysql.jdbc.Driver"); 25 } catch (ClassNotFoundException e) { 26 System.out.println("驅動加載出錯!"); 27 } 28 } 29 30 // 獲得連接 31 public static Connection getConnection() throws SQLException { 32 return DriverManager.getConnection(url, username, password); 33 } 34 35 // 釋放連接 36 public static void free(ResultSet rs, Statement st, Connection conn) { 37 try { 38 if (rs != null) { 39 rs.close(); // 關閉結果集 40 } 41 } catch (SQLException e) { 42 e.printStackTrace(); 43 } finally { 44 try { 45 if (st != null) { 46 st.close(); // 關閉Statement 47 } 48 } catch (SQLException e) { 49 e.printStackTrace(); 50 } finally { 51 try { 52 if (conn != null) { 53 conn.close(); // 關閉連接 54 } 55 } catch (SQLException e) { 56 e.printStackTrace(); 57 } 58 59 } 60 61 } 62 63 } 64 65 }
在寫SQL註入演示類之前看下數據庫結構(簡單的users表):
SQL註入演示類
1 package com.test.jdbc; 2 3 import java.sql.Connection; 4 import java.sql.ResultSet; 5 import java.sql.SQLException; 6 import java.sql.Statement; 7 8 public class SqlInner { 9 public static void main(String[] args) { 10 //Read("zhangsan"); // 如果普通查詢可以 11 Read("‘ or 1 or‘"); 12 } 13 public static void Read(String name) { 14 Statement st = null; 15 ResultSet rs = null; 16 Connection conn = null; 17 try { 18 conn = DBUtils.getConnection(); 19 st = conn.createStatement(); 20 String sql = "select * from users where lastname = ‘" + name + "‘"; // 主要註入發生地 21 System.out.println("sql: " + sql); // 打印SQL語句 22 rs = st.executeQuery(sql); 23 System.out.println("age\tlastname\tfirstname\tid"); 24 while (rs.next()) { 25 System.out.println(rs.getInt(1) + "\t" + rs.getString(2) 26 + "\t\t" + rs.getString(3) + "\t\t" + rs.getString(4)); 27 } 28 } catch (SQLException e) { 29 e.printStackTrace(); 30 } finally { 31 DBUtils.free(rs, st, conn); 32 } 33 } 34 }
如果用普通的鍵zhangsan來查詢,結果如下:
使用下面的‘ or 1 or‘,結果如下:
把所有結果都打印出來了。打印生成後的SQL語句如下:
這是一種SQL註入,用了2個單引號,分別把前後的兩個‘‘給封閉了,變成兩個空,然後通過or 1即or true符合所有條件。
使用PreparedStatement
1 package com.test.jdbc; 2 3 import java.sql.Connection; 4 import java.sql.PreparedStatement; 5 import java.sql.ResultSet; 6 import java.sql.SQLException; 7 8 public class SqlInner { 9 public static void main(String[] args) { 10 Read("‘ or 1 or‘"); 11 } 12 public static void Read(String name) { 13 PreparedStatement st = null; 14 ResultSet rs = null; 15 Connection conn = null; 16 try { 17 conn = DBUtils.getConnection(); 18 String sql = "select * from users where lastname = ?"; // 這裏用問號 19 st = conn.prepareStatement(sql); 20 st.setString(1,name); // 這裏將問號賦值 21 rs = st.executeQuery(); 22 System.out.println("age\tlastname\tfirstname\tid"); 23 while (rs.next()) { 24 System.out.println(rs.getInt(1) + "\t" + rs.getString(2) 25 + "\t\t" + rs.getString(3) + "\t\t" + rs.getString(4)); 26 } 27 } catch (SQLException e) { 28 e.printStackTrace(); 29 } finally { 30 DBUtils.free(rs, st, conn); 31 } 32 } 33 }
PreparedStatement用?代替變量,然後通過setString賦值。由於PreparedStatement內置了字符過濾,就相當於where name = ‘ or 1 or ‘,沒有對應記錄,所以結果為空。這體現了PreparedStatement的防註入功能。
—在特定的驅動數據庫下相對效率要高(不絕對)。
—因為已經預加載了,所以不需要頻繁編譯。
CallableStatement:繼承PreparedStatement類,由prepareCall方法創建,用於調用存儲過程。
Statement常用方法:
execute(String sql):返回值為true時,執行的是查詢語句,可以通過getResultSet方法獲取結果;返回值為false時,執行的是更新語句或DDL語句。
executeQuery(String sql):執行select語句,返回ResultSet結果集。
executeUpdate(String sql):執行insert/update/delete語句,返回影響的行數。
addBatch(String sql) :把多條SQL語句放到一個批處理中。
executeBatch():向數據庫發送一批SQL語句並執行,返回每條SQL語句執行後的影響行數的int數組。
2.4 ResultSet接口
結果集
檢索不同類型字段的常用方法:
getString(int index)、getString(String columnName):獲得在數據庫裏是varchar、char等類型的數據對象。
getFloat(int index)、getFloat(String columnName):獲得在數據庫裏是Float類型的數據對象。
getDate(int index)、getDate(String columnName):獲得在數據庫裏是Date類型的數據對象。
getBoolean(int index)、getBoolean(String columnName):獲得在數據庫裏是Boolean類型的數據對象。
getObject(int index)、getObject(String columnName):獲取在數據庫裏任意類型的數據對象。
對結果集進行滾動的方法:
next():移動到下一行。
Previous():移動到前一行。
absolute(int row):移動到指定行。
beforeFirst():移動resultSet的最前面。
afterLast() :移動到resultSet的最後面。
3 使用JDBC的步驟
加載JDBC驅動 → 建立數據庫連接Connection → 創建執行SQL語句的Statement → 處理執行結果ResultSet → 釋放資源。
註意,釋放資源順序:ResultSet → Statement → Connection。
3.1 加載JDBC驅動(只加載1次)
Class.forName("com.MySQL.jdbc.Driver");
3.2 建立數據庫連接Connection
1 Connection conn = DriverManager.getConnection("jdbc:mysql://host:port/database", "user", "password");
URL用於標識數據庫的位置,通過URL地址告訴JDBC程序連接哪個數據庫,URL的寫法為:
其他參數如:useUnicode=true&characterEncoding=utf8
3.3 創建執行SQL語句的Statement
1 //Statement 2 String id = "5"; 3 String sql = "delete from table where id=" + id; 4 Statement st = conn.createStatement(); 5 st.executeQuery(sql); 6 //存在sql註入的危險 7 //如果用戶傳入的id為“5 or 1=1”,那麽將刪除表中的所有記錄
1 //PreparedStatement 有效的防止sql註入(SQL語句在程序運行前已經進行了預編譯,當運行時動態地把參數傳給PreprareStatement時,即使參數裏有敏感字符如 or ‘1=1‘也數據庫會作為一個參數一個字段的屬性值來處理而不會作為一個SQL指令) 2 String sql = “insert into user (name,pwd) values(?,?)”; 3 PreparedStatement ps = conn.preparedStatement(sql); 4 ps.setString(1, “col_value”); //占位符順序從1開始 5 ps.setString(2, “123456”); //也可以使用setObject 6 ps.executeQuery();
3.4 處理執行結果ResultSet
1 ResultSet rs = ps.executeQuery(); 2 While(rs.next()){ 3 rs.getString(“col_name”); 4 rs.getInt(1); 5 //… 6 }
3.5 釋放資源
1 // 數據庫連接(Connection)非常耗資源,盡量晚創建,盡量早的釋放 2 // 都要加try catch finally以防前面關閉出錯,後面的就不執行了 3 try { 4 if (rs != null) { 5 rs.close(); 6 } 7 } catch (SQLException e) { 8 e.printStackTrace(); 9 } finally { 10 try { 11 if (st != null) { 12 st.close(); 13 } 14 } catch (SQLException e) { 15 e.printStackTrace(); 16 } finally { 17 try { 18 if (conn != null) { 19 conn.close(); 20 } 21 } catch (SQLException e) { 22 e.printStackTrace(); 23 } 24 } 25 }
4 什麽是Mybatis
官網 http://www.mybatis.org/mybatis-3/zh/index.html
MyBatis是一款優秀的持久層框架,它支持定制化SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的JDBC代碼和手動設置參數以及獲取結果集。MyBatis可以使用簡單的XML或註解來配置和映射原生信息,將接口和Java的POJOs(Plain Old Java Objects,普通的Java對象)映射成數據庫中的記錄。
5 JDBC演變到Mybatis過程
5.1 連接獲取和釋放
頻繁地開啟和關閉數據庫連接造成資源浪費,影響系統性能。
解決方案:
通過數據庫連接池反復利用已經建立的連接去訪問數據庫,減少連接的開啟和關閉的時間。
但數據庫連接池多種多樣,有可能采用DBCP連接池,也有可能采用容器本身的JNDI連接池。
解決方案:
從DataSource裏面獲取數據庫連接,具體實現由用戶來配置。
5.2 SQL語句統一存取
使用JDBC操作數據庫時,SQL語句基本都散落在各個Java類中,這樣有3個不足之處:
—可讀性很差,不利於維護以及做性能調優。
—改動Java代碼需要重新編譯、打包部署。
—不利於取出SQL語句在數據庫客戶端執行(因為之前編寫好的SQL語句通過+號進行拼湊,所以取出後需要刪掉中間的Java代碼)。
解決方案:
SQL語句統一存放到XML中。
5.3 傳入參數映射和動態SQL
根據前臺傳入參數的不同,如何動態生成對應的SQL語句呢?
解決方案:
使用if、choose、when、otherwise元素組裝SQL語句,#{變量名}傳遞SQL所需要的參數,${變量名}傳遞SQL語句本身。
5.4 結果映射和結果緩存
如何封裝結果處理?
解決方案:
為了將具體的值復制到對應的數據結構上,SQL處理器需要明確兩點:第一,需要返回什麽類型的對象?第二,返回的對象的數據結構與執行的結果如何映射?
如何存儲SQL執行結果即緩存來提升性能?
解決方案:
緩存數據都是key-value的格式,SQL語句和傳入參數兩部分合起來可以作為數據緩存的key值。
5.5 重復SQL語句
如何復用SQL語句?
解決方案:
將重復的SQL片段獨立成一個SQL塊,然後各個SQL語句引用重復的SQL塊。
參考資料
《深入淺出Mybatis技術原理與實戰》
JDBC教程
JDBC詳解
JDBC Statement接口實現的execute方法
JDBC PrepareStatement對象執行批量處理實例
JDBC之PreparedStatement
終結篇:MyBatis原理深入解析(一)
Java JDBC->Mybatis