[Project] Entity, JPA 매핑, 어노테이션

정경·2025년 10월 2일

Project

목록 보기
1/3
post-thumbnail

Save-Help 토이 프로젝트를 진행하며 여러 엔티티를 만들면서 @OneToMany, @ManyToOne 등을 사용했는데, Entity와 JPA 매핑과 어노테이션에 대해 정확한 개념 이해를 위해 글을 작성하게 되었습니다.

Entity란?

  • 자바 객체(클래스)를 데이터베이스 테이블과 매핑한 것
  • SpringBoot에서 JPA를 사용할 때, @Entity 애너테이션을 붙인 클래스가 엔티티
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 100)
    private String name;

    @Column(name = "login_id", nullable = false, length = 50)
    private String loginId;

    @Column(length = 255, unique = true)
    private String email;

    @Column(length = 255, unique = true)
    private String phone;

    @Column(length = 255)
    private String password;

    @CreationTimestamp
    private LocalDateTime createdAt;

    @UpdateTimestamp
    private LocalDateTime updatedAt;


    @Enumerated(EnumType.STRING)
    @Column(nullable = false, length = 20)
    private Provider provider = Provider.LOCAL;

    @Column(name = "provider_user_id", nullable = false, length = 255)
    private String providerUserId;

    @Column(name = "profile_image_url", length = 500)
    private String profileImageUrl;

    private String nickname;

    private String gender;

    private int age;

    private String incomeLevel;
    private boolean disabled;
    private boolean inEmergency;

}

위 코드는 DB의 user 테이블과 매핑되고, user 객체의 필드들이 각각 테이블 컬럼에 대응합니다.

엔티티의 특징

1. DB 테이블과 1:1 매핑

  • 클래스명이 테이블명이 되고 필드명이 컬럼명이 됩니다.
  • @Id가 Primary Key 값이 됩니다.

2. JPA가 엔티티를 관리

  • 개발자가 SQL을 직접 작성하지 않아도, 엔티티를 이용해 DB CRUD가 가능합니다.

3. 객체 중심 개발 가능
DB 중심이 아니라 객체 모델링(도메인 주도 설계)이 가능합니다.

JPA 연관관계 매핑

JPA 연관관계 매핑(JPA Relationship Mapping)이란 데이터베이스의 테이블 간의 관계를 Java 엔티티 객체 간의 관계로 매핑하는 것 입니다.

1. @OneToOne (1:1)
한 엔티티가 다른 엔티티와 1:1로 연결
예: 사용자(User) ↔ 사용자 프로필(UserProfile)

2. @ManyToOne (N:1)
여러 엔티티가 하나의 엔티티와 연결(외래키가 N쪽에 속함)
예: 게시글(Post) ↔ 작성자(User)

@Entity
public class Chat {

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

    // 메시지 발신자
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "sender_id")
    private User sender;

	//매시지 수신자
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "receiver_id")
    private User receiver;

    @Column(nullable = false, length = 1000)
    private String message;

    @Enumerated(EnumType.STRING)
    private ChatType type;

    private LocalDateTime sentAt;

    @Column(name = "is_read", nullable = false)
    private boolean read;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "call_id")
    private Call call;
}

Chat엔티티에서 한 명의 사용자(User)는 여러 개의 채팅 메시지(Chat)를 보낼 수 있고, 한 명의 사용자(User)는 여러개의 채팅 메시지를 받을 수 있으므로 @ManyToOne을 사용하였습니다.

3. @OneToMany (1:N)
하나의 엔티티가 여러 엔티티를 가짐 (컬렉션 사용).
하지만 실제 DB에서는 N쪽에 FK가 있기 때문에 @OneToMany 단독 사용은 보다 보통 mappedBy와 같이 쓴다고 합니다.
예: 사용자(User) ↔ 게시글(Post) (User 기준)

@Entity
public class Helper {

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

    @Column(nullable = false, length = 100)
    private String name;

    @Enumerated(EnumType.STRING)
    private HelperRole role;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "center_id")
    private CommunityCenter center;

    @Column(length = 20)
    private String phoneNumber;

    private boolean available = true;

    @OneToMany(mappedBy = "assignedHelper")
    private List<Emergency> emergencies = new ArrayList<>();

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "hospital_id")
    private Hospital hospital;
}

프로젝트에서 생성한 Helper라는 엔티티인데, emergencies라는 필드는 helper하나의 엔티티가 여러 긴급 상황들을 담당할 수 있으므로 @OneToMany를 사용하였습니다.

4. @ManyToMany (N:M)
엔티티와 엔티티가 다대다 관계.
하지만 DB는 다대다를 직접 지원하지 않기 때문에 중간 조인 테이블이 자동 생성됩니다.

단순 ManyToMany 예시

@Entity
public class Student {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @ManyToMany
    @JoinTable(
        name = "student_course",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private List<Course> courses = new ArrayList<>();
}

@Entity
public class Course {

    @Id @GeneratedValue
    private Long id;

    private String title;

    @ManyToMany(mappedBy = "courses")
    private List<Student> students = new ArrayList<>();
}

중간 테이블 엔티티 따로 만들기

@Entity
public class StudentCourse {

    @Id @GeneratedValue
    private Long id;

    @ManyToOne
    @JoinColumn(name = "student_id")
    private Student student;

    @ManyToOne
    @JoinColumn(name = "course_id")
    private Course course;

    private LocalDateTime enrolledDate;  // 추가 필드 예시
}

어노테이션


@Entity
public class CommunityCenter {

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

    // 센터 이름 
    @Column(nullable = false, length = 100)
    private String name;

    // 센터 타입
    @Enumerated(EnumType.STRING)
    private CenterType type;

    // 위치 정보 (위도, 경도)
    private Double latitude;
    private Double longitude;

    // 연락 가능한 전화번호
    @Column(length = 20)
    private String phoneNumber;

    // 운영 여부
    private boolean active = true;

    // 이 센터가 처리한 Emergency 요청들
    @OneToMany(mappedBy = "assignedCenter")
    private List<Emergency> emergencies = new ArrayList<>();
}

@Entity

  • 해당 클래스의 인스턴스들이 엔티티임을 명시합니다.

@Table

  • 클래스가 테이블이 되기 때문에 클래스 선언부에 작성합니다.
  • 테이블명을 결정합니다.

@Id

  • 해당 컬럼이 식별키(PK)라는 것을 의미합니다.
  • 모든 엔티티에 반드시 @Id를 지정해줘야 합니다.

@Column

  • 컬럼 이름을 명시합니다.
  • 데이터베이스 제약 조건을 설정합니다.
    • name(String) : 매핑할 데이터베이스 컬럼 이름
    • nullable(boolean) : null값 허용 여부
    • length(int) : String 타입 컬럼의 최대 길이
    • unique(boolean) : 중복을 허용하지 않음
    • insertable(boolean) : 엔티티 저장 시(INSERT 쿼리) 이 컬럼을 포함할지 여부
    • updatable(boolean) : 엔티티 수정 시(UPDATE 쿼리) 이 컬럼을 포함할지 여부
    • columnDefinition(String) : 컬럼의 타입, 길이, 제약 조건 등을 데이터베이스 SQL 형식으로 직접 지정할 수 있습니다.
    • precision, scale(int) : BigDecimal 같은 소수점 포함 숫자 타입에서 전체 자릿수(precision)와 소수점 이하 자릿수(scale)를 설정합니다.
profile
꾸준히 성장하는 백엔드 개발자 입니다!

0개의 댓글