반응형
프로젝트에 사용한 라이브러리 설치 (build.gradle)
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// DB
runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// Thymeleaf, Validation
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
implementation 'org.springframework.boot:spring-boot-starter-validation'
// Spring Security
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: '2.7.5'
ERD
Entity 설명
User
- id(primary key), login_id(로그인에 사용되는 아이디), password, nickname, user_role(등급), received_like_cnt(이 유저가 받은 좋아요 수), created_at(가입일)
- User은 여러개의 Board를 작성할 수 있음 => User : Board = 1 : N
- User은 여러개의 Comment를 작성할 수 있음 => User : Comment = 1 : N
- User은 여러개의 좋아요를 추가할 수 있음 => User : Like = 1 : N
Board
- id(primary key), title(제목), body(내용), category, like_cnt(이 글이 받은 좋아요 수), comment_cnt(이 글이 받은 댓글 수), created_at(작성일), last_modified_at(최근 수정일)
- Board와 Upload Image는 1:1 관계 => Board에 Upload Image의 Id(foreign key)를 넣음 => Board가 연관관계 주인
- Board에는 여러개의 Comment가 달릴 수 있음 => Board : Comment = 1 : N
- Board에는 여러개의 Like가 추가될 수 있음 => Board : Like = 1 : N
Comment
- id(primary key), body(내용), created_at(작성일), last_modified_at(최근 수정일)
Like
- Like는 user_id, board_id를 foreign key로 갖는데, user_id는 좋아요를 누른 유저의 아이디이고, board_id는 좋아요가 달린 게시글의 id
Upload Image
- id(primary key), original_filename(유저가 업로드한 이미지의 원본 파일명), saved_filename(서버에 저장된 파일명)
- 원본 파일명으로 파일을 저장하면 파일명이 중복되어 에러가 발생할 수 있음 => 이를 방지하기 위해 서버에는 중복되지 않는 UUID로 파일명을 수정 후 저장
Entity 구현 코드
User
- User가 삭제(탈퇴)되면 유저의 Board, Comment, Like 모두 삭제 => orphanrRemoval=true 설정
- UserRole에는 @Enumerated(EnumType.STRING)을 설정 => DB에 저장될 때, "BRONZE", "SILVER"와 같이 String 타입으로 저장됨
- User Entity에는 rankUp(), likeChange(), edit(), changeRole() 4개의 메소드가 존재
- rankUp() : BRONZE -> SILVER, SILVER -> GOLD로 승급할 때 사용
- likeChange() : 이 유저의 글에 좋아요가 추가되거나, 취소된 경우 유저의 receivedLikeCnt를 계산하여 수정할 때 사용
- edit() : 유저가 수정할 수 있는 nickname, password를 수정할 때 사용
- changeRole() : rankUp()과는 달리 관리자가 이 유저의 등급을 수정할 때 사용, BRONZE -> SILVER -> GOLD -> BLACKLIST -> BRONZE 순으로 변경
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String loginId; // 로그인할 때 사용하는 아이디
private String password; // 비밀번호
private String nickname; // 닉네임
private LocalDateTime createdAt; // 가입 시간
private Integer receivedLikeCnt; // 유저가 받은 좋아요 개수 (본인 제외)
@Enumerated(EnumType.STRING)
private UserRole userRole; // 권한
@OneToMany(mappedBy = "user", orphanRemoval = true)
private List<Board> boards; // 작성글
@OneToMany(mappedBy = "user", orphanRemoval = true)
private List<Like> likes; // 유저가 누른 좋아요
@OneToMany(mappedBy = "user", orphanRemoval = true)
private List<Comment> comments; // 댓글
public void rankUp(UserRole userRole, Authentication auth) {
this.userRole = userRole;
// 현재 저장되어 있는 Authentication 수정 => 재로그인 하지 않아도 권한 수정 되기 위함
List<GrantedAuthority> updatedAuthorities = new ArrayList<>();
updatedAuthorities.add(new SimpleGrantedAuthority(userRole.name()));
Authentication newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), updatedAuthorities);
SecurityContextHolder.getContext().setAuthentication(newAuth);
}
public void likeChange(Integer receivedLikeCnt) {
this.receivedLikeCnt = receivedLikeCnt;
if (this.receivedLikeCnt >= 10 && this.userRole.equals(UserRole.SILVER)) {
this.userRole = UserRole.GOLD;
}
}
public void edit(String newPassword, String newNickname) {
this.password = newPassword;
this.nickname = newNickname;
}
public void changeRole() {
if (userRole.equals(UserRole.BRONZE)) userRole = UserRole.SILVER;
else if (userRole.equals(UserRole.SILVER)) userRole = UserRole.GOLD;
else if (userRole.equals(UserRole.GOLD)) userRole = UserRole.BLACKLIST;
else if (userRole.equals(UserRole.BLACKLIST)) userRole = UserRole.BRONZE;
}
}
UserRole
public enum UserRole {
BRONZE, SILVER, GOLD, BLACKLIST, ADMIN;
}
Board
- 좋아요 수, 댓글 수를 저장한 이유는 리스트에서 정렬할 때 더 빠른 조회를 위해 따로 저장
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
public class Board extends BaseEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title; // 제목
private String body; // 본문
@Enumerated(EnumType.STRING)
private BoardCategory category; // 카테고리
@ManyToOne(fetch = FetchType.LAZY)
private User user; // 작성자
@OneToMany(mappedBy = "board", orphanRemoval = true)
private List<Like> likes; // 좋아요
private Integer likeCnt; // 좋아요 수
@OneToMany(mappedBy = "board", orphanRemoval = true)
private List<Comment> comments; // 댓글
private Integer commentCnt; // 댓글 수
@OneToOne(fetch = FetchType.LAZY)
private UploadImage uploadImage;
public void update(BoardDto dto) {
this.title = dto.getTitle();
this.body = dto.getBody();
}
public void likeChange(Integer likeCnt) {
this.likeCnt = likeCnt;
}
public void commentChange(Integer commentCnt) {
this.commentCnt = commentCnt;
}
public void setUploadImage(UploadImage uploadImage) {
this.uploadImage = uploadImage;
}
}
BoardCategory
- of 메소드를 통해 String 타입의 category를 BoardCategory 타입으로 변환하는 기능 추가
public enum BoardCategory {
FREE, GREETING, GOLD;
public static BoardCategory of(String category) {
if (category.equalsIgnoreCase("free")) return BoardCategory.FREE;
else if (category.equalsIgnoreCase("greeting")) return BoardCategory.GREETING;
else if (category.equalsIgnoreCase("gold")) return BoardCategory.GOLD;
else return null;
}
}
Comment
- 댓글은 수정이 가능하기 때문에 update() 메소드 추가
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
public class Comment extends BaseEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String body;
@ManyToOne(fetch = FetchType.LAZY)
private User user; // 작성자
@ManyToOne(fetch = FetchType.LAZY)
private Board board; // 댓글이 달린 게시판
public void update(String newBody) {
this.body = newBody;
}
}
Like
- MySQL에서 Table 명을 like로 사용할 수 없는데, @Table(name = ""like"")를 통해 like로 설정
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Table(name = "\"like\"")
public class Like {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private User user; // 좋아요를 누른 유저
@ManyToOne(fetch = FetchType.LAZY)
private Board board; // 좋아요가 추가된 게시글
}
UploadImage
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
public class UploadImage {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String originalFilename; // 원본 파일명
private String savedFilename; // 서버에 저장된 파일명
@OneToOne(mappedBy = "uploadImage", fetch = FetchType.LAZY)
private Board board;
}
BaseEntity
- 하나의 객체가 DB에 저장될 때, 자동으로 createdAt, lastModifiedAt을 저장하기 위해 사용되는 Entity
- 아래의 JpaAuditingConfig를 등록하고, Board와 같이 상속시켜 사용
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime lastModifiedAt;
}
JpaAuditingConfig
@Configuration
@EnableJpaAuditing
public class JpaAuditingConfig {
}
반응형
'Spring Boot > 프로젝트' 카테고리의 다른 글
[Spring Boot] 게시판 만들기 5 - 댓글, 좋아요, 파일 업로드 관련 기능 (0) | 2023.04.17 |
---|---|
[Spring Boot] 게시판 만들기 4 - 게시판 기능 (0) | 2023.04.17 |
[Spring Boot] 게시판 만들기 3 - 유저 기능 (0) | 2023.04.17 |
[Spring Boot] 게시판 만들기 1 - 설계 & 결과 (2) | 2023.04.16 |
[Spring Boot] CRUD 게시판 (DB 사용 X, 회원 기능 X) (2) | 2022.05.11 |