๐ํ๊ฒฝ
IntelliJ Ultimate
Java 17
Spring boot 3.2.3
Gradle - Groovy
Dependencies:
Spring Web
Thymeleaf
Spring Data JPA
lombok
MariaDB 10.11
Spring Dev tool
๐๋ฌธ์
๊ฒ์๊ธ DTO์ธ `BoardDTO`๋ฅผ DB์ ์ ์ฅํ๊ธฐ ์ํด ์ํฐํฐ์ธ `Board`์ `BoardImage`๋ก ๋ณํํ๊ณ ์ ํ๋ค.
`BoardDTO`๋ ํ์ผ ์ด๋ฆ `String`์ด ๋ด๊ธด `List`๋ฅผ ํ๋๋ก ๊ฐ์ง๋ค.
`BoardImage`๋ ๊ฐ๊ฐ uuid, ์๋ณธ ํ์ผ๋ช , ์์์ธ `uuid`, `fileName`, `ord`๋ฅผ ํ๋๋ก ๊ฐ์ง๋ฉฐ ์์ ์ํฐํฐ์ธ `Board`๋ฅผ ์ฐธ์กฐํ๋ค. ์ด๋ฏธ์ง๊ฐ ํฌํจ๋ ๊ธ ์์ฑ ์ `BoardDTO`๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์์ ๋ DB์ ์ ์ฅํ๊ธฐ ์ํ์ฌ `BoardDTO`์ `List`์ ๋ด๊ธด `String`์ ์ํฐํฐ์ธ `Board`์ ๊ทธ ํ์ ์ํฐํฐ์ธ `BoardImage`๋ก ๋ณํํ๋ ๊ณผ์ ์์, `BoardDTO`๊ฐ `uuid_์๋ณธํ์ผ๋ช .ํ์ฅ์๋ช `์ ํํ๋ก `String`์ ๊ฐ์ ธ์์ ๋ `uuid`์ `fileName`์ผ๋ก ๋๋์ด์ง ํ๋๋ก ๋ฐ๋ก ์ ์ฅํ๋ ๋ก์ง์ ์์ฑํ๋ค.
์ด๋ ํ์ผ๋ช ์ `split("_")`์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ ํ์ผ๋ช ์ `_`๊ฐ ํฌํจ๋์ด ์์ผ๋ฉด ์ฌ๋ฐ๋ฅด์ง ์์ ํ์ผ๋ช ์ด ์ ์ฅ๋๋ ํ์์ด ๋ฐ์ํ์ฌ ํด๊ฒฐํ๊ณ ์ ํ๋ค.
// ์ด๋ฏธ์ง ํ์ผ๋ช
List๋ฅผ ๊ฐ์ง๊ณ ์๋ ๊ฒ์๊ธ DTO
// BoardDTO
package org.zerock.b02.dto;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class BoardDTO {
private Long bno;
@NotEmpty
@Size(min = 3, max = 100)
private String title;
@NotEmpty
private String content;
@NotEmpty
private String writer;
private LocalDateTime regDate;
private LocalDateTime modDate;
// ์ฒจ๋ถ ํ์ผ ์ด๋ฆ ๋ชฉ๋ก
private List<String> fileNames;
}
// Board
package org.zerock.b02.domain;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.BatchSize;
import java.util.HashSet;
import java.util.Set;
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
@ToString(exclude = "imageSet")
public class Board extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // Auto Increment
private Long bno;
@Column(length = 500, nullable = false)
private String title;
@Column(length = 2000, nullable = false)
private String content;
@Column(length = 50, nullable = false)
private String writer;
// ์ํฐํฐ ๊ฐ์ฒด๋ ๋ถ๋ณํ๋๋ก ์ค๊ณํ๋ ๊ฒ์ด ์ข์ผ๋, ๊ฐ์ ์ ์ธ ์ฌํญ์ ์๋๋ฏ๋ก ์์ ์ด ํ์ํ๋ค๋ฉด ์์ ์ด ๊ฐ๋ฅํ ๋ถ๋ถ๋ง ๋ฉ์๋๋ก ์ค๊ณํ๋ค
public void update(String title, String content){
this.title = title;
this.content = content;
}
@OneToMany(mappedBy = "board", cascade = {CascadeType.ALL}, fetch = FetchType.LAZY, orphanRemoval = true)
@Builder.Default
@BatchSize(size = 20) // BatchSize ๊ฐ ์์ผ๋ฉด ํ์ด์ง ํ๋๋ฅผ ๋ถ๋ฌ์ฌ ๋ ํ์ด์ง ๋น ๊ฒ์๊ธ ๊ฐ์ ๋งํผ์ ์ฟผ๋ฆฌ๋ฅผ ์คํํด์ ๋ฌธ์ ๊ฐ ๋จ. ์ด N๋ฒ์ ์ฟผ๋ฆฌ๋ฅผ ํ๋ฒ์ ์คํํ ์ ์๊ฒ ํด์ค๋ค.
private Set<BoardImage> imageSet = new HashSet<>();
// Board ๊ฐ์ฒด ๋ด์์ BoardImage๋ฅผ ๋ชจ๋ ๊ด๋ฆฌํจ.
public void addImage(String uuid, String fileName){
BoardImage boardImage = BoardImage.builder()
.uuid(uuid)
.fileName(fileName)
.board(this)
.ord(imageSet.size())
.build();
imageSet.add(boardImage);
}
// BoardImage์ Board ์ฐธ์กฐ๋ฅผ null๋ก ๋ณ๊ฒฝํจ
public void clearImage(){
imageSet.forEach(boardImage -> {
boardImage.changeBoard(null);
});
this.imageSet.clear();
}
}
// ๊ฒ์๊ธ์ ํฌํจ๋๋ ์ด๋ฏธ์ง uuid, ์๋ณธ ํ์ผ๋ช
, ์์, ์์ ์ํฐํฐ์ธ Board๋ฅผ ์ฐธ์กฐํจ
// BoardImage
package org.zerock.b02.domain;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import lombok.*;
@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class BoardImage implements Comparable<BoardImage>{
@Id
private String uuid;
private String fileName;
private int ord;
@ManyToOne
private Board board;
@Override
public int compareTo(BoardImage other) {
return this.ord - other.ord;
}
public void changeBoard(Board board){
this.board = board;
}
}
// boardService ๋ด dtoToEntity ๋ฉ์๋์ ์ผ๋ถ
boardDTO.getFileNames().forEach(fileNames -> {
String[] arr = fileNames.split("_");
System.out.println(Arrays.toString(arr));
board.addImage(arr[0], arr[1]);
});
// dtoToEntity Test
@Test
public void dtoToEntityTest(){
String[] strs = {"thisisuuid_aaa_a_.jpg", "thisisuuid_abc.jpg"};
List<String> fileNames = new ArrayList<>(List.of(strs));
BoardDTO boardDTO = BoardDTO.builder()
.title("test title")
.content("test content")
.writer("test writer")
.fileNames(fileNames)
.build();
Board board = boardService.dtoToEntity(boardDTO);
log.info("dtoToEntity " + board);
log.info("board imageSet " + board.getImageSet());
}
board imageSet [BoardImage(uuid=thisisuuid, fileName=abc.jpg, ord=1, board=Board(bno=null, title=test title, content=test content, writer=test writer)),
BoardImage(uuid=thisisuuid, fileName=aaa, ord=0, board=Board(bno=null, title=test title, content=test content, writer=test writer))]
`fileName`์ ์๋ณธ ํ์ผ๋ช ์ธ `aaa_a_.jpg`์ `abc.jpg`๊ฐ ๋ด๊ฒจ์ผํ๋๋ฐ `aaa`์ `abc.jpg`๊ฐ ์ ์ฅ๋์ด์๋ค. `_`๊ฐ ํฌํจ๋ ํ์ผ์ด๋ฆ์ ๊ฒฝ์ฐ ํ์ผ๋ช ์ด ์จ์ ์น ๋ชปํ๊ฒ ์๋ ค ์ฌ๋ฐ๋ฅด์ง ์์ ๊ฐ์ด ์ ์ฅ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
์๋ฅผ ๋ค์ด `uuid_aaa_a_.jpg`๋ `_`๋ก splitํ ๊ฒฝ์ฐ `uuid` `aaa` `a` `.jpg`๋ก ์๋ฆฌ๊ฒ ๋๋ค. `addImage()`๋ฉ์๋์ `_`๋ก ๋๋ ๊ฐ์ ์ฒซ๋ฒ์งธ String๊ณผ ๋๋ฒ์งธ String์ ์ธ์๋ก ๋ฃ์ด์ฃผ๊ธฐ ๋๋ฌธ์ `BoardImage`์ `uuid`์ `fileName`์ `uuid`์ `aaa`๊ฐ ๋๋ค.
๐ํด๊ฒฐ
๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ ์์ธ์ ์๋ณธ ํ์ผ๋ช ์ `_`๊ฐ ์๋ค๋ ๊ฐ์ ํ์ String์ `_`๋ก ๋๋๋ ๋ก์ง์ ์์ฑํ๊ธฐ ๋๋ฌธ์ด๋ค. ์ ์ ๊ฐ ์ ๋ก๋ํ๋ ํ์ผ๋ช ์ `_`๊ฐ ํฌํจ๋ ๊ฐ๋ฅ์ฑ์ด ๋์ผ๋ฏ๋ก ์ด๋ฅผ ๋ณด์ํ๊ธฐ ์ํด ์ฒซ๋ฒ์งธ `_`๊ฐ ์์๋๋ ์ธ๋ฑ์ค๋ฅผ ์ฐพ์ ๊ทธ๋ฅผ ๊ธฐ์ค์ผ๋ก ํ์ผ๋ช ์ ์๋ฅด๊ธฐ๋ก ํ๋ค.
// BoardService ๋ด dtoToEntity์ ์ผ๋ถ
// "_"๋ก ๋๋๊ฒ ๋๋ฉด "_"๊ฐ ์ฌ๋ฌ ๊ฐ ํฌํจ๋ ์ด๋ฏธ์ง์ผ ๊ฒฝ์ฐ ์ฌ๋ฐ๋ฅด์ง ์์ ํ์ผ๋ช
์ด ์ ์ฅ๋๋ ์ค๋ฅ ๋ฐ์
// String[] arr = fileNames.split("_");
// ๋งจ ์ฒ์์ ์ค๋ "_"์ ์ธ๋ฑ์ค๋ฅผ ์ฐพ์๋ด ๊ทธ ์ธ๋ฑ์ค๋ฅผ ๊ธฐ์ค์ผ๋ก String ์ ์๋ผ๋ธ๋ค.
int start = fileNames.indexOf("_");
String[] arr = {fileNames.substring(0, start), fileNames.substring(start + 1)};
System.out.println(Arrays.toString(arr));
board.addImage(arr[0], arr[1]);
ํ ์คํธ ์ฝ๋๋ ์์ ๋์ผํ๋ฏ๋ก ์๋ตํ๋ค.
board imageSet [BoardImage(uuid=thisisuuid, fileName=abc.jpg, ord=1, board=Board(bno=null, title=test title, content=test content, writer=test writer)),
BoardImage(uuid=thisisuuid, fileName=aaa_a_.jpg, ord=0, board=Board(bno=null, title=test title, content=test content, writer=test writer))]
์๋ณธ ํ์ผ ๊ฐ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ ๋ ฅ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.