IntelliJ Spring Initializr를 이용한 스프링 프로젝트 생성
Create New Project를 누르고 Spring Initializr를 이용해 프로젝트를 생성한다


Group과 Artifact를 넣은 후 자바 버전을 11로 선택한다

스프링 부트 버전 2.3.0을 선택하고 필요한 의존성인 Lombok, Spring Web, Spring Data JPA, H2 Database, Validation을 선택한다

프로젝트 생성이 완료되면 다음과 같이 선택한 의존성이 pom.xml에 들어가 있는 것을 확인할 수 있다. 또한 pom.xml에 의존성을 추가 시킬 수 있다
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="<http://maven.apache.org/POM/4.0.0>" xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
xsi:schemaLocation="<http://maven.apache.org/POM/4.0.0> <https://maven.apache.org/xsd/maven-4.0.0.xsd>">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
데이터 모델 설계
모든 테이블에 공통적으로 id와, 생성 날짜, 수정 시간을 필드로 갖는다
account
사용자를 표현한 테이블로 생일, 성별, 키, 몸무게, 이메일, 이름, 사진 주소 등을 필드로 가진다
event
술 자리를 표현한 테이블로 술 자리 이벤트에서 총 마신 술의 양을 기록한다. 필드로 날짜, 이름, 총 마신 알코올 양을 기록한다
item
술의 종류를 표현한 테이블로 술의 도수, 이름, 카테고리를 필드로 갖는다
relationship
친구 관계를 표현한 테이블로 이를 이용해 친구를 알 수 있다. 필드로 사용자의 id와 status를 가진다. 이중 status는 요청, 수락, 차단 등의 상태를 표현하기 위해 추가하였다
account_event
술자리에서 사용자가 마신 술의 양을 표현한 테이블이다. 필드로 알코올의 양, 이름을 필드로 가지는데 실제 사용자의 계정에 표시될 술의 양이다
event_item
술자리의 술 종류를 표현한 테이블로, 술의 양, 도수를 필드로 갖는다. item에도 도수가 있는데 event_item에 도수가 있는 이유는 사용자가 술에 다른 것을 타서 먹는 경우를 표현하기 위해 만들었다
account_event_category
사용자가 설정한 술자리 카테고리이다. 필드로 색, 이름을 가지며 사용자가 설정할 수 있다
다이어그램

