在說明JDBC以前首先咱們先要說明一下 持久化
概念。 在程序運行過程當中咱們會產生大量並保存內存,而有些數據是使用比較頻繁的而且須要之後還會被使用。假如咱們僅僅使用內存存儲這些數據,那麼程序一旦運行結束這些數據就會丟失,這在電商行業將變得很是恐怖。所以,在業內就出現了 Mysql、ORACLE等這些數據庫能夠將這些數據持久化在磁盤上而且可以保證在以後使用獲取速度足夠快。 java
那麼在Java中怎麼獲取這些數據庫中的數據呢? 這就出現JDBC技術, 它提供一系列的接口和類,可讓開發人員可以忽略各個數據庫的差別,從而獲取數據或者操做數據。mysql
JDBC版本號git
JDBC的API程序員
java.sql
裏面的包若要獲取數據庫鏈接對象,須要通過一系列步驟github
① 下載對應數據庫的驅動jar包
在這裏咱們以mysql爲例, 下載地址 https://static.runoob.com/dow...sql
② 註冊驅動 (固然,JDBC4.0開始能夠自動加載驅動,可是,仍是建議手動註冊以確保之前JDK版本的兼容性)數據庫
Class.forName("com.mysql.cj.jdbc.Driver");
每一個數據庫的驅動註冊都不同,這裏mysql是使用com.mysql.cj.jdbc.Driver。其他的數據庫請查詢相關文檔
③ 經過DriverManager
獲取鏈接對象apache
Connection conn = DriverManager.getConnection("jdbc:mysql://數據庫IP:3306/study?rewriteBatchedStatements=true&useSSL=false&serverTimezone=UTC", "用戶名", "密碼");
咱們能夠看見獲取鏈接對象的時候,鏈接字符串爲 jdbc:mysql://127.0.0.1:3306/study , 這是JDBC鏈接MYSQL的固定格式, jdbc:mysql://數據庫IP地址/數據庫
① 獲取Statement
語句對象api
Statement statement = conn.createStatement();
② 獲取 PreparedStatement
語句對象緩存
conn.prepareStatement("");
PreparedStatement語句對象相比較Statement語句對象而言, 也擁有那一系列操做SQL的方法。 可是, 它能夠防止SQL注入,功能更增強大些。
③ 關閉鏈接
conn.close();
當要進行DDL語句操做時, 首先要按照上面步驟獲取Connection對象、獲取Statement對象等等, 而後, 執行相應的SQL語句就能夠啦。 具體可看下面代碼實現
// 註冊驅動 Class.forName("com.mysql.cj.jdbc.Driver"); // 獲取Connection對象 Connection conn = DriverManager.getConnection("jdbc:mysql://數據庫IP:3306/study?rewriteBatchedStatements=true&useSSL=false&serverTimezone=UTC", "用戶名", "密碼"); // 獲取Statement語句對象 Statement statement = conn.createStatement(); // 執行SQL語句, executeUpdate能夠執行DML語句,也能夠執行DDL語句 int row = statement.executeUpdate("CREATE TABLE `study`.`JDBC_DEMO` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT \"主鍵\", `name` VARCHAR(110) NOT NULL,PRIMARY KEY(`id`) )"); // 關閉語句對象 statement.close(); // 關閉鏈接對象 conn.close();
DML操做就是咱們平常說的增、刪、改操做。 其使用步驟和上面的DDL操做相似,區別在於SQL語句變化了。以下
Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = DriverManager.getConnection("jdbc:mysql://數據庫IP:3306/study?rewriteBatchedStatements=true&useSSL=false&serverTimezone=UTC", "用戶名", "密碼"); Statement statement = conn.createStatement(); // 從這裏咱們能夠看出, 就是SQL語句轉變爲 INSERT 語句啦 int row = statement.executeUpdate("INSERT INTO `JDBC_DEMO`(id, name) VALUES(2, 'xiaoming')"); if (row > 0) { System.out.println("操做成功"); } statement.close(); conn.close();
同理, 修改、刪除操做就修改爲相應的SQL語句便可
互聯網上大多數時候都是讀多寫少,所以, DQL操做須要格外留意一下。 對於DQL操做以前首先咱們要認識一下ResultSet
。
ResultSet
是咱們在執行查詢操做時返回的對於數據結果集的數據表封裝,咱們能夠經過相應的方法來獲取結果集中的數據。
獲取結果集
ResultSet set = statement.executeQuery("SELECT * FROM JDBC_DEMO");
經常使用方法
① next
用來判斷結果集中是否存在元素; 如有則返回true而且指針移動到下一個位置
set.next()
② close
關閉結果集
③ 相似 getXxx
方法,能夠根據索引或者列名獲取數據並轉換成相應的數據類型
// 從結果集中獲取當前指針的第一列數據, 該方式因爲是根據索引去獲取列數據,這可能會帶來必定的風險。所以,假如往後列的索引位置發生了改變則表明全部相關代碼都要修改一遍 set.getInt(1)
// 這種方式就比較好,經過索引名稱來獲取當前行對應列的數據,在這裏Xxx是String則表明獲取數據後需將其轉換爲String類型並返回 set.getString("name")
相似, 也存在 set.getObject(columnIndex)
、set.getObject(columnLabel)
這樣的方法,這種形式就是獲取數據的相似都是Object類型,所以,須要咱們主動去轉換類型
在上面咱們介紹了ResultSet
,那麼其實咱們基本也介紹了DQL操做的基本方法,完整代碼以下
Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = DriverManager.getConnection("jdbc:mysql://數據庫IP:3306/study?rewriteBatchedStatements=true&useSSL=false&serverTimezone=UTC", "用戶名", "密碼"); Statement statement = conn.createStatement(); // 執行Select語句獲取結果集,並經過next、getXxx方法獲取相應數據 ResultSet set = statement.executeQuery("SELECT * FROM JDBC_DEMO"); while (set.next()) { System.out.printf("id:%d name:%s", set.getInt("id"), set.getString("name")); } set.close(); statement.close(); conn.close();
在上面咱們已經說過了使用 statement
對象來進行數據庫的DDL、DML、DQL等操做,可是,它主要是用來操做靜態SQL。 這裏咱們要介紹一下PreparedStatement
。
PreparedStatement
是能夠將預編譯
的SQL存儲起來的對象, 它能夠高效地執行SQL並可以被傳入不一樣參數屢次調用。 它主要有如下特色
預編譯
特性能夠極大地提升性能,不過,這在ORACLE
是有效的,可是在MYSQL
是不支持的注意, 預編譯SQL不是真實的SQL,而是含有佔位符的SQL關於爲啥預編譯會提升性能?
正常狀況下 當咱們使用客戶端去訪問ORACLE服務器併發送SQL語句,會經歷 安全性檢查、語義分析、語法編譯、執行計劃、返回結果等階段。 而使用預編譯後會將預編譯SQL放入一塊預編譯池中,當下次有一樣預編譯SQL傳入的時候就直接從預編譯池撈回來並執行計劃就能夠了, 這樣因爲減小不少檢查從而提升性能。
使用步驟實際上是和Statement
對象是相似的, 首先是準備步驟
Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = DriverManager.getConnection("jdbc:mysql://數據庫IP:3306/study?rewriteBatchedStatements=true&useSSL=false&serverTimezone=UTC", "用戶名", "密碼");
① DDL
PreparedStatement statement = conn.prepareStatement("CREATE TABLE JDBC_DEMO2(id int(11) NOT NULL AUTO_INCREMENT, name2 varchar(255) NOT NULL,PRIMARY KEY(`id`))");
② DML
生成PreparedStatement
對象
PreparedStatement statement = conn.prepareStatement("INSERT INTO JDBC_DEMO(id,name) VALUES(?,?)");
設置SQL參數
// 根據索引設置對應位置的值 statement.setInt(1, 3); statement.setString(2, "xiaofeng")
執行更新
// 執行更新,這裏咱們與Statement對象比較能夠發現,此時executeUpdate是無參的 statement.executeUpdate();
完整示例代碼以下
Class.forName("com.mysql.cj.jdbc.Driver"); try ( Connection conn = DriverManager.getConnection("jdbc:mysql://數據庫IP:3306/study?rewriteBatchedStatements=true&useSSL=false&serverTimezone=UTC", "用戶名", "密碼"); PreparedStatement statement = conn.prepareStatement("INSERT INTO JDBC_DEMO(id,name) VALUES(?,?)"); ){ statement.setInt(1, 3); statement.setString(2, "xiaofeng"); statement.executeUpdate(); }catch(Exception e) { e.printStackTrace(); }
③ DQL
生成PreparedStatement
對象
PreparedStatement statement = conn.prepareStatement("select id,name from JDBC_DEMO where id = ?");
設置SQL參數
statement.setInt(1, 3);
執行查詢
// 這裏也要注意,執行的executeQuery是無參的餓 ResultSet set = statement.executeQuery();
完整代碼示例
Class.forName("com.mysql.cj.jdbc.Driver"); try ( Connection conn = DriverManager.getConnection("jdbc:mysql://數據庫IP:3306/study?rewriteBatchedStatements=true&useSSL=false&serverTimezone=UTC", "用戶名", "密碼"); PreparedStatement statement = conn.prepareStatement("select id,name from JDBC_DEMO where id = ?"); ){ statement.setInt(1, 3); ResultSet set = statement.executeQuery(); if (set.next()) { System.out.printf("id:%d name:%s\n", set.getInt("id"), set.getString("name")); } }catch(Exception e) { e.printStackTrace(); }
固然,上面代碼是查詢單條數據, 多條數據同理獲取便可。
事務
是關係型數據庫的一個重要特性,它的特色總結爲ACID
A
(Atomicity)
原子性: 事務中的所有操做要麼都成功,要麼都失敗
C
(Consistency)
一致性: 事務必須使數據庫從一個一致性狀態轉變爲另外一個一致性狀態
I
(Isolation)
隔離性: 多個事務之間是相互隔離的,一個事務的操做是不影響另一個事務的操做的
D
(Durability)
持久性: 事務一旦提交,它對數據庫中數據的改變是永久性的
事務使用
① 開始事務
conn.setAutoCommit(false);
② 回滾事務
conn.rollback();
③ 提交事務
conn.commit();
① 爲啥使用批量操做?
正常狀況咱們是使用上面那種方式一個個去執行SQL, 這種方式是容許的。可是, 如果有不少相似的SQL, 例如 INSERT INTO JDBC_DEMO(id,name) VALUES (3, "xiaoming")
、INSERT INTO JDBC_DEMO(id,name) VALUES (3, "xiaofeng")
. . . , 假如咱們也一個個執行SQL的話, 首先發送每一條SQL都有大量的網絡耗時,並且能夠看出其實這些SQL是很類似的。 那麼這個時候就須要使用批量操做
,將這些SQL一次性傳入數據庫服務器來提升性能。
② 批量操做使用
Statement
對象執行批量操做
// 建立Statement對象 Statement statement = conn.createStatement(); // 批量添加SQL statement.addBatch("INSERT INTO JDBC_DEMO(id,name) VALUES(4, 'xiaoheng')"); statement.addBatch("INSERT INTO JDBC_DEMO(id,name) VALUES(5, 'xiaoyu')"); // 執行SQL statement.executeBatch();
PreparedStatement
對象執行批量操做
// 建立PreparedStatement對象 PreparedStatement statement = conn.prepareStatement("INSERT INTO JDBC_DEMO(id,name) VALUES(?,?)"); // 綁定參數 statement.setInt(1, 6); statement.setString(2, "xiaoxiao"); // 添加批次 statement.addBatch(); // 綁定參數 statement.setInt(1, 7); statement.setString(2, "xiaoaa"); // 添加批次 statement.addBatch() // 執行SQL statement.executeBatch()
③ 注意
ORACLE
是支持批量操做的, 而MYSQL
是不支持批量操做的MySql
的JDBC
驅動,不是真正支持批量操做的,就算你在代碼中調用了批量操做的方法,MySql
的JDBC
驅動也是按照通常操做來處理的。可是, 在 MYSQL JDBC驅動版本5.1.13
以後能夠經過添加rewriteBatchedStatements
來提升一下
// 添加rewriteBatchedStatements參數 Connection conn = DriverManager.getConnection("jdbc:mysql://數據庫IP地址:3306/數據庫?rewriteBatchedStatements=true&useSSL=false&serverTimezone=UTC", "用戶名", "密碼");
① 爲何要獲取自動生成的主鍵
由於在某些場景須要咱們獲取到生成的主鍵,從而與另一張表進行關聯起來。好比,咱們有下面的表結構
user
用戶表
id | name |
---|---|
1 | xiaoming |
2 | xiaofeng |
integral
積分表
id | value | user_id |
---|---|---|
1 | 23 | 1 |
這個時候咱們須要在插入一條user
記錄的時候,同時,也對應插入一條integral
記錄。 這個時候就須要用到 獲取自動生成主鍵
的技巧了, 由於integral
表中須要插入user_Id
(即user的主鍵ID)
② 獲取主鍵
Statement
對象獲取主鍵
Statement statement = conn.createStatement();
statement.executeUpdate("INSERT INTO JDBC_DEMO(name) VALUES('ff')", Statement.RETURN_GENERATED_KEYS); // 獲取生成的結果集對象 ResultSet set = statement.getGeneratedKeys(); if (set.next()) { // 獲取主鍵 System.out.println(set.getLong(1)); }
PreparedStatement
對象獲取主鍵
// 生成PreparedStatement對象須要加 Statement.RETURN_GENERATED_KEYS 參數 PreparedStatement statement = conn.prepareStatement("INSERT INTO JDBC_DEMO(id,name) VALUES(?,?)", Statement.RETURN_GENERATED_KEYS);
statement.setInt(1, 23); statement.setString(2, "kkk"); statement.executeUpdate(); // 獲取生成的結果集對象 ResultSet set = statement.getGeneratedKeys(); if (set.next()) { // 獲取主鍵 System.out.println(set.getLong(1)); }
在正常狀況咱們每一次鏈接數據庫的時候都須要創建鏈接併發送SQL語句, 而每一次創建鏈接的時間都須要耗費時間,能夠想象假如如今有大量用戶訪問服務器的話會頻繁產生大量的數據庫鏈接, 這對於底層數據庫的壓力將很是大,嚴重狀況下甚至能夠致使數據庫宕機。
所以, 爲何不將現有已經創建起來的鏈接對象緩存起來,當咱們要使用的時候從鏈接池獲取出來,這樣就會減小每次鏈接數據庫帶來的開銷,而這就是 鏈接池
概念的出現。
① 簡介
在上面咱們已經敘述了爲啥要使用鏈接池,所以, Java就提供了一個javax.sql.DataSource
接口。 要求各大數據庫廠商遵循該接口並提供驅動jar包。
② 分類
目前實現DataSource
接口主要有如下幾種:
使用鏈接池主要是要獲取到DataSource
對象,經過它咱們就能夠從鏈接池中獲取到Connection
對象。具體使用步驟以下
① 下載jar包
http://commons.apache.org/pro... dbcp包
https://mirror.bit.edu.cn/apa... pool包
② 將上面兩個包Build Path
到Java
路徑
③ 獲得DataSource
並獲取Connection
對象
直接硬編碼獲取
// 獲取DataSource BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://數據庫IP地址:3306/study?rewriteBatchedStatements=true&useSSL=false&serverTimezone=UTC"); dataSource.setUsername("用戶名"); dataSource.setPassword("密碼");
// 獲取Connection對象 Connection conn = dataSource.getConnection();
從properties文件加載並獲取Connection
// 加載properties資源文件 ClassLoader loader = Thread.currentThread().getContextClassLoader(); InputStream in = loader.getResourceAsStream("dbcp.properties"); Properties p = new Properties(); p.load(in);
// 獲取DataSource對象 BasicDataSource dataSource = BasicDataSourceFactory.createDataSource(p);
Connection conn = dataSource.getConnection();
① 下載jar包
https://github.com/alibaba/dr... druid包
② 將jar包進行Build Path
③ 獲得DataSource
並獲取Connection
對象
直接硬編碼獲取
DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://數據庫IP地址:3306/study?rewriteBatchedStatements=true&useSSL=false&serverTimezone=UTC"); dataSource.setUsername("用戶名"); dataSource.setPassword("密碼");
Connection conn = dataSource.getConnection();
從properties文件加載並獲取Connection
// 加載properties資源文件 ClassLoader loader = Thread.currentThread().getContextClassLoader(); InputStream in = loader.getResourceAsStream("dbcp.properties"); Properties p = new Properties(); p.load(in);
DruidDataSource dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(p);
Connection conn = dataSource.getConnection();
JDBCTemplate是對於JDBC的一種封裝技巧, 可以讓咱們更好地使用JDBC, 接下來讓咱們體驗一下這段重構歷程吧。
在上古時代咱們在使用以下方式去使用JDBC
package com.fengyuxiang.sims.dao.impl; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import org.omg.PortableInterceptor.Interceptor; import com.fengyuxiang.sims.dao.IStudentDAO; import com.fengyuxiang.sims.domain.Student; import com.fengyuxiang.template.JdbcTemplate; public class StudentDAOImpl implements IStudentDAO { private Statement statement; public void setStatement(Statement statement) { this.statement = statement; } public int save(Student stu) { Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = DriverManager.getConnection("jdbc:mysql://數據庫IP:3306/study?rewriteBatchedStatements=true&useSSL=false&serverTimezone=UTC", "用戶名", "密碼"); Statement statement = conn.createStatement(); // 從這裏咱們能夠看出, 就是SQL語句轉變爲 INSERT 語句啦 int row = statement.executeUpdate("INSERT INTO `Student`(id, name) VALUES(2, 'xiaoming')"); if (row > 0) { System.out.println("操做成功"); } if (conn != null) { try { conn.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { try { if (statement != null) { statement.close(); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
能夠想一想在進行更新、刪除、查詢等操做都會不少這樣重複的代碼, 仔細想一想 其實上面除了SQL不同以外,其他都很相似。 因此咱們首先進行第一輪改造,抽象出JdbcUtil
工具類
JdbcUtil
工具類是將JDBC的一些常見操做封裝起來而後提供給外界使用,讓咱們看一下下面的代碼
package com.fengyuxiang.utils; import java.io.InputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; public class JdbcUtil { private static DruidDataSource dataSource = null; // 靜態代碼塊,會在類加載進JVM時被調用 static { // 經過類加載器獲取資源文件 dbcp.properties ClassLoader loader = Thread.currentThread().getContextClassLoader(); InputStream inStream = loader.getResourceAsStream("dbcp.properties"); Properties p = new Properties(); try { p.load(inStream); // 經過Druid鏈接池獲取DataSource對象 dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(p); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } // 獲取Connection對象 public static Connection getConn() throws SQLException { Connection conn = null; try { conn = dataSource.getConnection(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); throw e; } return conn; } // 關閉資源 public static void close(Connection conn, Statement statement, ResultSet set) { if (conn != null) { try { conn.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { try { if (statement != null) { statement.close(); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { try { if (set != null) { set.close(); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } }
經過上面代碼後,對於關閉鏈接、得到鏈接等一系列操做直接經過JdbcUtil
操做便可,讓咱們看一下使用JdbcUtil
工具類簡化後的代碼
package com.fengyuxiang.sims.dao.impl; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import org.omg.PortableInterceptor.Interceptor; import com.fengyuxiang.sims.dao.IStudentDAO; import com.fengyuxiang.sims.domain.Student; import com.fengyuxiang.template.JdbcTemplate; import com.fengyuxiang.utils.JdbcUtil; public class StudentDAOImpl implements IStudentDAO { private Statement statement; public void setStatement(Statement statement) { this.statement = statement; } public int save(Student stu) { // 經過JdbcUtil工具類獲取Connection對象 Connection conn = JdbcUtil.getConn(); // 獲取Statement對象 Statement statement = conn.createStatement(); int row = statement.executeUpdate("INSERT INTO `Student`(id, name) VALUES(2, 'xiaoming')"); if (row > 0) { System.out.println("操做成功"); } // 關閉資源 JdbcUtil.close(conn, statement, null); } }
經過這一步咱們代碼看上去是否是清爽許多了呀。可是, 問題又再次來到咱們的面前。咱們經過對於上述代碼分析能夠發現一些有趣的現象,除了SQL不同外其他JDBC代碼很類似。因此,接下來就讓咱們繼續重構上面的代碼,這就引出來 JdbcTemplate
類經過它來封裝那些重複的SQL操做。
JdbcTemplate出現是爲了解決JDBC
代碼重複的問題,最終咱們指望是隻要調用JdbcTemplate
而後傳入SQL
語句能夠直接操做數據庫。因此,讓咱們看看寫的JdbcTemplate
類
package com.fengyuxiang.template; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import com.fengyuxiang.sims.domain.Student; import com.fengyuxiang.utils.JdbcUtil; public class JdbcTemplate { public static int update(String sql, Object...params) throws SQLException { Connection conn = JdbcUtil.getConn(); // 傳入SQL並生成PreparedStatement對象 PreparedStatement statement = conn.prepareStatement(sql); int paramLen = params.length; for (int index = 0; index < paramLen; ++index) { statement.setObject(index+1, params[index]); } // 執行更新 int row = statement.executeUpdate(); // 關閉 JdbcUtil.close(conn, statement, null); return row; } public static List query(String sql, Object ...params) throws SQLException { Connection conn = JdbcUtil.getConn(); PreparedStatement statement = conn.prepareStatement(sql); int paramLen = params.length; for (int index = 0; index < paramLen; ++index) { statement.setObject(index+1, params[index]); } ArrayList<Student> list = new ArrayList<>(); // 執行查詢 ResultSet set = statement.executeQuery(); while(set.next()) { Student stu = new Student(); stu.setId(set.getInt("id")); stu.setName(set.getString("name")); list.add(stu); } // 關閉 JdbcUtil.close(conn, statement, set); return list; } }
經過這一步的話,咱們只要將SQL語句傳入JdbcTemplate
就能夠直接操做數據庫。以下
package com.fengyuxiang.sims.dao.impl; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import org.omg.PortableInterceptor.Interceptor; import com.fengyuxiang.sims.dao.IStudentDAO; import com.fengyuxiang.sims.domain.Student; import com.fengyuxiang.template.JdbcTemplate; import com.fengyuxiang.utils.JdbcUtil; import com.fengyuxiang.template.JdbcTemplate; public class StudentDAOImpl implements IStudentDAO { private Statement statement; public void setStatement(Statement statement) { this.statement = statement; } public int save(Student stu) { int row = 0; try { // 以前上面一大串複雜的JDBC代碼,在這裏只須要經過JdbcTemplate並執行一條SQL語句能夠所有完成了,你們能夠將這段代碼和咱們以前代碼對比同樣就看出其簡潔性 row = JdbcTemplate.update("INSERT INTO Student(id,name) VALUES (?,?) ", stu.getId(), stu.getName()); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return row; } }
不過,儘管如此仍是有一點美中不足就是在上面JdbcTemplate
進行DQL
語句操做時候,對應的domain
對象是固定的,好比上面就只能操做Student
對象。這樣就顯得JdbcTemplate
操做範圍特別有限,咱們總不能對於每一個不一樣的domain
對象都寫一個方法來解析吧。 因此, 這裏咱們就要重構這一部分,讓JdbcTemplate顯得通用些
通用結果集的出現主要是爲了適應不一樣的DAO
的查詢,經過一個方法就能夠獲取不一樣的數據結果。在這裏就須要使用到泛型以及JavaBean
的知識了。
① 定義一個結果集接口
package com.fengyuxiang.sims.dao; import java.sql.ResultSet; public interface IResultSetHandle<T> { <T> T handle(ResultSet set); }
② 實現IResultSetHandle
接口,這裏指處理單行記錄
package com.fengyuxiang.sims.dao.impl; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.ResultSet; import java.sql.SQLException; import org.omg.PortableInterceptor.Interceptor; import com.fengyuxiang.sims.dao.IResultSetHandle; public class ResultSetHandle<T> implements IResultSetHandle<T> { // 待組裝domain元素的Class對象 private Class classType; ResultSetHandle(Class classType) { this.classType = classType; } @Override public <T> T handle(ResultSet set) { try { // 利用反射實例化對象 Object obj = this.classType.newInstance(); // 獲取JavaBean BeanInfo beanInfo = Introspector.getBeanInfo(this.classType, Object.class); // 獲取屬性描述符 PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); if (set.next()) { for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { // 獲取屬性值 String propertyName = propertyDescriptor.getName(); // 獲取屬性對應的setter方法 Method writeMethod = propertyDescriptor.getWriteMethod(); // 調用屬性的setter方法以及結果集的getObject,進行值注入 writeMethod.invoke(obj, set.getObject(propertyName)); } } // 經過上面的方式,咱們就能夠傳入的class對象實例化並進行返回 return (T) obj; } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IntrospectionException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } }
② 實現IResultSetHandle
接口,這裏指處理多行記錄
package com.fengyuxiang.sims.dao.impl; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import com.fengyuxiang.sims.dao.IResultSetHandle; public class ResultSetListHandle<T> implements IResultSetHandle<List<T>> { // 待組裝domain元素的Class對象 private Class<T> classType; public ResultSetListHandle(Class<T> classType) { // TODO Auto-generated constructor stub this.classType = classType; } @Override public List<T> handle(ResultSet set) { // TODO Auto-generated method stub try { // 建立一個ArrayList集合用來裝T類型的對象 ArrayList<T> list = new ArrayList<>(); while(set.next()) { // 實例化對象 T obj = this.classType.newInstance(); // 根據class對象獲取bean信息 BeanInfo beanInfo = Introspector.getBeanInfo(this.classType, Object.class); // 獲取JavaBean的屬性描述符 PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { // 獲取屬性名稱 String propertyName = propertyDescriptor.getName(); // 獲取屬性的stter方法 Method method = propertyDescriptor.getWriteMethod(); // 調用屬性的setter方法以及結果集的getObject,進行值注入 method.invoke(obj, set.getObject(propertyName)); } // 將組裝好的obj對象,放入到list集合裏面並返回 list.add(obj); } return list; } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IntrospectionException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } }
經過上面三步咱們就實現一套代碼可以處理不一樣domain對象結果集的狀況。 首先讓咱們看一下沒有使用通用結果集下的DQL語句
public static List query(String sql, Object ...params) throws SQLException { Connection conn = JdbcUtil.getConn(); PreparedStatement statement = conn.prepareStatement(sql); int paramLen = params.length; for (int index = 0; index < paramLen; ++index) { statement.setObject(index+1, params[index]); } ArrayList<Student> list = new ArrayList<>(); // 執行查詢 ResultSet set = statement.executeQuery(); while(set.next()) { // 只能處理固定的domain對象 Student stu = new Student(); stu.setId(set.getInt("id")); stu.setName(set.getString("name")); list.add(stu); } // 關閉 JdbcUtil.close(conn, statement, set); return list; }
而使用結果集的DQL語句
public static <T> T query(String sql, IResultSetHandle<T> handler, Object ...params) throws SQLException { Connection conn = JdbcUtil.getConn(); PreparedStatement statement = conn.prepareStatement(sql); int paramLen = params.length; for (int index = 0; index < paramLen; ++index) { statement.setObject(index+1, params[index]); } ResultSet set = statement.executeQuery(); T list = handler.handle(set); JdbcUtil.close(conn, statement, set); return list; }
當咱們使用的時候就就能夠這樣使用
// 這裏咱們就能夠處理Student類型對象 Student stu = JdbcTemplate.queryOne("SELECT * FROM Student WHERE ID = ? LIMIT 1", new ResultSetHandle<Student>(Student.class), id);
// 這裏咱們就能夠處理Order類型對象 Order stu = JdbcTemplate.queryOne("SELECT * FROM Student WHERE ID = ? LIMIT 1", new ResultSetHandle<Order>(Order.class), id);
// 這裏咱們就能夠處理Good類型對象 Good stu = JdbcTemplate.queryOne("SELECT * FROM Student WHERE ID = ? LIMIT 1", new ResultSetHandle<Good>(Good.class), id);
....
能夠看出來, 經過一套代碼咱們就可以操做不一樣的domain對象並返回,並且代碼看來顯得更加優雅。
在咱們沒有沒有DAO的時候,若是咱們須要查詢數據的時候, 那麼在項目上咱們將大量使用以下代碼:
位置1
ResultSet set = statement.executeQuery("SELECT * FROM JDBC_DEMO"); while (set.next()) { System.out.printf("id:%d name:%s", set.getInt("id"), set.getString("name")); }
位置2
ResultSet set = statement.executeQuery("SELECT name FROM JDBC_DEMO"); while (set.next()) { System.out.printf("id:%d name:%s", set.getInt("id"), set.getString("name")); }
....
能夠想象隨着項目業務漸漸龐大,相似這些的代碼將會愈來愈多。假若有一天產品經理跑來跟你說 需求發生變化了, 這個時候你不得不將這些代碼再改造一遍, 這感受會瘋的。
那麼是否能夠將那些相似的、重複的代碼封裝起來一個類,而後咱們經過該類來統一訪問數據庫呢? 答案是yes。 而這個類就是 DAO
DAO
實際上是基於JDBC對相關的、重複的業務的再一次封裝, 它提供統一訪問數據庫
的接口規範以及實現類。
如果咱們要設計DAO
須要遵循一些規範,遵循規範便於程序員之間溝通交流。假設如今咱們設計一個Student
的DAO
① 包規範
com.fengyuxiang.sims.dao
: 表明DAO的接口
com.fengyuxiang.sims.dao.impl
: 表明DAO的接口實現
com.fengyuxiang.sims.domain
: 表明數據模型,用來描述對象的
com.fengyuxiang.sims.test
: 一些相關的測試類
② 起名規範
DAO接口: 接口起名使用 IXxxDAO/IXxxDao 好比: IStudentDAO、IStudentDao
DAO實現類: 使用 XxxDAOImpl/XxxDaoImpl 好比: StudentDAOImpl 、StudentDaoImpl
DAO測試類: 使用XxxDAOTest/XxxDaoTest 好比: StudentDAOTest、StudentDaoTest
③ 對象起名規範
IStudentDAO studentDAO = new StudentDAOImpl();