본문 바로가기

카테고리 없음

Spring 미니 프로젝트 [RN & Spring] - ERD 작성

데이터 모델링

  • 업무 내용을 분석하여 이해하고 약속된 표기법에 의해 표현하는 것
  • 데이터베이스의 골격을 이해하고, 그 이해를 바탕으로 SQL문장을 효율적으로 작성 가능

 

내가 만들고자 하는 것은 직전 학기에 만들어 봤던 만보기 앱 화면에 서버를 연동하여

수치와 프로필을 띄우는 것이 주된 목표이다.

 

  • 업무 파악
    • 걸음 수
    • 사용자 이름
    • 보유 포인트 (걸음 수에 따라 달라짐)

개념적 데이터 모델링

  • 데이터 간의 관계 구상
  • 피터 첸 표기법으로 ERD를 구상한다.

개체 타입이라 적힌 네모는 테이블을 의미한다.

 

 

이런 다이어그램은 처음이라 조금 미숙하다,,

 

논리적 데이터 모델링

  • 구체화된 업무 중심의 데이터 모델
  • 추상적인 데이터보다 구체화된 데이터로 작성한다.
  • key, 속성, 관계 등을 표시하고 우리가 흔히 보는 ERD 그림을 생각하면 된다.
  • 개념적 데이터 모델링 그림을 테이블 형태로 다시 그린다.

본격적으로 그려보기 전에 ERD 그리는 법에 대해 짚고 넘어간다.

 

  • Entity Relationship Diagram
  • entity개체와 relationship 관계를 중점적으로 표시하는 데이터베이스 구조

 

  • 엔티티
    • 정의 가능한 사물 또는 개념
    • DB의 테이블이 엔티티로 표현된다.
  • 엔티티 속성 (Attribute)
    • 엔티티의 속성을 나타낸다.
    • 회원 엔티티를 예로 들면, 회원 아이디나 이름, 비밀번호 등이 있다.
    • DB 테이블의 각 컬럼(필드)들이 엔티티 속성이다.
  • 엔티티 도메인(domain)
    • Attribute의 타입을 명시
    • 데이터베이스가 지원하는 타입을 작성해야 한다.
  • 엔티티 종류
    • 유형 엔티티: 물리적인 형태
    • 무형 엔티티: 개념적으로만 존재(인터넷 장바구니, 구매 이력 등)
    • 문서 엔티티: 절차상 사용되는 문서나 장부에 대한 엔티티
    • 이력 엔티티: 반복적으로 이루어지는 행위나 사건을 시간별로 저장하기 위한 엔티티
    • 코드 엔티티: 무형 엔티티의 일종, 각종 코드를 관리
  • Primary Key
    • 중복이 없고 NULL 값이 없는 유일한 값에 지정하는 식별자
    • 주 식별자는 유일한 속성이다.
    • NOT NULL : 해당 속성에 들어갈 값에 null을 비허용하면, N 을 표기한다.
  • Foreign Key
    • 외래식별자는 key의 일종으로 선을 이어주어 개체와의 관계를 표시한다.

엔티티들을 다 만들었으면 선을 이어 관계를 맺어준다.

 

