前言 在開發過程中,我們不難發現,客戶的需求以及產品的定位對開發內容的走向有很大的決策作用,而這些往往需要在一開始就儘可能考慮周全和設計完善。為什麼說是儘可能,因為我們都知道,需求這種東西,一言難盡...作為開發者,既然無法掌控需求的變更等因素,那我們就要把握好自身能決定的工具資源等,架構設計、技術 ...
前言
在開發過程中,我們不難發現,客戶的需求以及產品的定位對開發內容的走向有很大的決策作用,而這些往往需要在一開始就儘可能考慮周全和設計完善。為什麼說是儘可能,因為我們都知道,需求這種東西,一言難盡...作為開發者,既然無法掌控需求的變更等因素,那我們就要把握好自身能決定的工具資源等,架構設計、技術選型等等。有的人可能會說,我才多久經驗之類,架構什麼的不都是leader們大佬們的事情麽,選什麼資料庫用什麼技術又不是我能決定的。如果你有這種想法,我只能覺得你說得很對。不知道大家在開發過程中,有沒有遇到,某個場景或者效果用現有的工具技術你覺得無法實現或者很難實現,又或者你知道又可以實現的技術框架卻不會用?echarts、POI等,對於我來說,都符合上述場景。總的來說,有的東西在開發過程中可以當作學習,有些東西卻需要你額外花時間在工作之外去研究。當然,如果你說你工作中不管需要什麼新技術,你一天之類就可以掌握,那也是能力。而且這不是嘲諷什麼的,是實實在在的,我現在愈發覺得學習能力的重要性,他對一個人的潛在能力增長曲線的影響太大,當然你有能力卻不學習,那也註定是一條直線射穿。Spring、Strus 2、Hibernate、Mybatis、Spring MVC是比較常見的Web項目會用到的框架,持久層目前來看主要有Hibernate、Mybatis、Spring data jpa等,目前我在用Spring data jpa,這個東西真的厲害,用起來比我以前的想法又上了一個臺階,而且跟hibernate支持的也很好,Hibernate本身也是jpa規範的實現。接下來文中項目中類與方法設計的思路是之前基於Hibernate的,正好回顧一下,認識下其中的不足,好結合現在用的Jpa改進。
項目結構一覽
以web項目技術框架選型為Spring+Hibernate+Spring MVC為例,忽略配置以及web前端文件,後臺結果最精簡情況大致如下:
從資料庫方向依次向前介紹:
1.domain層,這個東西的標準解釋是領域模型,我們orm對象關係映射的實體類一般就放在該層下,即實體層,類似的叫法,entity、model、pojo等作用大抵如此;
2.repository層,資料庫訪問層,這個就是dao層,存放資料庫訪問操作的方法;
3.service層,業務邏輯層,用於存放業務邏輯方法的地方,與dao層相比,有的人可能開始會覺得這個service層沒有什麼必要,或者不太清楚兩者的區別。首先關於兩者區別:業務邏輯層,故名思意,他的重要定位是業務,業務需求什麼,他就要提供什麼,舉個例子,dao層提供了你刪除商品的方法和添加日誌的方法,這兩個方法分別對應有兩個實體對象商品和日誌,操作的也是資料庫相應的單個表,但是實際情況是你通過單個方法總感覺哪裡不對勁,業務場景下,如果需要你刪除東西後會有相應的日誌記錄,而日誌又不會憑空捏造,需要有事件對應。這時候,service層的作用就體現出來了,簡單的可以這麼理解,複雜的資料庫處理你靠單個dao方法無法實現的時候,你在service層去構造這個方法就行,通過SpringIOC特性,在service層註入你需要的調用的dao方法所在的類或者介面,實際場景一般都是介面。其次關於必要性,我曾經也想過,直接在controller裡面註入多個dao類或者介面不是也能達到想要的效果嗎?Controller顧名思義,控制器,它在web結果中,主要起到一個接受和轉發請求並控制的作用,當你的業務邏輯相對簡單的時候,你覺得看不出多大區別,當實際項目業務邏輯相對複雜很多的情況下,這個controller就有點炸了,像個身兼多職操勞過度的苦工,另外結合上一個疑惑,還有一個很重要的概念就是事務,而Spring的事務管理做得也很到位,通過編程式事務管理或者聲明式事務管理都可以實現,而事務一般就設置在業務邏輯也就是service層上,關於事務也是很重要和精髓的一塊,需要學習也值得學習。
4.controller層,控制層,用struts 2也許會叫action層吧,或者通用點,叫web層其實也可以。他可以接收不同的請求url,調用相應的service層代碼,操作資料庫,跳轉到制定頁面,也可以不跳轉,直接返回數據,這裡的數據目前用json的比較多,典型的應用場景:ajax發起非同步請求,DispatcherServlet捕獲請求後對url進行解析,分發到相應的控制類中的相應方法中執行其中代碼,該方法上加上@ResponseBody即可。
關於Hibernate在項目中的定位
Hibernate是一個典型的ORM持久層框架,ORM即Object Relational Mapping(對象關係映射),即實體類與資料庫表之間的關係映射,通俗的講,一張表對應一個實體類,一條記錄對應一個實體類對象,欄位對應實體類屬性。對資料庫的操作在Hibernate中有一個重要對象session,通過session封裝一系列資料庫操作方法在資料庫訪問層,所以在上面的結構中,Hibernate主要操作和作用的有domain層和repository層,下麵示例也省略其他層代碼。
示例
domain層,這裡簡單使用,Mysql資料庫主鍵也用的int自增型,實際應用數據量較大等情況下會用varchar,保存使用UUID賦值主鍵。
1 @Entity 2 @Table(name="TBL_USER") 3 public class User { 4 private Integer id; 5 private String username; 6 private String password; 7 @Id 8 @GeneratedValue(strategy=GenerationType.AUTO) 9 public Integer getId() { 10 return id; 11 } 12 public void setId(Integer id) { 13 this.id = id; 14 } 15 public String getUsername() { 16 return username; 17 } 18 public void setUsername(String username) { 19 this.username = username; 20 } 21 public String getPassword() { 22 return password; 23 } 24 public void setPassword(String password) { 25 this.password = password; 26 } 27 @Override 28 public String toString() { 29 return "User [id=" + id + ", username=" + username + ", password=" + password + "]"; 30 } 31 32 33 34 }
repository層
BaseDao泛型介面
public interface BaseDao<T> { public void edit(Object obj); //添加或者更新一條記錄 public void delete(int id); //根據主鍵刪除一條記錄 public T load(int id); //根據主鍵查找一條記錄(懶載入) public T get(int id); //根據主鍵查找一條記錄(非懶載入) }
BaseDao泛型實現類
public class BaseDaoImpl<T> implements BaseDao<T> { @Resource private SessionFactory factory; private Class<T> clazz = GeneriscUtil.getGenericType(this.getClass()); protected Session getSession() { return factory.getCurrentSession(); } public void edit(Object obj) { getSession().saveOrUpdate(obj); } public void delete(int id) { Object object = getSession().get(clazz, id); if(object != null) { getSession().delete(object); } } public T load(int id) { return (T) getSession().load(clazz, id); } public T get(int id) { return (T) getSession().get(clazz, id); } }
泛型工具類
public class GeneriscUtil { @SuppressWarnings("rawtypes") public static Class getGenericType(Class clazz){ Type genType = clazz.getGenericSuperclass();//獲取泛型父類 Type[] types = ((ParameterizedType) genType).getActualTypeArguments(); if (!(types[0] instanceof Class)) { return Object.class; } return (Class) types[0]; } }
最後:UserDao介面和UserDaoImpl實現類
public interface UserDao extends BaseDao<User> { User login(String username, String password); }
@Repository("UserDao") public class UserDaoImpl extends BaseDaoImpl<User> implements UserDao { public User login(String username, String password) { String hql = "from User u where u.username=:un and u.password=:pwd"; Query query = getSession().createQuery(hql); query.setString("un", username) .setString("pwd", password); Object obj = query.uniqueResult(); return obj != null ? (User)obj : null; } }
還有其的XXXDao和XXXDaoImpl,通過繼承和實現,可以讓全部Impl實現類很輕鬆地就擁有基本的CRUD等通用資料庫操作方法,如果某個實體有特殊的Dao操作只需要在相應Dao介面實現,然後在Impl實現類中實現該特殊方法就行,以前認識到的知識點在這裡有了深入理解。
泛型是一種思想,也是一種技術,動態的獲取類型,讓編程更加靈活,這裡利用泛型,我們可以先不制定實體類對象具體類型,構造一個泛型的Dao介面和實現類,讓後面各自具體的實體Dao去繼承泛型的時候再確定類型,從而也得到了泛型中定義的基本方法。
繼承體現的是代碼重用思想,當一個方法被構造多次的時候我們就要思考代碼重用的問題了,在這裡每個實體對象都需要基本的資料庫操作方法,如果一個個的去定義將會十分繁瑣和枯燥,通過繼承我們可以省去很多代碼。
實現不同於繼承,它沒有節省代碼,而且又涉及到抽象的概念,在單個父類與多個子類的繼承關係中,父類某個方法對應的子類實現有差異時,我們對於父類的該方法,即可定義為抽象方法,子類各自實現具體細節即可。舉個例子:動物父類,子類有鳥、魚等,定義動物時,會定義睡覺方法,但是每種動物睡覺情況都不一樣,鳥睡樹上,魚睡水下,你沒法指定具體實現細節,所以就可以定義一個抽象的睡覺方法void sleep();具體的動物實現介面後,重寫該抽象方法,展示實現細節。
總結繼承和實現:繼承通用的,實現特殊的。在文中構造的持久層中,我們的所有實體通過繼承輕鬆地擁有了所有的通用資料庫操作方法,各自特殊的操作需求可在各自介面中生命然後在實現類中實現即可。
最後,文中的項目中其實還存在許多不足的地方,主鍵的類型其實也可以用泛型,dao層有通用組件,service層是否應該也有通用的,若是有該如何構造等等,一些問題我在現在用的spring data jpa中得到了答案,所以應該會另外梳理一下寫出來。