[SpringBoot(3)] JPA를 통한 관계형 DB 설계

배지원·2022년 11월 26일
0

실습

목록 보기
18/24

지금까지 해오던 프로젝트에 댓글기능을 구현하기 위해서 이전 프로젝트와 별개로 따로 프로젝트를 하나 생성해서 JPA에서 DB 테이블을 Join 하여 호출하는 법을 실습하려고 한다.

총 3개의 DB를 생성하여 2개가 1개의 DB를 가르키도록 설계할 것이다.

DB 구조


① Book,Author Join

  • Book Entity와 Author Entity만 Join 하기

1. JPA Join 사용 X

  • Entity에서 Join 기능을 사용하지 않고 Service에서 각 DB별 데이터를 호출하여 Response DTO에 담는 방법

(1) Domain

Book Entity

@Entity
@Getter
@Table(name = "book")
@AllArgsConstructor
@NoArgsConstructor
public class Book {
    @Id
    private Long id;
    private String name;
    
    private Long authorId;
}
  • Book Entity에는 책 이름이 저장되어있고 Author DB에 있는 authorId의 값의 Id 값이 저장되어 있다.

Author Entity

@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class Author {
    @Id
    private Long id;
    private String name;
}

Book DTO

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
public class BookResponse {
        private long BookId;
        private String BookName;
        private String AuthorName;

        public static BookResponse of(Book book,String authorName){
                return BookResponse.builder()
                        .BookId(book.getId())		// Book DB
                        .BookName(book.getName())	// Book DB
                        .AuthorName(authorName)		// Author DB
                        .build();
        }
}
  • Json 형식으로 반환해주기 위해 구조를 맞추기 위한 DTO 생성
  • 책 ID, 책 제목, 책 작가의 이름을 담고 있음

(2) Repository

Book

@Repository
public interface BookRepository extends JpaRepository<Book,Long> {
}

Author

@Repository
public interface AuthorRepository extends JpaRepository<Author,Long> {
}

(3) Service

Book

@Service
public class BookService {
    private final BookRepository bookRepository;
    private final AuthorRepository authorRepository;

    public BookService(BookRepository bookRepository, AuthorRepository authorRepository) {
        this.bookRepository = bookRepository;
        this.authorRepository = authorRepository;
    }

    
    public Page<Book> TotalList(Pageable pageable){
        return bookRepository.findAll(pageable);
    }
    
    // Json 형식으로 반환
    public List<BookResponse> jsonList(Pageable pageable){
        Page<Book> books = bookRepository.findAll(pageable);
        List<BookResponse> bookResponses = books.stream()
                .map(book ->{
                    // 위에 찾은 전체 데이터인 books에는 id,bookname,authorid 3개의 데이터로 모든 데이터가 리스트로 저장이 되 있다.
                    // 그 중에서 authorid인 부분의 값을 가져와 authorRepository를 통해 author DB에서 데이터를 찾아와
                    // BookResponse의 of메서드(Builder)를 통해 데이터를 매칭 시켜서 반환해준다.

                    // 아래 방법은 DB에서 Join을 사용하지 않고 DB에서 데이터를 각자 찾아 Build를 통해 데이터를 삽입하는 방식이다.
                    Optional<Author> optionalAuthor = authorRepository.findById(book.getAuthorId());
                    return BookResponse.of(book,optionalAuthor.get().getName());
                }).collect(Collectors.toList());
        return bookResponses;
    }
}
  • Author는 데이터만 호출하는 용도이므로 Service는 Book에 대해서만 설계해줌
    (1) Book을 통해 모든 데이터를 찾아 Page list에 저장한다.
    (2) Json형식으로 반환하기 위해 BookResponse형식이 리스트로 변환하기 위해 stream.map을 통해 저장한다.
    (3) 이때, Book Entity에는 author의 id를 가르키고 있으므로 id가 의미하는 값을 가져와야함. 따라서 id를 통해 Author DB에서 값을 찾아와 BookResponse에 저장한다.

(4) Controller

Book

@RestController
@RequestMapping("/api/v1/books")
public class BookController {

    private final BookService bookService;

    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    // List 형식으로 전체 출력(View로 데이터 넘길때 사용)
    @GetMapping("")
    public Page<Book> get(Pageable pageable){
        return bookService.TotalList(pageable);
    }

    // Json 형식으로 전체 출력
    @GetMapping("/json")
    public ResponseEntity<List<BookResponse>> JsonData(Pageable pageable){
        return ResponseEntity.ok().body(bookService.jsonList(pageable));
    }
}

2. JPA Join 사용

  • Entity에서 Join 기능을 사용하여 Mapping하여 데이터 찾는 법
  • 위의 코드에서 바뀐 부분만 작성함

