데이터베이스 (1) - 데이터베이스의 기본과 연관관계

2ㅣ2ㅣ·2024년 10월 25일

CS

목록 보기
12/13
post-thumbnail

1. 데이터베이스의 기본

데이터베이스(DB, DataBase)

  • 일정한 규칙, 혹은 규약을 통해 구조화되어 저장되어 있는 데이터 모음

DBMS(DataBase Management System)

  • 데이터베이스를 제어, 관리하는 통합 시스템
  • 특정 DBMS마다 정의된 쿼리언어를 통해 삽입, 삭제, 수정 , 조회 등을 수행할 수 있음

MySQL

show database;
show tables;

Postgresql

\l 
\dt
  • 실시간 접근과 동시 공유가 가능함

DB - DBMS 관계

  • 위의 그림처럼 응용 프로그램 - DBMS - DB의 구조로 데이터를 주고 받음
    • e.g.) SpringBoot - PostgreSQL - DB

1.1 Entity

  • 사람, 장소, 물건, 사건, 개념 등 여러 개의 속성을 지닌 명사
  • 서비스의 요구사항에 맞춰 속성이 정해짐

약한 엔티티와 강한 엔티티

  • 엔티티는 약한 엔티티와 강한 엔티티로 나뉨
  • A가 혼자서는 존재하지 못하고 B의 존재 여부에 따라 종속적이라면 → A: 약한 엔티티 , B: 강한 엔티티

1.2 릴레이션

  • 데이터베이스에서 정보를 구분하여 저장하는 기본 단위
  • 엔티티에 관한 데이터를 데이터베이스는 릴레이션 하나에 담아서 관리함
  • 관계형 데이터베이스 : 테이블 , NoSQL 데이터베이스 : 컬렉션

테이블과 컬렉션

  • 데이터베이스 종류는 크게 관계형 데이터베이스와 NoSQL 데이터베이스로 구분됨
관계형 데이터베이스NoSQL
DBMS 예시MySQL, PostgreSQLRedis, MongoDB
구조레코드 - 테이블 - 데이터베이스도큐먼트 - 컬렉션 - 데이터베이스

레코드 - 테이블 - 데이터베이스의 구조

  • 다음 그림처럼 하나 이상의 레코드가 쌓여서 하나의 테이블이 되고
  • 하나 이상의 테이블이 모여서 하나의 데이터베이스가 됨

1.3 속성

  • 릴레이션에서 관리하는 구체적이며 고유한 이름을 갖는 정보
    • e.g.) 회원이라는 릴레이션의 속성 : 이름, 아이디, 주소, 전화번호, 성별

1.4 도메인

  • 릴레이션에 포함된 각각의 속성들이 가질 수 있는 값의 집합
    • e.g.) 성별이라는 속성의 도메인 : 또는

필드와 레코드

데이터베이스에서 필드와 레코드로 구성된 테이블을 만들 수 있음

nameIDaddressphonenumber
큰돌kundol서울112
가영kay대전114
빅뱅big카이루119
..
  • 여기서 name, ID, address, phonenumber가 필드에 해당
  • 각 필드에 해당하는 데이터들을 레코드라고 함
  • 회원이라는 엔터티member 라는 테이블속성인 이름, 아이디 등을 가지고 있으며, name , ID, address, phonenumber 등의 필드를 가짐
  • 그리고 이 테이블에 쌓이는 행(row) 단위의 데이터를 레코드라고 함
    • 레코드를 튜플이라고도 함

“책” 이라는 엔터티를 한 번 정의해보기 - MySQL 기준

💡
책의 아이디 : INT
책의 제목 : VARCHAR(255)
책의 저자 아이디 : INT
책의 출판년도 : VARCHAR(255)
책의 장르 : VARCHAR(255)
생성 일시 : DATETIME
업데이트 일시 : DATETIME

위와 같은 엔터티를 MySQL로 구현하려면, 다음과 같은 쿼리를 입력하면 됨

CREATE TABLE book (
	id INT NOT NULL AUTO_INCREMENT,
	title VARCHAR(255),
	author_id INT,
	publishing_year VARCHAR(255),
	genre VARCHAR(255),
	created_at DATETIME,
	updated_at DATETIME,
	PRIMARY KEY (id)
);

필드 타입

  • 필드는 타입을 가짐
    • e.g.)이름은 문자열, 전화번호는 숫자
    • 이러한 타입은 DBMS마다 다르지만, 현재는 MySQL 기준으로 설명

필드 타입 - 숫자

숫자 타입으로는 TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT 등이 있음

타입용량최솟값(부호 있음)최솟값(부호 없음)최댓값(부호 없음)최댓값(부호 있음)
TINYINT1-1280127255
SMALLINT2-3276803276765535
MEDIUMINT3-83886080838860716777215
INT4-2147483648021474836474294967295
BIGINT8-2630263-1264-1