의 엔티티 관계에서 부모의 키를 자식에서 PK로 사용하는지 일반 속성으로 사용하는지에 따라 다르다.

 

  • 실선: 강한 연결관계 표현 / 자식 주식별자의 구성에 포함 / 부모 엔티티에 종속
  • 점선: 약한 연결관계 표현 / 자식 일반 속성에 포함 / 자식 주식별자 구성을 독립적으로 구성
  • 식별관계(실선): 부모 자식 관계에서 부모의 주 식별자(PK)를 외래 식별자(FK)로 참조해서 자신의 주 식별자로 설정
  • 비식별관계(점선): 부모 자식 관계에서 부모의 주 식별자를 외래 식별자로 참조해서 일반 속성으로 사용

 

  1. 일대일 관계
    1. 학생과 신체정보
  2. 일대다 관계
    1. 학생과 취미
    2. 회원과 오늘의 걸음 수 (2/6, 2/7의 걸음 수 등)
  3. 다대다 관계
    1. 제품과 제조업체
  4. 다대다 관계의 해소
    1. 두 개의 엔티티만으로 부족한 경우가 있다.
    2. 1:N , N:1로 조정하는 작업이 필요하다.
    3. 중간 엔티티가 두 엔티티의 공유 속성 역할을 한다.
  • ERD관계의 참여도
    • |   (작대기) 표시가 있으면 필수적으로 있어야 하는 개체 --- 필수 관계
    • O  (동그라미) 표시가 있으면 없어도 되는 개체 --- 선택 관계
    • 대응되는 객체가 있을 수도 있고 없을 수도 있을 때 선택 관계를 사용한다.
  • 일과 다
    • 하나만 존재하는 경우 작대기로 표현
    • 다수일 경우 삼지창 모양으로 표현

 

물리적 데이터 모델링

지금까지 구성한 ERD를 바탕으로 SQL 코딩을 통해 직접 데이터베이스를 설계하는 일이다.

난 이번에 h2 DB를 사용하여 로컬에서 간단히 확인할 예정이다.

 

스프링 입문에서 경험했던 스프링 데이터 JPA를 사용해보고자 했다.

처음엔 SQL 을 잘 모르기 때문에 scheme 파일을 만들어서 직접 테이블을 생성해보았다.

-- 1. 회원 테이블 (모든 테이블의 부모)
CREATE TABLE MEMBER (
    id VARCHAR(50) PRIMARY KEY,       -- 회원 ID (문자열)
    username VARCHAR(50) NOT NULL,    -- 사용자 이름
    nickname VARCHAR(50),             -- 닉네임
    user_point INT DEFAULT 0          -- 보유 포인트 (기본값 0)
);

-- 2. 걸음 수 정보 (자식: 회원과 1:N)
CREATE TABLE STEP_INFO (
    step_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, -- 자동 증가 ID
    user_id VARCHAR(50) NOT NULL,     -- 누구의 걸음인가?
    step_count INT NOT NULL,          -- 걸음 수
    record_date DATE NOT NULL,        -- 날짜
    FOREIGN KEY (user_id) REFERENCES MEMBER(id) -- 외래키 연결
);

-- 3. 포인트 이력 (자식: 회원과 1:N)
CREATE TABLE POINT_HISTORY (
    history_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    user_id VARCHAR(50) NOT NULL,
    amount INT NOT NULL,              -- 변동량 (+100, -3000)
    description VARCHAR(255),         -- 내용 (걷기보상, 쿠폰구매)
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 발생 시간
    FOREIGN KEY (user_id) REFERENCES MEMBER(id)
);

-- 4. 쿠폰 정보 (자식: 회원과 1:N)
CREATE TABLE COUPON_INFO (
    coupon_id VARCHAR(50) PRIMARY KEY, -- 쿠폰 ID (문자열로 가정)
    user_id VARCHAR(50) NOT NULL,
    coupon_name VARCHAR(100) NOT NULL, -- 쿠폰 이름 (추가 권장)
    valid_date DATE NOT NULL,          -- 유효기간
    is_used BOOLEAN DEFAULT FALSE,     -- 사용 여부
    FOREIGN KEY (user_id) REFERENCES MEMBER(id)
);

 

제미나이를 활용하여 테이블을 생성하는 쿼리를 만들어냈다.

하지만, JPA는 이런 테이블을 자동으로 만들어주고, 메서드 이름만으로 기능을 사용 가능하다!

 

데이터 모델을 자바 코드로 직관적으로 옮길 수 있다

package NetZero.domain;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;

@Entity
@NoArgsConstructor
@Getter
public class Member {

    @Id
    @Column(name = "member_id")
    private String id;
    private String username;
    private String nickname;
    private int userPoint;

    @OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
    private List<PointHistory> pointHistories = new ArrayList<>();

