🖤 UMC 서버 7주차 스터디 - [ Springboot ] JPA를 통한 엔티티 설계, 매핑 & 프로젝트 파일 구조 이해 🖤
1. Springboot 초기 설정
플러그인 설치
Atom Material Icons 설치
패키지 설정
domain
JPA에서 사용하기 위한 엔티티 클래스를 저장하기 위한 패키지
controller
http 요청에 대한 응답을 주는 클래스의 모임
응답을 주기 위한 과정들을 service에서 처리하도록 한다
service
비즈니스 로직이 필요한 클래스들의 모임
contoller에서 service의 메소드를 호출
service는 repository의 메소드를 호출
repository
database와 통신을 하는 계층
Spring Data JPA를 이용해서 만든 repository를 이용
dto
클라이언트가 body에 담아서 보내는 데이터를 받기 위한 클래스
Database에서 받아온 데이터를 클라이언트에게 보여주기 위한 클래스
Database에서 받아온 데이터를 dto를 한번 거쳐서 클라이언트에게 보여주는 이유는
Database에서 받아온 데이터를 그대로 응답으로 주게 될 경우
Database의 변경 사항이 생겼을 때 당연히 엔티티에도 변경이 생기게 되고
엔티티의 변경이 프론트엔트 개발자에게까지 영향을 주기 때문이다
converter
데이터 형식 간의 변환을 수행하는 역할
* entity -> dto
* entity의 생성
service에서 entity를 생성하도록 할 수도 있지만
converter에서 entity의 생성을 맡게 함으로써
service가 비즈니스 로직에만 집중할 수 있도록 하는 것이 단일 책임 원칙 측면에서 좋다
converter의 사용 위치
- service에서 dto 생성
service에서 converter를 통해 dto를 controller에게 리턴해주는 것
* controller에 엔티티가 노출X -> 보안적인 측면에서 유리
- controller에서 dto 생성
service에서 entity를 리턴하고
controller에서 converter를 통해 dto를 만들어서 응답을 주는 것
* service의 함수가 범용성이 커짐 -> 유지보수 측면에서 유리
2. Entity 매핑
Entity : 데이터베이스 테이블
DB 준비
로컬 DB 혹은 RDS에 DB 생성 -> 나는 RDS를 잘 관리할 자신이 없기 때문에... 로컬 DB를 선택했다
mysql install
$brew install mysql
mysql 기본 설정
$mysql.server start
비밀번호를 생성하고 환경 설정을 진행했다
mysql 접속
$mysql -u root -p
Database 생성
CREATE DATABASE [DB명]
show databases;
application.yml 설정
application.yml - 외부에서 환경 변수 삽입
로컬 DB 연결
spring:
datasource:
url: jdbc:mysql://localhost:3306
username: root
password: number5598
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: create
default_batch_fetch_size: 1000
entity 생성 - domain 패키지
아래의 ERD를 기준으로 entity 매핑을 진행한다
enum 설정
정해진 값들 중에 특정 값이 저장되는 요소들 ( Gender, MemberStatus 등등 )의 경우
enum을 사용하는 것이 좋다
Created_at, Updated_at -> base 패키지 생성
Created_at이나 Updated_at의 경우, 모든 엔티티에 포함되는 요소이기 때문에
따로 패키지를 만들어 abstract Class로 생성해놓는 것이 좋다
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Getter
public abstract class BaseEntity {
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
member
@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Member extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberId;
private String name;
private String address;
private String specAddress;
@Enumerated(EnumType.STRING)
private Gender gender;
@Enumerated(EnumType.STRING)
private SocialType socialType;
@Enumerated(EnumType.STRING)
private MemberStatus status;
private LocalDate inactiveDate;
private String email;
private Integer point;
}
food_category
@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class foodCategory extends BaseEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long foodCategoryId;
private String name;
}
terms
@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Terms extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long termsId;
private String title;
private String body;
private Boolean optional;
}
review
@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Review extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long reviewId;
private String title;
private Float score;
}
region
@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Region extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long regionId;
private String name;
}
store
@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Store extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long storeId;
private String name;
private String address;
private Float score;
}
mission
@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Mission extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long missionId;
private Integer reward;
private LocalDate deadline;
private String missionSpec;
}
매핑 테이블
MemberAgree
@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class MemberAgree extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberAgreeId;
}
MemberMission
@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class MemberMission extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberMissionId;
@Enumerated(EnumType.STRING)
private MissionStatus status;
}
MemberPrefer
@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class MemberPrefer extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberPreferId;
}
연관관계 매핑
연관 관계 주인 : 실제 데이터베이스에서 외래키를 가지는 엔티티
단방향 연관관계
연관 관계 주인에게만 연관 관계 주입
1:N 관계의 경우 N에 해당하는 테이블이 외래키를 가지며 연관 관계 주인에 해당
1:1 관계의 경우 둘 중 하나, 원하는 엔티티를 연관 관계 주인으로 설정
예시 )
member와 member_prefer, member와 review 가 1 : N 관계를 맺고 있다
여기서 member_prefer와 review가 연관 관계 주인에 해당한다
MemberPrefer
@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class MemberPrefer extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "memberId")
private Member member;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "foodCategoryId")
private FoodCategory foodCategory;
}
- 연관 관계 주입 : @ManyToOne 어노테이션 사용
fetch = FetchType.LAZY 는 지연 로딩을 의미한다
- 외래키의 이름 설정 : @JoinColumn 어노테이션 사용
@JoinColumn에서 'name=' 뒤에는 연관 관계 설정할 entity의 PK 이름을 넣는다
양방향 연관관계
연관 관계 주인과 연관 관계 주인이 아닌 엔티티에게 모두 연관 관계 주입
1:N 관계에서 1에 해당하는 엔티티에게 설정
N에 해당하는 엔티티가 삭제되어도 1에 해당하는 엔티티를 보존하기 위함
예시 )
Term
@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Terms extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long termsId;
private String title;
private String body;
private Boolean optional;
@OneToMany(mappedBy = "terms", cascade = CascadeType.ALL)
private List<MemberAgree> memberAgreeList = new ArrayList<>();
}
- 연관 관계 주입 : @OneToMany 어노테이션 이용
mappedBy를 통해 N에 해당하며 @ManyToOne 이 설정된 멤버변수를 연결한다
cascade = CascadeType.ALL 은 Member의 변화에 따라 Review, MemberPrefer 등의 엔티티가 영향을 받는다는 것을 의미한다
칼럼별 세부 설정
칼럼 별로 유니크, 디폴트 값, null 이 가능한지 등 세부적인 설정을 해줄 수 있다
@Column(세부 설정 내용)