從零一起學Spring Boot之LayIM專案長成記(三) 資料庫的簡單設計和JPA的簡單使用。

丶Pz發表於2017-11-05

前言

  今天是第三篇了,上一篇簡單模擬了資料,實現了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 深入瞭解

 

相關文章