設計模式是在特定場景下對特定問題的解決方案,這些解決方案是通過反覆論證和測試總結出來的。實際上,除了軟件設計,設計模式也被普遍應用於其餘領域,好比UI設計和建築設計等。Java軟件設計模式大都來源於GoF的23種設計模式。java
這段時間一直在錄製Java EE視頻課程,其中在JDBC(Java數據庫鏈接)中使用了模板方法設計(Template Method),下面給你們分享一下。mysql
在生活中完成一些「任務」有着固定的步驟,例如,我要完成「喝茶」任務,須要的步驟以下:
①燒水→②沏茶→③喝茶
而不少任務也有相似的步驟,例如,我要完成「喝咖啡」任務,固然是速溶咖啡那種。須要的步驟以下:
①燒水→②衝咖啡→③喝咖啡
對應兩個任務他們有相似的3個步驟,步驟①和③是相同的,而步驟②是不一樣的。這樣能夠設計一個父類TaskTemplate代碼以下:git
public abstract class TaskTemplate { public final void 任務() { // 步驟① 燒水(); // 步驟② 沖泡(); // 步驟③ 喝(); } private void 燒水() { System.out.println("燒水..."); } protected abstract void 沖泡(); private void 喝() { System.out.println("喝..."); } }
TaskTemplate是一個抽象類,其中「任務()」方法中定義了執行「任務」的流程,其中「燒水()」和「喝()」是兩個具體方法,因爲父類中沒法肯定沖泡什麼,所以「沖泡()」方法是抽象方法,留給子類實現。「任務()」就是模板方法。
「喝茶」任務實現類TeaTask代碼以下:github
public class TeaTask extends TaskTemplate { @Override protected void 沖泡() { System.out.println("來壺鐵觀音。"); } }
「喝咖啡」任務實現類CoffeeTask代碼以下:sql
public class CoffeeTask extends TaskTemplate { @Override protected void 沖泡() { System.out.println("衝卡布奇諾咖啡+糖+奶。"); } }
他們的類圖如圖1所示。
這就是模板方法設計模式了,那麼如何使用呢?示例代碼以下:數據庫
public class Main { public static void main(String[] args) { System.out.println("------喝茶任務------"); TaskTemplate template = new TeaTask(); template.任務(); System.out.println("------喝咖啡任務------"); template = new CoffeeTask(); template.任務(); } }
輸出結果以下:編程
------喝茶任務------ 燒水... 來壺鐵觀音。 喝... ------喝咖啡任務------ 燒水... 衝卡布奇諾咖啡+糖+奶。 喝...
上述代碼模板子類是有名類,而有時候子類個數太多,也能夠採用匿名內部類做爲模板子類。修改Main調用代碼以下:設計模式
public class Main { public static void main(String[] args) { System.out.println("------喝茶任務------"); TaskTemplate template = new TaskTemplate() { ① @Override protected void 沖泡() { System.out.println("來壺鐵觀音。"); } }; template.任務(); System.out.println("------喝咖啡任務------"); template = new TaskTemplate() { ② @Override protected void 沖泡() { System.out.println("衝卡布奇諾咖啡+糖+奶。"); } }; template.任務(); } }
上述代碼第①行是實現了喝茶任務子類功能,代碼第②行是實現了喝咖啡任務子類功能。框架
上面的介紹的設計模式或許很容易理解,可是又有什麼用途呢?使用設計模式是學習的難點。下面先來看看糟糕的JDBC代碼:ide
public class Main { public static void main(String[] args) { //查詢數據 read(); //數據插入 create(); //數據更新 update(); //刪除數據 delete(); } /** * 查數據 */ private static void read() { // 載數據庫驅動 loadDBDriver(); String sql = "select name, userid from user where userid > ? order by userid"; Connection connection = null; PreparedStatement ps = null; try { // 建立數據庫鏈接 connection = getConnection(); // 建立語句對象 ps = connection.prepareStatement(sql); // 綁定參數 ps.setInt(1, 0); ResultSet rs = ps.executeQuery(); //遍歷結果集 while (rs.next()) { System.out.printf("name: %s id: %d \n", rs.getString("name"), rs.getInt("userid")); } } catch (SQLException e) { e.printStackTrace(); } finally { if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } } } /** * 插入數據 */ private static void create() { // 載數據庫驅動 loadDBDriver(); String sql = "insert into user (userid, name) values (?, ?)"; Connection connection = null; PreparedStatement ps = null; try { // 建數據庫鏈接 connection = getConnection(); // 建立語句對象 ps = connection.prepareStatement(sql); // 綁定參數 ps.setInt(1, 999); ps.setString(2, "Tony999"); // 執行SQL語句 int count = ps.executeUpdate(); System.out.printf("成功插入%d條數據.\n", count); } catch (SQLException e) { e.printStackTrace(); } finally { if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } } } /** * 更新數據 */ private static void update() { // 載數據庫驅動 loadDBDriver(); String sql = "update user set name=? where userid =?"; Connection connection = null; PreparedStatement ps = null; try { // 建立數據庫鏈接 connection = getConnection(); // 建立語句對象 ps = connection.prepareStatement(sql); // 綁定參數 ps.setString(1, "Tom999"); ps.setInt(2, 999); // 執行SQL語句 int count = ps.executeUpdate(); System.out.printf("成功更新%d條數據.\n", count); } catch (SQLException e) { e.printStackTrace(); } finally { if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } } } /** * 刪除數據 */ private static void delete() { // 載數據庫驅動 loadDBDriver(); String sql = "delete from user where userid = ?"; Connection connection = null; PreparedStatement ps = null; try { // 建立數據庫鏈接 connection = getConnection(); // 建立語句對象 ps = connection.prepareStatement(sql); // 綁定參數 ps.setInt(1, 999); // 執行SQL語句 int count = ps.executeUpdate(); System.out.printf("成功刪除%d條數據.\n", count); } catch (SQLException e) { e.printStackTrace(); } finally { if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } } } /** * 創建數據庫鏈接 * * @return 返回數據庫鏈接對象 * @throws SQLException */ private static Connection getConnection() throws SQLException { String url = "jdbc:mysql://localhost:3306/mydb?verifyServerCertificate=false&useSSL=false"; String user = "root"; String password = "12345"; Connection connection = DriverManager.getConnection(url, user, password); return connection; } /** * 加載數據庫驅動 */ private static void loadDBDriver() { // 1. try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
上述代碼中訪問數據的方法有4個read()、create()、update()和delete()。其中create()、update()和delete()三個方法代碼很是類似,只是SQL語句和綁定參數不一樣而已。雖然read()方法與create()、update()和delete()方法不一樣,可是差異也不大。
JDBC代碼主要的問題是:大量的重複代碼!!!
從上一節代碼總結數據庫編程通常過程,如圖2所示。
從圖3中可見查詢(Read)過程最多須要7個步驟。修改(C插入、U更新、D刪除)過程最多須要6個步驟。其中有些步驟是不變的,而有些步驟是可變的。如圖3所示,查詢過程當中一、二、5和7步是不可變的全部查詢都是同樣的,而三、4和6步不一樣,第3步在「建立語句對象」時須要指定SQL語句,這是「此查詢」與「彼查詢」的不一樣之處;因爲SQL語句的不一樣綁定參數也可能不一樣,因此第4步也是不一樣的;另外,第6步是「遍歷結果集」也會根據查詢的不一樣字段,以及字段提取後處理的方式不一樣而有所不一樣。
使用代碼模板方法模式,能夠將一、二、5和7步定義在父類在,將三、4和6步定義在子類中。代碼以下:
public abstract class JdbcTemplate { public final void query() { // 一、載數據庫驅動 loadDBDriver(); Connection connection = null; PreparedStatement ps = null; try { // 二、建立數據庫鏈接 connection = getConnection(); // 三、建立語句對象 四、綁定參數 ps = createPreparedStatement(connection); // 五、執行查詢 ResultSet rs = ps.executeQuery(); // 六、遍歷結果集 while (rs.next()) { proce***ow(rs); } } catch (SQLException e) { e.printStackTrace(); } finally { // 七、釋放資源 if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } } } /** * 遍歷結果集時,處理結果集 * @param rs 結果集 * @throws SQLException */ public abstract void proce***ow(ResultSet rs) throws SQLException; ③ /** * 建立語句對象,其中包括指定SQL語句,綁定參數。 * @param conn 鏈接對象 * @return 語句對象 * @throws SQLException */ public abstract PreparedStatement createPreparedStatement(Connection conn) throws SQLException; ④ /** * 創建數據庫鏈接 * * @return 返回數據庫鏈接對象 * @throws SQLException */ private static Connection getConnection() throws SQLException { String url = "jdbc:mysql://localhost:3306/mydb?verifyServerCertificate=false&useSSL=false"; String user = "root"; String password = "12345"; Connection connection = DriverManager.getConnection(url, user, password); return connection; } /** * 加載數據庫驅動 */ private static void loadDBDriver() { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
在查詢方法中代碼第①行調用抽象方法createPreparedStatement(connection)建立預處理的語句對象,事實上在建立語句對象時,還能夠爲其綁定參數,因此代碼第①行調用createPreparedStatement(connection)過程當中實現「三、建立語句對象」和「四、綁定參數」。
代碼第②行是在遍歷結果集過程當中調用抽象方法proce***ow(rs)處理結果集。通常而言全部遍歷結果集都是while (rs.next()) {…}循環語句實現的,只是提取的字段不一樣,提取以後的處理過程不一樣。
那麼調用read()方法代碼以下:
/** * 查數據 */ private static void read() { String sql = "select name, userid from user where userid > ? order by userid"; JdbcTemplate template = new JdbcTemplate() { ① @Override public PreparedStatement createPreparedStatement(Connection conn) throws SQLException { // 綁定參數 PreparedStatement ps = conn.prepareStatement(sql); // 綁定參數 ps.setInt(1, 0); return ps; } @Override public void proce***ow(ResultSet rs) throws SQLException { System.out.printf("name: %s id: %d \n", rs.getString("name"), rs.getInt("userid")); } }; ② template.query(); ③ }
上述代碼第①行~第②行採用匿名內部類子類化JdbcTemplate類,而且實例化它,而沒有采用有名類子類化JdbcTemplate類,這是由於每一次查詢都須要一個JdbcTemplate子類,以及該子類的實例。這樣會須要建立不少個JdbcTemplate子類。代碼第③行調用模板方法query()執行查詢。
圖4所示是修改過程,其中一、二、5和6步是不可變的全部修改(插入、刪除和更新)都是同樣的,而3和4步是不一樣的。
使用代碼模板方法模式,能夠將一、二、5和7步定義在父類在,將三、4和6步定義在子類中。代碼以下:
public abstract class JdbcTemplate { public final void update() { // 一、載數據庫驅動 loadDBDriver(); Connection connection = null; PreparedStatement ps = null; try { // 二、建立數據庫鏈接 connection = getConnection(); // 三、建立語句對象 四、綁定參數 ps = createPreparedStatement(connection); ① // 五、執行SQL語句 int count = ps.executeUpdate(); System.out.printf("成功修改%d條數據.\n", count); } catch (SQLException e) { e.printStackTrace(); } finally { // 六、釋放資源 if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }
代碼第①行的createPreparedStatement(connection)方法與查詢時共用該方法,當子類實現該方法時建立預編譯語句對象和綁定參數。
那麼調用create()方法的代碼以下:
/** * 插入數據 */ private static void create() { String sql = "insert into user (userid, name) values (?, ?)"; JdbcTemplate template = new JdbcTemplate() { @Override public PreparedStatement createPreparedStatement(Connection conn) throws SQLException { // 綁定參數 PreparedStatement ps = conn.prepareStatement(sql); // 綁定參數 ps.setInt(1, 999); ps.setString(2, "Tony999"); return ps; } @Override public void proce***ow(ResultSet rs) throws SQLException {} ① }; template.update(); }
插入數據的模板也是採用匿名內部類子類化JdbcTemplate,因爲插入過程不須要遍歷結果集,因此抽象方法proce***ow()採用空實現,見代碼第①行。另外update()也是模板方法。
更新數據和刪除數據方法與插入數據方法是相似的,代碼以下:
/** * 更新數據 */ private static void update() { String sql = "update user set name=? where userid =?"; JdbcTemplate template = new JdbcTemplate() { @Override public PreparedStatement createPreparedStatement(Connection conn) throws SQLException { // 綁定參數 PreparedStatement ps = conn.prepareStatement(sql); // 綁定參數 ps.setString(1, "Tom999"); ps.setInt(2, 999); return ps; } @Override public void proce***ow(ResultSet rs) throws SQLException { } }; template.update(); } /** * 刪除數據 */ private static void delete() { String sql = "delete from user where userid = ?"; JdbcTemplate template = new JdbcTemplate() { @Override public PreparedStatement createPreparedStatement(Connection conn) throws SQLException { // 綁定參數 PreparedStatement ps = conn.prepareStatement(sql); // 綁定參數 ps.setInt(1, 999); return ps; } @Override public void proce***ow(ResultSet rs) throws SQLException { } }; template.update(); }
讀者能夠比較一下,採用了模板設計方法後是否是代碼變得很簡單了呢!
JDBC模板子類不要採用有名子類化JDBC模板父類,這會使咱們爲每個查詢和修改操做而編寫一個子類,這個數量會不少。
再有,從上面的代碼可見,模板設計方法仍是能夠進行優化的。事實上還能夠更加抽象一下,即採用接口替代兩個抽象方法,這樣會更加靈活,並且可使用Lambda表達式替代內部類。這種方式就Spring框架的實現Jdbc模板的實現方法,感興趣的同窗能夠看看Spring的源代碼。另外,能夠經過關東昇老師《Java Web從入門到實戰》視頻課程第5章JDBC技術瞭解具體細節。
代碼下載地址:https://github.com/tonyguan/JdbcTemplate
《Java Web從入門到實戰》視頻課程:
一、進入51CTO學院該課程