programing

Java 8 날짜 시간 유형이 Spring Boot과 함께 객체로 직렬화됨

css3 2023. 6. 24. 09:30

Java 8 날짜 시간 유형이 Spring Boot과 함께 객체로 직렬화됨

Java 8 날짜 시간 유형의 필드를 가진 엔티티가 있습니다.문제는 이러한 필드가 개체로 직렬화된다는 것입니다.추가했습니다.jackson-datatype-jsr310Spring Boot 1.5.7이 자동으로 구성되도록 하기 위해JavaTimeModuleJava 8 날짜 시간 유형을 처리합니다.모듈이 등록되지 않은 것 같습니다(JavaTimeModule 생성자에 브레이크포인트를 넣었습니다).고객이 필요하지 않다는 것을 알고 있습니다.ObjectMapper저는 그 문제에 대해 몇 시간 동안 읽었고 해결책은 항상 추가하는 것입니다.jackson-datatype-jsr310의존성이지만 제 경우에는 작동하지 않습니다.

엔티티:

@Entity
public class DateTimeEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private LocalDate localDate;

    private LocalDateTime localDateTime;

    private Instant instant;

    private OffsetDateTime offsetDateTime;

    private ZonedDateTime zonedDateTime;

}

Rest Controller 방법:

@GetMapping("/datetimes/{id}")
public ResponseEntity<DateTimeEntity> getById(@PathVariable Long id) {
    DateTimeEntity dateTimeEntity = dateTimeRepository.findOne(id);
    return new ResponseEntity<DateTimeEntity>(dateTimeEntity, HttpStatus.OK);

}

JSON 개체가 반환되었습니다.

    {
    "id": 1,
    "localDate": null,
    "localDateTime": null,
    "instant": {
        "epochSecond": 1508772600,
        "nano": 0
    },
    "offsetDateTime": {
        "offset": {
            "totalSeconds": 0,
            "id": "Z",
            "rules": {
                "fixedOffset": true,
                "transitionRules": [],
                "transitions": []
            }
        },
        "dayOfMonth": 23,
        "dayOfWeek": "MONDAY",
        "dayOfYear": 296,
        "month": "OCTOBER",
        "monthValue": 10,
        "year": 2017,
        "hour": 15,
        "minute": 30,
        "nano": 0,
        "second": 0
    },
    "zonedDateTime": {
        "offset": {
            "totalSeconds": 0,
            "id": "Z",
            "rules": {
                "fixedOffset": true,
                "transitionRules": [],
                "transitions": []
            }
        },
        "zone": {
            "totalSeconds": 0,
            "id": "Z",
            "rules": {
                "fixedOffset": true,
                "transitionRules": [],
                "transitions": []
            }
        },
        "dayOfMonth": 23,
        "dayOfWeek": "MONDAY",
        "dayOfYear": 296,
        "month": "OCTOBER",
        "monthValue": 10,
        "year": 2017,
        "hour": 15,
        "minute": 30,
        "nano": 0,
        "second": 0,
        "chronology": {
            "id": "ISO",
            "calendarType": "iso8601"
        }
    }
}

POM 파일:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>framework-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <mockito.version>2.11.0</mockito.version>
        <org.mapstruct.version>1.2.0.Final</org.mapstruct.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-jdk8</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>${jackson.version}</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.asciidoctor</groupId>
                <artifactId>asciidoctor-maven-plugin</artifactId>
                <version>1.5.5</version>
                <executions>
                    <execution>
                        <id>output-html</id>
                        <phase>generate-resources</phase>
                        <goals>
                            <goal>process-asciidoc</goal>
                        </goals>
                        <configuration>
                            <backend>html</backend>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

스프링 부트를 2.3.7에서 2.5.1로 업그레이드하면서 이 문제가 발생하기 시작했습니다.

개체 매퍼 @Bean을 정의한 경우 시간 모듈을 등록할 수 있습니다.

@Bean
public ObjectMapper defaultMapper() {
    ObjectMapper objectMapper = new ObjectMapper(); 
    objectMapper.registerModule(new JavaTimeModule()); 
    return objectMapper;
}

대부분의 경우, 코더는 잭슨 직렬화를 사용할 때 "새로운 객체 맵퍼()"를 생성하므로, 시간 모듈이 등록된 사전 구성된 기본값을 자동 배선하는 대신 바닐라 맵퍼를 사용하는 것을 주의해야 합니다.

앞서 언급했듯이 잭슨-데이터 유형-jsr310이 필요하지만 이는 관리되는 버전으로 봄 부팅에 포함되어 있습니다.

개체 매퍼빈을 수동으로 정의하지 않으면 스프링 부트는 등록된 시간 모듈과 함께 개체 매퍼빈을 자동으로 제공합니다.

ObjectMapper 사용자 지정 방법에 따라:

com.fasterxml.jackson.databind 유형의 빈입니다.모듈은 자동으로 구성된 Jackson2ObjectMapperBuilder에 자동으로 등록되고 모듈이 생성하는 ObjectMapper 인스턴스에 적용됩니다.이는 응용프로그램에 새 기능을 추가할 때 사용자 지정 모듈을 제공하는 글로벌 메커니즘을 제공합니다.

종속성을 추가하는 것만으로는 충분하지 않습니다.@Bean다음과 같은 모듈:

