-
Spring Boot: Repository Extension 패턴 (CustomRepositoryImpl)컴퓨터/Spring Boot 2024. 5. 8. 15:12728x90반응형
소개
프로젝트들을 보다 보면 가끔 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'컴퓨터 > Spring Boot' 카테고리의 다른 글
Spring boot + Kotlin Coroutine + WebFlux + Security 5 + MySQL 기본 셋업 (0) 2024.03.18 Spring Boot: Kotlin + Cassandra 에서 @CreatedDate null 해결 (0) 2023.12.16 Spring Boot: Kotlin + ScyllaDB 도커 시작 (0) 2023.12.14