기록/BACKEND

[SpringBoot] JPA repository로 ManyToOne관계 쿼리 작성

5월._. 2022. 8. 2.
728x90

이번에 새로 JPA를 사용하게 되었는데, 기존 sql문과는 완전히 다르게 접근해야하는 부분이 있어서 작성해보려고 한다.

내 프로젝트에서 Board와 Comment 엔티티는 다음과 같다.

 

1. Entity

Board

@Entity
@Table(name = "Board")
@Data
@DynamicInsert
@DynamicUpdate
public class Board {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "BoardNo", nullable = false)
   private Integer boardNo;

   @ManyToOne(fetch = FetchType.LAZY, optional = false)
   @OnDelete(action = OnDeleteAction.CASCADE)
   @JoinColumn(name = "UserNo", nullable = false)
   private User user;

   @Column(name = "BoardType")
   private Integer boardType;

   @Column(name = "BoardTitle", length = 45)
   private String boardTitle;

   @Column(name = "BoardContent", length = 1000)
   private String boardContent;

   @Column(name = "BoardRegDate")
   private Instant boardRegdate;

   @Column(name = "BoardUpdate")
   private Instant boardUpdate;

}

 

Comment

JPA에서는 fk를 관리할 때 객체로 가져온다. 따라서 Comment 클래스에서 Board객체를 저장한다. 

한 Board는 여러 Comment를 가질 수 있으므로 @ManyToOne, Board가 삭제될 때 Comment도 삭제돼야하므로 Cascade를 설정했다.

마지막으로 Comment 테이블 어떤 컬럼이 fk인지 명시하기 위해 JoinColumn 이름에 BoardNo를 작성했다. 내 DB테이블은 이미 작성이 완료된 상태라 이런 방식이 가능했다.

@Entity
@Table(name = "Comment")
@Data
@DynamicInsert
@DynamicUpdate
public class Comment {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "CommentNo", nullable = false)
   private Integer commentNo;

   @ManyToOne(fetch = FetchType.LAZY, optional = false)
   @OnDelete(action = OnDeleteAction.CASCADE)
   @JoinColumn(name = "BoardNo", nullable = false)
   private Board board;

   @ManyToOne(fetch = FetchType.LAZY, optional = false)
   @OnDelete(action = OnDeleteAction.CASCADE)
   @JoinColumn(name = "UserNo", nullable = false)
   private User user;

   @Column(name = "CommentAnswer", length = 100)
   private String commentAnswer;

   @Column(name = "CommentRegDate")
   private Instant commentRegDate;
}

 

2. Repository

이제 쿼리 작성을 해야한다. 내가 가장 당황스러웠던 부분이자 이 글을 쓰게 된 이유다.

 

함수명으로 쿼리 작성

내가 실행하고 싶었던 쿼리는 다음과 같다.

SELECT * FROM Comment WHERE BoardNo=?

 

JPA 문서를 보면 규칙에 맞춰 레파지토리 함수를 작성하면 자동으로 쿼리가 생성된다고 나와있다. 이 클래스는 Comment 테이블을 조회하고 가져오는 repository다. 처음에는 findByBoard, findByBoardNo로 시도했는데 첫 번째는 그냥 오류가 나고, 두 번째는 BoardNo라는 컬럼이 없다고 떴다. DB테이블에 버젓이 있는 게 보이는데 자꾸 없다고 하니 너무 답답했다.

 

답은 BoardBoardNo였다. JPA는 객체로 관리한다는 점이 핵심이다. Comment 엔티티에는 Board 엔티티 "객체"가 있으므로 그 객체의 BoardNo를 찾아야한다. 따라서 findByBoardBoardNo라는 함수명이 나와야하는 것이다.

@Repository
public interface CommentRepository extends JpaRepository<Comment, Integer> {
    List<Comment> findByBoardBoardNo(int BoardNo);
}

 

@Query로 쿼리문 작성

JPA가 객체지향이기 때문에 일반 sql문과 다른 게 또 있다. 

먼저, 실행하고자 하는 쿼리는 다음과 같다. group 테이블에서 userno, now()가 startdate와 enddate사이에 있는 튜플을 찾는 것이다.

SELECT *
FROM `Group`
WHERE UserNo=?
AND GroupStartDate <= now()
AND GroupEndDate >= now();

 

이 쿼리를 그대로 @Query안에 작성하는 데는 몇가지 문제가 있다.

1. select * 가 불가능하다. *이 아니라 엔티티명을 작성해야 한다.

2. Group 엔티티는 user 객체만 있으므로 user.userNo로 찾는다.

3. hibernate는 now()함수가 없다. 대신 currnet_timestamp로 now를 대체한다.

public interface GroupRepository extends JpaRepository<Group, Integer> {
    @Query("select g " +
            "from Group g " +
            "where g.user.userNo=?1 " +
            "and g.groupStartDate <= current_timestamp " +
            "and g.groupEndDate >= current_timestamp")
    Group getCurrentGroup(int userNo);
}

 

3. 결론

쿼리든 함수명이든 JPA는 객체지향이라는 점을 명심하고 있으면 나같이 쿼리 한 줄에 몇시간 씩 붙잡고 있지는 않을 것 같다. 특히 BoardBoardNo같은건 대체 왜 찾아도 안나오는지, 다른 사람들은 다른 방식으로 잘 하는지 너무 궁금하다.

댓글