4

I am trying to persist java.time.LocalDateTime using Hibernate and JPA. I used Jadira Framework ("org.jadira.usertype:usertype.core:3.2.0.GA" & "org.jadira.usertype:usertype.extended:3.2.0.GA"). I created package-info.java file and created @TypeDefs({@TypeDef(defaultForType = java.time.LocalDateTime.class, typeClass = org.jadira.usertype.dateandtime.threeten.PersistentLocalDateTime.class)}) there. I tested the solution and the java.time.LocalDateTime fields are stored/retrieved to my MySQL database in DATETIME columns (almost) correctly.

The only problem is that the values in database are +2 hours to the correct time value from fields in Java. I'm in CEST (UTC+2) so I understood that this is some problem with time zones. I debugged the code of PersistentLocalDateTime and this is what I found.

  1. PersistentLocalDateTime is using org.jadira.usertype.dateandtime.threeten.columnmapper.AbstractTimestampThreeTenColumnMapper
  2. AbstractTimestampThreeTenColumnMapper has field ZoneOffset databaseZone by default set to ZoneOffset.of("Z") (UTC).
  3. Because it is thinking that my database is in UTC timezone (and the application is in UTC+2) it adds two hours to my time during conversion to database (and subtracts two hours from my time during conversion from database). So in the application I see the correct date and time but in database I not.

I found that a can add parameters to the @TypeDef so I specified them as below:

@TypeDef(defaultForType = LocalDateTime.class, typeClass = PersistentLocalDateTime.class,
    parameters = {
        @Parameter(name = "databaseZone", value = "+02:00")
    }),

but I've got an exception:

java.lang.IllegalStateException: Could not map Zone +02:00 to Calendar
at org.jadira.usertype.dateandtime.threeten.columnmapper.AbstractTimestampThreeTenColumnMapper.getHibernateType(AbstractTimestampThreeTenColumnMapper.java:59)

I debugged a little bit more. AbstractTimestampThreeTenColumnMapper has two methods:

public final DstSafeTimestampType getHibernateType() {

    if (databaseZone == null) {
        return DstSafeTimestampType.INSTANCE;
    }

    Calendar cal = resolveCalendar(databaseZone);
    if (cal == null) {
        throw new IllegalStateException("Could not map Zone " + databaseZone + " to Calendar");
    }

    return new DstSafeTimestampType(cal);
}

private Calendar resolveCalendar(ZoneOffset databaseZone) {

    String id = databaseZone.getId();
    if (Arrays.binarySearch(TimeZone.getAvailableIDs(), id) != -1) {
        return Calendar.getInstance(TimeZone.getTimeZone(id));
    } else {
        return null;
    }
}

getHibernateType method throws the exception because resolveCalendar method returns null. Why it returns null? Because time zones IDs from java.time.ZoneOffset and java.util.TimeZone does not match. As far as I see the only possible value which match is Z. Any other values causes exceptions.

Is there any way to setup this correctly? Or is it a bug in the Jadira Framework?

Piotr Pradzynski
  • 4,190
  • 5
  • 23
  • 43

1 Answers1

1

It looks like a serious bug. The problem is that jadira.usertype.databaseZone parameter is parsed to ZoneOffset instead ZoneId. This way, resolveCalendar method compares 2 different types Zone and Offset. What is funny, parameter is named databaseZone but it does not contain zone. It contains only offset.

https://github.com/JadiraOrg/jadira/issues/42

https://github.com/JadiraOrg/jadira/issues/43

Libre13
  • 211
  • 2
  • 7