JDBC 編程

在Java語言中,有一個專門鏈接數據庫的規範(JDBC),專門負責鏈接數據庫進行數據操做。各個數據庫提供商會根據這套規範(接口)編寫相關的實現類,封裝成一個 jar 包供用戶下載使用。因此在進行編程時,須要將相應的 jar 包導入到工程文件下的 lib 目錄下,並創建依賴。java

1 鏈接數據庫並建表

這裏咱們使用的是 mysql 數據庫。mysql

1.1 加載註冊驅動

經過下述語句實現註冊驅動,原理是這句語句會將 Driver.class 這份字節碼加載到 JVM 中,而後 JVM 會執行該字節碼的靜態代碼塊,mysql 提供的這個驅動包中,Driver 的字節碼內的靜態代碼塊就完成了驅動對象的建立和註冊。sql

1 //加載註冊驅動
2 Class.forName("com.mysql.jdbc.Driver");

1.2 鏈接數據庫

當咱們註冊了驅動以後,能夠經過 DriverManager 獲取與數據庫的鏈接,須要傳入三個參數:數據庫的地址,登陸用戶名,登錄密碼。注意 Connection 和 DriverManager 類都是 java.sql 包下的,dbName 是數據庫的名字。數據庫

1 //鏈接數據庫
2 Connection conn = DriverManager.getConnection(jdbc:mysql://localhost:3306/dbName", "root", "123");

驗證已經獲取鏈接,能夠在 mysql 控制檯,使用命令:show processlist 查看運行進程。編程

1.3 操做數據庫

當獲得與數據的鏈接以後,咱們還須要經過該鏈接得到一個語句對象,該語句對象內包含咱們要對數據庫執行的操做,也就是 SQL 語句。咱們執行 SQL 語句通常使用語句對象的 executeUpdate 和 executeQuery 方法,前者用於 DDL 和 DML 操做,返回收影響的行數;後者用於執行 DQL 操做,返回結果集對象。具體使用方法以下瀏覽器

1 //語句對象的得到
2 Statement st = conn.createStatement();
3 //執行語句,這裏的語句用於建表
4 st.executeUpdate("create table t_student (id int primary key auto_increment, name varchar(20), age int)")
5 //釋放資源,先進後出,須要處理異常
6 st.close();
7 conn.close();

1.4 "賈璉欲執事"

執行一次語句的步驟能夠歸爲「賈(加)璉(連)欲(語)執(執)事(釋)」這一句話,合在一塊兒的源碼爲服務器

 1 public class CreateTableTest {
 2     public static void main(String[] args) throws ClassNotFoundException, SQLException {
 3         Class.forName("com.mysql.jdbc.Driver");
 4         Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/jdbc_demo", "root", "1234");
 5         String sql = "create table t_student (id int primary key auto_increment, name varchar(20), age int)";
 6         
 7         Statement st = conn.createStatement();
 8         st.execute(sql);
 9         
10         st.close();
11         conn.close();
12     }
13 }

2 DAO 層設計

實際開發中,JavaWeb 開發代碼通常分爲三層,分層結構是 JavaWeb 開發中的一種設計思想,這樣會讓咱們開發井井有條,每一層只要完成對應的功能便可,使得項目便於開發和維護。工具

  1. Web層/表現層 : 主要接受前臺瀏覽器用戶的參數,給瀏覽器響應數據等等
  2. Service層/業務成/服務層:主要處理業務功能,日誌,權限,事物,等等
  3. DAO層/持久層:專門負責和數據庫交互,數據處理相關代碼
DAO : Data Access Object 數據訪問對象

2.1 DAO 設計結構

在實際開發中,能夠按照下列結構進行設計性能

  1. 馬賽克的地方是公司域名倒寫加項目名
  2. 項目包下的 dao 包內放接口,規範了處理某個數據庫須要的方法,名方式以 DAO 結尾
  3. dao 包內的 impl 包放具體的實現,命名方式以 DAOImpl 結尾
  4. 與 dao 包同級的 pojo 包內放用於接收數據庫信息的簡單普通對象
  5. test 包是用於測試實現類的各個方法
  6. util 包存放工具類
  7. dp.properties 存放了一些數據庫鏈接須要用到的信息。

2.2 代碼的重構 - 代碼複用

在 1.4 中示範了怎麼鏈接數據庫進行一次建表操做,實際上 DAO 中的實現類內就是一個個這樣的方法,好比常見的增、刪、改、查,若是每一個方法內都寫這樣一些列流程,會在後期維護中產生很大麻煩,假如鏈接密碼改了,每一個方法都要修改,這顯然不現實,因此咱們要利用工具類以及配置文檔優化代碼。測試

  • 鏈接數據庫

實現類中的方法應該專一於功能的實現,得到鏈接是該方法須要的一個結果,該方法並不關注這個過程,不該該放在方法內混淆語義。咱們能夠把鏈接數據庫的代碼放在 JdbcUtils 這個工具類內,該類的成員都是類成員,而後實現類的方法中直接經過類調用 getConnection() 方法得到鏈接。同時,註冊驅動這個步驟咱們只須要執行一次就夠了,咱們能夠把這個步驟放在工具類的靜態代碼塊中,在該類初始化的時候會自動執行一次。

 1 public class JdbcUtils {
 2     
 3     private static String driverClassName = "com.mysql.jdbc.Driver";
 4     private static String url = "jdbc:mysql://127.0.0.1:3306/jdbc_demo";
 5     private static String username = "root";
 6     private static String password = "1234";
 7     
 8     static {
 9         try {
10             Class.forName(driverClassName);
11         } catch (ClassNotFoundException e) {
12             e.printStackTrace();
13         }
14     }
15     
16     public static Connection getConnection() throws SQLException {
17         Connection conn = DriverManager.getConnection(url, username, password);
18         return conn;
19     }
20 }
  • 關閉資源

關閉資源也是一塊雞肋代碼,重複且冗長,未重構前每一個方法的關閉過程以下

 1 //假設是查詢方法,除了關閉鏈接、語句還要關閉結果集,每次關閉都須要異常處理
 2 /*
 3 Connection conn;
 4 PreparedStatement ps;
 5 ResultSet rs;
 6 */
 7 try {
 8     //方法須要實現的功能
 9     return xxx;
10 } catch (Exception e) {
11     e.printStackTrace();
12 } finally {
13     //try 代碼塊中的 return 語句必定會在 finally 執行完後執行,因此關閉系統資源放在 finally 中。先進後出
14     try {
15         if(rs != null) {
16             rs.close();
17         }
18     } catch (SQLException e) {
19         e.printStackTrace();
20     } finally {
21         try {
22             if(ps != null) {
23                 ps.close();
24             }
25         } catch (SQLException e) {
26             e.printStackTrace();
27         } finally {
28             try {
29                 if(conn != null) {
30                     conn.close();
31                 }
32             } catch (SQLException e) {
33                 e.printStackTrace();
34             }
35         }
36     }
37 }

能夠看到這一段關閉資源的方法很是冗長,且每一個方法都要關閉一遍。咱們能夠把這個關閉的流程寫進工具類內,實現類的方法只須要調用 JdbcUtils.close() 方法便可。

 1 //Statement 是 PreparedStatement 的父類,這樣不論是 Statement 或者 PreparedStatement 類型的均可以用這個方法關閉
 2 //對於 DML 語句,沒有 ResultSet 對象,能夠第三個變量穿 null 便可
 3 public static void close(Connection conn, Statement s, ResultSet rs) {
 4     try {
 5         if(rs != null) {
 6             rs.close();
 7         }
 8     } catch (Exception e) {
 9         e.printStackTrace();
10     } finally {
11         try {
12             if(s != null) {
13                 s.close();
14             }
15         } catch (Exception e) {
16             e.printStackTrace();
17         } finally {
18             try {
19                 if(conn != null) {
20                     conn.close();
21                 }
22             } catch (Exception e) {
23                 e.printStackTrace();
24             }
25         }
26     }
27 }
  • 創建鏈接須要的信息

把鏈接須要的這些信息放在代碼塊中仍是不太方便,若是用戶修改了帳號密碼,或者主機地址改變,都須要從新修改代碼,這不太知足咱們日常應用的要求。因此咱們能夠把這些信息放在一個 .properties 文件中,Java 程序直接去讀取這些信息。那之後修改了帳戶密碼,只須要本身打開這個 .properties 文件修改相應的字段便可。

#key=value
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/jdbc_demo
username=root
password=1234
.properties 內存放了一些鍵值對,注意等號左右沒有空格,結尾也沒有標點,# 做爲註釋

那如何讀取這些信息呢?

Java 中經過類加載器讀取配置文件得到一個輸入流,能夠經過兩種方法得到類加載器

1 //1.經過某一類的字節碼實例能夠獲取
2 ClassLoader cl = Object.class.getContextClassLoader();
3 //2.經過當前線程得到
4 ClassLoader cl = Thread.currentThread().getContextClassLoader();
5 
6 //經過類加載器讀取配置文件得到輸入流
7 InputStream in = cl.getResourceAsStream("dp.properties");

那如何得到相應的信息呢?

咱們能夠經過 Properties 對象得到相應的信息。Properties 類是 Map 抽象類下一個專門用於讀取配置文件的類,將輸入流加載進去,便可經過「key」得到「value」。

1 Properties p = new Properties();
2 p.load(in);
3 System.out.println(p.getProperty("driverClassName"));
  • 重構後的工具類代碼
 1 public class JdbcUtils {
 2     
 3     private static Properties p = new Properties();
 4 
 5     static {
 6         ClassLoader cl = Thread.currentThread().getContextClassLoader();
 7         InputStream in = cl.getResourceAsStream("dp.properties");
 8         try {
 9             p.load(in);
10             Class.forName(p.getProperty("driverClassName"));
11         } catch (Exception e) {
12             e.printStackTrace();
13         }
14     }
15     
16     public static Connection getConnection() throws SQLException {
17         Connection conn = DriverManager.getConnection(p.getProperty("url"), p.getProperty("username"), p.getProperty("password"));
18         return conn;
19     }
20     
21     public static void close(Connection conn, Statement s, ResultSet rs) {
22         try {
23             if(rs != null) {
24                 rs.close();
25             }
26         } catch (Exception e) {
27             e.printStackTrace();
28         } finally {
29             try {
30                 if(s != null) {
31                     s.close();
32                 }
33             } catch (Exception e) {
34                 e.printStackTrace();
35             } finally {
36                 try {
37                     if(conn != null) {
38                         conn.close();
39                     }
40                 } catch (Exception e) {
41                     e.printStackTrace();
42                 }
43             }
44         }
45     }
46 }

2.3 引用鏈接池,管理鏈接

按照上面的作法,每次用戶進行一次操做,都會創建鏈接,接着執行完成後銷燬鏈接。雖然每次鏈接、斷開鏈接用時不長,但當用戶數量上來意後,對系統資源的消耗就會變得很高。因而咱們引入鏈接池管理鏈接。鏈接池裏面擁有必定數量的鏈接(通常5 - 10個),當經過鏈接池getConnection 時,鏈接池提供一個鏈接供方法使用,當使用完畢後方法執行鏈接的 close 方法,這個時候並非直接關閉鏈接,而是將鏈接返回給鏈接池。

在Java中,鏈接池使用 javax.sql.DataSource 接口來表示鏈接池。注意:DataSource 僅僅只是一個接口,由各大服務器廠商來實現。經常使用的 DataSource 的實現:

  • DBCP: Spring推薦的
  • C3P0: Hibernate推薦的
  • Druid : (德魯伊)阿里巴巴開源的,性能最好,速度最快

介紹 Druid 使用方法

 1 //須要導入 Druid 的 jar 包
 2 //方法一:
 3 //1 建立鏈接池對象,不能使用 DataSource 類型,由於 setXxx 方法時 DruidDataSource 類獨有的
 4 DruidDataSource dds = new DruidDataSource();
 5 //2 設置鏈接數據庫的信息
 6 dds.setDriverClassName(p.getProperty("driverClassName"));
 7 dds.setUrl(p.getProperty("url"));
 8 dds.setUsername(p.getProperty("username"));
 9 dds.setPassword(p.getProperty("password"));
10 dds.setMaxActive(10);  //最大鏈接數
11 Connection conn = dds.getConnection();
12 
13 //方法二
14 //經過鏈接池工程得到鏈接池
15 //1 經過工廠的靜態方法得到鏈接池,傳入上文中的 Properties 對象做爲參數,工程自動讀取配置信息
16 DataSource ds = DruidDataSourceFactory.createDataSource(p);
17 //2 得到鏈接
18 Connection conn = ds.getConnection();

使用鏈接池後的工具類

 1 public class JdbcUtils {
 2     
 3     private static Properties p = new Properties();
 4 
 5     //用工廠建立鏈接池對象,工廠底層對 properties 直接讀取,只需傳入一個 Properties 對象
 6     private static DataSource dds;
 7     static {
 8         ClassLoader cl = Thread.currentThread().getContextClassLoader();
 9         InputStream in = cl.getResourceAsStream("dp.properties");
10         try {
11             p.load(in);
12             dds = DruidDataSourceFactory.createDataSource(p);
13         } catch (Exception e) {
14             e.printStackTrace();
15         }
16     }
17     
18     public static Connection getConnection() throws SQLException {
19         return dds.getConnection();
20     }
21     
22     public static void close(Connection conn, Statement s, ResultSet rs) {
23         try {
24             if(rs != null) {
25                 rs.close();
26             }
27         } catch (Exception e) {
28             e.printStackTrace();
29         } finally {
30             try {
31                 if(s != null) {
32                     s.close();
33                 }
34             } catch (Exception e) {
35                 e.printStackTrace();
36             } finally {
37                 try {
38                     if(conn != null) {
39                         conn.close();
40                     }
41                 } catch (Exception e) {
42                     e.printStackTrace();
43                 }
44             }
45         }
46     }
47 }

3 事務

JDBC 中是默認提交事務的,也就是每一條 Statement 執行後,自動提交事務。在實際業務中,咱們可能須要把多個操做看成一個原子性操做來進行,要麼全作,要麼所有作。要實現這個要求,咱們只須要把自動提交事務給關閉,在經過回滾(rollback)和提交(commit)來完成一次事務。

 1 //銀行轉帳例子
 2 public class TransactionTest {
 3     @Test
 4     public void testName() throws Exception {
 5         Connection conn = null;
 6         Statement st = null;
 7         ResultSet rs = null;
 8         try {
 9             conn = DruidUtil.getConnection();
10             //將事務設置爲手動提交
11             conn.setAutoCommit(false);
12             
13             st = conn.createStatement();
14             // 1.檢查張無忌的帳號餘額是否大於等於1000.
15             rs = st.executeQuery("SELECT balance FROM account WHERE name = '張無忌' AND balance >=1000");
16             if(!rs.next()) {
17                 throw new RuntimeException("親,您的帳戶餘額不夠");
18             }
19             // 餘額>=1000:GOTO 2:
20             // 餘額 <1000:提示:親,你的餘額不足.
21             // 2.在張無忌的帳號餘額上減小1000.
22             st.executeUpdate("UPDATE account SET balance = balance-1000 WHERE name = '張無忌'");
23             
24             System.out.println(1/0);  //製造異常
25             
26             // 3.在趙敏的帳戶餘額尚增長1000.
27             st.executeUpdate("UPDATE account SET balance = balance+1000 WHERE name = '趙敏'");
28             
29             //提交事務
30             conn.commit();
31         } catch (Exception e) {
32             e.printStackTrace();
33             //回滾事務
34             conn.rollback();    
35         }finally {
36             DruidUtil.close(conn, st, rs);
37         }
38     }
39 }
40 //最終錢不會由於異常而變少
相關文章
相關標籤/搜索