BaseEntity 구현
만든 시간, 수정 시간, id등은 모든 Entity에 공톡적으로 들어가는 기능이다. 따라서 @MappedSuperclass annotation을 이용하여 BaseEntity를 만들고 다른 Entity는 모두 BaseEntity를 상속 받게 하려고 한다
먼저 프로젝트 java/com/b511/drink 폴더 아래 domain 패키지를 만든다. 다음으로 BaseEntity를 다음과 같이 만들어 준다 AuditingEntityListener를 통해 엔티티가 생성되거나 수정될 때 시간을 넣어 준다
@Getter
@NoArgsConstructor
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@CreatedDate
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime modifiedDate;
public boolean isNew() {
return this.id == null;
}
}
BaseTimeEntity를 상속 받은 BaseEntity를 다음과 같이 만들어 준다. 실제 엔티티는 모두 BaseEntity를 상속 받게 된다
@Getter
@NoArgsConstructor
@MappedSuperclass
public class BaseEntity extends BaseTimeEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
public boolean isNew() {
return this.id == null;
}
}
Account Entity 구현
Account Entity 속성 중 생일, 성별, 키, 몸무게 등은 @Embedable로 정의 하여 Account Entity가 더 보기 편하게 정의 하였다.
@Getter
@NoArgsConstructor
@Entity
public class Account extends BaseEntity {
@NotEmpty
private String name;
@NotEmpty
private String email;
@NotEmpty
private String picture;
@Embedded
private AccountInfo accountInfo;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "account")
private Set<Relationship> relationships;
@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Relationship relationship;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "account")
private Set<AccountEvent> accountEvents;
}
AccountInfo 중 Gender는 enum 타입을 이용해 남자, 여자를 더욱 편하게 구별되게 하였고 저장을 0, 1로 될수 있게 EnumType.ORDINAL로 설정하였다
@Getter
@NoArgsConstructor
@Embeddable
public class AccountInfo {
@NotEmpty
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate birthDate;
@NotEmpty
@Enumerated(EnumType.ORDINAL)
private Gender gender;
@NotEmpty
private Double height;
@NotEmpty
private Double weight;
}
public enum Gender {
male, female;
}
Relationship Entity 구현
Account Entity는 친구 관계를 표현하기 위해 recusive하게 연관관계를 매핑해야한다. 이를 JPA에서 연관 관계의 주인을 표기하기 위해 Relationship Entity와 @OneToMany, @ManyToOne으로 두번 연결하였다.
@Getter
@NoArgsConstructor
@Entity
public class Relationship extends BaseEntity {
@ManyToOne
@NotEmpty
private Account account;
@OneToMany(mappedBy = "relationship")
@NotEmpty
private Set<Account> accounts;
@Enumerated(value = EnumType.STRING)
@NotEmpty
private RelationshipStatus status;
}
또한 친구관계의 상태인 보류, 승낙, 거절, 차단 등을 표시하기 위해 enum으로 설정하였고 테이블 상에는 String으로 들어가지만 이 4개의 값만 들어 갈 수 있다
public enum RelationshipStatus {
Pending, Accepted, Declined, Blocked;
}
Item Entity 구현
알코올의 도수는 최대 100이므로 @Max(100)을 validation을 이용해 설정하였다
@Getter
@NoArgsConstructor
@Entity
public class Item extends BaseEntity {
@NotEmpty
private String name;
@NotEmpty
@Max(100)
private double alcoholByVolume;
@Enumerated(value = EnumType.STRING)
@NotEmpty
private CategoryofItem categoryofItem;
}
술의 카테고리는 enum을 이용해 문자열로 저장될수 있게 구현하였다
public enum CategoryofItem {
Wine, Soju, Beer;
}
Event Entity 구현
@Getter
@NoArgsConstructor
@Entity
public class Event extends BaseEntity {
@OneToMany(cascade = CascadeType.ALL, mappedBy = "event")
@NotEmpty
private Set<AccountEvent> accountEvents;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "event")
@NotEmpty
private Set<EventItem> eventItems;
@NotEmpty
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate drinkDate;
private String name;
@NotEmpty
private Double totalAlcoholByVolume;
}
EventItem Entity 구현
@Getter
@NoArgsConstructor
@Entity
public class EventItem extends BaseEntity {
@ManyToOne
@NotEmpty
private Event event;
@ManyToOne
private Item item;
@NotEmpty
@Max(100)
private Double alcoholByVolume;
@NotEmpty
private Double volume;
}
AccountEvent Entity 구현
AccountEventCategory는 사용자가 자신의 카테고리를 만들수 있게하기 위해 enum이 아닌 Entity로 구현하였다
@Getter
@NoArgsConstructor
@Entity
public class AccountEvent extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY)
@NotEmpty
private Event event;
@ManyToOne(fetch = FetchType.LAZY)
@NotEmpty
private Account account;
@NotEmpty
private Double accountAlcoholByVolume;
private String name;
@ManyToOne
private AccountEventCategory category;
}
enum으로 Color를 만들어서 정해진 색 중 사용자가 표시할 색을 설정할수 있게 하였다
@Getter
@NoArgsConstructor
@Entity
public class AccountEventCategory extends BaseEntity {
@NotEmpty
private String name;
@NotEmpty
@Enumerated(EnumType.STRING)
private Color color;
@OneToMany(mappedBy = "category")
private Set<AccountEvent> accountEvents;
}
public enum Color {
red, blue, yellow, green, purple
}