풀스택 개발자 과정 32일차

너구·2026년 6월 22일

풀스택 성장과정

목록 보기
32/79

지역 도서관 웹사이트 만들기

application.properties

spring.application.name=locallibray

# 데이터베이스 연결하기

# 데이터베이스 주소
spring.datasource.url=jdbc:mysql://localhost:3306/locallibray_db?createDatabaseIfNotExist=true
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 데이터베이스 관리자 이이디/비번
spring.datasource.username=root
spring.datasource.password=1234

# JPA 설정
# 서버를 시작할 때 엔티티로부터 테이블을 생성한다
# 만약 기존에 테이블이 있으면 삭제하고 새롭게 생성한다
spring.jpa.hibernate.ddl-auto=create

Spirng Boot로 웹사이트를 만들 때 사용하는 파일이다.
프로젝트의 기본 설정을 담당하고 데이터베이스 연결 설정이 들어간다.

enums/Status.java

package com.example.locallibray.enums;

// 도서 사본의 상태를 나타내는 이넘 (관련있는 상수의 집합)
public enum Status {
    
    MAINTENANCE("보수중"),
    AVAILABLE("대출가능"),
    LOANED("대출중"),
    RESERVED("예약됨");

    // enum 상수의 속성
    // label: 뷰에서 출력용으로 활용할 데이터
    private final String label;

    // enum 생성자: 이넘에 속한 상수에게 속성을 할당한다
    private Status(String label) { // 기본값이 private
        this.label = label;
    }

    // getter: 라벨에 접근용
    public String getLabel() {
        return label;
    }
}

이 파일은 도서관 시스템에서 상태 값을 안전하고 통일성 있게 관리하는 파일이다.
도서관 시스템에서 상태를 문자열로 쓰면 오타 위험이 있다.
코드 전체에서 값이 제각각 사용되어 유지보수가 어려우며 헷갈릴 위험이 있다.
enum을 써서 오타를 방지하고 코드 통일성을 확보하였다.

entity/Book.java

package com.example.locallibray.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;

// 이 클래스를 엔티티로 정의한다
@Entity // 엔티티: 데이터베이스 테이블과 매핑되는 객체 (도메인)
public class Book {
    
    @Id // 이 필드를 기본키(Primary Key)로 설정
    // 기본키의 생성방식(Generation Type)을 AUTO_INCREMENT로 설정
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;

    // 책 제목
    private String title;

    // 책 요약
    @Column(length = 1000) // 최대 길이 1000자 = varchar(1000)
    private String summary;

    // 외래키 지정하기
    // @ManyToOne: 다대일(N:1) 관계를 정의하는 어노테이션
    // 다대일: 여러개의 책(Book)이 하나의 저자(Author)에 속하는 관계
    // fetch: 데이터 가져오기 전략. EAGER, LAZY가 있다
    // EAGER(즉시 가져오기): 연관된 엔티티를 항상 함께 조회한다
    // LAZY(미루기): 연관된 엔티티가 필요한 순간에 가져온다
    // 예) Book 테이블에서 집계 함수(COUNT 등)을 활용할 때 저자 조회를 할 필요가 없음
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id") // author_id: 외래키의 이름을 설정한다
    private Author author;

    // ISBN (국제 책번호)
    private String isbn;

    // 기본 생성자
    public Book() {}

    // 객체 생성용 생성자
    public Book(String title, String summary, String isbn, Author author) {
        this.title = title;
        this.summary = summary;
        this.isbn = isbn;
        this.author = author;
    }
    
    // Getters
    public long getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public String getSummary() {
        return summary;
    }

    public Author getAuthor() {
        return author;
    }

    public String getIsbn() {
        return isbn;
    }

}

지역 도서관 웹사이트에서 책 데이터의 구조를 정의하는 핵심 클래스이다.
쉽게 말하자면 데이터베이스의 책 테이블을 java 코드로 표현한 것이다.
도서관 웹사이트에서 책 한 권의 정보를 DB와 연결해주는 설계도라고 생각하면 편하다.

entity/Genre.java

