3주차는 1, 2 주차에 작성한 기능명세서, API 문서, DB 명세서를 바탕으로 실제 프로젝트를 개발을 진행하는 주차이다. 따라서 개발이 본격적으로 시작된다.
먼저 DB 명세를 기반으로 엔티티 클래스 개발을 시작하였다.
spring:
datasource:
url: jdbc:mysql://DB이름.RDS주소:3306/campus?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
username: username
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
default_batch_fetch_size: 100
logging.level:
org.hibernate.SQL: debug
DB 는 aws MySql RDS를 사용하였다.
package couch.camping.domain.member.entity;
import lombok.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.Collection;
@Entity
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Member implements UserDetails {
//uid
@Id
private String username;
@Column
private String email;
@Column
private String nickname;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getPassword() {
return null;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return false;
}
}
package couch.camping.domain.notification.entity;
import couch.camping.domain.base.BaseTimeEntity;
import couch.camping.domain.member.entity.Member;
import couch.camping.domain.review.entity.Review;
import lombok.*;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter @Setter
@Builder
@EntityListeners(AuditingEntityListener.class)
public class Notification extends BaseTimeEntity {
@Id @GeneratedValue
@Column(name = "notification_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "review_id")
private Review review;
private boolean isChecked;
}
package couch.camping.domain.notification.entity;
import couch.camping.domain.base.BaseTimeEntity;
import couch.camping.domain.member.entity.Member;
import couch.camping.domain.review.entity.Review;
import lombok.*;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter @Setter
@Builder
@EntityListeners(AuditingEntityListener.class)
public class Notification extends BaseTimeEntity {
@Id @GeneratedValue
@Column(name = "notification_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "review_id")
private Review review;
private boolean isChecked;
}
package couch.camping.domain.reviewlike.entity;
import couch.camping.domain.base.BaseTimeEntity;
import couch.camping.domain.member.entity.Member;
import couch.camping.domain.review.entity.Review;
import lombok.*;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter @Setter
@Builder
@EntityListeners(AuditingEntityListener.class)
public class ReviewLike extends BaseTimeEntity {
@Id @GeneratedValue
@Column(name = "review_like_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "review_id")
private Review review;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
}
package couch.camping.domain.camp.entity;
import lombok.*;
import javax.persistence.*;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter
@Setter
@Builder
public class Camp {
@Id @GeneratedValue
@Column(name = "camp_id")
private Long id;
@Column(name = "like_cnt")
private Integer like;
private Integer rate;
private String facltNm;
private String lineIntro;
@Lob
private String intro;
@Lob
private String featureNm;
private String induty;
private String lctCl;
private String doNm;
private String sigunguNm;
private String addr1;
private Float mapX;
private Float mapY;
private String tel;
@Lob
private String direction;
private String homepage;
@Lob
private String resveUrl;
private String resveCl;
private Integer autoSiteCo;
private Integer glampSiteCo;
private Integer caravSiteCo;
private Integer sitedStnc;
private Integer indvdlCaravSiteCo;
private String glampInnerFclty;
private String caravInnerFclty;
private String operPdCl;
private String operDeCl;
private String trlerAcmpnyAt;
private String caravAcmpnyAt;
private Integer toiletCo;
private Integer swrmCo;
private Integer wtrplCo;
private String brazierCl;
private String sbrsCl;
private String sbrsEtc;
private String posblFcltyCl;
private String posblFcltyEtc;
private Integer fireSensorCo;
private Integer extshrCo;
private String themaEnvrnCl;
private String eqpmnLendCl;
private String animalCmgCl;
private String tourEraCl;
private String firstImageUrl;
}
package couch.camping.domain.camplike.entity;
import couch.camping.domain.base.BaseTimeEntity;
import couch.camping.domain.camp.entity.Camp;
import couch.camping.domain.member.entity.Member;
import lombok.*;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter
@Setter
@Builder
@EntityListeners(AuditingEntityListener.class)
public class CampLike extends BaseTimeEntity {
@Id @GeneratedValue
@Column(name = "camp_like_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "camp_id")
private Camp camp;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
}
package couch.camping.domain.base;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseTimeEntity {
@Column(updatable = false)
@CreatedDate
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime lastModified;
}
package couch.camping.domain.base;
import lombok.Getter;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
@MappedSuperclass
@Getter
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity extends BaseTimeEntity {
@CreatedBy
@Column(updatable = false)
private String createdBy;
@LastModifiedBy
private String lastModifiedBy;
}
BaseTimeEntity 의 경우 data jpa 기능을 사용하여 생성, 수정 시간을 위한 엔티티 클래스이며, BaseEntity의 경우 data jpa와 spring security 기능을 사용하여 작성자와 수정자를 자동으로 매핑시킨다.
JPA 의 상속은 부모 클래스가 @Entity 어노테이션을 가지고 있거나 또는 @MappedSuperclass 어노테이션을 가지고 있어야 상속이 가능하다. @Entity 클래스를 상속할 경우 super-sub 타입으로 관계가 맺어지고 위의 BaseEntity와 BaseTimeEntity의 경우 해당 클래스의 필드 값만 데이터베이스에 매핑하여 사용한다.
엔티티 클래스의 @NoArgsConstructor(access = AccessLevel.PROTECTED) 기본 생성자를 protected 로 설정한 이유는, hibernate의 경우 proxy를 사용하여 호출하기 때문에 private이 아닌 protectd로 설정하였다.
카우치 코딩 스터디의 경우 배포를 heroku를 사용한다. heroku는 front-end, back-end, database 등 모든 서비스가 하나의 서버에서 모놀리틱하게 이루어진다. 하지만 프론트의 oAuth와 백엔드의 oAuth 기능을 테스트하는 과정을 테스트하기 위해 aws의 ec2와 rds를 사용하여 배포를하였다. 추후 aws 환경에서 git actions 를 통해 ci/cd 를 추가할 예정이다.