매일 조금씩

[JPA] 복합키일 때 셀프 조인 구현하기 - 부모(parent)/자식(children), @OneToMany, @ManyToOne, @JoinColumn, @JoinColumns 본문

공부/JPA

[JPA] 복합키일 때 셀프 조인 구현하기 - 부모(parent)/자식(children), @OneToMany, @ManyToOne, @JoinColumn, @JoinColumns

mezo 2022. 4. 22. 21:45
728x90
반응형

 

 

파일과 폴더의 정보가 모두 'document'라는 테이블에 함께 저장된다고 하자. 

이때, 폴더는 파일의 부모가 되므로 한테이블내에서 부모(parent)와 자식(children)이 존재한다. 

PK는 'log_file_id' 와 'version' 이다.

 

 

이경우 

이를 셀프조인을 사용하지 않고 폴더 안의 파일/폴더 리스트를 가져오려면..

1. 쿼리로 직접 셀프조인을 구현하여 리스트를 받아온다. 

2. 'file_parent_id' 컬럼 값이 폴더의 'log_file_id' 값인 것을 찾아 java의 Object인 List<>에 담는다. 

 

 

 

 

 

 

하지만 이는 JPA에서 손쉽게 해결할수 있다. 

다음 코드를 보자. 

@Entity
@Data
@NoArgsConstructor
@Table(name="document")
@IdClass(FilePK.class)
public class FileEntity {
  @Id
  @Column(name = "log_file_id")
  private String logicalId;

  @Id
  @Column(name = "version")
  private Integer version;

  @Column(name = "file_parent_id")
  private String parentId;
  
  // 이하컬럼 생략 ..

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumns({
      @JoinColumn(name = "file_parent_id", referencedColumnName = "log_file_id", insertable = false, updatable = false),
      @JoinColumn(name = "version", referencedColumnName = "version", insertable = false, updatable = false)
  })
  private FileEntity parent;

  @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
  private List<FileEntity> children = new ArrayList<>();
}

위처럼 @ManyToOne, @JoinColumns, @JoinColumn, @OneToMany 를 사용하여 조인하면된다. 

 

 

다음은 서비스 코드이다. 

@Service
@RequiredArgsConstructor
@Slf4j
public class FileServiceImpl implements FileService {

  private final FileRepository fileRepository;
  private final ModelMapper modelMapper = new ModelMapper();

  @Override
  @Transactional
  public void deleteFilesByIsHardDelete(List<DeleteFileRequestDto> deleteFileRequestDtoList, boolean isHardDelete)
      throws IOException {
    if(isHardDelete){
      deletePhysicalFileByNumberOfLogicalFiles(deleteFileRequestDtoList);
    } else{
      deleteFileInTrashBin(deleteFileRequestDtoList);
    }
  }

  private void deleteFileInTrashBin(List<DeleteFileRequestDto> deleteFileRequestDtoList) {
    for(DeleteFileRequestDto updateDelYNItem : deleteFileRequestDtoList){
      FileEntity fileEntity = fileRepository.findByLogicalId(updateDelYNItem.getLogicalId());
      if(updateDelYNItem.getIsFolder().equalsIgnoreCase("Y")){
          for(FileEntity children : fileEntity.getChildren()){
            children.setSoftDeleteForChildren(userId, children.getChannel());
            fileRepository.save(children);
          }
        fileRepository.deleteByLogicalId(fileEntity.getLogicalId());
      }else {
        fileEntity.setSoftDelete(userId);
        fileRepository.save(fileEntity);
      }
    }
  }

  private void deletePhysicalFileByNumberOfLogicalFiles(List<DeleteFileRequestDto> deleteFileRequestDtoList)
      throws IOException {
    for(DeleteFileRequestDto deleteItem : deleteFileRequestDtoList){
      String physicalFileId = fileRepository.findByLogicalId(deleteItem.getLogicalId()).getPhysicalId();
      if (deleteItem.getIsFolder().equalsIgnoreCase("N")){
        fileRepository.deleteByLogicalId(deleteItem.getLogicalId());
        deleteThumbnailPhoto(deleteItem.getLogicalId());
        int numOfRemainingFiles = fileRepository.findByPhysicalId(physicalFileId);
        if(numOfRemainingFiles == 0){
          Files.deleteIfExists(Paths.get(FILE_ROOT_PATH, domainKey, physicalFileId));
        }
      }
    }
  }
}

위처럼 @Transactional 을 써줘야 fetch관련 에러가 발생하지않는다. 

또한 getChildren()과 같이 쉽게 관련 리스트를 가져올수있다. 

728x90
반응형