package com.example.locallibray.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Genre {
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String name;

    public Genre() {}
    
    // Getters
    public Genre(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

}

웹사이트에서 책의 장르 정보를 저장하는 데이터구조이다.
쉽게 말하자면 책이 어떤 종류인지 구분하기 위한 기준을 만드는 역할이다.
도서관 책들을 체계적으로 분류하기 위한 카테고리 설계도이다.

entity.BookGenre.java

package com.example.locallibray.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;

// 책-장르 다대다(N:M) 관계를 구현하기 위해 필요한 엔티티
// 책ID, 장르ID 두 개의 컬럼으로 구성된다
@Entity
public class BookGenre {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // 책ID
    // 다대일(N:1): 여러개의 책이 같은 장르를 공유할 수 있다
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="book_id") // 책id를 외래키로 지정
    private Book book;

    // 장르ID
    // 다대일: 여러개의 장르가 하나의 책에 속할 수 있다
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "genre_id")
    private Genre genre;

    // 기본 생성자
    public BookGenre() {}

    // 객체 생성용 생성자
    public BookGenre(Book book, Genre genre) {
        this.book = book;
        this.genre = genre;
    }

    // Getters
    public Long getId() {
        return id;
    }

    public Book getBook() {
        return book;
    }

    public Genre getGenre() {
        return genre;
    }
}

이 파일은 지역 도서관 웹사이트에서 책과 장르의 연결관계를 정리하는 중간 테이블이다.
쉽게 말하자면 Book과 Genre 사이의 관계를 지정하는 역할이다.
Book 하나 Genre 하나를 연결해서 이 책은 어떤 장르에 속하는가를 기록한다.

entity/BookInstance.java

package com.example.locallibray.entity;

import java.time.LocalDate;

import com.example.locallibray.enums.Status;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;

@Entity
public class BookInstance {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // 책 ID (외래키)
    // 다대일: 여러개의 책 사본이 하나의 책에 속한다
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "book_id") // 외래키 이름
    private Book book;

    // 출판사
    private String imprint;

    // 상태: 대출중, 대출가능 등
    private Status status;

    // 반납일자: 데이터베이스의 date 자료형과 대응한다
    // yyy-mm-dd (년-월-일)
    private LocalDate dueBack;

    // 생성자, 게터
    public BookInstance(Book book, String imprint, Status status, LocalDate dueBack) {
        this.book = book;
        this.imprint = imprint;
        this.status = status;
        this.dueBack = dueBack;
    }

    // Getters
    public Long getId() {
        return id;
    }

    public Book getBook() {
        return book;
    }

    public String getImprint() {
        return imprint;
    }

    public Status getStatus() {
        return status;
    }

    public LocalDate getDueBack() {
        return dueBack;
    }
    
}

이 파일은 실제로 존재하는 책 한 권(사본)을 관리하는 엔티티이다.
같은 책이라도 여러 권 있을 때 각각을 구분해주는 역할이다.

entity/Author.java

package com.example.locallibray.entity;

import java.time.LocalDate;

import org.springframework.data.annotation.Id;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;

// 저자 엔티티로 등록
@Entity
public class Author {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // 이름
    private String firstName;

    // 성
    private String familyName;

    // 출생일
    private LocalDate birthDate;

    // 사망일
    private LocalDate deathDate;

    // 생성자, 게터
    public Author() {}

    public Author(String firstName, String familyName, LocalDate birthDate, LocalDate deathDate) {
        this.firstName = firstName;
        this.familyName = familyName;
        this.birthDate = birthDate;
        this.deathDate = deathDate;
    }

    // Getters
    public Long getId() {
        return id;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getFamilyName() {
        return familyName;
    }

    public LocalDate getBirthDate() {
        return birthDate;
    }

    public LocalDate getDeathDate() {
        return deathDate;
    }

    // 저체 이름을 반환한다 (유틸리티용)
    public String getName() {
        return firstName + " " + familyName;
    }
}

이 파일은 책을 쓴 사람(저자)의 정보를 관리하는 엔티티이다.
책 데이터와 연결되는 사람 정보 테이블이라고 보면 된다.
한 마디로 책을 쓴 사람의 정보를 체계적으로 관리하는 데이터구조라고 볼 수 있다.

repository/BookRepository.java

package com.example.locallibray.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.locallibray.entity.Book;

// 리포지토리: 데이터 접근 객체(DAO) 마이베티스의 매퍼 역할과 같다
// 인터페이스를 정의하면 스프링이 내부적으로 구현한다
// Book: 리포지토리가 관리하는 엔티티
// Long: 엔티티에서 기본키의 타입
public interface BookRepository extends JpaRepository<Book, Long> {
    
    // 기본적인 쿼리들은 여기에 내장되어 있다. 필요한 것만 추가하면 된다