Domain

Book Entity

    @Entity
    @Getter
    @Table(name = "book")
    @AllArgsConstructor
    @NoArgsConstructor
    public class Book {
        @Id
        private Long id;
        private String name;

        // JPA가 Foreign Key를 걸어준다.
        @ManyToOne
        @JoinColumn(name = "author_id") // Book DB의 author_id와 아래의 변수를 Join시킴(즉, 아래의 변수를 FK로 만들어준다)
        private Author author;      // Author 테이블을 호출함
}
  • Book Entity에서 author_id를 Author와 Join하여 FK를 지정해준다.
  • ManyToOne을 통해 단방향으로 해준 이유는 저자는 한명이상으로 구성이 될 수 있기 때문에 author_id를 통해 2개의 값을 찾아 올 수 있기 때문이다.

BookResponse DTO

public class BookResponse {
        private long BookId;
        private String BookName;
        private String AuthorName;

        public static BookResponse of(Book book){
                return BookResponse.builder()
                        .BookId(book.getId())
                        .BookName(book.getName())
                        .AuthorName(book.getAuthor().getName())    // Author Entity의 이름을 가져옴
                        .build();
        }
}
  • 이전과 달리 참조변수를 Book 하나만 사용함(즉, Book에서 join하여 author의 name값을 가져올 수 있단느 뜻)

Service

BookService

@Service
public class BookService {
    private final BookRepository bookRepository;
    private final AuthorRepository authorRepository;

    public BookService(BookRepository bookRepository, AuthorRepository authorRepository) {
        this.bookRepository = bookRepository;
        this.authorRepository = authorRepository;
    }

    
    public Page<Book> TotalList(Pageable pageable){
        return bookRepository.findAll(pageable);
    }
    
    // Json 형식으로 반환
    public List<BookResponse> jsonList(Pageable pageable){
        Page<Book> books = bookRepository.findAll(pageable);
        List<BookResponse> bookResponses = books.stream()
            .map(book -> BookResponse.of(book)).collect(Collectors.toList());   // books에 있는 데이터를 book으로 리스트 1개씩 전부 받아 BookResponse.of로 보내어 준다.
        return bookResponses;
    }
}
  • stream.map을 통해 Book구조의 list를 BookResponse구조의 list로 변경할때도 book하나만 넘겨줘도 됨(이미 Entity에서 join했기 때문에 Entity에서 author.name의 값이 Book Entity에 저장되어 있음)

결과



② Book,Author,Publisher Join

  • Book Entity와 Author Entity, Publisher Entity Join 하기

Publisher Join

  • 이전 코드에서 추가된 코드만 작성함

Domain

Book Entity

@Entity
@Getter
@Table(name = "book")
@AllArgsConstructor
@NoArgsConstructor
public class Book {
        @Id
        private Long id;
        private String name;

        // JPA가 Foreign Key를 걸어준다.
        @ManyToOne                      // 단방향(한개 이상의 데이터를 호출할 수 있으므로, 저자가 여러명)
        @JoinColumn(name = "author_id") // Book DB의 author_id와 아래의 변수를 Join시킴(즉, 아래의 변수를 FK로 만들어준다)
        private Author author;      // Author 테이블을 호출함

        @OneToOne                       // 단방향(무조건 한개의 데이터만 호출할 수 있으므로, 출판사는 한곳에서만)
        @JoinColumn(name = "publisher_id")  // publisher_id를 Publisher의 FK로 연결함
        private Publisher publisher;
}
  • Book DB에 저장된 publisher_id 번호를 Publisher DB와 Join하여 해당 번호가 가르키는 값을 가져와 저장함
  • 이때 Author와 달리 @OneToOne을 사용한 이유는 Author(저자)는 1명이상의 여러명인 경우가 있으므로 1개 이상의 데이터를 받아오기 위해 @ManyToOne을 통해 단방향을 지정해주었고
    Publisher(출판사)는 1곳이여만 하므로 @OneToOne을 통해 단방향을 지정해 줬다.

Publisher Entity

@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class Publisher {
    @Id
    private Long id;
    private String name;
    private String address;
}

BookResponse DTO

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
public class BookResponse {
        private long BookId;
        private String BookName;
        private String AuthorName;
        private String PublisherName;
        
        public static BookResponse of(Book book){
                return BookResponse.builder()
                        .BookId(book.getId())
                        .BookName(book.getName())
                        .AuthorName(book.getAuthor().getName())    // Author Entity의 이름을 가져옴
                        .PublisherName(book.getPublisher().getName())   // Publisher Entity의 이름을 가져옴
                        .build();
        }
}
  • publishername에 대한 builder 추가함
profile
Web Developer

0개의 댓글