필드 타입- 날짜

날짜 타입으로는 DATE, DATETIME, TIMESTAMP 등이 있음

DATE

  • 날짜 부분은 있지만, 시간 부분은 없는 값에 사용
  • 지원되는 범위 : 1000-01-01 ~ 9999-12-31
  • 3바이트의 용량을 가짐

DATETIME

  • 날짜 및 시간 부분을 모두 포함하는 값에 사용
  • 지원되는 범위 : 1000-01-01 00:00:00 ~ 9999-12-31 23:59:59
  • 8바이트의 용량을 가짐

TIMESTAMP :

  • 날짜 및 시간 부분을 모두 포함하는 값에 사용
  • 지원되는 범위 : 1970-01-01 00:00:01 ~ 2038-01-19 03:14:07
  • 4바이트의 용량을 가짐

필드 타입 - 문자

문자 타입으로는 CHAR, VARCHAR, TEXT, BLOB, ENUM, SET 이 있음

CHAR와 VARCHAR

  • CHARVARCHAR 모두 그 안에 수를 입력하여 몇 자까지 입력할 지 결정
  • CHAR고정 길이 문자열, 0 ~ 255의 길이
    • CHAR(100)으로 선언하면, 10글자를 저장해도 무조건 100바이트로 저장됨
    • 따라서 유동적이지 않은 길이를 가진 데이터의 경우에 효율적
  • VARCHAR가변 길이 문자열, 0 ~ 65535 사이의 값으로 지정 가능
    • 입력된 데이터에 따라 용량을 가변시켜 저장
    • 10글자를 입력한 경우, 10 바이트 + 길이 기록용 1바이트 = 총 11바이트로 저장
    • 유동적인 길이를 가진 데이터는 VARCHAR로 저장하는 것이 효율적

TEXT와 BLOB

  • TEXT와 BLOB 모두 큰 데이터를 저장할 때 쓰이는 타입

ENUM과 SET

  • ENUM와 SET 모두 문자열을 열거한 타입

  • ENUMENUM('x-small', 'small', 'medium', 'large', 'x-large')과 같은 형태로 쓰임

    • 이 중에서 하나만 선택하는 단일 선택만 가능
      • ENUM 리스트에 없는 잘못된 값을 삽입하면 빈 문자열이 대신 삽입
    • ENUM을 사용하면 x-small 등이 0,1 등으로 매핑되어 메모리를 적게 사용하는 이점을 얻음
    • 최대 65,535개의 요소를 넣을 수 있음
  • SETENUM과 비슷하지만

    • 여러 개의 데이터를 선택할 수 있고, 비트 단위의 연산이 가능함
    • 최대 64개의 요소를 집어넣을 수 있음
  • ENUM이나 SET을 쓰면 공간적으로 이점을 볼 수 있지만, 애플리케이션의 수정에 따라 데이터베이스의 ENUM이나 SET 에서 정의한 목록을 수정해야 한다는 단점이 있음

추가 - 각 필드에 해당하는 JPA 매핑은 어떻게?

숫자 타입에 대한 JPA 매핑

MySQL 타입JPA 타입
TINYINT@Column(columnDefinition = "TINYINT") 또는 byte
SMALLINT@Column(columnDefinition = "SMALLINT") 또는 short
MEDIUMINT@Column(columnDefinition = "MEDIUMINT") (직접 지정 필요)
INTint
BIGINTlong

@ColumncolumnDefinition을 활용하면 특정 DBMS의 고유한 데이터 타입을 사용할 수 있음

날짜 타입에 대한 JPA 매핑

MySQL 타입JPA 타입
DATEjava.time.LocalDate
DATETIMEjava.time.LocalDateTime
TIMESTAMPjava.time.Instant 또는 java.time.LocalDateTime

@Temporal 어노테이션은 이제 자바 8부터는 잘 사용되지 않아서, 자바의 java.time API로 처리해주는 게 더 나은 선택

문자 타입에 대한 JPA 매핑

MySQL 타입JPA 타입
CHAR@Column(columnDefinition = "CHAR(n)") 또는 String
VARCHARString
TEXT@Lob를 붙인 String
BLOB@Lob를 붙인 byte[] 또는 Blob
ENUM@Enumerated(EnumType.STRING)
SET@Column(columnDefinition = "SET('value1', 'value2', ...)") 또는 직접 커스텀 타입으로 구현

ENUM을 매핑할 때는 @Enumerated 어노테이션을 사용해서 JPA에서 매핑해줘야 함

관계

데이터베이스의 테이블은 하나만 있는 것이 아니라, 여러 개의 테이블이 있고, 이러한 테이블들은 서로의 관계가 정의될 수 있음

1. 1:1 관계 (One-to-One Relationship)

