Instagram Clone Coding #2 DB와 JPA

📝 요구사항

  • 회원은 여러 개의 포스트를 작성할 수 있다.
  • 회원은 다른 회원을 팔로우할 수 있다. (하나의 유저는 여러 명을 팔로우할 수 있고, 여러 명한테 팔로우 될 수 있다.)
  • 하나의 포스트는 여러 개의 이미지를 가질 수 있다.
  • 포스트에는 여러 개의 댓글이 달릴 수 있다.
  • 포스트에 좋아요를 누를 수 있다.
  • 댓글에는 대댓글이 달릴 수 있다.
  • 댓글에는 좋아요를 누를 수 있다.

📏 ERD

위 내용을 바탕으로 dbdiagram.io를 사용해서 ERD를 작성했다.

✅ JPA

  • ORM(Object Relational Mapping : 객체 Object와 관계형 DB의 데이터를 자동으로 Mapping 해주는 것)의 일종

  • 객체 지향적으로 프로그래밍을 하면, JPA가 이를 관계형 데이터베이스에 맞게 SQL을 대신 생성해서 실행함.

  • JPA는 인터페이스로 구현체가 필요함 - Hibernate, Eclipse Link 등, 하지만 Spring에서 JPA를 사용할 때는 이 구현체를 직접 다루지는 않고 Spring Data JPA라는 모듈을 이용함 (JPA <- Hibernate <- Spring Data JPA)
    Hibernate 대신 Spring Data JPA를 쓰는 이유? -> 구현체 교체의 용이성 (Hibernate 외의 다른 구현체로 쉽게 교체), 저장소 교체의 용이성 (관계형 DB 이외의 다른 저장소로 쉽게 교체 - 예를 들어 MongoDB로 교체가 필요하다면 Spring Data JPA에서 Spring Data MongoDB로 의존성만 교체하면 된다)

🔍 적용하기

  1. build.gradle에 의존성 추가
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // jpa 추가
	runtimeOnly 'mysql:mysql-connector-java' // mysql
}
  1. Entity 클래스 생성
package com.clonecoding.instagrambackend.domain;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Getter
@EntityListeners(AuditingEntityListener.class)
@Entity
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    private String text;

    @CreatedDate
    private LocalDateTime createdAt;

    private Long views = 0L;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @OneToMany(mappedBy = "post", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Image> images;

    @OneToMany(mappedBy = "post", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Comment> comments;

    @OneToMany(mappedBy = "post", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
    private List<PostLike> likes;

    public Post() {}

    @Builder
    public Post(String text, User user, List<Image> images) {
        this.text = text;
        this.user = user;
        this.images = images;
    }
}

DB 테이블의 column을 각각 변수로 지정한다.
@OneToMany, @ManyToOne 으로 연관 관계를 설정해줄 수 있다.

  1. JPA Repository 생성
    Entity의 CRUD가 가능하도록 한다.
    Entity 클래스와 함께 위치해야 한다.
package com.clonecoding.instagrambackend.domain;

import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Optional;

public interface PostRepository extends JpaRepository<Post, Long> {
    List<Post> findByUser(User user);

    @Query("SELECT count(c) from Post p join p.comments c where p.id = ?1")
    Long countComments(Long id);

    @Query("SELECT count(l) from Post p join p.likes l where p.id = ?1")
    Long countLikes(Long id);
}

JpaRepository는 Dao 같은 DB Layer 접근자로 인터페이스로 생성한다. 단순히 인터페이스를 생성 후, JpaRepository<Entity 클래스, PK타입>을 상속하면 기본 CRUD 메서드가 자동으로 생성이 된다.

JpaRepository가 제공하지 않는 기능을 사용해야 할 때는 위에 @Query를 쓰면 원하는 쿼리를 지정해줄 수 있다.

좋은 웹페이지 즐겨찾기