만들어놨던 ERD를 가지고 JPA 매핑을 해보는 시간
그 전에 JPA가 무엇인지 이론적인 내용을 알아야한다. 그래서 그 전글에 정리해두었다.!
https://wsw3727.tistory.com/55
간단히 복습을 해보면,
JPA(Java Persistence API) 란
JPA는 자바 애플리케이션에서 관계형 데이터베이스를 관리하고 상호작용하기 위한 자바 표준 API이다. 이를 통해 개발자는 SQL 쿼리를 직접 작성하지 않고도 데이터베이스 작업을 수행할 수 있다.
JPA의 필요성
객체 중심의 객체지향 어플리케이션과 테이블 중심의 관계형 데이터베이스는 서로 목표가 다르다. 이를 패러다임 불일치라고 하며, 이는 개발 과정에서 많은 비용을 발생킨다. JPA는 이러한 문제를 해결하기 위해 객체와 테이블 간의 매핑을 자동화하여 개발자의 생산성을 높인다.
Persistence Framework
자바 애플리케이션에서 관계형 데이터베이스의 사용을 돕는 프레임워크로, SQL Mapping과 OR Mapping으로 구분된다. SQL Mapping은 자바 코드와 SQL을 분리하고, OR Mapping은 객체와 관계형 데이터베이스 간의 매핑을 담당하여 패러다임의 불일치를 해결한다. JPA는 자바 진영의 ORM(Object-Relational Mapping) 기술 표준이다.
영속성 컨텍스트 (Persistence Context)
영속성 컨텍스트는 JPA에서 엔티티의 상태를 관리하는 환경을 의미한다. 이는 데이터베이스와의 동기화를 통해 엔티티 객체의 생명주기를 관리하고, 엔티티를 데이터베이스와의 연결 상태에서 유지한다. 영속성 컨텍스트는 1차 캐시, 변경 감지, 쓰기 지연 등의 기능을 제공하여 성능을 최적화한다.
엔티티의 생명주기 상태
비영속 상태 (Transient): 영속성 컨텍스트에 의해 관리되지 않는 상태.
영속 상태 (Persistent): 영속성 컨텍스트에 의해 관리되는 상태.
준영속 상태 (Detached): 영속성 컨텍스트가 닫히거나 엔티티가 분리된 상태.
삭제 상태 (Removed): 엔티티가 삭제되어 영속성 컨텍스트와 데이터베이스에서 제거될 상태.
JPA는 이러한 기능들을 통해 객체와 관계형 데이터베이스 간의 패러다임 불일치를 해결하고, 개발자의 생산성을 높여준다. 이를 통해 비즈니스 로직과 데이터 접근 로직을 분리하고, 데이터베이스 접근을 더욱 효율적으로 관리할 수 있다.
엔티티를 설계하기 앞서, 각 패키지를 알아보자
패키지
- domain 패키지: JPA에서 사용하기 위한 엔티티 클래스를 저장하기 위한 패키지이다.
- controller 패키지: http요청이 오면, 그에 대한 응답을 주는 클래스의 모임이다.
- service 패키지: 비지니스 로직이 필요한 클래스의 모임이며 가장 복잡한 코드가 들어간다. controller에서 service의 메소드를 호출하게 되며, service는 repository의 메소드를 호출하게 된다.
- repository 패키지: database와 통신을 하는 계층으로, Spring Data JPA를 이용해서 만든 레포지토리를 이용
- dto 패키지: 클라이언트가 body에 담아서 보내는 데이터를 받기 위한 클래스. 혹은 데이터베이스에서 받아온 데이터를 클라이언트에게 보여주기 위한 클래스
- converter 패키지: 데이터 형식 간의 변환을 수행하는 역할. 레포지토리에서 받아온 엔티티를 dto로 바꾸는 과정을 한다.
- service에서 dto 생성: service에서 converter를 통해 dto를 controller에게 리턴하는 것
- controller에서 dto 생성: service에서 entity를 리턴하고 controller에서 converter를 통해 dto을 만들어서 응답을 주기도한다.
Entity 매핑
1. DB를 준비한다. 나의 경우 로컬에서 MySQL로 DB를 생성해주었다.
create database study;
use study;
create user 'hansol'@'localhost' IDENTIFIED BY 'hansol';
GRANT ALL PRIVILEGES ON study.* TO 'hansol'@'localhost';
FLUSH PRIVILEGES;
2. 그리고 intellij로 돌아가서 application.yml도 설정해준다.
spring:
datasource:
url: jdbc:mysql://localhost:3306/study
username: hansol
password: hansol
driver-class-name: com.mysql.cj.jdbc.Driver
sql:
init:
mode: never
jpa:
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
show_sql: true
format_sql: true
use_sql_comments: true
hbm2ddl:
auto: update
default_batch_fetch_size: 1000
3. 도메인 패키지에서 엔티티 만들기
위의 ERD 맞춰 엔티티를 생성해주었다.
어노테이션
여기서 사용되는 어노테이션에 대해서 알아보자 💡
ex) Member 엔티티
@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
@Entity 어노테이션을 통해 해당 클래스가 JPA의 엔티티임을 명시한다.
@Getter 어노테이션은 getter를 만들어준다.
@Builder 어노테이션은 빌더 패턴을 사용하기 위함이다.
(빌더패턴이란 객체 생성시 복잡한 생성자 호출을 단순화 하고 객체 생성의 유연성을 제공하는 디자인 패턴이다.)
엔티티를 만들다 보면 정해진 값들 중에 특정 값이 저장되는 컬럼이 있다. ex) merber엔티티에 gender, status 등
그때 Enum을 사용해준다.
Enum (Enumeration)
Enum은 관련된 상수들을 하나의 그룹으로 묶어서 관리할 수 있게 해주는 특별한 클래스이다.
아래와 같이 미리 정해진 값들을 저장해준다.
public enum Gender {
MALE, FEMALE
}
Enum의 적용은 @Enumerated 어노테이션을 이용해 enum을 entity에 적용할 수 있다.
이때 반드시 EnumType을 STRING으로 해야 한다. (기본 값인 ORDINAL을 사용하면 순서가 바꾸게 될 경우 에러가 생길 수 있기에)
@Enumerated(EnumType.STRING)
private Gender gender;
매핑 테이블의 설계
매핑 테이블은 따로 패키지에 모아서 관리하는 것이 편하기에 domain > mapping 패키지를 만들어 그 안에 클래스로 관리해준다.
연관 관계 매핑은 단방향 매핑, 그리고 양방향 매핑이 있다. 양방향 매핑을 사용하게 될 경우 버그가 생길 위험이 있지만, 양방향 매핑으로 인해 객체 그래프 탐색은 프로그래밍을 굉장히 편리하게 해준다.
연관 관계 주인이란, 실제 데이터베이스에서 외래키를 가지는 엔티티(테이블)를 의미한다. 단방향 매핑이란 연관 관계 주인에게만 연관 관계를 주입하는 것이고 양방향 매칭은 연관 관계 주인이 아닌 엔티티에게도 연관 관계를 주입하는 것이다.
단방향 매핑: 관계가 한쪽 엔티티에서만 정의됩니다.
단방향 매핑: 관계가 한쪽 엔티티에서 정의된다. 예: Member -> Address
양방향 매핑: 관계가 양쪽 엔티티에서 모두 정의된다. 예: Member <-> Address
단방향 매핑
위의 테이블을 보면 member_prefer 테이블의 경우 member와 food_category 모두에 대한 외래키를 가지고 있다.
1:N의 경우 N에 해당되는 테이블이 외래키를 가지며 N에 해당되는 엔티티가 연관 관계의 주인이다.
1:1의 경우 둘 중 하나만 외래키를 가지면 되기에 원하는 엔티티를 연관 관계 주인으로 설정하면 된다.
그래서 member_prefer 테이블이 연관 관계 주인이므로 이 엔티티에 연관 관계 매핑을 해보자
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
private FoodCategory foodCategory;
위와 같이 N:1에서 N에 해당되는 엔티티가 1에 해당하는 엔티티와 연관 관계를 매핑할 때 @ManytoOne 어노테이션을 사용한다.
@JoinColumn은 실제 데이터베이스에서 해당 칼럼(외래키)의 이름을 설정하는 것이다.
양방향 매핑
양방향 매칭의 장점은 객체 그래프 탐색으로 인한 이점이 있으며, 필수적인 기능인 cascade 설정이 기능하다는 점이다.
그러나 JPA에서는 반대로 연관관계의 주인이 아닌, 참조의 대상이 되는 엔티티에 설정을 해야한다. 이는, 단방향 매핑 만으론 cascade 설정을 하는 것이 문제가 있다는 것이다. 단, 양방향 매핑을 할 경우 연관 관계 편의 메서드가 필요하게 된다.
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<MemberAgree> memberAgreeList = new ArrayList<>();
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<MemberPrefer> memberPreferList = new ArrayList<>();
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<Review> reviewList = new ArrayList<>();
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<MemberMission> memberMissionList = new ArrayList<>();
양방향 매핑의 경우 1:N에서 1에 해당되는 엔티티에게 설정한다.
@OneToMany 어노테이션으로 1에 해당하는 엔티티가 N에 해당하는 엔티티와 관계가 있음을 명시하며, 이때 N에 해당하는 엔티티에서 ManyToOne이 설정된 멤버 변수를 mappedBy를 한다.
칼럼 별 세부적인 설정
JPA에서 칼럼에 대한 세부적인 설정은 @Column을 이용해서 한다.
@Enumerated(EnumType.STRING)
@Column(columnDefinition = "VARCHAR(15) DEFAULT 'ACTIVE'")
private MemberStatus status;
위와 같이 mysql은 문자열을 무조건 ''로 감싸야한다.
이제 엔티티의 세부적인 설정을 다 하고 build를 하면 아까 만든 study 데이터베이스에 테이블이 생긴걸 볼 수 있다
⚡ 이슈 DataSourceProperties에 DataSourceBeanCreationException 발생
🐞 이슈 설명
로컬에 MySQL 데이터베이스를 생성했음에도 불구하고 데이터베이스 URL이 연결되지 않았다는 오류가 발생
🧐 문제 원인
DataSourceProperties$DataSourceBeanCreationException 이 오류는 애플리케이션이 데이터베이스 URL을 찾지 못했기 때문에 발생
🔍 원인 분석
문제의 원인은 application.yml 파일의 파일명이 aplication.yml로 잘못 설정되어 있는 것. 이로 인해 Spring Boot 애플리케이션이 설정 파일을 찾지 못하여 데이터베이스 연결 정보를 로드할 수 없었다.
💡 해결 방법
파일명을 aplication.yml에서 application.yml로 수정하여 문제를 해결했다. yml 파일의 파일명은 정확히 설정되어야 하며, 오타가 있을 경우 Spring Boot가 설정 파일을 찾지 못하여 다양한 오류가 발생할 수 있다.
📄 해결 과정
src/main/resources 디렉토리 아래에 위치한 aplication.yml 파일명을 application.yml로 변경
./gradlew clean build ./gradlew bootRun
오류가 해결되었는지 확인
🚀 교훈
작은 오타 하나가 문제를 야기할 수 있으므로 항상 꼼꼼히 확인하는 습관을 가지자 ㅠ
reference
2023 최용욱(똘이) All rights reserved.
'IT 동아리 > UMC' 카테고리의 다른 글
[Chapter 11] 무중단 CI/CD (Github Action+AWS Elastic Beanstalk) (0) | 2024.06.28 |
---|---|
[Chapter 6] API URL의 설계, REST API, 프로젝트 세팅 (0) | 2024.05.17 |
[Chapter 5] 실전 SQL - Query를 작성하는 방법 (1) | 2024.05.10 |
[Chapter 3] Web Server & Web Application Server(WAS), Reverse Proxy (1) | 2024.05.02 |
[Chapter 2] AWS (VPC & Internet & Gateway & EC2) (2) | 2024.04.10 |