예시: 사용자프로필

  • 설명: 한 명의 사용자는 하나의 프로필을 가질 수 있고, 그 프로필 역시 오직 한 사용자에만 연결되어 있다.
  • DB 모델링:
    • 테이블 User (사용자 ID)
    • 테이블 Profile (프로필 ID, 사용자 ID)
    • Profile 테이블에 user_id를 외래키로 사용하여 1:1 관계를 정의

JPA 구현:

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

    @OneToOne(mappedBy = "user")
    private Profile profile;
}

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

    @OneToOne
    @JoinColumn(name = "user_id")
    private User user;
}
  • 주의할 점: mappedBy를 사용하여 양방향 관계에서 주인을 명확히 해야 순환 참조 문제가 발생하지 않음

2. 1:N 관계 (One-to-Many Relationship)

예시: 사용자게시물

  • 설명: 한 명의 사용자는 여러 개의 게시물을 작성할 수 있지만, 하나의 게시물은 한 명의 사용자에게만 속함
  • DB 모델링:
    • 테이블 User (사용자 ID)
    • 테이블 Post (게시물 ID, 사용자 ID)
    • Post 테이블에 user_id를 외래키로 설정

JPA 구현:

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

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<Post> posts;
}

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

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}
  • 주의할 점: CascadeType.ALL을 설정하면, 부모 엔티티(User)가 저장되거나 삭제될 때 관련된 자식 엔티티(Post)도 함께 처리됨
  • 이것이 편리할 때도 있지만, 데이터가 많아지면 성능 이슈가 생길 수 있으니 주의해야 함

3. N:M 관계 (Many-to-Many Relationship)

예시: 학생수업

  • 설명: 한 명의 학생은 여러 수업을 들을 수 있고, 하나의 수업은 여러 명의 학생이 들을 수 있음
  • DB 모델링:
    • 테이블 Student (학생 ID)
    • 테이블 Course (수업 ID)
    • 중간 테이블 Student_Course (학생 ID, 수업 ID)로 N:M 관계를 표현할 수 있음

JPA 구현 (중간 테이블 자동 생성):

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

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

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

    @ManyToMany(mappedBy = "courses")
    private Set<Student> students = new HashSet<>();
}
  • 위와 같이 @ManyToMany로 중간 테이블을 자동 생성하게 만들 수 있지만,
  • 실제로는 중간 테이블을 직접 엔티티로 관리하는 것이 더 유연하고 권장됨
  • 예를 들어, 수업을 듣는 날짜 등의 정보를 추가해야 할 때 중간 테이블이 엔티티로 관리되면 편리함

JPA 구현 (중간 테이블 엔티티로 관리):

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

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

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

    private LocalDate enrollmentDate;
}

⭐️ Spring Boot, Spring JPA에서 신경 써야 할 것 ⭐️

  1. 지연 로딩(Lazy Loading)과 즉시 로딩(Eager Loading):

    • @OneToMany와 같은 관계에서는 지연 로딩(Lazy)이 기본값으로 설정된다.
      • 데이터베이스에서 해당 관계를 미리 로딩하지 않고 실제로 필요할 때만 로드하는 방식
    • 즉시 로딩(Eager)은 관계 데이터를 미리 로딩하지만, 성능 저하를 초래할 수 있다.
      • 그래서 관계가 많을수록 가능하면 지연 로딩을 사용하는 게 좋다.
  2. N+1 문제:

    • 관계형 데이터베이스에서는 N+1 문제가 생길 수 있다.
    • 예를 들어, 한 사용자가 여러 게시물을 가지고 있다면, 그 사용자를 가져올 때 N번의 추가 쿼리가 발생해 성능에 문제가 될 수 있다.
      • 이 문제는 Fetch Join을 통해 해결할 수 있다.
    @Query("SELECT u FROM User u JOIN FETCH u.posts WHERE u.id = :id")
    User findUserWithPosts(@Param("id") Long id);
  • N+1 문제와 Fetch Join에 대한 추가 설명

N+1 문제

N+1 문제는 ORM (Object-Relational Mapping)에서 자주 발생하는 성능 문제로, 한 번의 쿼리로 메인 엔티티를 가져온 후, 그 엔티티와 관련된 N개의 하위 엔티티를 가져오기 위해 추가로 N번의 쿼리가 발생하는 현상
예시 상황
1. N+1 문제 발생 예시

  • User 테이블에서 10명의 사용자를 가져온다고 가정한다.
  • 각 사용자는 여러 개의 Post(게시물)을 가지고 있다.
    1차 쿼리: 사용자를 가져오는 쿼리
SELECT * FROM user;

2차 쿼리: 각 사용자의 게시물을 가져오기 위한 추가 쿼리로, 사용자 10명에 대해 10번 실행된다.

