1. 程式人生 > >Java JDBC->Mybatis

Java JDBC->Mybatis

參考 exception 動態生成 5.5 void main 使用 定義 utf

  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