인프런 커뮤니티 질문&답변

kim님의 프로필 이미지

작성한 질문수

실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발

상품 수정

참조에 관해 질문드립니다

해결된 질문

19.10.11 08:58 작성

·

172

1

A->B->C 로 참조가 되어 있어서 A를 지우려면 B를 먼저 삭제하고 B를 지우려면 C가 먼저 삭제되어져야 한다고 했을 때, 만약 C가 A를 참조하게 될 경우 싸이클이 생겨서 서로 삭제가 안되는 문제가 생기는데인.

웹던이나 DB단에서 c->a로 참조를 걸려할 때 잘못된 것으로 인지하고 막고 싶은데. 보통 이럴때는 어떤식으로 해결을 해야하나요?

답변 7

4

김영한님의 프로필 이미지
김영한
지식공유자

2019. 10. 12. 16:49

안녕하세요. kim님

정상 동작하는 예제가 아니어서 아쉬움이 있지만, 대략적인 질문의 의도를 파악했습니다.

저도 과거에 비슷한 문제를 해결해야 하는 경우가 있었는데요. 저는 rootId라는 추가 필드를 만들어서 A,B,C,D 모두에 함께 저장했습니다. 예를 들어서 A가 루트면 A의 아이디를 A,B,C,D 모두에 rootId라는 개념으로 저장하는 것이지요.(A,B,C,D에 rootId 필드가 있고, 모두 A 값이 들어갑니다.) 그러면 데이터베이스에서 조회할 때  rootId를 기준으로 조회하면 A와 관련있는 A,B,C,D를 모두 조회할 수 있습니다. 이런 방식을 조금 응용하시면 상황에 맞는 해결책을 찾을 수 있을 거에요^^

그리고 가격은 소수점까지 고려해야 하면 BigDecimal을 사용하시는게 가장 좋습니다. 실무에서 돈을 계산할 때는 소수점 아래를 반올림 할지, 내림할지 등등 다양한 정책을 고려해야 하기 때문입니다^^

그럼 좋은하루 되세요~

1

kim님의 프로필 이미지
kim
질문자

2019. 10. 12. 18:05

답변감사합니다. D에서 루트아이디를 찾고 루트아이디를 모두 갖고 있는 데이터를 찾아서 그 데이터들과는 참조를 못하게 하면 되겠네요 항상 많은 것을 배워갑니다 ^^

0

kim님의 프로필 이미지
kim
질문자

2019. 10. 12. 12:41


import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Getter
@Setter
@Entity
public class Book {

@Id
@GeneratedValue
@Column(name = "book_id")
private Long id;

private String name;

private Double price;

@OneToMany(mappedBy ="book", cascade = CascadeType.ALL)
private List<BookReference> bookReferences = new ArrayList<>();

public void addBookReference(BookReference reference){
bookReferences.add(reference);
reference.updateBook(this);
}
}


import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;

@Getter
@Setter
@Entity
public class BookReference {

@Id
@GeneratedValue
@Column(name = "bookReference_id")
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "book_id")
private Book book;

private Long refId;

public void updateBook(Book book) {
this.book=book;
}
}


import com.example.demo.domain.entity.Book;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BookRepository extends JpaRepository<Book, Long> {
}


import com.example.demo.domain.entity.BookReference;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface BookReferenceRepository extends JpaRepository<BookReference, Long> {

@Query("select br from BookReference br where br.book.id = :id")
List<BookReference> findListById(@Param("id")Long id);
}



import com.example.demo.domain.repository.BookReferenceRepository;
import com.example.demo.domain.repository.BookRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;
import java.util.Optional;

import static org.junit.Assert.*;

@RunWith(SpringRunner.class)
@SpringBootTest
public class BookTest {

@Autowired
BookRepository bookRepository;

@Autowired
BookReferenceRepository bookReferenceRepository;

@Test
public void 참조값_확인하기(){


Book book1 = new Book();
book1.setName("book1");
BookReference bookReference1 = new BookReference();
bookReference1.setRefId(2L);
book1.addBookReference(bookReference1);

Book book2 = new Book();
book2.setName("book2");
BookReference bookReference2 = new BookReference();
bookReference2.setRefId(3L);
book2.addBookReference(bookReference2);

Book book3 = new Book();
book1.setName("book3");
BookReference bookReference3 = new BookReference();
bookReference3.setRefId(4L);
book3.addBookReference(bookReference3);

Book book4 = new Book();
book1.setName("book4");

//todo A->B->C->D로 참조 하게 함
//todo D는 A, B, C를 참조하지 않게 해야 함
Long id1 = bookRepository.save(book1).getId();
bookRepository.save(book2);
bookRepository.save(book3);
bookRepository.save(book4);



//todo 리스트로 받은 이유는 여러곳에서 하나를 참조할 수도 있기 때문입니다.
List<BookReference> bookReferences= bookReferenceRepository.findListById(id1);//todo 1

//todo 이런 식으로 상위 참조를 찾아가면서 반복적으로 맨위까지 거슬러 올라가야함
for(BookReference bookReference : bookReferences){
bookReferenceRepository.findListById(bookReference.getBook().getId()); //todo 2
}


}
}
1번을 시작으로 2을 실행하고 최상위까지 올라갈떄까지 2번을 계속 실행을 해야하는데 이러면 sql실행을 많이 실행해야하는 비효율성이 
생길것 같아서 어덯게 효율적으로 방지할 수 있는지 궁금합니다.

그리고 예제를 만들면서 생각난 질문인데, 가격은 소수점까지 표현해야하는데 그럼 Double, Float, Bigdecimal이 있는데 어떤 것이
더 맞는 Object인지 궁금합니다.

급하게 만들어본거라 소스가 참 부족합니다 ^^;;

0

kim님의 프로필 이미지
kim
질문자

2019. 10. 11. 22:17

저도 한번 상상을 해본거라 ㅎㅎ 한번 만들어서 내일 중으로 올려보겠습니다

0

김영한님의 프로필 이미지
김영한
지식공유자

2019. 10. 11. 21:32

안녕하세요 kim님

질문의 의도를 알 것 같으면서도 정확한 답변을 드리기에 모호한 부분이 좀 있어요^^

제가 kim님의 정확한 의도를 파악해서 명확한 답변을 드릴 수 있도록 동작하는 예제 코드를 작성해서 올려주세요^^!

0

kim님의 프로필 이미지
kim
질문자

2019. 10. 11. 20:28

예를 들면은 A->B->C->D가 있으면 D는  A, B, C어느 것도 참조를 하면 안되게되는데 결국은 A,B,C라는 존재를 알려면 D에서 sql을 실행해 C를 찾고 또 C로 B를 찾고 N개면 N-1번의 sql를 실행해야 되는 것 같아서요, 극단적으로 10만건이면 99999번의 sql을 싫행해야할 거 같은데 이러면 비효율적이라서 혹시나 좀 더 좋은 방법이 없나해서요

0

김영한님의 프로필 이미지
김영한
지식공유자

2019. 10. 11. 18:19

안녕하세요 kim님

간단히 c->a 참조를 걸려할 때, 체크 로직을 하나 넣어주면 될 것 같은데요^^?

서버에서 검증하면 저장 시점에 검증을 하고, 검증은 DB에서 데이터를 다시 조회해서 c -> a 관계가 순환 참조 관계가 되는지 체크하는 것이지요.

kim님의 프로필 이미지

작성한 질문수

질문하기