programing

봄 JPA - "java.lang."잘못된 인수예외:투영 유형은 인터페이스여야 합니다!"(네이티브 쿼리 사용)

megabox 2023. 7. 13. 20:46
반응형

봄 JPA - "java.lang."잘못된 인수예외:투영 유형은 인터페이스여야 합니다!"(네이티브 쿼리 사용)

Oracle 데이터베이스에서 타임스탬프 날짜를 검색하려고 하는데 코드가 느려집니다.

자바.java.java잘못된 인수예외:투영 유형은 인터페이스여야 합니다!

원래 쿼리는 스프링 JPA 메소드나 JPQL을 사용하기에는 너무 복잡하기 때문에 네이티브 쿼리를 사용하려고 합니다.

제 코드는 아래 코드와 유사합니다(죄송하지만 회사 정책상 원본을 붙여넣을 수 없습니다).

엔티티:

@Getter
@Setter
@Entity(name = "USER")
public class User {

    @Column(name = "USER_ID")
    private Long userId;

    @Column(name = "USER_NAME")
    private String userName;

    @Column(name = "CREATED_DATE")
    private ZonedDateTime createdDate;
}

투영:

public interface UserProjection {

    String getUserName();

    ZonedDateTime getCreatedDate();
}

리포지토리:

@Repository
public interface UserRepository extends CrudRepository<User, Long> {

    @Query(
            value = "   select userName as userName," +
                    "          createdDate as createdDate" +
                    "   from user as u " +
                    "   where u.userName = :name",
            nativeQuery = true
    )
    Optional<UserProjection> findUserByName(@Param("name") String name);
}

Spring Boot 2.1.3과 Hibernate 5.3.7을 사용하고 있습니다.

저는 매우 유사한 예상과 관련하여 동일한 문제가 있었습니다.

public interface RunSummary {

    String getName();
    ZonedDateTime getDate();
    Long getVolume();

}

이유는 모르겠지만, 문제는ZonedDateTime의 유형을 변경했습니다.getDate()로.java.util.Date그리고 예외는 사라졌습니다.트랜잭션 외부에서 Date를 ZoneDateTime으로 다시 변환했지만 다운스트림 코드는 영향을 받지 않았습니다.

프로젝션을 사용하지 않으면 ZoneDateTime이 즉시 작동합니다.그 사이에 해결책이 될 수 있을 것 같아서 답변으로 글을 올립니다.


Spring-Data-Commons 프로젝트의 버그에 따르면, 이것은 투영에서 선택적 필드에 대한 지원을 추가함으로써 발생한 회귀입니다. (분명히 다른 수정에 의해 실제로 발생한 것은 아닙니다. 왜냐하면 2020년에 다른 수정이 추가되었기 때문이고 이 질문/답변은 오래 전입니다.)그럼에도 불구하고 Spring-Boot 2.4.3에서 해결된 것으로 표시되었습니다.

기본적으로, 당신은 당신의 투영에 Java 8 시간 클래스를 사용할 수 없었고, 오직 이전 날짜 기반 클래스만 사용할 수 있었습니다.위에 올린 해결 방법은 2.4.3 이전의 Spring-Boot 버전에서 이 문제를 해결할 것입니다.

투영 인터페이스에서 메서드를 호출하면 스프링이 데이터베이스에서 수신한 값을 가져와 메서드가 반환하는 유형으로 변환합니다.이 작업은 다음 코드로 수행됩니다.

if (type.isCollectionLike() && !ClassUtils.isPrimitiveArray(rawType)) { //if1
    return projectCollectionElements(asCollection(result), type);
} else if (type.isMap()) { //if2
    return projectMapValues((Map<?, ?>) result, type);
} else if (conversionRequiredAndPossible(result, rawType)) { //if3
    return conversionService.convert(result, rawType);
} else { //else
    return getProjection(result, rawType);
}

의 경우에는getCreatedDate원하는 방법java.time.ZonedDateTime부터java.sql.Timestamp그리고 그 이후로ZonedDateTime컬렉션 또는 배열(if1)이 아니며 지도(if2)가 아니며 스프링에는 다음과 같은 변환기(if3)가 없습니다.Timestamp로.ZonedDateTime이 필드가 다른 중첩 투영(접미사)이라고 가정하면 이 필드는 해당되지 않으며 예외가 발생합니다.

두 가지 솔루션이 있습니다.

  1. 타임스탬프를 반환한 다음 수동으로 ZoneDateTime으로 변환
  2. 컨버터 생성 및 등록
public class TimestampToZonedDateTimeConverter implements Converter<Timestamp, ZonedDateTime> {
    @Override
    public ZonedDateTime convert(Timestamp timestamp) {
        return ZonedDateTime.now(); //write your algorithm
    }
}
@Configuration
public class ConverterConfig {
    @EventListener(ApplicationReadyEvent.class)
    public void config() {
        DefaultConversionService conversionService = (DefaultConversionService) DefaultConversionService.getSharedInstance();
        conversionService.addConverter(new TimestampToZonedDateTimeConverter());
    }
}