    @OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
    private List<StepInfo> stepInfos = new ArrayList<>();

    @OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
    private List<CouponInfo> couponInfos = new ArrayList<>();

    public Member(String id, String username, String nickname, int userPoint){
        this.id = id;
        this.username = username;
        this.nickname = nickname;
        this.userPoint = 0;
    }

}

 

domain 패키지를 만들어서 4가지의 엔티티 클래스를 만들었다. 위 코드는 엔티티들의 부모인 Member엔티티이다.

member 엔티티를 부모로 두고 일대다 로 연결하여 정보를 주고받고자 하였다.

 

새로 볼 수 있는 애노테이션들을 학습하고자 

<자바 ORM 표준 JPA 프로그래밍> 책을 참고하여 학습했다.
(사진)
우선 중요한 것부터 정리해보자면
  • @ NoArgsConstructor
    • 스프링 기본 시간에 배웠던 롬복 라이브러리인데 Required와 다른 형태이다.
      파라미터가 없는 기본 생성자를 자동으로 만들어준다.
    • JPA는 DB에서 데이터를 가져와서 객체를 만들 때, reflection을 이용하는데
      일단 빈 객체를 만들고 값을 채워 넣는 방식이다.
    • 코드 상에 생성자를 만들어 둔 이유:
      • @ NoArgsConstructor 는 JPA를 위해서, 직접 만든 생성자는 개발자를 위해 만든 것이다.
      • 직접 만든 생성자가 없다면 set 메서드를 호출해야 하는 소요가 크다!
        • member.setId("user1");
          member.setUsername("홍길동");
          member.setNickname("WalkingMan");
          member.setUserPoint(0);
      • 안전성
        • 기본 생성자만 있다면 실수로 이름을 넣지 않아도 컴파일 에러가 나지 않는다.
        • 필수 값을 받는 생성자를 직접 만들어두면 컴파일 에러를 통해 확인 가능하다!
          -> 컴파일 에러는 가장 좋은 에러라고 배운 적이 있다!
      • 테스트 코드 작성이 용이하다
        • set 메서드로 도배될 일이 없다
  • @OneToMany
    • 일대다 관계를 표현한다.
  • mappedBy
    • 양쪽에서 서로 참조하는 상황이 생길 수 있다. member.getCoupon(), coupon.getMember()
      DB테이블의 외래 키는 오직 하나에만 존재한다.(Coupon쪽)
    • 외래키 FK를 가진 것은 coupon이기 때문에 연관관계의 주인으로 설정한다. 여기서
      주인이 아닌 쪽은 "mappedBy" 를 써서 양보한다. 그리고 주인인 쪽은 "JoinColumn"을 활용한다.
    • mappedBy = "member" 는 Coupon클래스에 있는 member 필드에 의해 관리된다 라는 뜻이다!
      따라서 mappedBy 가 붙은 쪽, 즉 주인이 아닌 쪽은 읽기 전용이 된다.  -> 처음에 의도한대로 Member에 따른 걸음 수, 쿠폰, 쿠폰 이력 조회가 가능해진다!!
  • cascade (영속성 전이)
    • 부모 엔티티에게 변화가 생겼을 때 그 변화를 자식 엔티티에도 전파시킨다.
    • CascadeType.ALL: 모든 변화를 전파합니다. (저장, 삭제 등) => 아주 용이하지만 데이터가 싹 날아갈 수 있
    • CascadeType.PERSIST: 부모를 저장(save)할 때 자식도 같이 저장합니다.
    • CascadeType.REMOVE: 부모를 삭제(delete)하면 자식도 같이 삭제합니다.

 

 

 

h2 콘솔에서 확인했을 때 테이블이 정상적으로 생성되었음을 확인할 수 있다!


 

다음에는 지금 듣고 있는 HTTP 강의를 바탕으로 API를 설계하고 DTO,Converter를 구현해보는 시간을 가질 예정이다!

쫄지마!