현재 진행하고 있는 프로젝트에서 분실물 찾기 게시판을 담당하여 구현하고 있다. 어떻게 보면 CRUD밖에 없어서 시시하다고 생각할 수 있겠지만 기본은 언제나 중요하다고 하니까. 스스로 기본기가 부족하다고 느꼈고 차근차근 다시 쌓아볼 예정이다.
아직 프로젝트를 완성하진 못했지만 지금까지 구현한 기능에 대해 정리하고, 고민했던 거나 궁금했던 걸 기록해보기 위한 목적으로 작성하게 되었다 ꒰⑅◡̎ ꒱𓈒𓏸
데이터베이스
우선 데이터베이스 설계부터 살펴보면,
LostFoundBoard: 분실물 찾기 게시판
LostFoundBoardImage: 분실물 찾기 게시판 이미지
Comment: 분실물 찾기 게시판 댓글
(현재 고민하고 있는 게 초기에는 불필요한 관계라고 판단하여 Comment에서 Member 연관관계를 맺지 않았다. 그런데 게시글 상세보기에서 사용자 프로필 사진을 같이 보내줘야하는데 매번 조회 쿼리를 통해서 알아내야한다는 점이다. 만약 일대다 연관관계를 맺게 된다면 Fetch Join으로 Comment를 조회하면서 Member도 같이 조회할 수 있으므로 성능상 이점을 가지게 될 것이라고 생각했다. 아직 댓글 기능을 구현하지 않았기에 추후 변경할 예정이다. 변경 완료!)
도메인 코드
1. LostFoundBoard
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class LostFoundBoard extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "waterPlace_id")
private WaterPlace waterPlace;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
@Column(nullable = false, length = 20)
private String title;
@Column(nullable = false, length = 256)
private String content;
private Boolean isPosting; // 게시 여부
private Boolean isResolved; // 해결 완료
private LostFoundEnum lostFoundEnum; // 찾아요/찾았어요 카테고리
@PrePersist
public void prePersist() {
this.isPosting = this.isPosting == null? true : isPosting;
this.isResolved = this.isResolved == null? false : isResolved;
}
@Builder
public LostFoundBoard(Long id, WaterPlace waterPlace, Member member, String title, String content, Boolean isPosting, Boolean isResolved, LostFoundEnum lostFoundEnum) {
this.id = id;
this.waterPlace = waterPlace;
this.member = member;
this.title = title;
this.content = content;
this.isPosting = isPosting;
this.isResolved = isResolved;
this.lostFoundEnum = lostFoundEnum;
}
}
2. LostFoundBoardImage
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class LostFoundBoardImage {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long lostFoundBoardId;
private ImageFile imageFile;
@Builder
public LostFoundBoardImage(Long id, Long lostFoundBoardId, ImageFile imageFile) {
this.id = id;
this.lostFoundBoardId = lostFoundBoardId;
this.imageFile = imageFile;
}
}
3. Comment
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Comment extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long lostFoundBoardId;
@Column(nullable = false, length = 30)
private String authorEmail;
@Column(nullable = false, length = 256)
private String content;
@Builder
public Comment(Long id, Long lostFoundBoardId, String authorEmail, String content) {
this.id = id;
this.lostFoundBoardId = lostFoundBoardId;
this.authorEmail = authorEmail;
this.content = content;
}
}
✏️ 개인적으로 기록하고 싶은 것
- @NoArgsConstructor(access = AccessLevel.PROTECTED)
- 기본 생성자의 접근 제어를 PROTECTED로 설정해놓게 되면 무분별한 객체 생성에 대해 한번 더 체크할 수 있는 수단이 되기 때문에 사용한다.
- 왜 기본 생성자의 접근 제한자를 애매하게 protected로 하는 걸까? 바로 프록시 때문
- 지연로딩을 할 때 실제 객체를 상속한 프록시 객체를 생성한다. 프록시 객체는 실제 객체의 참조변수를 가지고 있어야 하기 때문에 super를 호출한다. 하지만 실제 객체의 기본 생성자가 private로 되어있다면 super를 호출할 수 없다.
- 즉 프록시 객체를 생성하는데에 있어 기본 생성자의 접근 제한의 최소한 protected라는 것이다.
- @Builder
- 빌더 패턴으로 객체를 생성할 수 있게 해주는 어노테이션
- 생성자 파라미터가 많을 때 고려해볼 수 있는 것
- 데이터의 순서에 상관없이 객체 생성이 가능
- @PrePersist
- JPA 엔티티가 비영속 상태에서 영속 상태가 되는 시점 이전에 실행됨
- 만약 그 시점에 LostFoundBoard 객체의 isPosting, isResolved 값이 존재하지 않다면 기본 값으로 false를 세팅하기 위해 사용했음
이제 기본 설계에 대한 설명은 끝이 난 것 같다. 다음 글에서는 게시판 게시글 작성에 대해 관련해서 정리해볼 예정이다.
사실 설계라는 게 처음에 잘 해놓으면 프로젝트 끝날 때까지 편한데 한 번에 완벽하게 해놓기가 쉽지 않은 것 같다.
도중에 여러 번의 수정 과정도 있었고, 현재도 수정해야할 게 남아있다.
기록하면서 잘못된 부분이 또 보이지 않을까 싶다.
오늘은 여기까지!
댓글