MENU

【SpringBoot】SpringBootでいいね機能①

こんにちは、くまごろーです。
今日は「いいね機能」の実装についてまとめます。

ポートフォリオに、SNSではおなじみの「いいね機能」を追加したかったんですが、「SpringBoot いいね機能」とかで調べても記事が全然でてこなかったんですよね。(私の調べ方が下手なだけな可能性ありますが)
なので、自分なりに考えて頑張ってみました。
 
本当はAjax等使用して、フロント処理にしたかったんですが、力及ばず・・・
今回はバックエンド処理で機能追加しました。次回記事をアップする頃には、フロント処理に修正したい・・・

 
 
1 今回導入した機能詳細

  • 「いいね」は、1つの投稿に対し1ユーザー1回しかできない。(「いいね」した投稿のハートアイコンをクリックすると、「いいね」が取り消される)
  • viewファイルでは、自分が既に「いいね」した投稿は塗りつぶされたハート、「いいね」していない投稿は枠線のみのハートアイコンを表示する。
  • ハートアイコンの横に、「いいね」の総数を表示する。

 
2 modelクラス

「いいね」を格納するモデルは以下の様に設定しました。
 
<Like.java

@Data
@Entity
@Table(name="likes")
public class Like {
	
  @Id //主キー
  @GeneratedValue(strategy = GenerationType.IDENTITY) 
  @NotNull
  @Column(name = "like_id")
  private int likeId; 
	
  @NotNull
  @Column(name = "user_id")
  private String username;
	
  @NotNull
  @Column(name = "post_id")
  private int postId;  //「いいね」の対象となる投稿ID
	
  @Column(name = "created_at")
  @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
  private LocalDateTime createdAt;

}

 
2 controllerクラスでの「いいね」実行処理
 
「いいね」の実行処理はこんな感じ。
<PostController.java

// いいね実行
@RequestMapping("/like/{postId}")
@Transactional
public String Like(@PathVariable("postId") int postId, @ModelAttribute("like") Like like, Authentication loginUser, Model model) {
  //すでにいいねしていた場合、いいねを取り消す
  if(likeRepository.existsByUsernameAndPostId(loginUser.getName(), postId) == true) {
      likeRepository.deleteByUsernameAndPostId(loginUser.getName(), postId);
  }else {  //いいねしていなかった場合、投稿へのいいねを登録する
    like.setPostId(postId);
    like.setUsername(loginUser.getName());
    LocalDateTime ldt = LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS);
    like.setCreatedAt(ldt);
    likeRepository.save(like);
  }

    return "redirect:/postmain?postdetail";
}




3 Controllerクラスでの「いいね」表示処理 
 
2の処理登録された「いいね」を、1で述べた条件通りに表示させるのが一番大変でした。他にもっといい方法があるんだろうなとは思いますが、結局このような形になりました。

<PostController>

@GetMapping("/postmain")
  public String goToPost(@ModelAttribute("posts") Post post, Authentication loginUser, Model model) {
    //投稿ごとの「いいね」の総数を取得※1
    Map<Integer, BigInteger> likeCount = likeRepository.findLikeCount();
    model.addAttribute("user", userRepository.findByUsername(loginUser.getName()));
    model.addAttribute("posts", postRepository.findAllPosts());
    model.addAttribute("likeCount", likeCount);
    //自分が「いいね」した投稿一覧を取得※2
    model.addAttribute("myLikes", likeRepository.findMyLikes(loginUser.getName()));

  return "postmain";
}

 
 
※1
LikeRepositoryのfindLikeCountメソッドはこんな感じに作りました。
SQLのcount関数で、投稿ごとの「いいね」数を集計しています。

結果をMapクラスで受けていますが、KeyとValueは以下の割り当てになってます。

  • Key・・・投稿ID
  • Value・・・「いいね」の総数

@Repository
public interface LikeRepository extends JpaRepository<Like, Long> {

  @Query(value = "select post_id, count(*) from likes group by post_id", nativeQuery = true)
  public List<Object[]> getLikeCount();

  default Map<Integer, BigInteger> findLikeCount() {
    Map<Integer, BigInteger> map = new HashMap<Integer, BigInteger>();
    for (int i = 0; i < getLikeCount().size(); i++) {
      int key = (int) getLikeCount().get(i)[0];
      BigInteger value = (BigInteger) getLikeCount().get(i)[1];
      map.put(key, value);
    }
    return map;
  }

}


※2
こちらでは、LikeRepositoryのfindMyLikesメソッドで自分がいいねした投稿IDを検索しています。Integer型のListで結果を取得する形にしました。

@Query(value = "select post_id from likes where user_id = :username", nativeQuery = true)
public Object[] getMyLikes(String username);
	
default List<Integer> findMyLikes(String username) {
  List<Integer> myLikes = new ArrayList<Integer>();
  Object[] myLikesObj = getMyLikes(username);
    for (int i = 0; i < myLikesObj.length; i++) {
      myLikes.add((Integer)myLikesObj[i]);
    }
    
  return myLikes;
}

ちなみに、SQLでの集計結果を別エンティティで取得する方法は以下にまとめてあります。

kumagoro-95.hatenablog.com
 
  
4 viewクラスでの処理

<!-- もしいいねしていたら -->
<!-- list.contains()メソッドでその投稿にいいねしているかチェック-->
<th:block th:if="${myLikes.contains(post.postId) == true}">
<!-- ぬりつぶされたハートアイコンを表示 -->
<a th:href="@{/like/__${post.postId}__}"><i class="fas fa-heart fa-2x pink-text text-accent-2"></i></a>
<!-- いいねの総数を表示 -->
<!-- Map.get()メソッド -->
[[${likeCount.get(post.postId)}]]
</th:block>

<!-- いいねしていなかったら -->
<th:block th:unless="${myLikes.contains(post.postId) == true}">
<!-- 枠線のみのハートアイコンを表示 -->
<a th:href="@{/like/__${post.postId}__}"><iclass="far fa-heart fa-2x pink-text text-accent-2"></i></a>
<!-- いいねの総数を表示 -->
[[${likeCount.get(post.postId)}]]
</th:block>


5 実行結果
 
こんな感じになりました。
f:id:kumaGoro_95:20210129194106p:plain
 
今度はこれをフロントでの処理にしたい・・・