JAVAIARY

N:1(다대일) 연관관계 1 본문

Project/2023.02~ ) Study toy 프로젝트

N:1(다대일) 연관관계 1

shiherlis 2023. 3. 1. 15:00

연관관계와 관계형 데이터베이스 설계

  • 관계형 데이터베이스 - 개체(entity)간의 관계(relation)를 통해 구성

 

  • ex) 회원과 게시글의 관계
    • 1명의 회원은 여러(N)개의 게시글을 작성할 수 있음
    • 1개의 게시글은 1명의 회원에 의해서 작성됨
  • 하나의 ID(기본키)가 여러 게시글에서 참조되도록 설계

1. 엔티티 생성

1-2. Member 엔티티 생성

@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString
public class Member extends BaseEntity{
    @Id
    private String email;
    private String password;
    private String name;
    
}
  • 회원 엔티티는 PK만을 가지고 있으므로 별도의 FK 필요 없음

 


1-2. Board 엔티티 생성

@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString
public class Board extends BaseEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long bno;
    private String title;
    private String content;
    
    //작성자 처리는 추후에 추가
}

1-3. Reply 엔티티 생성

 

@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString
public class Reply extends BaseEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long rno;
    private String text;
    private String replyer;
    
    //Board와의 연관관계 추후 작성
}

2. @ManyToOne 어노테이션

  • FK(외래키)를 이용한 참조를 위한 어노테이션

2-1. Board에 작성자(writer) FK 추가

2-2. Reply 에 게시판(board) FK 추가


3. Repository 생성 

public interface MemberRepository extends JpaRepository<Member, String> {
}
public interface BoardRepository extends JpaRepository<Board, Long> {
}
public interface ReplyRepository extends JpaRepository<Reply, Long> {
}

4. 연관관계 InsertTest 

4-1. RepoitoryTests 생성

4-2. MemberRepositoryTests

@SpringBootTest
public class MemberRepositoryTests {
    @Autowired
    private MemberRepository memberRepository;
    
    @Test
    public void insertMembers(){
        IntStream.rangeClosed(1,100).forEach(i -> {
            Member member = Member.builder()
                    .email("user"+i+"@aaa.com")
                    .password("1111")
                    .name("user"+i)
                    .build();
            
            memberRepository.save(member);
        });
    }
}

4-3. BoardRepositoryTests

@SpringBootTest
public class BoardRepostoryTests {

    @Autowired
    private BoardRepository boardRepository;

    @Test
    public void insertBoard() {
        IntStream.rangeClosed(1, 100).forEach(i -> {
            Member member = Member.builder().email("user" + i + "@aaa.com").build();

            Board board = Board.builder()
                    .title("Title..." + i)
                    .content("Content...." + i)
                    .writer(member).
                    build();

            boardRepository.save(board);
        });
    }
}

4-4. ReplyRepositoryTests

@SpringBootTest
public class ReplyRepositoryTests {

    @Autowired
    private ReplyRepository replyRepository;

    @Test
    public void insertReply() {
        IntStream.rangeClosed(1, 300).forEach(i -> {
            // 1부터 100까지의 임의의 번호
            long bno = (long) (Math.random() * 100) + 1;
            String email = "user" + bno + "@naver.com";

            Board board = Board.builder().bno(bno).build();

            Reply reply = Reply.builder()
                    .text("Reply Text..." + i)
                    .board(board)
                    .replyer(email)
                    .build();

            replyRepository.save(reply);
        });
    }
}

5. Join 처리 Test

5-1.  BoardRepositoryTests

@Test   //select
public void testRead1(){
    Optional<Board> result = boardRepository.findById(100L);

    Board board = result.get();

    System.out.println(board);
    System.out.println(board.getWriter());
}

실행 쿼리

  • @ManyToOne 으로 엮인 엔티티들이 자동으로 조인처리되어 함께 가져옴

5-2. ReplyRepositoryTests

@Test
public void readReply1(){
    Optional<Reply> result = replyRepository.findById(1L);

    Reply reply = result.get();

    System.out.println(reply);
    System.out.println(reply.getBoard());

}

실행 쿼리


6. fetch를 통한 Lazyloading(지연로딩) 처리 - 권장됨

  • fetch : JPA에서 연관관계의 데이터를 어떻게 가져올 것인가
    어노테이션 속성으로 fetch모드 지정
  • Eager Loading: 즉시 로딩
    특정 엔티티를 조회할 때 연관관계를 가진 모든 엔티티를 같이 로딩
    연관관계가 복잡할수록 조인으로 인한 성능저하 초래
  • Lazy Loading: 지연 로딩
    장점: 조인을 하지 않기 때문에 하나의 테이블을 이용하는 경우에는 빠른 처리 가능
    단점: 필요한 순간에 쿼리를 실행해야 하기 떄문에 연관관계가 복잡한 경우, 여러번의 쿼리가 실행

7. JPQL 사용하기

  • JPQL
    • 테이블이 아닌 엔티티 객체를 대상으로 검색하는 객체지향 쿼리
    • SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않음
      = JPQL 문법만 알면 여러 데이터베이스를 쉽게 다룰 수 있게 됨
    • JpaRepository(인터페이스)에서 JPQL을 분석하여  데이터베이스를 조회
      = 자동 번역
    • 방언(Dialect)만 변경하면 JPQL을 수정하지 않고 자연스럽게 DB 변경 가능

PostgreSQL 사용시 build

1) 엔티티 클래스 내부에 연관관계가 있는 경우

@ManyToOne(fetch = FetchType.LAZY)  // 명시적으로 Lazy 로딩 지정
  • @ManyToOne어노테이션을 사용하는 경우 지연로딩 명시적 표시
@Query("select b, w from Board b left join b.writer w where b.bno =:bno")
Object getBoardWithWriter(@Param("bno") Long bno);
  • testRead1() 테스트를 실행시키면 오류발생

  • @Transactional 어노테이션 추가

  • 정상적으로 Board조회 후, Member 조회

연관관계에서는 @ToString() 주의

exclude의 속성값으로 지정된 변수는 toString()에서 제외하기 때문에 지연 로딩을 할 때는 반드시 지정해 주는 것이 좋음.

2) 연관관계가 없는 엔티티 조인 처리 = on 사용

@Query("SELECT b, r FROM Board b LEFT JOIN Reply r ON r.board = b WHERE b.bno = :bno")
List<Object[]> getBoardWithReply(@Param("bno") Long bno);
  • reply 내의 board는 참조를 하고 있으나, board는 reply를 참조하고 있지 않으므로 on 을 통해
    r.board = b.bno 를 명시해줌
@Test
public void testGetBoardWithReply(){
    List<Object[]> result = boardRepository.getBoardWithReply(100L);
    
    for(Object[] arr : result){
        System.out.println(Arrays.toString(arr));
    }

}