    // 검색할 때 쓸 쿼리
    // keyword: 검색어
    // Containing: 검색어(keyword)를 포함하는 제목(title)을 찾는다
    // IgnoreCase: 대/소문자를 구분하지 않는다
    List<Book> findByTitleContainingIgnoreCase(String keyword);

    // 검색 결과의 개수 세기
    // 집계함수 (COUNT)를 활용한다
    Long countByTitleContainingIgnoreCase(String keyword);
}

이 파일은 Book 데이터를 DB에서 가져오고 저장하는 역할을 담당하는 인터페이스이다.
쉽게 말하자면 데이터베이스와 java를 연결해주는 통로인 것이다.
Book 데이터를 데이터베이스에서 가져오고 저장하는 자동화된 통로이다.

repository/GenreRepository.java

package com.example.locallibray.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.locallibray.entity.Genre;

public interface GenreRepository extends JpaRepository<Genre, Long>{
    
    
}

이 파일은 장르 데이터를 데이터베이스와 연결해서 조회/저장하는 통로 역할을 한다.
DB에서 꺼내오고 저장하는 기능을 담당하는 인터페이스인 것이다.

repository/BookGenreRepository.java

package com.example.locallibray.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.locallibray.entity.BookGenre;

public interface BookGenreRepository extends JpaRepository<BookGenre, Long> {
    
    // 장르 ID의 값이 전달받은 id와 일치하는 행들을 조회한다
    // 특정 장르에 해당하는 책들을 찾을 때 활용한다
    List<BookGenre> findByGenreId(Long id);

    // 장르 id값이 입력받은 id와 일치하는 행의 개수를 센다
    // 특정 장르에 해당하는 책의 개수를 센다
    Long countByGenreId(Long ind);

    // 책 ID의 값이 전달받은 id와 일치하는 행들을 조회한다
    // 특정 책의 장르들을 확인할 때 사용한다
    List<BookGenre> findByBookId(Long id);
}

이 파일은 Book과 Genre 사이의 연결 데이터(BookGenre)를 DB에서 관리하는 Repository이다.
책과 장르의 연결 정보를 조회/저장/삭제하는 통로이다.

repository/BookInstanceRepository.java

package com.example.locallibray.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.locallibray.entity.BookInstance;

// 책 사본 관리 DAO
public interface BookInstanceRepository extends JpaRepository<BookInstance, Long>{

    // 책 ID로 사본 찾기
    List<BookInstance> findByBookId(Long id);

    // 책 ID로 사본의 개수 구하기
    Long countByBookId(Long id);
}

이 파일은 BookInstance(실제 책 한 권 단위 데이터)를 데이터베이스에서 조회/저장/관리하는 통로이다.
도서관에 있는 '실물 책 한 권 한 권'을 DB에서 다루는 기능이다.

repository/AuthorRepository.java

package com.example.locallibray.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.locallibray.entity.Author;

// 저자 엔티티 관리
public interface AuthorRepository extends JpaRepository<Author, Long> {
    
}

이 파일은 Author(저자) 데이터를 데이터베이스와 연결해서 조회/저장/관리하는 통로 역할이다.

dto/BookListResponse.java

package com.example.locallibray.dto;

// 책 목록 조회용 DTO
public class BookListResponse {
    
    private String title; // 책 제목
    private String authorName; // 저자 이름
    // 유틸리티 기능
    // 책 상세보기 페이지로 이동하는 링크 주소를 만들어서 보낸다 (재사용 가능)
    private String url;

    // 
    public BookListResponse() {}
    
    // Getter/Setters
    public String getTitle() {
        return title;
    }
    
    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthorName() {
        return authorName;
    }

    public void setAuthorName(String authorName) {
        this.authorName = authorName;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(Long id) {
        this.url = "/book" + id;
    }
}

이 파일은 책 목록을 화면(프론트엔드)으로 보내기 위한 '데이터 전달용 객체(DTO)'이다.
DB에서 가져온 Book 데이터를 사용하기 좋게 가공해서 전달하는 포장상자이다.


마무리

오늘은 새로은 프로젝트를 나갔다.
사실 저번주에 나간 boardapp 프로젝트도 완벽히 다 이해하지 못한 상태라 걱정이 많다.
그래도 걱정하면 바뀌는 건 없다.
걱정할 시간에 공부를 더 하는 게 내 인생에 훨씬 도움이 될 것이다.
열심히 공부해서 개쩌는 개발자가 될 거다.

0개의 댓글