ABOUT ME

-

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

    소개

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

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

     

    문제

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

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

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

    JSON 응답 예시

    java
    @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를 만들고

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

     

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

    java
    // 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나 다른 타입으로 변환할 수 있는 내장 컨버터를 찾지 못했다는 말이다.

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

    java
    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 레포지토리 인터페이스를 만든다.

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

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

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

    java
    @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가 가능)

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

     

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

    REST API client

    728x90

    댓글