@Bean
public Module dateTimeModule(){
    return new JavaTimeModule();
}

플러스jackson-datatype-jsr310모듈이 더 이상 사용되지 않습니다. 대신 JavaTimeModule을 사용해야 합니다.

사용해 볼 수도 있습니다.

ObjectMapper objectMapper = JsonMapper.builder()
        .addModule(new JavaTimeModule())
        .build();

JavaTimeModule은 패키지 com.fasterxml.jackson.datatype.jsr310에서 가져온 것입니다. jackson-datatype-jsr310-2.13.3.jar 라이브러리를 사용했습니다.

해결책은 클래스 경로에 종속성을 추가하는 것이었습니다.어떤 이유에서인지, 그것은 IDE에 없었습니다.

종속성은 더 이상 사용되지 않지만 여전히 사용됩니다.spring-boot-autoconfigure모듈.스프링 부트 코드 참조

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

클래스 경로에 있는 경우 Java 8 날짜 및 시간 개체는 타임스탬프로 직렬화됩니다.

Spring Batch Jackson2에 문제가 있는 경우ExecutionContextStringSerializer 솔루션은 다음과 같습니다.

@Configuration
public class JacksonConfig {

@Bean
public BatchConfigurer batchConfigurer(DataSource dataSource, PlatformTransactionManager transactionManager) {
    return new DefaultBatchConfigurer(dataSource) {
        @Override
        protected JobRepository createJobRepository() throws Exception {
            Jackson2ExecutionContextStringSerializer serializer = new Jackson2ExecutionContextStringSerializer();
            ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule()).findAndRegisterModules();
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
            serializer.setObjectMapper(objectMapper);

            JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
            factory.setDataSource(dataSource);
            factory.setTransactionManager(transactionManager);
            factory.setSerializer(serializer);
            return factory.getObject();
        }
    };
}

}

출처: https://docs.spring.io/spring-batch/docs/current/reference/html/job.html

당신이 현재 JAX-RS용 String Boot 2.5.4 + Apache CXF(Java config)를 사용하고 있는 경우, 아래 구성 문제가 해결되었습니다.

dependencies{
    constraints{
            implementation("org.apache.cxf:cxf-spring-boot-starter-jaxrs:3.4.4")
            implementation("com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.12.5")
            implementation("com.fasterxml.jackson.module:jackson-modules-java8:2.12.5")
    }
}

dependencies{
    implementation("org.apache.cxf:cxf-spring-boot-starter-jaxrs")
    implementation("com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider")
    implementation("com.fasterxml.jackson.module:jackson-modules-java8")
}

스프링 부트(org.springframework.http.converter.json).Jackson2ObjectMapperBuilder)는 클래스 경로에서 다음과 같은 잘 알려진 모듈을 자동으로 등록합니다.

  • jackson-datatype-jdk8 : java.util과 같은 다른 Java 8 유형을 지원합니다.선택적.
  • jackson-datatype-jsr310 : Java 8 Date & Time API 유형 지원
  • jackson-datatype-joda : Joda-Time 유형 지원
  • Jackson-module-kotlin : 필요한 클래스가 클래스 경로에 있는 경우 Kotlin 클래스 및 데이터 클래스에 대한 지원이 자동으로 구성됩니다.

그러나 CXF를 사용하려면 올바른 직렬화 공급자를 사용하여 초기화해야 합니다.또한 Spring Boot 구성된 ObjectMapper를 JacksonJsonProvider에 전달하지 않으면 다음과 같은 오류가 발생합니다.

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type 'java.time.Instant' not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling

이 문제를 해결하려면 아래 코드와 같이 스프링이 구성된 ObjectMapper를 사용하여 JacksonJsonProvider를 시작해야 합니다.

    @Autowired
    DataService dataService;
    @Autowired
    EventService eventService;
    @Autowired
    SessionService sessionService;
    @Autowired
    StatusService statusService;

    @Autowired
    private Bus bus;
    @Autowired
    private ObjectMapper objectMapper;

    @Bean
    public JacksonJsonProvider jsonProvider() {
        JacksonJsonProvider provider = new JacksonJsonProvider();
        provider.setMapper(objectMapper);
        return provider;
    }


    @Bean
    public Server rsServer() {
        JAXRSServerFactoryBean server = new JAXRSServerFactoryBean();
        server.setProviders(Stream.of(jsonProvider()).collect(Collectors.toList()));
        server.setBus(bus);
        server.setServiceBeans(Stream
                .of(dataService, eventService, sessionService, statusService)
                .collect(Collectors.toList()));
        return server.create();
    }

는 Spring Data Couchbase를 했습니다.@Bean의 방식AbstractCouchbaseConfiguration그것은 그것을 생산합니다.ObjectMapperSpring이 자체적으로 생성하고 수정하지 않도록 합니다(자동으로 Java-time 모듈 항목 포함).

다음은 문제가 되는 소스 파일입니다. https://github.com/spring-projects/spring-data-couchbase/blame/4.2.x/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java#L309

버그 보고서는 다음과 같습니다. https://github.com/spring-projects/spring-data-couchbase/issues/1209

이 문제는 Spring-Data-Couchbase 4.3에서 수정되었습니다.

언급URL : https://stackoverflow.com/questions/47120363/java-8-date-time-types-serialized-as-object-with-spring-boot