본문 바로가기

개발

[JPA] FindAll이 같은 값만 나와요

좌: 기대한 결과 / 우: 실제 결과

문제 상황

API에서 entity 리스트를 반환해야 하는데, 동일한 원소만 지속적으로 반환되는 문제가 발생했다.

요청에는 문제가 없었고, 데이터가 매핑된 쿼리 결과 로그를 다른 SQL 툴에서 실행 결과는 정확했다. JPA가 생성한 쿼리 역시 문제가 없었다.

 

혹시나 해서 JPARepository에서 반환 타입을 List 대신 List<Map<Object, Object>>로 변경하니 올바른 결과가 나왔다.

흥미로운 점은 두 경우 모두 반환되는 개수는 동일했지만, List는 쿼리 결과의 첫 번째 원소만 채워져서 반환됐다.

배포 시간이 촉박해, 일단 Map을 DTO로 변환한 후 원인을 파악하고 해결하기로 결정했다.

뭐가 문제였을까?

복합키 문제였다.

@IdClass@EmbededId를 통해 복합키를 설정해야 했는데, 복합키 컬럼 하나에만 @Id 어노테이션을 적용했다.
이로 인해 해당 복합키 컬럼으로 findAll을 호출할 때, 첫 번째 결과만 포함된 entity 리스트가 반환됐다.

@Id 어노테이션이 어떤 원리로 동작하는지 파악하긴 어려웠지만, 앞으로 entity를 만들고 JpaRepository 메소드를 구현할 때 키 구성을 꼼꼼히 봐야 한단 교훈을 얻었다.

해결 방안

자동 증가하는 ID 컬럼을 추가하고, 해당 컬럼에 @Id 어노테이션을 적용했다.

코드 관점에서는 복합키 객체를 만들어야 했지만, 요청 파라미터가 하나뿐이었고 나머지 복합키 컬럼을 파라미터로 추가하거나 DB에서 조회하기엔 로직이 복잡해졌다.

 

그렇다고 복합키를 해제하기엔 다른 팀에서 데이터를 넣어주고 있어, 쉽게 변경할 수 없었다.

따라서 고유 값을 가지면서도 의미 없는 컬럼을 생성하고, 여기에 @Id 어노테이션을 적용하는 방법이 가장 적절하다고 판단했다.

테스트가 실패한 이유

1. 원래 동일한 값이 나오는 경우가 있음

도메인 특성상, 한 지역 내 서로 다른 데이터들은 같은 결과를 반환할 수 있다.

 

2. 테스트 코드의 허점

해당 기능에 대한 테스트는 작성했으나, 결과의 개수만 확인했을 뿐, 모든 요소를 철저히 검증하지는 않았다.
특히, 첫 번째 원인으로 인해 모든 요소를 검증하는 로직을 구현하는 것이 매우 어려웠다.

 

드물게 사용되다 보니, 테스트 케이스가 부족했다. 이번 경험을 통해 테스트 케이스를 구체화할 수 있는 기회가 됐다.

테스트 환경 만들기

DDL

CREATE TABLE public.my_entity (
    key1 VARCHAR(255) NOT NULL,
    key2 VARCHAR(255) NOT NULL,
    key3 VARCHAR(255) NOT NULL,
    some_data VARCHAR(255),
    PRIMARY KEY (key1, key2, key3)
);

 

데이터 삽입

DO $$
BEGIN
    FOR i IN 1..50 LOOP
        INSERT INTO my_entity (key1, key2, key3, some_data)
        VALUES (
            floor(random() * 5 + 1),
            'key2_' || i,
            'key3_' || i,
            'Some data ' || i
        );
    END LOOP;
END $$;

 

삽입 결과

SELECT * FROM public.my_entity

 

entity

@Table(name="my_entity", schema = "public")
public class MyEntity {

    @Id // 복합키, key1에만 @Id를 지정했다.
    private String key1;

    @Column // 복합키
    private String key2;

    @Column // 복합키
    private String key3;

    @Column
    private String someData;

    ...
}

 

Repository

public interface MyEntityRepository extends JpaRepository<MyEntity, String> {

    List<MyEntity> findAllByKey1(final String key1);

    // 임시로 해결한 코드
    //@Query(value = "SELECT * FROM public.my_entity WHERE key1 = ?", nativeQuery = true)
    //List<Map<String, String>> findAllByKey1(final String key1);
}

 

수정한 entity

@Table(name="my_entity", schema = "public")
public class MyEntity {

    @Id	// DDL에서 PK로 지정하진 않았다.
    private Long id;

    @Column	// 복합키 컬럼1
    private String key1;

    @Column	// 복합키 컬럼2
    private String key2;

    @Column	// 복합키 컬럼3
    private String key3;

    @Column
    private String someData;

    // ...
}

마치면서

관성적으로 접근했던 리스트 원소 개수만 세고 넘어가는 테스트가 문제가 될 수 있단 것을 알았다.

또한 @Id 어노테이션이 JPA가 동작하는데 큰 영향을 준다는 것을 알았다.

'개발' 카테고리의 다른 글

[PostgreSQL] FAQ를 번역해 보았다. (스압)  (2) 2025.01.29
[PostgreSQL] FDW로 Cross Database 해결하기  (0) 2025.01.19
파일 옮길 땐 tar를 쓰자  (0) 2024.11.19
ArrayBuffer, Blob  (0) 2024.11.09
단위 테스트 적용하기  (0) 2024.10.26