최원종의 개발 블로그

V3 (사용자 관리 및 연관 관계 설정) -1 연관관계 설정하기 본문

Spring boot 입문

V3 (사용자 관리 및 연관 관계 설정) -1 연관관계 설정하기

chl6698 2026. 5. 11. 17:26

User 엔티티 설계 및 생성 코드

package com.tenco.blog.user;

import jakarta.persistence.*;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;

import java.sql.Timestamp;

@Data
@NoArgsConstructor
@Table(name = "user_tb")
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    // 사용자명 중복 방지를 위한 유니크 제약 조건 설정
    @Column(unique = true)
    private String username;

    private String password;
    private String email;
    // 엔티티가 영속화 될 때 자동으로 현재 시간을 주입해라 pc -> db
    @CreationTimestamp
    private Timestamp createdAt;

    @Builder
    public User(Integer id, String username, String password, String email, Timestamp createdAt) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.email = email;
        this.createdAt = createdAt;
    }
}

주요 어노테이션 설명

@Entity"JPA가 관리하는 엔티티임을 선언" - 클래스를 DB 테이블과 매핑하겠다고 선언하는 핵심 마커.
@Table"이름표 달기" - DB에 생성될 테이블 명을 명시. (user_tb)
@Id"주민번호 부여" - 해당 필드를 테이블의 기본키(Primary Key)로 설정.
@GeneratedValue"번호 뽑기" - PK 생성 전략을 결정함. IDENTITY는 DB의 Auto-Increment를 따름.
@Column"제약 사항" - 컬럼의 속성을 정의함. unique = true는 중복 데이터를 금지함.
@CreationTimestamp"자동 기록" - 객체가 생성(INSERT)되는 시점의 시간을 자동으로 기록함.
@Builder"조립식 생성" - 생성자 인자가 많아도 가독성 있게 객체를 생성하게 돕는 패턴.

 

 

필드 및 로직 상세 분석

1. 기본키(PK) 전략 (Integer id)

GenerationType.IDENTITY를 사용하여 데이터베이스(MySQL 등)가 번호를 자동으로 할당하게 함.

개발자가 직접 ID를 관리하지 않아도 되므로 데이터 무결성이 보장됨.

2. 유니크 제약조건 (username)

@Column(unique = true) 설정을 통해 동일한 아이디를 가진 사용자의 중복 가입을 DB 레벨에서 원천 차단함.

중복 가입 시도 시 DataIntegrityViolationException이 발생함.

3. Lombok의 활용 (@Data, @NoArgsConstructor)

@Data: Getter, Setter 등을 자동 생성하여 코드 다이어트(Boilerplate 제거)를 실현함.

@NoArgsConstructor: JPA가 리플렉션을 통해 객체를 생성할 때 필요한 기본 생성자를 자동으로 만들어줌.

4. 빌더 패턴 (@Builder)

생성자에 빌더 패턴을 적용하여, 객체 생성 시 필드 순서에 상관없이 이름으로 값을 세팅할 수 있음.

코드 예시: User.builder().username("ssar").build();

 


게시글을 볼 때  작성자 정보도 함께 출력하는 코드(객체지향 사고 +DB JOIN의 개념)

Board 코드

package com.tenco.blog.board;

import com.tenco.blog.user.User;
import com.tenco.blog.util.MyDateUtil;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;

import java.sql.Timestamp;

@Data // get,set, toString ..
// @Entity - JPA가 이 클래스를 데이터베이스 테이블과 매핑하는 객체로 인식하게 설정
// 즉, 이 어노테이션이 있어야 JPA가 관리 함
@Entity
@Table(name = "board_tb")
@NoArgsConstructor
@AllArgsConstructor // 전체 멤벼 번수를 넣을 수 있는 생성자.
@Builder
public class Board {

    // @id : 이 필드가 기본키임을 설정 함
    @Id
    // IDENTITY 전략: 데이터베이스게 기본 AUTO_INCREMENT 기능 사용
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
//    private String username;  삭제해야 함.
    private String title;
    private String content;

    // 연관관계 설정 해주어야 한다.
    // 다대일 연관관계 : 여러개 게시글이 하나의 사용자에게 속한다.
    // FetchType 전략 : EAGER, LAZY
    //   EAGER - 조회시 한번에 다 들고 와라 ( 1번 게시글 조회시 한번 조인까지 해라)
    //   LAZY - 처음부터 Board 조회할 때 User 정보를 가져오지 마. 필요할 때 한번 더 조회 해.
    @ManyToOne(fetch = FetchType.EAGER)
    // @OneToMany
    // @OneToOne
    @JoinColumn(name = "user_id") // 외래키 컬럼명 표시 됨
    private User user;

    // @CreationTimestamp : 하이버네이트가 제공하는 어노테이션
    // 특정 하나의 엔티티가 저장이 될 때 현재 시간을 자동으로 저장해 설정
    // now() 명시할 필요 없음
    // pc --> db (자동 날짜 주입)
    @CreationTimestamp
    private Timestamp createdAt;

