一直沒做過SSH(Struts2+Spring+Hibernate)的實際項目,只是三個框架學的還熟練,但整合起來使用就不知道了。所以前段時間在網上找了一套SSH實際項目的視頻來學習(確切的說是買的...),一直沒時間來總結,得到的經驗主要是SSH的整合及配置,更多的則是SSH之外的一些經驗,比如代
一直沒做過SSH(Struts2+Spring+Hibernate)的實際項目,只是三個框架學的還熟練,但整合起來使用就不知道了。所以前段時間在網上找了一套SSH實際項目的視頻來學習(確切的說是買的...),一直沒時間來總結,得到的經驗主要是SSH的整合及配置,更多的則是SSH之外的一些經驗,比如代碼的書寫及規範上就給了我很大的啟發,很多經驗只有從實際項目中才能得到。總體來說,SSH整合起來使用不是很難,配置文件也基本是固定的模式。
先看下項目截圖
1.首頁
2.商品詳細
3.購物車
4.訂單管理
5.用戶註冊
6.後臺管理系統首頁【後臺系統使用的是easyui】
7.管理用戶
8.項目工程結構
*******************************************************************************************************************************************************************
*******************************************************************************************************************************************************************
經驗總結
一.首先說說這個項目
1.之前做過的項目大多只有後臺系統,沒有前端,不過學了這個系統之後,其實前端也不難,前端頁面主要由專門的人員做好後,你只需要把數據顯示到上面就行了,然後你可能還需要寫一些js的代碼。
然後說說前端頁面,前端頁面把一些公共的部分提取出來,其它頁面只需引入即可。公共部分如頂部,底部:
通用頁面放到一個文件夾里:
在頁面引入即可:
2.系統不變的數據在系統啟動時就緩存到application中。
比如首頁商品分類,導航欄內容等,這些數據基本是不會變的,可以在系統啟動時就載入出來。就需要寫一個監聽器類,在服務啟動時,將資料庫中的數據載入進記憶體,並將其賦值給一個屬性名,其它的 Servlet 就可以通過 getAttribute 進行屬性值的訪問
3.導航,如下圖。以前我寫類似的導航代碼時一般是用js判斷各個層級,然後拼湊出一個導航來。然而作者是在服務端生成導航代碼,他將生成導航的方法寫到一個工具類中,每次訪問哪個頁面的時候,直接生成這段導航代碼,然後返回到前端。這種做法不僅提高了開發效率,也提高了代碼的重用性,好!
導航工具類:
1 package com.lizhou.tools;
2
3 import java.util.List;
4
5 /**
6 * 導航工具類
7 * @author Administrator
8 *
9 */
10 public class NavTool {
11
12 /**
13 * 生成一級導航
14 * @param subName
15 * @return
16 */
17 public static String genNavCode(String subName){
18 StringBuffer navCode=new StringBuffer();
19 navCode.append("您現在的位置:");
20 navCode.append("<a href='index.jsp'>首頁</a> ");
21 navCode.append("> ");
22 navCode.append(subName);
23 return navCode.toString();
24 }
25
26 /**
27 * 生成多級導航
28 * @param hrefList 導航標簽鏈接的集合
29 * @return
30 */
31 public static String genNavCode(List<String> hrefList){
32 StringBuffer navCode=new StringBuffer();
33 navCode.append("您現在的位置:");
34 navCode.append("<a href='index.jsp'>首頁</a> ");
35 navCode.append("> ");
36 for(String href : hrefList){
37 navCode.append(href);
38 navCode.append("> ");
39 }
40 String nav = navCode.substring(0, navCode.lastIndexOf("> "));
41 return nav;
42 }
43 }
二、說一說使用SSH開發的感受
之前做一些完整的項目時一直使用的是JDBC+Servlet+Jsp,然後還有就是DBUtils等一些包來簡化一些工作量。我會將mysql的一些操作封裝成一個工具類,會寫一個BaseDao來操作資料庫,BaseDao能解決簡單的增刪改查功能,但要解決一些比較複雜的,比如涉及到級聯的時候,可能就不適用了,需要重新寫個類來實現相關的功能。自己設計好bean類後,還需要去設計表結構,而如果類有改動,則需要重新設計表,複雜的時候可能設計到幾張表,如果有外鍵關係的話更惱火......
使用SSH的話,首先來說你需要設計好類結構,然後加上註解或者聲明式配置好,hibernate會自動生成表結構,而如果類有改動,表結構也會自動更改,這算是大大節省了開發時間了。然後,hibernate只需要一個BaseDao就基本能解決所有的數據操作,因為hibernate支持泛型。還有傳數據和參數的問題,我們只需在action里寫一個欄位即可,省了很多代碼。
總的來說,使用SSH框架,我們只需要關心業務邏輯的實現即可,其它的很多問題不必擔心。不過我在想我是不是離底層越來越遠了......我個人還是挺喜歡寫原生代碼的...不喜歡框架。
三、代碼上得到的一些經驗
1.一類操作寫到一個方法里:比如,根據產品名稱查詢,根據產品類別查詢,模糊查詢,查詢所有等操作本屬於一個操作:查詢產品。在以前,我可能會分別寫幾個方法來查詢,getByProductName(String name), getByProductType(String type), getProductList(),然而這些方法只是sql語句不一樣而已,代碼非常冗餘。這些參數本就是產品這個類的欄位,所以完全可以將這些查詢參數封裝到一個Product對象里,在一個方法里每個條件一個判斷語句,然後拼湊Sql語句即可:getProductList(User user).
比如下麵這個service里查詢產品的操作:使用一個集合來裝參數,根據條件拼SQL語句。這樣減少了代碼,邏輯性也更強了,維護也方便。
1 public List<Product> getProductList(Product product, PageBean pageBean) {
2 StringBuffer sbHQL = new StringBuffer("FROM Product ");
3 //存放參數
4 List<Object> param = new LinkedList<Object>();
5 if(product != null){
6 //獲取特價商品
7 if(product.getSpecialPrice() == 1){
8 sbHQL.append("AND specialPrice=1 ORDER BY specialPriceTime DESC ");
9 }
10 //獲取熱賣商品
11 if(product.getHot() == 1){
12 sbHQL.append("AND hot=1 ORDER BY hotTime DESC ");
13 }
14 //獲取大類下的商品
15 if(product.getBigType() != null){
16 //添加參數
17 param.add(product.getBigType().getId());
18 sbHQL.append("AND bigType.id=? ");
19 }
20 //獲取大類下的某個小類商品
21 if(product.getSmallType() != null){
22 //添加參數
23 param.add(product.getSmallType().getId());
24 sbHQL.append("AND smallType.id=? ");
25 }
26 //模糊搜索
27 if(!StringTool.isEmpty(product.getName())){
28 //添加參數
29 param.add("%"+product.getName()+"%");
30 sbHQL.append("AND name like ?");
31 }
32 }
33 String hql = sbHQL.toString().replaceFirst("AND", "WHERE");
34 //是否分頁
35 if(pageBean != null){
36 return baseDao.find(hql, param, pageBean);
37 } else{
38 return baseDao.find(hql, param);
39 }
40 }
2.關於json的一個技巧:可以寫一個處理器來轉換JSON中的一些對象。比如產品有一個日期屬性,類型為java.util.Date。如果不重新處理這個日期,
首先看一下應用場景:
1 /**
2 * 查詢產品集合
3 * @return
4 * @throws Exception
5 */
6 public String list() throws Exception{
7 //分頁對象
8 PageBean pageBean = new PageBean(page, Integer.parseInt(rows));
9 //根據查詢條件和分頁對象查詢產品集合
10 List<Product> productList = productService.getProductList(product, pageBean);
11 //產品總記錄數
12 long total = productService.getProductCount(searchProduct);
13
14 //JSON配置
15 JsonConfig config = new JsonConfig();
16 //過濾掉的屬性
17 config.setExcludes(new String[]{"orderProductList"});
18 JSONArray rows = JSONArray.fromObject(productList, config);
19 JSONObject result = new JSONObject();
20 result.put("rows", rows);
21 result.put("total", total);
22 //返回json字元串
23 ResponseTool.write(ServletActionContext.getResponse(), result);
24 System.out.println(result.toString());
25 return null;
26 }
這裡沒有對產品的日期做處理,輸出如下結果:"hotTime":{"date":1,"day":3,"hours":0,"minutes":0,"month":0,"nanos":0,"seconds":0,"time":1388505600000,"timezoneOffset":-480,"year":114}
可以看到把Date類型直接按對象來解析了,這不是我們想要的。在以前我的處理辦法是在Product里再添加一個String類型的日期欄位,這種方法雖然可行,但其實不符合程式設計,用起來著實不爽O(∩_∩)O哈哈~。
然後這次學到了,可以寫一個處理器類,該類需要實現JSON的一個介面:net.sf.json.processors.JsonValueProcessor
實現類如下:
1 package com.lizhou.tools;
2
3 import java.text.SimpleDateFormat;
4
5 import net.sf.json.JsonConfig;
6 import net.sf.json.processors.JsonValueProcessor;
7
8 /**
9 * json-lib 日期處理類
10 * @author bojiangzhou
11 *
12 */
13 public class DateJsonValueProcessor implements JsonValueProcessor{
14
15 /**
16 * 日期格式
17 */
18 private String format;
19
20 public DateJsonValueProcessor(String format){
21 this.format = format;
22 }
23
24 public Object processArrayValue(Object value, JsonConfig jsonConfig) {
25 // TODO Auto-generated method stub
26 return null;
27 }
28
29 public Object processObjectValue(String key, Object value, JsonConfig jsonConfig) {
30 if(value == null)
31 {
32 return "";
33 }
34 if(value instanceof java.sql.Timestamp)
35 {
36 String str = new SimpleDateFormat(format).format((java.sql.Timestamp)value);
37 return str;
38 }
39 if (value instanceof java.util.Date)
40 {
41 String str = new SimpleDateFormat(format).format((java.util.Date) value);
42 return str;
43 }
44
45 return value.toString();
46 }
47
48 }
然後只需註冊該處理器即可:config.registerJsonValueProcessor(java.util.Date.class, new DateJsonValueProcessor("yyyy-MM-dd"));
1 /**
2 * 查詢產品集合
3 * @return
4 * @throws Exception
5 */
6 public String list() throws Exception{
7 //分頁對象
8 PageBean pageBean = new PageBean(page, Integer.parseInt(rows));
9 //根據查詢條件和分頁對象查詢產品集合
10 List<Product> productList = productService.getProductList(product, pageBean);
11 //產品總記錄數
12 long total = productService.getProductCount(searchProduct);
13
14 //JSON配置
15 JsonConfig config = new JsonConfig();
16 //過濾掉的屬性
17 config.setExcludes(new String[]{"orderProductList"});
18 //註冊日期處理器
19 config.registerJsonValueProcessor(java.util.Date.class, new DateJsonValueProcessor("yyyy-MM-dd"));
20 JSONArray rows = JSONArray.fromObject(productList, config);
21 JSONObject result = new JSONObject();
22 result.put("rows", rows);
23 result.put("total", total);
24 //返回json字元串
25 ResponseTool.write(ServletActionContext.getResponse(), result);
26 System.out.println(result.toString());
27 return null;
28 }
輸出結果:"hotTime":"2014-01-01"
同樣的,也可以實現其它級聯對象處理器,比如產品里有個BigType(大類別,有id,name,remark,productList),但我在顯示產品列表時只需要大類別的名稱和id,這時就可以寫一個對象處理器。
如下:
1 package com.lizhou.tools;
2
3 import java.beans.PropertyDescriptor;
4 import java.lang.reflect.Method;
5
6 import net.sf.json.JSONObject;
7 import net.sf.json.JsonConfig;
8 import net.sf.json.processors.JsonValueProcessor;
9
10 /**
11 * 解決對象級聯問題
12 * @author Administrator
13 *
14 */
15 public class ObjectJsonValueProcessor implements JsonValueProcessor{
16
17 /**
18 * 保留的欄位
19 */
20 private String[] properties;
21
22 /**
23 * 處理類型
24 */
25 private Class<?> clazz;
26
27 /**
28 * 構造方法
29 * @param properties
30 * @param clazz
31 */
32 public ObjectJsonValueProcessor(String[] properties,Class<?> clazz){
33 this.properties = properties;
34 this.clazz =clazz;
35 }
36
37 public Object processArrayValue(Object arg0, JsonConfig arg1) {
38 // TODO Auto-generated method stub
39 return null;
40 }
41
42 public Object processObjectValue(String key, Object value, JsonConfig jsonConfig) {
43 PropertyDescriptor pd = null;
44 Method method = null;
45 StringBuffer json = new StringBuffer("{");
46 try{
47 for(int i=0;i<properties.length;i++){
48 pd = new PropertyDescriptor(properties[i], clazz);
49 method = pd.getReadMethod();
50 String v = String.valueOf(method.invoke(value));
51 json.append("'"+properties[i]+"':'"+v+"'");
52 json.append(i != properties.length-1?",":"");
53 }
54 json.append("}");
55 }catch (Exception e) {
56 e.printStackTrace();
57 }
58 return JSONObject.fromObject(json.toString());
59 }
60
61 }
同樣,註冊該處理器即可:
1 config.registerJsonValueProcessor(ProductBigType.class, new ObjectJsonValueProcessor(new String[]{"name", "id"}, ProductBigType.class));
2 config.registerJsonValueProcessor(ProductSmallType.class, new ObjectJsonValueProcessor(new String[]{"name", "id"}, ProductSmallType.class));
*******************************************************************************************************************************************************************
*******************************************************************************************************************************************************************
總的來說就是這些,還有些比較細節的忘了,最大的收穫就是經驗還是得從實際項目中來....
然後還遇到一個非常糾結的問題:那就是session的過期時間。場景是這樣的,以前做項目的時候我在session中存的數據幾乎是在10秒內就消失了,比如我存登錄用戶的信息(User對象)到session中,如果一直刷新頁面不會消失,但停留幾秒鐘,再刷新一次,數據就消失了,但是session並沒有銷毀。以前百度過很多資料,包括修改session過期時間,都沒用,也有說是tomcat的配置問題,我就懶得改了。
直到做這個項目,我發現存在session中的登錄用戶信息、包括購物車裡的數據同樣是如此,過不了多久就消失了;但是存的其它的一些信息,比如字元串形式的數據,一直沒有消失,在不關閉瀏覽器的情況下,那些數據一直都在;我就糾結了,難道消失的只是那些對象型數據?後面再找時間測試,如果你們知道問題所在,請告訴我,O(∩_∩)O謝謝!!!