먼저 다음과 같이 ArticleFile
이라는 엔티티가 있다고 해보자.
@Entity
public class ArticleFile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long byteSize;
private String fileName;
private String fileExtension;
@OneToOne(fetch = LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "article_id")
private Article article;
...
}
ArticleFile
에는 id를 제외하고 byteSize, fileName, fileExtension
이 있다.
현재 목적은 게시글을 삭제하고 게시글에 포함된 파일을 Amazon S3에서 삭제
하는 것이다.
모든 필드를 조회할 경우
먼저 JpaRepository에 게시글 id를 받아서 articleFile을 조회하는 쿼리 메소드를 추가해보자.
public interface ArticleFileRepository extends JpaRepository<ArticleFile, Long> {
Optional<ArticleFile> findByArticleId(Long articleId);
}
추가한 쿼리 메소드를 사용하면 다음과 같이 Amazon S3에 저장된 파일을 삭제할 수 있을 것이다.
@Transactional
public void deleteArticleFile(Long articleId) {
ArticleFile articleFile = articleFileRepository.findByArticleId(articleId)
.orElseThrow(EntityNotFoundException::new);
String fileName = articleFile.getFileName();
S3Service.deleteFile(fileName);
}
하지만 이때 findByArticleId
를 통해 생성된 쿼리는 ArticleFile의 모든 필드(byteSize, fileName, fileExtension)를 포함하게된다. 즉, fileName을 조회하기위해 모든 필드를 불러오는 비효율적인 쿼리가 생성되는 것이다.
필요한 특정 필드만 조회
특정 필드만 조회할 경우 간단하게 다음과 같이 인터페이스를 추가해주기만 하면된다.
public interface FileNameInterface {
String getFileName();
}
Repository에는 메소드 구분을 위해 다음과 같이 findFileNameByArticleId
라고 정의했다.
public interface ArticleFileRepository extends JpaRepository<ArticleFile, Long> {
Optional<FileNameInterface> findFileNameByArticleId(Long articleId);
}
이제 이렇게 새로 생성한 쿼리 메소드를 사용한다면 다음과 같이 코드를 작성할 수 있다.
@Transactional
public void deleteArticleFile(Long articleId) {
s3Service.deleteFile(searchFileName(articleId));
}
private String searchFileName(Long articleId) {
return articleFileRepository.findFileNameByArticleId(articleId)
.orElseThrow(EntityNotFoundException::new)
.getFileName();
}
이전처럼 모든 필드를 조회하고 파일명을 가져오는 로직보다 좀 더 명확하고 쿼리를 최적화할 수 있었다.
select
articlefil0_.file_name as col_0_0_,
from
article_file articlefil0_
left outer join
article article1_
on articlefil0_.article_id=article1_.id
where
article1_.id=?