SELECT * FROM post WHERE user_id = 1;
SELECT * FROM post WHERE user_id = 2;
...
SELECT * FROM post WHERE user_id = 10;
  • 이렇게 되면 사용자 수(N명)에 대해 추가로 N번의 쿼리가 실행되니까, 총 N+1개의 쿼리가 실행되는 문제
  • 데이터가 많아질수록 쿼리 횟수가 기하급수적으로 늘어나서 성능에 큰 부담을 줄 수 있다.

Fetch Join

  • Fetch Join은 N+1 문제를 해결하기 위한 방법 중 하나
  • JPA는 기본적으로 연관된 데이터를 지연 로딩(Lazy Loading)하게 되는데, Fetch Join을 사용하면 연관된 데이터를 즉시 로딩(Eager Loading)하면서 한 번의 쿼리로 필요한 데이터를 모두 가져오게 된다.

Fetch Join을 사용한 쿼리:

@Query("SELECT u FROM User u JOIN FETCH u.posts")
List<User> findAllUsersWithPosts();

이 쿼리는 User와 관련된 Post들을 한 번의 쿼리로 모두 가져와서 N+1 문제를 방지한다.
실행되는 SQL 쿼리:

SELECT u.*, p.*
FROM user u
JOIN post p ON u.id = p.user_id;
  1. 양방향 매핑 시 주의:
    • 양방향 매핑을 사용할 때 순환 참조 문제가 발생할 수 있다.
      • 이를 방지하기 위해, 엔티티를 주로 단방향 매핑으로 설계하는 것이 좋고,
      • 양방향 매핑은 정말 필요한 경우에만 사용해야 한다.

  • 테이블 간의 관계를 조금 더 명확하게 하고, 테이블 자체의 인덱스를 위해 설정된 장치
  • 기본키, 외래키, 후보키, 슈퍼키, 대체키가 있음

키들의 관계

기본키

  • Primary Key(PK) 라고도 하며, 유일성최소성을 만족하는 키

💡
유일성, 최소성?
유일성테이블 내에서 중복될 수 없다는 의미
최소성은 그 속성의 조합이 최소한으로 데이터의 고유성을 보장해야 한다는 의미

  • 테이블의 데이터 중 고유하게 존재하는 속성이며, 기본키에 해당하는 데이터는 중복되어서는 안 됨
  • 복합키를 기본키로 설정하게 되면 최소성을 만족하지 못함
  • 기본키는 자연키인조키중에 골라 설정함
    • 자연키

      💡 유저 테이블을 만든다고 가정하면, 주민등록번호, 이름, 성별 등의 속성이 존재한다.
      이 중 이름, 성별 등은 중복된 값이 들어올 수 있으므로 부적절하고, 남는 것은 주민등록번호이다.
      이런 식으로 중복된 값들을 제외하며 중복되지 않는 것을 ‘자연스럽게’ 뽑다가 나오는 키자연키라고 함

    • 인조키

      💡 유저 테이블을 만든다고 했을 때 회원 테이블을 생성한다고 가정하면 주민등록번호, 이름, 성별 등의 속성이 존재한다.
      여기에 인위적으로 유저 아이디를 부여한다. 이를 통해 고유 식별자가 생겨난다.
      OracleSequence, MySQLAuto Increment 등으로 설정한다.
      이렇게 인위적으로 생성한 키를 인조키라고 함. 자연키와는 대조적으로 변하지 않는데, 따라서 보통 기본키는 인조키로 설정함

  • Java/ORM :
    • JPA에서 기본키 설정: @Id 어노테이션을 사용해 기본키를 설정 가능
    • 여기서 @GeneratedValue(strategy = GenerationType.IDENTITY)MySQL에서 Auto Increment처럼 인조키를 자동으로 생성하는 것
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
}

외래키

  • Foreign Key(FK) 라고도 하며, 다른 테이블의 기본키를 그대로 참조하는 값
  • 개체간의 관계를 식별하는 데 사용
  • 외래키는 중복되어도 괜찮다. 하지만, 참조된 테이블의 값이 변동되면 해당 테이블도 영향을 받을 수 있다.
  • Java/ORM:
@ManyToOne
@JoinColumn(name = "department_id")
private Department department;

후보키

  • 기본키가 될 수 있는 후보들이며, 유일성최소성동시에 만족하는 키

대체키

  • 후보키들 중에서 기본키로 선택되지 않은 키대체키라고 함
  • 대체키는 기본키와 같은 속성을 가지고 있지만, 기본키로 사용되지 않은 것들임

슈퍼키

  • 슈퍼키는 유일성을 만족하는 속성 또는 속성의 집합이다.
    • 하지만 최소성을 만족하지 않을 수 있다.
    • 즉, 여분의 속성이 포함된 유일한 키
profile
https://sususoo.tistory.com/

0개의 댓글