이번에 새로 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같은건 대체 왜 찾아도 안나오는지, 다른 사람들은 다른 방식으로 잘 하는지 너무 궁금하다.
'기록 > BACKEND' 카테고리의 다른 글
[SpringBoot] DB정보 엑셀로 다운로드하기 (0) | 2022.08.05 |
---|---|
[SpringBoot] 엑셀 DB에 업로드하기 (0) | 2022.08.04 |
[SpringBoot] JWT토큰 인터셉터 처리하기 (0) | 2022.07.30 |
[SpringBoot] 비밀번호 암호화하기 (0) | 2022.07.23 |
[SpringBoot] 자바 메일 보내기 (0) | 2022.07.21 |
댓글