前言
今天是第三篇了,上一篇簡單模擬了資料,實現了LayIM頁面的資料載入。那麼今天呢就要用資料庫的資料了。閒言少敘,書歸正傳,讓我們開始吧。
資料庫
之前有好多小夥伴問我資料庫是怎麼設計的。我個人用關係型資料庫比較多,一般就是根據業務來分析,一對一的關係,一對多的關係,多對多的關係等,那麼對於LayIM就根據這幾個關係出發。而且先根據業務來設計。它初始化的資料我們都見過了,資料中分別包含以下四個部分。
- 個人使用者資訊
- 好友分組資訊
- 群組資訊
大部分業務都是圍繞著使用者轉的,那麼我們一條一條的分析。首先,個人使用者資訊不必說,這裡我們就根據LayIM所需的使用者欄位設計就好,另外加上createAt欄位或者其他欄位都行。很簡單,使用者表SQL如下:
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `avatar` varchar(255) DEFAULT NULL, `sign` varchar(255) DEFAULT NULL, `user_name` varchar(255) DEFAULT NULL, `create_at` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=984389 DEFAULT CHARSET=utf8;
好友分組資訊,一個使用者可以擁有多個分組,而每個分組下又可以有多個好友。 所以,維護好友關係需要兩張表,分組表和使用者(好友)分組關係表
CREATE TABLE `friend_group` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `create_at` bigint(20) DEFAULT NULL, `name` varchar(255) DEFAULT NULL, `uid` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`), KEY `FKawe6k1o9mujam11lxiwcwbqpu` (`uid`), CONSTRAINT `FKawe6k1o9mujam11lxiwcwbqpu` FOREIGN KEY (`uid`) REFERENCES `user` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=770369 DEFAULT CHARSET=utf8;
群組資訊,一個使用者可以有多個群,一個群內有多個使用者,他們是多對多的關係。所以維護這個關係需要兩張表。群組表和群員關係表。
CREATE TABLE `big_group` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `create_at` bigint(20) DEFAULT NULL, `avatar` varchar(255) DEFAULT NULL, `create_uid` bigint(20) DEFAULT NULL, `description` varchar(255) DEFAULT NULL, `group_name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), KEY `FKdw1jlswinwe6gas1tqk1trgia` (`create_uid`), CONSTRAINT `FKdw1jlswinwe6gas1tqk1trgia` FOREIGN KEY (`create_uid`) REFERENCES `user` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
剩下的兩個表沒有寫,分別是 (分組,使用者)關係表,(群組,使用者)關係表。 沒錯,他們都是多對多的關係。你可以在張三的好友列表裡,也可以在李四的好友列表。同理,你可以在多個群裡。
JPA-表的建立
上面簡單介紹了表的結構,雖然寫出了SQL語句,但是呢在專案裡我們並不會手動建立表。因為JPA會幫我們實現自動建立。如下圖,我使用的是update,(即實體類變化等會重新修改表結構)
下面就使用者表寫一個對應的類。(例)
//加上@Entity註解 @Entity public class UserInfo { //主鍵註解 @Id @GeneratedValue @Column(nullable = false) private long id; //列 可以自定義列名,長度等,或者是否為空 @Column(name = "user_name",length = 20) private String name; }
執行程式,檢視一下表,已經成功建立,沒有問題。
但是隻是簡單的表還不夠,因為我們知道,表之間是有關係的,於是JPA中的關係註解就派上用場了。
JPA-關係的對映
之所以在上文中提到了一對多,多對多等詞,其實就是為了在這裡具體解釋。JPA提供了關係註解。@OneToMany,@OneToOne,@ManyToOne,@ManyToMany等。下面我會根據LayIM所需要的關係一一講解。
首先,一個使用者對應多個好友分組。而一個分組只屬於某個(當前)使用者。這裡可以理解為使用者與好友分組的關係是一對多,即 @OneToMany。程式碼中是這樣體現的。
//一對多,一個使用者有多個好友分組 @OneToMany(mappedBy = "user") private List<FriendGroup> friendGroups;
在FriendGroup類中
@ManyToOne @JoinColumn(name = "uid",nullable = false) private User user;
其中呢,name ="uid" 代表,FriendGroup表中的uid欄位對應的是User表中的主鍵(id)欄位。也就是說,當我們查詢一個User實體時,friendGroups欄位會帶上該使用者下的好友分組。當然其中還涉及到了懶載入機制。(具體我也沒研究,大體就是說,當呼叫 getFriendGroups()方法的時候才會根據關係從資料庫查詢相應的結果,這個後邊會有體現)
其次,LayIM載入中會把當前使用者的群組資訊直接載入出來。這裡我們也可以直接用 @OneToMany實現,原理同上。(剛開始我做錯了,我只是列出了該使用者建立的群,沒有考慮到使用者加入的群)
第三,@OneToOne 。這個註解呢在獲取好友分組下的好友列表中用到了。比如,一個好友分組對應多個好友關係。那麼每個好友關係從當前登入者的角度來說就是一對一的。這句話要怎麼理解呢?就是說從程式上來講,我們知道每個分組ID對應的多個UserId,因為表中存的是Id,但是我們取資料的時候需要把該使用者的其他資訊拿出來,那麼使用@OneToOne註解,就會幫我們自動關聯查詢。程式碼如下:
@OneToOne @JoinColumn(name="friend_uid") private User friend;
程式碼中的friend_uid欄位即使用者id。(因為User類中的主鍵已經確定,所以不用再User類中增加 @OneToOne註解)
程式碼實現
(2017-11-7 日晚:此段程式碼已重構,為了寫出我當時的思路,下文內容不會刪掉,重構後程式碼講解:http://www.cnblogs.com/panzi/p/7793854.html)
說了這麼多,想必大家都有點暈了,下面就是程式碼實現了。在這裡呢,我將拿好友分組舉例,這個最簡單。寫一個單元測試,如下:
public User getUser(Long userId){ return userRepository.findOne(userId); }
@Test public void testGetUserFriendGroups() { User user = userService.getUser(100000L); System.out.println("使用者好友分組的個數為:"+user.getFriendGroups().size()); }
執行結果:
小 tips
當你做單元測試的時候,會遇到懶載入沒有Session的問題,如果遇到了,在欄位上面新增 FetchType.EAGER即可
@OneToMany(mappedBy = "user",fetch = FetchType.EAGER)
但是程式執行可以不用加這個,可以在配置檔案中增加open-in-view :true 解決
JPA預設方法
在講解具體實現之前呢,還要介紹一下JpaRepository這個介面,這個玩意挺好玩的,除了預設的方法,還可以自定義比如 findBy,getBy,findBy..OrderBy..等等方法。這裡只介紹當前用到的,至於沒有用到的,有興趣的同學可以自行研究。程式碼很簡單,新建一個UserRepository繼承自JpaRepository即可
public interface UserRepository extends JpaRepository<User,Long> { }
這樣寫了之後,UserRepository預設會有一些方法,增刪改查或者分頁。這裡呢,我們沒有用到分頁,只是簡單的查詢。沒錯就是 findOne方法
LayIM init介面實現
好了,講了這麼多,可能大家都雲裡霧裡的,沒有關係,下面就進入LayIM init介面實現部分。當然作為初學者來說,我就使用了笨方法,為什麼說笨方法呢,方法思路如下:
方法笨就笨在裡面的for迴圈。(應該是有優化的,後期在看)
for(FriendGroup friendGroup : friendGroups) { List<RelationShip> relationShips = friendGroup.getRelationShips(); List<UserViewModel> userViewModels = new ArrayList<UserViewModel>(); //遍歷 relationShips 獲取userViewModels的集合 for(RelationShip relationShip : relationShips){ UserViewModel userViewModel = LayimMapper.INSTANCE.mapUser(relationShip.getFriend()); userViewModels.add(userViewModel); } //獲取主鍵 Long friendGroupId = friendGroup.getId(); for (FriendGroupViewModel viewModel : friends) { if (viewModel.getId().equals(friendGroupId)) { viewModel.setList(userViewModels); } } }
程式碼當中我使用了 LayimMapper,他依賴於一個實體轉換工具:MapStruct。有興趣大家自行去官網看看。不過我個人覺得有點類似程式碼生成器,因為在編譯過後,target裡面會多出一個類來。
其實還是挺方便的,畢竟自己寫也是這麼寫,這樣還大大節約了時間。
好了,本篇內容囉嗦了一大堆,我也不知道講了些啥,總之就是我在學習之中的一些問題和開發思路。知識點很多,所以在我這裡介紹的都是皮毛,要深追究下去,每個知識點都可以寫上幾篇。最後看一下執行效果。介面同上一篇的一樣。
對應資料庫:
後臺執行的語句:
總結
本篇到此為止就結束啦,Jpa部分講解的比較少,決定下一篇重點學習記錄一下。這個基礎介面實現了。LayIM還有一個getMembers介面,你是不是也會了呢?在我學習的過程中,包括關係註解,MapStruct應用和其他實踐上自己給自己挖了很多坑,也出現過很多莫名奇妙的問題,有些東西自己動手了才知道。愉快的週末結束啦。我們下一篇見。
下篇預告:從零一起學Spring Boot之LayIM專案長成記(四) Spring Boot JPA 深入瞭解