Spring Boot 2.4.0 업데이트:

버전 2.4.0 이후 스프링은 새로운 기능을 생성합니다. DefaultConversionService을 통해 얻는 대신 객체getSharedInstance반사를 이용하는 것 외에는 그것을 얻는 적절한 방법을 알지 못합니다.

@Configuration
public class ConverterConfig implements WebMvcConfigurer {
    @PostConstruct
    public void config() throws NoSuchFieldException, ClassNotFoundException, IllegalAccessException {
        Class<?> aClass = Class.forName("org.springframework.data.projection.ProxyProjectionFactory");
        Field field = aClass.getDeclaredField("CONVERSION_SERVICE");
        field.setAccessible(true);
        GenericConversionService service = (GenericConversionService) field.get(null);

        service.addConverter(new TimestampToZonedDateTimeConverter());
    }
}

열 유형을 원하는 특성 유형에 매핑하기 위해 새 특성 변환기를 만들 수 있습니다.

@Component
public class OffsetDateTimeTypeConverter implements 
              AttributeConverter<OffsetDateTime, Timestamp> {

    @Override
    public Timestamp convertToDatabaseColumn(OffsetDateTime attribute) {
       //your implementation
    }

    @Override
    public OffsetDateTime convertToEntityAttribute(Timestamp dbData) {
       return dbData == null ? null : dbData.toInstant().atOffset(ZoneOffset.UTC);
    }

}

프로젝션에서는 아래와 같이 사용할 수 있습니다.이것은 컨버터를 호출하는 명시적인 방법입니다.못해서 가 없습니다.@Value필요할 때마다 주석을 추가합니다.

@Value("#{@offsetDateTimeTypeConverter.convertToEntityAttribute(target.yourattributename)}")
OffsetDateTime getYourAttributeName();

Spring Boot v2.4.2에서도 동일한 문제가 있었습니다.
저는 저를 위해 이 추악한 해킹을 썼습니다.

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.convert.Jsr310Converters;
import org.springframework.data.util.NullableWrapperConverters;

@Configuration
public class JpaConvertersConfig {

    @EventListener(ApplicationReadyEvent.class)
    public void config() throws Exception {
        Class<?> aClass = Class.forName("org.springframework.data.projection.ProxyProjectionFactory");
        Field field = aClass.getDeclaredField("CONVERSION_SERVICE");
        field.setAccessible(true);
        Field modifiers = Field.class.getDeclaredField("modifiers");
        modifiers.setAccessible(true);
        modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        DefaultConversionService sharedInstance = ((DefaultConversionService) DefaultConversionService.getSharedInstance());
        field.set(null, sharedInstance);
        Jsr310Converters.getConvertersToRegister().forEach(sharedInstance::addConverter);
        NullableWrapperConverters.registerConvertersIn(sharedInstance);
    }
}

은 선습니다를 했습니다.userId로서 필드에.Long도 본질로에 .UserProjection getUserId은 다음과 같습니다.String Change 일치항목로므이변경않는지하▁which변.

String getUserId();

로.

Long getUserId();

나는 받지 못했습니다.interface에서 지원되는지 여부를 확신하지 못하고 작동합니다.ZonedDateTime비록 제 마음속에 지지하지 않을 이유는 없지만요.

이를 위해 저는 프로젝션과 함께 사용하는 클래스를 만들었습니다(물론 이 인터페이스는 구현할 수 있지만 단순성을 위해 생략했습니다).

@Getter
@Setter
@AllArgsConstructor
public class UserProjection {
    private String userName;
    private ZonedDateTime createdDate;
}

쿼리에서 다음과 같이 NEW 연산자를 사용하기 때문에 이를 위해서는 JPQL이 필요합니다.

@Query(value = " SELECT NEW org.example.UserProjection(U.userName, U.createdDate) "
        + " FROM USER U " // note that the entity name is "USER" in CAPS
        + " WHERE U.userName = :name ")

스프링 데이터 jpa의 문제는 일부 유형을 데이터베이스에서 Java 유형으로 변환할 수 없습니다.결과와 데이터베이스 반환 번호로 부울을 얻으려고 했을 때 거의 같은 문제가 있었습니다.

자세한 내용은 다음 사이트를 참조하십시오. https://github.com/spring-projects/spring-data-commons/issues/2223 https://github.com/spring-projects/spring-data-commons/issues/2290

언급URL : https://stackoverflow.com/questions/55247358/spring-jpa-java-lang-illegalargumentexception-projection-type-must-be-an-int

반응형