ABOUT ME

-

Total
-
  • Spring Boot: Repository Extension 패턴 (CustomRepositoryImpl)
    컴퓨터/Spring Boot 2024. 5. 8. 15:12
    728x90
    반응형

    소개

    프로젝트들을 보다 보면 가끔 RepositoryImpl과 같은 파일을 본 적이 있다.

    그냥 Repository를 다르게 구현한 것일까 하고 넘어갔다가 개발을 하다가 위 방식으로 해결했다.

     

    문제

    MySQL 에서 spatial 타입을 사용 중이다.

    POINT 타입으로 저장되어 있기 때문에 클라이언트 JSON에서는

    latitude/longitude (경/위도)로 float64 (double) 타입으로 반환하고 싶었다.

    JSON 응답 예시

    @Entity
    @Table(name = "Markers")
    public class Marker {
    
        @Id
        @Column(name = "MarkerID")
        private Integer markerID;
    
        @Column(columnDefinition = "geometry(Point, 4326)")
        private Point location;
        
        ....
    }

     

    Java 17에는 Record가 있기 때문에 간단하게 DTO를 만들고

    public record MarkerSimple(
            int markerId,
            double latitude,
            double longitude
    ) {}

     

    native query든 JPQL 이든 그냥 받으면 될 줄 알았다.

    // native
    @Repository
    public interface MarkerRepository extends JpaRepository<Marker, Integer> {
        @Query(value = "SELECT m.markerId, ST_X(m.location) as latitude, ST_Y(m.location) as longitude FROM Markers", nativeQuery = true)
        List<MarkerSimple> findAllSimplifiedMarkers();
    }
    
    // non-native
    public interface MarkerRepository extends JpaRepository<Marker, Integer> {
        @Query("SELECT new MarkerSimple(m.markerId, function('ST_X', m.location), function('ST_Y', m.location)) FROM Markers m")
        List<MarkerDTO.MarkerSimple> findAllSimplifiedMarkers();
    }

     

    하지만, 다음과 같은 에러가 나온다. 알맞은 변환기가 없다고 한다.

    즉, Spring Data JPA가 자동으로 결과를 DTO나 다른 타입으로 변환할 수 있는 내장 컨버터를 찾지 못했다는 말이다.

    이 경우, 수동으로 결과를 매핑해야 할 필요가 있다.

    org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [dto.MarkerDTO$MarkerSimple]

     

    해결

    이것을 해결하기 위해서 여러 방법이 있지만 그중에 자동  mapping으로 안 되는 것들은 custom mapping을 만들어서 사용할 수 있다.

     

    Custom 레포지토리 인터페이스를 만든다.

    public interface MarkerRepositoryCustom {
        List<MarkerDTO.MarkerSimple> findAllSimplifiedMarkers();
    }

    Custom 레포지토리 Impl 클래스를 만들고 직접 매핑을 구현한다. (Repository Extension 패턴)

    new MarkerSimple 부분을 보면 record에 직접 얻은 값들을 매핑하는 것을 볼 수 있다.

    @Repository
    public class MarkerRepositoryCustomImpl implements MarkerRepositoryCustom {
    
        private final JdbcTemplate jdbcTemplate;
    
        @Autowired
        public MarkerRepositoryCustomImpl(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
    
        @Override
        public List<MarkerDTO.MarkerSimple> findAllSimplifiedMarkers() {
            String sql = "SELECT MarkerID, ST_X(Location) AS latitude, ST_Y(Location) AS longitude FROM Markers";
            return jdbcTemplate.query( // 더 복잡하거나 세밀한 데이터베이스 연산을 수행 가능
                    "SELECT MarkerID, ST_X(Location) as Latitude, ST_Y(Location) as Longitude FROM Markers",
                    (rs, rowNum) -> new MarkerDTO.MarkerSimple(
                            rs.getInt("MarkerID"),
                            rs.getDouble("Latitude"),
                            rs.getDouble("Longitude")
                    )
            );
        }
    }

     

    그러면 원래 Repository에 Custom을 extends 해준다. (extends Interface (JpaRepository), Class 라 extends가 가능)

    @Repository
    public interface MarkerRepository extends JpaRepository<Marker, Integer>, MarkerRepositoryCustom {
        List<MarkerDTO.MarkerSimple> findAllSimplifiedMarkers();
    }

     

    API를 다시 불러보면 잘 해결된다.

    REST API client

    728x90

    댓글