0

I'm using Spring Boot 1.5.3, Spring Data REST, Spring JPA, Hibernate. I'm working in UTC with java.time.* in the server, and my clients are supposed to send back dates in UTC too. I customized a bit my REST configuration:

@Configuration
public class RestConfig extends RepositoryRestConfigurerAdapter {

@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
    return new Jackson2ObjectMapperBuilderCustomizer() {

        @Override
        public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {

            jacksonObjectMapperBuilder.serializers(InstantSerializer.INSTANCE);
            jacksonObjectMapperBuilder.serializers(new ZonedDateTimeSerializer(ISO_FIXED_FORMAT));
            jacksonObjectMapperBuilder
                    .serializers(new LocalDateSerializer(new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd").toFormatter()));
            jacksonObjectMapperBuilder.serializers(new LocalDateTimeSerializer(
                    new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").toFormatter()));
        }

    };
}

}

I created a custom method in a repository:

@Transactional
@PreAuthorize("isAuthenticated()")
public interface DailyCodeRepository extends PagingAndSortingRepository<DailyCode, Long> {


    @Query("SELECT d FROM DailyCode d WHERE (:code IS NULL OR code=:code) AND (:from IS NULL OR date>=:from) AND (:to IS NULL OR date<=:to)")
    public Page<DailyCode> findAllWithParameter(@Param("code") @RequestParam(value = "code", required = false) String code,
            @Param("from") @RequestParam(value = "from", required = false) @DateTimeFormat(iso=ISO.DATE) LocalDate from,
            @Param("to") @RequestParam(value = "to", required = false) @DateTimeFormat(iso=ISO.DATE) LocalDate to, Pageable pageable);
}

The first problem is that the method accept the ISO format only if I put the annotation @DateTimeFormat(iso=ISO.DATE), otherwise it picks the format of my locale (Italy). I want to set it globally as done for the response (see my Jackson2ObjectMapperBuilderCustomizer).

The second problem is that date parameters sent from the client are interpreted like 1 day before, so a request like this:

http://localhost:8080/api/v1/dailyCodes/search/findAllWithParameter?from=2017-07-07&to=2017-07-07

causes this query on the database:

select dailycode0_.`id` as id1_7_, dailycode0_.`created_by` as created_2_7_, dailycode0_.`created_date` as created_3_7_, dailycode0_.`last_modified_by` as last_mod4_7_, dailycode0_.`last_modified_date` as last_mod5_7_, dailycode0_.`sid` as sid6_7_, dailycode0_.`version` as version7_7_, dailycode0_.`code` as code8_7_, dailycode0_.`date` as date9_7_ from `daily_code` dailycode0_ where (null is null or dailycode0_.`code`=null) and ('2017-07-06' is null or dailycode0_.`date`>='2017-07-06') and ('2017-07-06' is null or dailycode0_.`date`<='2017-07-06') limit 20

So it queries 1 day before and it's wrong. I guess it's a timezone problem but I can't figure out how to solve that.

This is the relevant part of my properties file:

spring.mvc.date-format= `yyyy-MM-dd`
# REST
spring.data.rest.default-page-size= 20
spring.data.rest.base-path=/api/v1
spring.data.rest.enable-enum-translation=true

#Jackson

# to avoid an error loading lazy objects
spring.jackson.serialization.fail-on-empty-beans=false
spring.jackson.serialization.write-dates-as-timestamps=false
spring.jackson.mapper.infer-property-mutators=false
spring.jpa.properties.hibernate.jdbc.time_zone = UTC
drenda
  • 5,846
  • 11
  • 68
  • 141

1 Answers1

1

You've several issues in your code:

The first problem is that the method accept the ISO format only

You need a OffsetDateTime (not ZonedDateTime, read this) and custom converter. Read this.

date parameters sent from the client are interpreted like 1 day before

Why? What time zone are you interpreting this date for? Your local time zone may be different from the server's which may be different from the client's.

Jackson2ObjectMapperBuilderCustomizer

What's this used for?

spring.mvc.date-format=yyyy-MM-dd

This shouldn't be necessary, and I don't think does anything for Spring data anyway.

Abhijit Sarkar
  • 21,927
  • 20
  • 110
  • 219
  • The method should accept just a date without time information. I don't need that in this case. I'm using Spring Data REST facility, so there is not implementation of that method. The server time zone is Europe/Rome, but I'm working and storing dates in UTC format in the DB thanks to the configuration shown. About Jackson2ObjectMapperBuilderCustomizer I edited my question adding the class, according to https://github.com/spring-projects/spring-boot/issues/4217 – drenda Jul 07 '17 at 19:06
  • @drenda You obviously need to convert the string sent by the client into a date time, regardless of whatever you send to `findAllWithParameter`. If you blindly pass the string sent by the client to the method, you're assuming all your clients come from the same location, which is obviously wrong. Then there's the question of conversion between `findAllWithParameter` and DB time, which you say is UTC. Spring Data methods can have bodies if they need to. You'll need a custom repository in that case. – Abhijit Sarkar Jul 07 '17 at 19:10
  • Because use UTC is a best practice, my client (in this case an Angular application) will send all dates in UTC format. So I don't see the need to have a lot of boilerplate code for a thing that should be quite standard. Thanks – drenda Jul 07 '17 at 19:40
  • @drenda If date time handling was "quite standard", they'd not have rewritten the code in JDK 8. Also, the important piece of information missing in your question is that you're depending on the client to do the conversion from local time to UTC. Anyways, sounds like you've found what you came here for. – Abhijit Sarkar Jul 07 '17 at 20:04
  • Use UTC is a best practice and because of that I guess manage that is common use case. Anyway I didn't find the solution to my problems regarding the code. – drenda Jul 07 '17 at 20:08
  • 1
    @drenda Let's rehash it then. 1. You can't set date format globally in Spring data. 2. Like I said multiple times before, your method `findAllWithParameter` takes a `LocalDate`, not UTC, your client sends a String, somewhere you're converting the String to `LocalDate` and that's where the bug is. `LocalDate` is obviously not UTC if you're In Italy. You need to convert the client string to UTC using a time zone. – Abhijit Sarkar Jul 07 '17 at 20:33