WIKI - Spring Data JPA Update Query & ManyToMany Query
[환경]
Spring Tool Suite(STS) 3.9.5 RELEASE
Spring Boot 2.1.8.RELEASE
jdk 1.8.0_181
MySQL 8.0.17
저번 포스터에서 JPA ManyToMany 관계에 대해서 살펴봤습니다. Entity 구성이 끝났으니 이제 ManyToMany 관계에서 어떻게 Join 테이블에 접근하는지 알아야 합니다.
Spring Data JPA의 JpaRepository를 사용할 경우 기본적인 SELECT, INSERT, DELETE 쿼리를 메소드명의 조합으로 작성할 수 있는데, UPDATE 쿼리의 경우 @Query 어노테이션을 사용하여 작성해야 합니다.
이번 포스터에서는 이 두 가지 내용을 다뤄보도록 하겠습니다.
1. @ManyToMany @Query
@JoinTable 어노테이션으로 다대다 관계를 나타내는 board_tag 테이블을 만들었습니다. 그럼 어떻게 board_tag 테이블에 CRUD(Create, Read, Update, Delete)를 수행할까요?
Create(INSERT)의 경우 Board 클래스의 addTags() 메소드를 사용해 Board에 해당하는 Tag를 더해주고, BoardRepository의 save() 메소드를 사용해 board 테이블에 INSERT 할 때 board_tag 테이블도 같이 INSERT 할 수 있습니다.
@Autowired
private BoardRepository boardRepository;
Board board = new Board();
Tag tag1 = new Tag();
Tag tag2 = new Tag();
Tag tag3 = new Tag();
board.addTags(tag1);
board.addTags(tag2);
board.addTags(tag3);
boardRepository.save(board);
Read(SELECT)의 경우 Board 클래스의 getTags() 메소드를 통해 해당 Board에 속해있는 Tag들을 얻을 수 있다. 그렇다면 Board Entity를 불러오지 않고 boardNo(id)를 사용하여 Tag들만 가져오는 방법이 있을까요?
@Query 어노테이션을 사용하면 가능합니다. 기본적으로 JPA 모듈은 쿼리를 메소드 이름에서 파생되도록 지원합니다.
Board findBoardByBoardNo(int boardNo);
하지만 모든 쿼리를 메소드 이름만으로 자동으로 생성할 수는 없습니다. 그렇기 때문에 존재하는 것이 @Query 어노테이션입니다.
//BoardRepotiroy
@Query("select b from Board b inner join b.tags bt where bt.no = ?1")
List<Board> findBoardsByTagNo(int no);
위와 같이 Board Entity와 Board의 tags Set을 Join 해주면 됩니다. 여기서 bt가 Board의 tags라는 것을 잊으면 안 됩니다. bt.no는 Tag Entity의 no 속성을 의미합니다. 그렇다면 반대로 Board의 id 값으로 Tag를 찾으려면 어떻게 해야 할까요?
//TagRepotiroy
@Query("select t from Tag t inner join t.boards bt where bt.boardNo = ?1")
List<Tag> findTagsByBoardNo(int boardNo);
만약 Board Entity의 id 속성명이 ‘boardNo’가 아니라 ‘no’ 였다면 where 절이 findBoardsByTagNo()와 같이 ‘bt.no = ?1’이 됩니다.
개인적으로 이 부분이 정말 햇갈렸습니다.
Delete(DELETE)의 경우 매우 간단합니다. Board Entity의 id 값만 알면 BoardRepository의 deleteById() 메소드를 사용하여 해당 Board와 Board에 속해있는 tags Set까지 제거하여 board_tag 테이블의 튜플을 제거할 수 있습니다.
@Autowired
private BoardRepository boardRepository;
boardRepository.deleteById(boardNo);
Update(UPDATE)가 가장 골치 아픈 경우였습니다. 직관적으로 생각해보면 Board 클래스의 setTags(); 메소드를 써서 해당 Board의 tags Set을 바꿔주면 정상적으로 Update가 될 것 같지만 막상 시도해보면 이상한 결과가 나옵니다.
Board의 tags에 ‘A’ Tag가 있었는데 setTags()를 통해 ‘A’, ‘B’ Tag들을 가지고 있는 tags로 바꿔주면 DB의 tag 테이블에는 ‘A’, ‘A’, ‘B’가 들어가게 됩니다.
이 문제는 ORM에 대한 이해 부족이 원인이었습니다. 단순하게 ‘A’라는 String을 DB에 저장하는 것이 아닌 Tag Entity를 통해 DB에 값을 전달하기 때문에 Tag Entity 객체를 새롭게 만들어주면 Tag 테이블의 tag Column이 유니크 Column이라도 다른 객체로 인식되어 ‘A’, ‘A’가 저장되는 현상이 발생합니다. 저는 이와 같은 문제를 해결하기 위해 간단한 메소드를 작성했습니다.
private void addTags(Board board, String[] tags) {
board.getTags().clear();
Tag tag;
for(String s : tags) {
if(!isEmpty(s)) {
if(!tagRepository.existsByTag(s)) {
tag = new Tag(s);
tagRepository.save(tag);
log.info("insert new tag");
}
else {
tag = tagRepository.findByTag(s);
log.info("find a tag");
}
board.addTags(tag);
}
}
}
2. Spring Data JPA Update Query
JpaRepository에서 @Query 어노테이션을 사용하여 UPDATE 쿼리를 작성할 경우 일반적인 SELECT 쿼리와 다르게 @Modifying 어노테이션을 사용해야 합니다.
@Modifying
@Query("update Board b set b.title = ?1, b.contents = ?2, b.writer = ?3, b.date = ?4"
+ ", b.tags = ?5 where b.boardNo = ?6")
void updateBoardByBoardNo(String title, String contents, String writer, String date, Set<Tag> tags, int boardNo);