在MySQL和Glassfish中保存ZonedDateTime

时间:2015-08-09 21:27:41

标签: java mysql primefaces jsf-2.2 glassfish-4

这是我在这个论坛上的第一个问题,请耐心等待。

Oracle说“ZonedDateTime是一个具有完全限定时区的日期和时间。这可以在任何时间点解决偏移。经验法则是,如果你想在不依赖于上下文的情况下表示日期和时间对于特定服务器,您应该使用ZonedDateTime。“这正是我想要做的,因为应用程序正在全局处理交互,但MySQL似乎只将DATETIME保存为TIMESTAMP,但它显然将其保存为UTC,以便它可以转换为任何时区。我们将运行的服务器将在几个时区运行,我们不知道哪个服务器将在哪里运行,因为云提供商将根据需求和维护动态移动它们。

因此,在这个应用程序中维护日期/时间/区域似乎完全符合新的ZonedDateTime构造,但我一直困惑于试图在PrimeFaces和其他组件代码仍然提供的遗留日期之间保持一切想要处理时间戳的MySQL和MySQL最终会在2038年老化。

我们不想使用任何外部日期库,如Joda或Apache。

我的问题相当简单,但答案似乎对我来说难以捉摸,细微差别似乎很多:将java ZonedDateTime保存到MySQL数据库的最佳做法是什么,将其读回来以便工作可以由用户通过java即时计算全局执行,这些计算看起来适合本地用户,并且无论Glassfish服务器还是MySQL服务器的位置都可能是正确的,这些服务器可能各自处于彼此不同的时区以及从白天到一天?

1 个答案:

答案 0 :(得分:0)

我认为这种方式让我们感到震惊:当MySQL保存为时间戳时,MySQL将日期保存为UTC,所以只要我这样做,MySQL就在哪里并不重要。

Glassfish可以通过查询服务器来告诉您它的居住地,但它也可以为家庭办公室设置一个属性,为您提供在服务器所在的任何地方都具有一致性的操作基础。您可以在web.xml中执行此操作

<context-param>
    <param-name>GLASSFISH_HOME_TIME_ZONE</param-name>
    <param-value>America/New_York</param-value>
</context-param>

数据bean需要完成大部分工作,以便与所有数据使用中的数据保持一致。未更新为ZonedDateTime或仅部分更新的组件库的问题通常会使用getter调用数据,因此使用重载应该允许组件库找到它喜欢的特定方法。我创建了一个看起来像这样的数据bean:

公共类DataBean {

private final ZoneId GLASSFISH_HOME_TIME_ZONE = ZoneId.of(FacesContext.getCurrentInstance().getExternalContext().getInitParameter( "GLASSFISH_HOME_TIME_ZONE"));
private ZonedDateTime dateToUseInGlassfish = null;

public DataBean (
    Timestamp dateFromMySQL) 
{
    if ( dateFromMySQL == null ) {
        this.dateToUseInGlassfish = null;
    } else {
        this.dateToUseInGlassfish = LocalDateTime.ofInstant(dateFromMySQL.toInstant(), GLASSFISH_HOME_TIME_ZONE ).atZone( GLASSFISH_HOME_TIME_ZONE );
    }
}

/** Formatter for Date/Time */
private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("MM/dd/yyyy ' at ' h:mm a z");

/** Formatter for Date only */
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("MM/dd/yyyy");

/** Get the date string formatted with date and time */
public String getDateToUseInGlassfishDateTimeFormatted() {
    if ( dateToUseInGlassfish == null ) { return null; }
    String formattedDate = dateTimeFormatter.format( dateToUseInGlassfish );
    return formattedDate;
}

/** Get the date string formatted with date only */
public String getgetDateToUseInGlassfishDateFormatted() {
    if ( dateToUseInGlassfish == null) { return null; }
    String formattedDate = dateFormatter.format( dateToUseInGlassfish );
    return formattedDate;
}

/** Get the date ZDT formatted (for calculations) */
public ZonedDateTime getgetDateToUseInGlassfish() {
    return dateToUseInGlassfish;
}

/** Get the date as Date (for component libraries that automatically fetch then throw up with ZDT) */
public Date getDateToUseInGlassfishDate() {
    if ( dateToUseInGlassfish == null) { return null; }
    return Date.from( dateToUseInGlassfish.toInstant());
}

/** Set the date from ZDT (results from calculations stored in bean) */
public void setDateToUseInGlassfish( ZonedDateTime dateToUseInGlassfish ) {
    this.dateToUseInGlassfish = dateToUseInGlassfish;
}

/** Set the date from Date with an automatic convert to ZDT */
public void setDateToUseInGlassfish( Date dateToUseInGlassfish ) {
    if (dateToUseInGlassfish == null) {
        this.dateToUseInGlassfish = null;
    } else {
        this.dateToUseInGlassfish = LocalDateTime.ofInstant( Instant.ofEpochMilli( dateToUseInGlassfish.getTime()), GLASSFISH_HOME_TIME_ZONE ).atZone( GLASSFISH_HOME_TIME_ZONE );
    }
}

将日期作为MySQL的时间戳获取,将其作为UTC时间点获取,它看起来像这样:

ResultSet resultSet = preparedSelectQuoteSql.executeQuery()) {
    while (resultSet.next()) {
        quoteBean = new QuoteBean(
            resultSet.getTimestamp("MySQLDateColumn")
        );
    }
}

将它从ZonedDateTime插入/更新到MySQL将自动转换为UTC的时间戳,以便我们可以让MySQL生活在我们想要的任何地方,并及时回读相同的瞬间:

if ( insertValue instanceof ZonedDateTime ) {
    if ( insertValue != null ) {
        Timestamp convertedDate = Timestamp.from( ((ZonedDateTime) insertValue).toInstant() );
        preparedStatement.setTimestamp( paramNumber, convertedDate );
    } else {
        preparedStatement.setNull ( paramNumber, Types.TIMESTAMP );
    }
}

我认为这有效,但我欢迎批评。