∞、項目亮點/技巧/學習點
1.在前端使用EL表達式取值提示用戶賬號/密碼錯誤
在登陸界面使用的EL表達式(
- 由於在用戶登陸之前,這個div中的EL表達式取不到值,所以它不會顯示,而一旦用戶輸入錯誤,后端就會向前端響應參數(req.setAttribute("error","用戶名或密碼錯誤")😉 + 重定向前端視圖,這就使用div中的EL表達式取到了error的值,從而在登陸界面上進行顯示
2.使用session存儲用於的全部信息
在用戶登陸上的時候,將從數據庫中將用戶的全部信息封裝的pojo中,並將pojo存到session中,這就便於后面我們需要使用這個用戶的某些信息的時候,不用再去查詢數據庫,而直接從session中獲取對應的pojo對象的屬性即可
- 比如修改密碼的時候使用Ajax與session中User對象的passwor做對比
- 比如我們使用過濾器,在每一次用戶發起請求的時候都去查看用戶是否登陸,保證數據安全
- 比如我們用session控制用戶最長在線時間,只要長時間沒操作將自動注銷登陸
- 比如注銷功能,直接將session中用戶對象屬性刪除即可配合過濾器將用戶注銷
- ...
3.SQL語句的動態拼接
- 在實現用戶管理功能的模塊中,首次用到了StringBuffer+集合List的組合來實現動態拼接SQL語句和存儲pstmt對象所需要的參數數組的功能
- StringBuffer提供動態拼接SQL語句的功能,使得我們可以在基礎的SQL語句上加上獨特的篩選條件
- 集合List通過存儲pstmt對象所需要的參數數組的功能,使得不管我們要在后面添加多少篩選條件都只是用一個集合List存儲,在最后傳輸的時候調用List.toArray()即可將其轉為數組
4.某一功能的開發流程
-
要在前端實現一個功能,我們需要按照Dao開始-->service-->servlet-->JSP的順序來開發
並在進行寫代碼之前分析實現步驟/模塊功能划分(很重要)
只有我們先想好了怎么做,然后再去編寫代碼才會快,且有條不紊,切忌看完要求之后馬上開始寫代碼
5.在項目中多復用,少創建
-
本項目中,我們復用了servlet
- 通過前端傳遞的指定參數名稱的參數的值,調用同一個servlet中不同的方法(將實現多個功能的servlet獨立封裝為一個個的方法)
- 通過前端傳遞的指定參數名稱的參數的值,調用同一個servlet中不同的方法(將實現多個功能的servlet獨立封裝為一個個的方法)
在本項目中我們復用了SQL
- 前面第3點我們提到了使用"StringBuffer+集合List的組合來實現動態拼接SQL語句和存儲pstmt對象所需要的參數數組的功能",這個功能給我們帶來的效果就是通過判斷傳遞的參數是否為NULL,我們可以動態的決定要在基礎的SQL語句后面添加什么篩選條件,這些條件中的參數又是什么
- 復用的效果就是:使用這一條基本SQL語句+StringBuffer+集合List我們可以實現前端按照用戶名查詢、按照職位名稱查詢和整表查詢
6.合理使用隱藏域
在本項目中隱藏域的使用是配合servlet復用使用的,我們就是通過隱藏域來讓前端向后端提交method參數的值,然后通過判斷method的值決定到底調用哪一個封裝在復用servlet中的方法
7.合理使用過濾器
通過合理的適合於過濾器我們可以為后端服務器程序攔截許多的非法請求、垃圾請求、無效請求,也可以解決中文亂碼問題
注意:在使用過濾器的時候,正常情況下我們只需要實現doFilter(),並且最重要的就是在方法的最后放行,否則這個請求是不能到達服務器程序並被響應的
chain.doFilter(req,resp);//過濾器放行
8.合理的編寫公用的Dao
在本項目中,在最開始我們就編寫了一個BaseDao類,這個類不針對某一張表進行CRUD,內部沒有綁定任何的SQL語句
這個類我們一共只定義了1個static代碼塊+4個方法
- 靜態代碼塊用於初始化JDBC4大參數
- 4個方法分別為
- 獲取數據庫連接對象
- 查詢數據
- 修改數據
- 關閉資源
- 注意,這些方法之間不要出現相互調用的情況,我們應該在具體的對某一張表進行操作的時候再來調用這個類中的某些方法;且這些方法使用的數據庫對象全部都需要從外部傳遞
package com.thhh.dao;
/**
* 注意理解這個類中的方法之所以要傳入這些數據庫操縱對象是因為為了統一的關閉資源
* 而傳入的對象中可以都是null,具體的對象獲取在方法里面進行;也可以只有conn實例化,其他對象的實例化同樣放在具體的方法里進行
*/
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
//操作數據庫的公共類
public class BaseDao {
private static String DRIVER;
private static String URL;
private static String USERNAME;
private static String PASSWORD;
//靜態代碼塊用於初始化JDBC4大參數,且靜態代碼塊只會在第一次調用這個類的時候執行一次
static {//靜態代碼塊,在調用這個類的地方優先自動執行
//讀取配置文件
//1、創建properties對象
Properties properties = new Properties();
//2、通過類加載器加載資源文件為字節輸入流
InputStream in = BaseDao.class.getClassLoader().getResourceAsStream("db.properties");
try {
properties.load(in);
} catch (IOException e) {
e.printStackTrace();
}
DRIVER = properties.getProperty("DRIVER");
URL = properties.getProperty("URL");
USERNAME = properties.getProperty("USERNAME");
PASSWORD = properties.getProperty("PASSWORD");
}
//1、編寫獲取數據庫的連接對象的公共方法
public static Connection getConnection(){
Connection conn= null;
try {
//1、加載驅動類
Class.forName(DRIVER);
//2、獲取連接對象
conn = DriverManager.getConnection(URL,USERNAME,PASSWORD);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return conn;
}
//2、編寫查詢公共方法 —— 注意查詢的結果返回為ResultSet結果集
/**
* 用於查詢數據的公共方法,注意:使用發送SQL語句的對象為PreparedStatement
* @param sql:查詢的sql語句,由前端傳遞
* @param params:sql語句中占位符的值
*
*===============這下面的3個參數之所以在調用的時候傳遞原因就在於這3個都是資源,我們需要關閉,如果我們直接在這個方法里獲取資源對象的話,那么我們就應該在這個方法中關閉資源===============
*===============但是調用處還在等待這個方法返回結果集,所以我們不應該在這個地方獲取這3個對象,而應該由調用出傳遞,這樣可以統一管理和關閉資源===============
*
* @param conn:調用出使用BaseDao.getConnection()獲取到數據庫連接對象傳入
* @param pstmt:調用出只是傳入null的引用。這個對象真正的實例化放在這個方法里面
* @param rs:返回的結果集,和pstmt只是傳入null的引用。這個對象真正的實例化放在這個方法里面
*
* @return:返回查詢到的結果集
*/
public static ResultSet executeQuery(String sql,Object[] params,Connection conn,PreparedStatement pstmt,ResultSet rs){
try {
pstmt = conn.prepareStatement(sql);
for (int i=1;i<= params.length;i++){//循環遍歷參數數組,並將參數設入SQL中
pstmt.setObject(i,params[i-1]);//注意:數組的index從0開始,而PreparedStatement中設置占位符的值的index從1開始
}
rs = pstmt.executeQuery();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return rs;
}
//3、編寫修改公共方法
/**
* 用於修改數據的公共方法,注意:使用發送SQL語句的對象為PreparedStatement
* @param sql:修改數據的sql語句模板
* @param params:模板中占位符對應的值
*
* =====下面兩個對象需要調用出傳入也是為了統一管理和關閉資源=====
* @param conn:調用出使用BaseDao.getConnection()獲取到數據庫連接對象傳入
* @param pstmt:調用出只是傳入null的引用。這個對象真正的實例化放在這個方法里面
*
* @return 返回受影響的行數
*/
public static int executeUpdate(String sql,Object[] params,Connection conn,PreparedStatement pstmt){
int result = 0;
try {
pstmt = conn.prepareStatement(sql);
for (int i=1;i<= params.length;i++){//循環遍歷參數數組,並將參數設入SQL中
pstmt.setObject(i,params[i-1]);//注意:數組的index從0開始,而PreparedStatement中設置占位符的值的index從1開始
}
result = pstmt.executeUpdate();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return result;
}
//4、編寫關閉資源公共方法
/**
* 關閉資源
* @param conn:調用出使用BaseDao.getConnection()獲取到數據庫連接對象傳入
* @param pstmt:調用出只是傳入null的引用。這個對象真正的實例化放在這個方法里面
* @param rs:返回的結果集,和pstmt只是傳入null的引用。這個對象真正的實例化放在這個方法里面
* @return:返回關閉資源的結果
*
* 注意:關閉資源的時候要倒着關
*/
public static boolean close(Connection conn,PreparedStatement pstmt,ResultSet rs){
boolean flag = true;
if (rs!=null){
try {
rs.close();
rs = null;//讓這個變量為null,gc就會自動對其進行回收
} catch (SQLException throwables) {
throwables.printStackTrace();
flag = false;//關閉失敗就將flag設置false
}
}
if (pstmt!=null){
try {
pstmt.close();
pstmt = null;
} catch (SQLException throwables) {
throwables.printStackTrace();
flag = false;
}
}
if (conn!=null){
try {
conn.close();
conn = null;
} catch (SQLException throwables) {
throwables.printStackTrace();
flag = false;
}
}
return flag;//返回關閉結果
}
}
9.各層功能單一,使得項目維護更高效
這一點其實可以和第4點寫在一起,但是為了加深印象就分開寫了
第4點里面說了:項目開步驟應該按照Dao開始-->service-->servlet-->JSP的順序來開發
這里補充說明:在開發的時候我們應該保證開發的每一層只是在完成自己這一層應該有的功能,應該其他層完成的功能,堅決不要混到其他層來開發
各層的功能遵循SpringMVC的划分:
-
DAO只專注於與數據庫交互
- service專注於完成業務邏輯編寫
-
servlet專注獲取前端請求、調用service、和跳轉視圖
-
JSP專注於頁面展示
各層之間的功能不要混用,各層之間的聯系通過項目調用來構建
10. 多利用"面向接口編程"的思想
在本項目中,不管是開發Dao層還是開發service層,我們都使用了面向接口編程的思想
首先編寫接口定義,再去實現接口定義;這樣我們開發過程中就實現了設計和實現分離,整個項目的接口也就很清晰
11.多測試
在開發中,我們每寫完一個功能之后,最好就使用junit對剛剛寫的方法進行測試,這保證了開發的穩步前進
在Dao層有SQL語句的地方我們最好是寫一句輸出SQL語句的代碼,這樣每次執行的時候我們都可以看到SQL語句具體是什么樣的,也容易進行排錯
12.細心
開發過程中往往出現bug的地方都很微小,但是就是微小才不容易發現,從而導致了程序報錯
- 比如:SQL語句拼接時的空格問題
- 比如:for循環時等號取不取的問題
- 比如:sql.append(" AND u.userRole = ?");中將u.userRole寫成r.roleCode
13.小黃鴨調試法
傳說中程序大師隨身攜帶一只小黃鴨,在調試代碼的時候會在桌上放上這只小黃鴨,然后詳細地向鴨子解釋每行代碼
一邊闡述代碼的意圖一邊觀察它實際上的意圖並做調試,這兩者之間的任何不協調會變得很明顯,並且更容易發現自己的錯誤
類似的,有一種現象叫做cone of answers,這是一個常見的現象。你的朋友跑來問你一個問題,但是當他自己把問題說完,或者說到一半的時候就想出了答案走了,留下一臉茫然的你。是的,這個時候你就起到了那只小黃鴨的作用
總的來說,在你試圖表述自己的想法的過程中,自然地在促使自己去整理思路,重新考慮問題
14.一些簡寫(背下來)
- CRUD:crud是指在做計算處理時的增加(Create)、檢索(Retrieve)、更新(Update)和刪除(Delete)幾個單詞的首字母簡寫,crud主要被用在描述軟件系統中數據庫或者持久層的基本操作功能
- ACID:ACID,指數據庫事務正確執行的四個基本要素的縮寫,包含:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability);一個支持事務(Transaction)的數據庫,必須要具有這四種特性,否則在事務過程(Transaction processing)當中無法保證數據的正確性,交易過程極可能達不到交易方的要求
15.實際開發中,多用事務
在實際的開發中,CRUD操作應該都是用事務操作來提交,這是因為這4種操作在執行的時候都可能失敗,所以為了安全,最好的解決方案就是使用事務機制編寫代碼
注意事務的使用位置:在try開始的時候開啟事務,在業務邏輯完成的時候提交事務,在catch中回滾事務
16.開發手冊
推薦<阿里巴巴開發手冊>,主要就是規約我們開發過程中的規范問題,寫代碼的結構問題 —— 很重要