    // createdAt -> 포멧 하는 메서드 만들어 보기
    public String getTime() {
        return MyDateUtil.timestampFormat(createdAt);
    }

    // 수정 편의 기능 만들기
    public void update(BoardRequest.UpdateDTO updateDTO) {
        // this.username = updateDTO.getUsername(); 삭제 예정
        this.title = updateDTO.getTitle();
        this.content = updateDTO.getContent();

        // 더티체킹 - 변경 감지 동작 과정
        // 1. 최초 조회시 영속성 컨텍스트 1차 캐쉬에 데이터를 스냅샷으로 보관 함.
        // 2. 영속화된 엔티티가(board)의 멤버 변수값이 변경이 된다면
        //    1차에서 보관했던 값과 2차에서 수정된 필드값을 비교 함.
        // 3. 변화가 발생이 되었다면 트랜잭션 커밋 시점에 변경된 필드값 UPDATE 쿼리 자동 생성
        // 4. 물리적은 DB에 반영 됨.
    }

}

 


 

연관관계 매핑

private User user;: 기존의 username 같은 단순 텍스트가 아닌 객체 자체를 필드로 가짐.

FetchType.EAGER: 게시글 조회 시 작성자(User) 정보를 즉시 조인(Join)하여 가져옴

Tip: 게시글 목록에서 작성자 이름이 매번 필요할 때 유용하지만,
실무에서는 성능 최적화를 위해 주로 LAZY를 선호함

 

 

 

수정 편의 메서드와 더티 체킹

update() 메서드 내부에 별도의 save() 호출이 없는 이유는 JPA의 변경 감지(Dirty Checking) 기능 덕분.

조회 시점: 영속성 컨텍스트(PC)에 엔티티가 들어오면 스냅샷을 찍어 보관함.

수정 시점: 자바 객체의 필드값(title, content)을 변경함.

커밋 시점: 트랜잭션 종료 시 스냅샷과 현재 엔티티를 비교함.

반영 시점: 변경 사항이 발견되면 JPA가 자동으로 UPDATE 쿼리를 생성하여 DB에 전송함.

db 데이터 재설정

-- User 테이블 데이터 (5명의 사용자)
INSERT INTO user_tb (username, password, email, created_at) VALUES
('admin', '1234', 'admin@blog.com', NOW()),
('ssar', '1234', 'ssar@nate.com', NOW()),
('cos', '1234', 'cos@gmail.com', NOW()),
('hong', '1234', 'hong@naver.com', NOW()),
('kim', '1234', 'kim@daum.net', NOW());

-- 2단계: Board 테이블 데이터 (10개의 게시글)
-- 주의: user_id는 위에서 생성된 사용자의 id를 참조

-- admin 사용자가 작성한 게시글 (3개)
INSERT INTO board_tb (title, content, user_id, created_at) VALUES
('블로그 개설을 환영합니다!', '안녕하세요! 새로운 블로그가 오픈했습니다. 많은 관심과 참여 부탁드립니다.', 1, NOW()),
('공지사항: 이용수칙 안내', '블로그 이용 시 지켜야 할 기본적인 수칙들을 안내드립니다. 건전한 소통 문화를 만들어가요.', 1, NOW()),
('업데이트 소식', '새로운 기능들이 추가되었습니다. 댓글 기능과 좋아요 기능을 곧 만나보실 수 있습니다.', 1, NOW());

-- ssar 사용자가 작성한 게시글 (3개)
INSERT INTO board_tb (title, content, user_id, created_at) VALUES
('Spring Boot 학습 후기', 'Spring Boot를 처음 배우면서 느낀 점들을 공유합니다. JPA가 정말 편리하네요!', 2, NOW()),
('JPA 연관관계 정리노트', '오늘 배운 @ManyToOne, @OneToMany 연관관계에 대해 정리해봤습니다. 헷갈리는 부분이 많아요.', 2, NOW()),
('코딩테스트 문제 추천', '백준과 프로그래머스에서 풀어볼 만한 문제들을 추천드립니다. 알고리즘 공부 화이팅!', 2, NOW());

-- cos 사용자가 작성한 게시글 (2개)
INSERT INTO board_tb (title, content, user_id, created_at) VALUES
('React vs Vue 비교', '프론트엔드 프레임워크 선택에 고민이 많았는데, 각각의 장단점을 비교해봤습니다.', 3, NOW()),
('개발자 취업 팁 공유', '신입 개발자로 취업하면서 도움이 되었던 팁들을 공유합니다. 포트폴리오가 중요해요!', 3, NOW());

-- hong 사용자가 작성한 게시글 (1개)
INSERT INTO board_tb (title, content, user_id, created_at) VALUES
('첫 번째 게시글입니다', '안녕하세요! 블로그에 처음 글을 올려봅니다. 앞으로 자주 소통해요~', 4, NOW());

-- kim 사용자가 작성한 게시글 (1개)
INSERT INTO board_tb (title, content, user_id, created_at) VALUES
('맛집 추천 - 강남역 근처', '강남역 근처에서 점심 먹기 좋은 맛집들을 추천드립니다. 가성비도 좋아요!', 5, NOW());