학습을 하다보면 질문이 참 많다. 그 질문에 스스로 답을 찾아나가는 것만으로도 공부가 많이 된다. 그런데 과거에는 chatGPT가 없었고 그래서 스스로 질문에 답을 찾는데에 굉장히 많은 시간이 들었고, 심지어 끝끝내 찾지 못 하는 경우도 있었다. 그런데 chatGPT가 나오고 나서, 학습하는데에 굉장히 많은 효율이 발생한다. 질문에 답을 찾는데에도 시간이 현격히 감소했고 심지어 답을 찾지 못 하는 경우는 거의 없다고 봐야 한다. chatGPT 유료 버전을 꽤 초기부터 사용해왔던터라 무료 버전은 어떤지 잘 모르겠지만, 유료 버전은 정말 "Awesome!" 그 자체다.
여러 가지 분야들이 있겠지만, 개발 공부에 있어서는 정확도도 굉장히 높다는 생각이 든다. chatGPT와 MS의 관계는 굉장히 밀접한 관계이다. 그리고 MS는 github 를 가진 회사이다. 그래서 더더욱 개발 공부에 있어서 chatGPT의 도움을 굉장히 많이 받을 수 있었다. KES 박람회에 갔을 때, AI 엔지니어로 일하고 계신 어느 분과 대화할 수 있었다. 그 때 그분은 본인의 회사에는 chatGPT가 알려주는대로만 코드를 쓰는 직원이 있다고 했다. 그 뜻을 모르면서도 말이다. 그렇지만, 잘 작동하니 문제가 없다는 식의 태도를 보여서 짜증이 난다는 듯한 뉘앙스의 말을 해주었다. 적어도 어떤 방식으로 작동하는지는 이해하고 복사붙여넣기를 해도 해야 하는게 아니냐고 힘주어 말했었다. 그만큼 chatGPT를 활용해서 개발을 하고 있다는 점을 지적하고자 꺼낸 이야기다.
그러니, chatGPT를 잘 활용하는 것도 굉장히 중요한 역량이라고 생각한다. 그럴려면 질문이 있어야 한다. 의미있는 질문. 정말로 효용 가치를 만들어낼 수 있는 의미있는 질문 말이다. 어쨌든, 다시 학습을 이어나가보겠다.
질문 1. 굳이 originalFileName 과 fileName 을 구분 지은 이유를 모르겠어. 실효성이 별로 없어보인달까? 아무리 이름이 중복적이더라도, 굳이 랜덤 문자열 생성을 통해서 구분지을 필요가 있을까? 실제로 윈도우에서는 폴더 내에 동일한 이름의 파일이 들어오게 되면 파일 이름 끝에 (1) 이런식으로 붙여서 다른 파일로 만들어버릴 수도 있는거잖아. 왜 굳이 저렇게까지 하는걸까? 심지어 파일은 PK 인 id 에 의해서 구분도 지어질 수 있는데 말이야.
좋은 질문이에요! 왜 굳이 originalFileName과 fileName을 구분하고, 랜덤 문자열을 추가해 파일 이름을 관리하는지 자세히 설명드릴게요.
1. 문제 상황
윈도우나 다른 운영체제에서는 동일한 이름의 파일이 폴더 내에 들어오면 자동으로 (1) 같은 숫자를 붙여 이름을 바꿀 수 있습니다. 하지만 서버에서는 이런 자동 처리 기능이 없습니다. 만약 다음과 같은 상황이 발생한다면 문제가 생길 수 있습니다.
상황 1: 같은 이름의 파일 업로드
- 파일 이름이 동일하다면, 기존 파일이 덮어씌워질 가능성이 있습니다.
- 예: image.png 파일을 업로드한 후 동일한 이름의 파일을 다시 업로드하면, 기존 파일이 손실됩니다.
상황 2: 데이터베이스와 파일 시스템 간의 불일치
- 데이터베이스는 고유한 id로 파일을 구분할 수 있지만, 파일 시스템은 이름으로 파일을 구분합니다.
- 두 파일 이름이 같으면 데이터베이스와 파일 시스템의 매칭에 문제가 생깁니다.
2. 왜 랜덤 문자열을 추가할까?
목적 1: 파일 이름의 고유성 보장
- 랜덤 문자열을 추가하면 서버 파일 시스템에서 중복 이름으로 인한 문제를 완전히 방지할 수 있습니다.
- 예: image.png → abc123image.png
- 이는 특히 다수의 사용자가 같은 이름의 파일을 업로드하는 환경에서 매우 중요합니다.
목적 2: 원본 파일 이름 보존
- originalFileName을 유지하면 파일의 원래 이름을 데이터베이스에 저장하고, 사용자에게 보여줄 수 있습니다.
- 예: 사용자가 업로드한 파일 이름: my_photo.png
- 서버 내부 파일 이름: xyz789my_photo.png
- 이렇게 하면 사용자에게는 친숙한 원래 이름을 보여줄 수 있고, 서버에서는 고유성을 유지할 수 있습니다.
목적 3: 파일 이름 충돌 방지
- 파일 이름을 자동으로 (1) 같은 숫자를 붙여 구분하는 것도 가능하지만, 이 방식은 복잡성을 추가합니다.
- 기존 파일 이름을 확인하고 숫자를 추가하는 로직을 별도로 작성해야 합니다.
- 랜덤 문자열 추가 방식은 훨씬 간단하고 안전한 방법입니다.
3. PK로만 구분하면 안 될까?
데이터베이스의 PK(ID)로만 구분한다고 해도, 파일 시스템에는 여전히 파일 이름이 필요합니다. 아래와 같은 문제가 있을 수 있습니다:
문제 1: 같은 ID에서 여러 파일 업로드
- 책 ID(book_id)가 동일한 경우:
- 예: 123번 책에 cover.png와 thumbnail.png를 업로드.
- 같은 폴더에 저장될 때 파일 이름이 중복될 수 있습니다.
문제 2: 파일 시스템의 한계
- 파일 이름은 데이터베이스의 PK와 독립적으로 작동합니다.
- 파일 시스템은 PK를 인식하지 못하므로 이름 충돌 방지를 위해 추가적인 고유성을 부여해야 합니다.
4. 현실 세계의 예
다른 서비스에서도 유사한 방식으로 고유성을 보장합니다:
- Google Drive: 내부적으로 파일 이름에 고유 ID를 부여하지만, 사용자에게는 원래 이름을 보여줍니다.
- AWS S3: 파일 이름에 고유 키(랜덤 문자열 또는 UUID)를 사용합니다.
5. 결론
랜덤 문자열을 추가하는 것은:
- 파일 이름의 고유성 보장: 서버에서 파일이 덮어씌워지지 않도록.
- 원본 이름 보존: 사용자 경험을 위해.
- 간결성과 안전성 확보: 파일 시스템과 데이터베이스의 불일치를 방지.
이런 이유로 파일 이름에 랜덤 문자열을 추가하는 것이 실무적으로 매우 유용합니다. 😊
질문 2. @OneToMany, @ManyToOne 이런식으로 관계 설정을 하는 것에 대해서 이해는 하고 있어. 그런데, 만약에 BookImage 가 Book book 의 id 가 2번인 book 과 연결이 되어 있는 bookImage 들이 있다고 해보자고. 그러면, BookImage 라는 엔티티는 getter, setter 어노테이션이 붙어있는데, bookImage 에서 book 정보도 getter, setter 할 수도 있는건가? 아니면 그냥 관계 설정만 되어 있는건가? 값 변경과 같은 설정에 영향을 주는건 할 수 없더라도 book 의 PK, bookImage의 FK 값의 정보만 getter 로 가져오는게 아니라 book의 다른 여러 정보도 가지고 올 수는 있는건가? 예를 들어서, bookImage.getBook().getPrice() 이런식으로 가능한건가?
좋은 질문이에요! @OneToMany와 @ManyToOne 관계를 설정했을 때, 관계 엔터티에서 어떤 데이터를 가져올 수 있는지, 그리고 데이터의 변경이 가능한지 차근차근 설명드릴게요.
1. 관계 설정의 기본
@ManyToOne과 @OneToMany는 JPA에서 두 엔터티 간의 관계를 정의할 때 사용됩니다.
- @ManyToOne: 다수의 BookImage가 하나의 Book과 연결됩니다.
- @OneToMany: 하나의 Book이 여러 개의 BookImage와 연결됩니다.
2. 관계 설정의 실제 동작
BookImage 엔티티
@ManyToOne
@JoinColumn(name = "book_id")
private Book book;
- 이 설정은 BookImage 엔티티에서 Book 엔티티를 참조할 수 있게 합니다.
- book_id는 데이터베이스의 외래 키(FK)이며, 이를 통해 Book 엔티티의 데이터를 가져올 수 있습니다.
getter/setter를 통한 데이터 접근
JPA 관계 설정이 되어 있다면, BookImage 객체에서 book 객체에 접근할 수 있습니다:
bookImage.getBook();
결과
- getBook()은 Book 객체 전체를 반환합니다. 따라서, Book의 모든 필드와 메서드에 접근할 수 있습니다.
- 예: bookImage.getBook().getPrice()
- 예: bookImage.getBook().getAuthor()
값 변경
- BookImage의 book 필드는 setter를 통해 다른 Book 객체로 변경할 수도 있습니다:
Book newBook = new Book();
bookImage.setBook(newBook);
3. 관계 설정의 지연 로딩과 즉시 로딩
JPA에서는 관계 설정에 따라 데이터를 가져오는 방식이 달라집니다. BookImage에서 Book 객체에 접근할 때, 설정된 로딩 전략에 따라 동작이 달라집니다.
(1) 지연 로딩(LAZY)
- @ManyToOne(fetch = FetchType.LAZY) (기본값)
- Book 객체에 접근할 때만 데이터베이스에서 추가로 Book 데이터를 조회합니다.
Book book = bookImage.getBook(); // 이 시점에 SELECT 쿼리가 실행됩니다.
(2) 즉시 로딩(EAGER)
- @ManyToOne(fetch = FetchType.EAGER)
- BookImage를 조회할 때, Book 데이터도 즉시 함께 조회합니다.
List<BookImage> bookImages = bookImageRepository.findAll();
// 이 시점에 BookImage와 Book이 JOIN된 SELECT 쿼리가 실행됩니다.
4. 실무적인 사용
getBook()의 활용
BookImage에서 getBook()을 통해 Book 객체에 접근하면:
- Book 객체의 모든 정보에 접근할 수 있습니다.
- 예를 들어, bookImage.getBook().getPrice()를 호출하면, 연결된 책의 가격 정보도 가져올 수 있습니다.
값 변경
BookImage의 book 필드는 setter를 통해 값을 변경할 수 있습니다. 이를 통해 BookImage를 다른 Book 객체와 연결할 수도 있습니다:
Book newBook = bookRepository.findById(3L).get();
bookImage.setBook(newBook); // 기존 book 대신 새 book 객체와 연결
bookImageRepository.save(bookImage); // 변경된 정보 저장
5. 데이터베이스 관점에서의 동작
- 데이터 조회:
- bookImage.getBook() 호출 시, book_id를 이용해 Book 데이터를 가져옵니다.
- SQL:
SELECT * FROM book WHERE id = {book_id};
- 값 변경:
- bookImage.setBook(newBook)를 호출하면, BookImage의 book_id 값이 업데이트됩니다.
- SQL:
UPDATE book_image SET book_id = {newBook.id} WHERE id = {bookImage.id};
6. 요약
- getter로 BookImage에서 연결된 Book 객체를 가져올 수 있습니다.
- Book 객체의 모든 정보(price, author 등)에 접근 가능합니다.
- setter를 통해 BookImage를 다른 Book 객체와 연결할 수도 있습니다.
- 주의: 지연 로딩(LAZY)과 즉시 로딩(EAGER)에 따라 데이터베이스 쿼리 동작이 달라집니다.
이제 bookImage.getBook().getPrice() 같은 접근 방식이 어떻게 가능한지 이해가 되셨나요? 😊
지연 로딩(LAZY)와 즉시 로딩(EAGER)에 대해 초보자의 눈높이에 맞춰 자세히 설명드릴게요! 이 두 가지는 연관 관계를 설정한 엔티티 데이터를 언제 데이터베이스에서 불러올지를 결정하는 로딩 전략입니다.
1. 로딩 전략이란?
JPA에서 연관된 엔티티 데이터를 가져오는 시점을 설정하는 방법입니다.
비유
- 즉시 로딩(EAGER): 음식을 주문하면, 아직 먹을 준비가 되지 않았더라도 요리사(데이터베이스)가 미리 모든 메뉴를 만들어서 테이블에 가져다 놓습니다.
- 지연 로딩(LAZY): 음식을 주문하면, 정말로 먹겠다고 요청할 때마다 요리사가 하나씩 요리를 가져다 줍니다.
2. 즉시 로딩(EAGER)
특징
- 엔티티를 조회할 때, 연관된 엔티티 데이터도 즉시 함께 조회합니다.
- 즉, 데이터베이스에서 연관된 데이터를 한꺼번에 가져옵니다.
코드 예제
@Entity
public class BookImage {
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "book_id")
private Book book;
}
동작
BookImage bookImage = bookImageRepository.findById(1L).get();
- bookImage를 조회하는 순간, Book 데이터도 함께 조회됩니다.
- 실행되는 SQL:
SELECT * FROM book_image bi
JOIN book b ON bi.book_id = b.id
WHERE bi.id = 1;
- bookImage와 연관된 Book 객체가 즉시 로드됩니다.
장점
- 필요한 데이터를 한 번에 가져와서, 추가적인 데이터베이스 쿼리가 발생하지 않습니다.
단점
- 불필요한 데이터를 미리 가져오므로, 메모리 낭비가 발생할 수 있습니다.
- 연관된 데이터가 많을 경우 성능이 저하될 가능성이 있습니다.
3. 지연 로딩(LAZY)
특징
- 연관된 엔티티 데이터를 실제로 사용할 때까지 데이터베이스 조회를 미룹니다.
- 즉, BookImage만 먼저 가져오고, Book 데이터는 필요할 때 추가로 가져옵니다.
코드 예제
@Entity
public class BookImage {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "book_id")
private Book book;
}
동작
BookImage bookImage = bookImageRepository.findById(1L).get();
// bookImage.getBook()을 호출할 때 데이터베이스 조회 발생
Book book = bookImage.getBook();
- 첫 번째 쿼리: BookImage만 조회.
SELECT * FROM book_image WHERE id = 1;
- 두 번째 쿼리: getBook() 호출 시 Book 데이터를 조회.
SELECT * FROM book WHERE id = {book_id};
장점
- 처음에는 꼭 필요한 데이터만 가져와 성능과 메모리 사용량을 줄일 수 있습니다.
- 연관된 데이터를 반드시 필요할 때만 가져옵니다.
단점
- 연관 데이터를 사용할 때마다 추가적인 데이터베이스 쿼리가 발생할 수 있습니다.
- 여러 번의 쿼리로 인해 성능이 저하될 가능성이 있습니다.
4. 언제 사용해야 할까?
즉시 로딩(EAGER)을 사용하는 경우
- 연관된 데이터를 항상 사용할 것이 명확한 경우.
- 예: BookImage를 조회할 때, 반드시 Book 데이터를 같이 필요로 한다면 즉시 로딩이 적합합니다.
지연 로딩(LAZY)을 사용하는 경우
- 연관된 데이터를 사용할지 여부가 불확실한 경우.
- 예: BookImage만 조회할 일이 많고, Book 데이터는 가끔 필요할 때.
5. 실무에서의 주의점
1) N+1 문제
지연 로딩 사용 시, 연관 데이터를 반복적으로 조회하면 다수의 쿼리가 발생할 수 있습니다.
예시
List<BookImage> bookImages = bookImageRepository.findAll();
for (BookImage bookImage : bookImages) {
System.out.println(bookImage.getBook().getPrice());
}
- 실행되는 SQL:
- 1번: BookImage 목록 조회 쿼리.
- N번: 각 BookImage의 Book 데이터를 조회하는 쿼리.
해결 방법
- @EntityGraph나 join fetch를 사용해 연관 데이터를 한 번에 가져옵니다:
@Query("SELECT bi FROM BookImage bi JOIN FETCH bi.book")
List<BookImage> findAllWithBooks();
2) 로딩 전략의 과도한 사용
즉시 로딩을 남용하면 메모리 낭비와 성능 저하가 발생할 수 있습니다. 기본적으로 지연 로딩을 사용하고, 꼭 필요한 경우에만 즉시 로딩으로 설정하세요.
6. 요약
로딩 전략 | 특징 | 장점 | 단점 |
즉시 로딩(EAGER) | 데이터를 한 번에 조회 | 추가 쿼리 없음 | 불필요한 데이터 로드 |
지연 로딩(LAZY) | 필요한 순간에 조회 | 효율적인 초기 조회 | 추가 쿼리 발생 가능 |
실무에서는 **기본적으로 지연 로딩(LAZY)**를 사용하고, 최적화가 필요한 부분만 **즉시 로딩(EAGER)**로 바꿉니다. 😊
그러면 프로그램에서는 default 로 지연 로딩으로 설정이 되어 있는건가?
아무것도 입력하지 않으면 어떻게 설정이 되어 있는거야?
네, 프로그램에서 기본적으로 JPA는 **지연 로딩(LAZY)**으로 설정되어 있습니다.
하지만 이는 관계의 유형에 따라 기본 설정이 다릅니다. 각각의 경우를 살펴볼게요.
1. 기본 로딩 전략
(1) @OneToMany와 @ManyToMany
- 기본값: 지연 로딩(LAZY)
- 이유: @OneToMany나 @ManyToMany는 연관된 데이터가 많을 가능성이 큽니다. 만약 즉시 로딩으로 설정하면, 불필요한 데이터까지 모두 로드되어 성능 저하를 초래할 수 있습니다.
@OneToMany(mappedBy = "book", cascade = CascadeType.ALL)
private List<BookImage> bookImages;
// 기본값: LAZY
(2) @ManyToOne와 @OneToOne
- 기본값: 즉시 로딩(EAGER)
- 이유: 일반적으로 @ManyToOne이나 @OneToOne은 연관된 데이터가 많지 않으므로, 즉시 로딩으로 설정해도 큰 성능 문제가 없다고 간주됩니다. 또한, 이러한 연관 데이터는 자주 사용될 가능성이 큽니다.
@ManyToOne
@JoinColumn(name = "book_id")
private Book book;
// 기본값: EAGER
2. 아무 설정도 하지 않으면?
- JPA는 관계의 유형에 따라 기본 로딩 전략을 적용합니다.
- 기본값을 명시적으로 지정하지 않아도 다음처럼 동작합니다:
- @OneToMany와 @ManyToMany: 지연 로딩(LAZY)
- @ManyToOne와 @OneToOne: 즉시 로딩(EAGER)
권장 사항
기본값에 의존하기보다는 명시적으로 로딩 전략을 지정하는 것이 좋습니다. 이렇게 하면 코드의 동작이 더 명확해지고, 유지보수성이 높아집니다.
예:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "book_id")
private Book book;
@OneToMany(mappedBy = "book", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<BookImage> bookImages;
3. 로딩 전략을 명시적으로 지정하는 이유
기본값에 의존하지 않고 명시적으로 로딩 전략을 설정하는 이유는 다음과 같습니다:
- 코드의 가독성:
- 다른 개발자가 기본값을 기억하지 못하더라도 코드를 보고 로딩 전략을 알 수 있습니다.
- 예측 가능한 동작:
- 기본값을 사용하면 예상치 못한 쿼리가 실행될 수 있습니다. 명시적으로 설정하면 이러한 문제를 방지할 수 있습니다.
- 성능 최적화:
- 프로젝트의 요구사항에 따라 적절한 로딩 전략을 설정해야 성능을 최적화할 수 있습니다.
4. 요약
- 기본값:
- @OneToMany와 @ManyToMany: 지연 로딩(LAZY).
- @ManyToOne와 @OneToOne: 즉시 로딩(EAGER).
- 권장 사항:
- 기본값에 의존하지 말고 명시적으로 설정하세요.
- 일반적으로:
- @OneToMany, @ManyToMany: fetch = FetchType.LAZY.
- @ManyToOne, @OneToOne: 필요에 따라 fetch = FetchType.LAZY 또는 EAGER.
이렇게 하면 코드의 동작을 명확하게 이해하고 제어할 수 있습니다. 😊
질문 3. 질문 2번의 연장되는 질문이긴 한데, Book 엔티티에서는 @OneToMany 하고 나서, private List<BookImage> bookImages; 라고 해서 연결된 관계를 설정해줬어. 그러면 book에 연결된 bookimage의 리스트 정보를 가지고 올 수 있는 getter도 만들어지는건가? 그러니까 Book table 에만 column 으로 List<BookImage>가 만들어지지는 않지만 그래도 데이터에 접근하려고 하면 접근할 수는 있는건가? 그리고 setter로 데이터 변경도 할 수 있는건가?
좋은 질문이에요! Book 엔터티에서 @OneToMany를 사용해 List<BookImage>와 연결한 경우, 해당 리스트에 접근하고 수정할 수 있는지에 대해 자세히 설명드릴게요.
1. Book 엔터티와 관계 설정
Book 엔터티에서 @OneToMany 관계를 설정하면, Book과 BookImage 간의 연결이 데이터베이스 수준에서 정의됩니다.
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "book", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<BookImage> bookImages;
// Getter와 Setter (Lombok 사용 가능)
}
(1) List<BookImage>는 데이터베이스에 컬럼으로 저장되지 않음
- List<BookImage>는 Book 엔터티의 속성으로 존재하지만, 데이터베이스 테이블(Book)에는 이 리스트가 컬럼으로 저장되지 않습니다.
- 대신, BookImage 테이블에 book_id라는 외래 키(FK)가 추가되어 Book과의 관계를 나타냅니다.
(2) Getter를 통한 데이터 접근
List<BookImage>에 접근할 수 있는 Getter가 만들어집니다.
- 예를 들어:
Book book = bookRepository.findById(1L).get();
List<BookImage> bookImages = book.getBookImages(); // book에 연결된 모든 bookImage 가져오기
데이터베이스에서 동작
- fetch = FetchType.LAZY인 경우:
- book.getBookImages()를 호출할 때 BookImage 데이터를 조회하는 추가 SQL 쿼리가 실행됩니다.
SELECT * FROM book_image WHERE book_id = 1;
- fetch = FetchType.EAGER인 경우:
- Book 데이터를 조회할 때 BookImage 데이터도 함께 가져옵니다.
SELECT * FROM book b
JOIN book_image bi ON b.id = bi.book_id
WHERE b.id = 1;
(3) Setter를 통한 데이터 변경
Book 객체의 bookImages 리스트를 변경하거나, 새로운 데이터를 추가할 수도 있습니다.
새로운 BookImage 추가
Book book = bookRepository.findById(1L).get();
BookImage newImage = new BookImage();
newImage.setFileName("example.png");
newImage.setBook(book); // 관계 설정
book.getBookImages().add(newImage); // 리스트에 추가
bookRepository.save(book); // 저장
데이터베이스에서 동작
- CascadeType.ALL이 설정되어 있다면, bookRepository.save(book) 호출 시 newImage도 자동으로 저장됩니다.
- 실행되는 SQL:
- BookImage 삽입:
INSERT INTO book_image (file_name, book_id) VALUES ('example.png', 1);
(4) Setter로 전체 리스트 변경
List<BookImage>를 새로 할당하는 것도 가능합니다:
Book book = bookRepository.findById(1L).get();
List<BookImage> newImages = new ArrayList<>();
BookImage newImage1 = new BookImage();
newImage1.setFileName("image1.png");
newImage1.setBook(book);
BookImage newImage2 = new BookImage();
newImage2.setFileName("image2.png");
newImage2.setBook(book);
newImages.add(newImage1);
newImages.add(newImage2);
book.setBookImages(newImages); // 리스트 전체 교체
bookRepository.save(book);
주의
- 리스트를 교체하면 기존 데이터(BookImage)가 제거되지 않으므로, 필요하다면 수동으로 삭제하거나, orphanRemoval = true를 설정해야 합니다:
@OneToMany(mappedBy = "book", cascade = CascadeType.ALL, orphanRemoval = true)
private List<BookImage> bookImages;
2. 요약
- List<BookImage>는 컬럼으로 저장되지 않음
- BookImage 테이블의 book_id 외래 키로 관계가 관리됩니다.
- Getter로 데이터 접근 가능
- book.getBookImages()를 호출해 Book과 연결된 모든 BookImage 데이터를 가져올 수 있습니다.
- 로딩 전략(LAZY/EAGER)에 따라 데이터베이스 조회 시점이 달라집니다.
- Setter로 데이터 변경 가능
- 리스트에 새로운 BookImage를 추가하거나, 전체 리스트를 교체할 수 있습니다.
- CascadeType 설정에 따라 부모(Book)를 저장하면 관련된 자식(BookImage)도 자동으로 저장됩니다.
이제 관계 설정을 통한 데이터 접근과 변경이 어떻게 동작하는지 감이 좀 잡히셨나요? 😊
질문이 엄청 많아서, 다음 게시글로 넘어가서 계속 이어나가겠다.
'Spring Boot (+ RESTful)' 카테고리의 다른 글
RESTful 웹 서비스 구축 - # Project 04 - 이미지 업로드 (5) (0) | 2025.01.07 |
---|---|
RESTful 웹 서비스 구축 - # Project 04 - 이미지 업로드 (4) (0) | 2025.01.06 |
RESTful 웹 서비스 구축 - # Project 04 - 이미지 업로드 (2) (0) | 2025.01.04 |
RESTful 웹 서비스 구축 - # Project 04 - 이미지 업로드 (1) (0) | 2025.01.03 |
RESTful 웹 서비스 구축 - @ControllerAdvice / @ExceptionHandler (1) | 2025.01.02 |