Mappare la colonna del timestamp del database sul calendario UTC (JPA) e passarlo come data UTC tramite WebService (jax-ws)
Domanda
Sembra un compito semplice.
Ottieni il valore di data e ora UTC dal DB e passalo come data UTC tramite il servizio Web.
Abbiamo la colonna DATE_COLUMN del timestamp e memorizziamo l'ora nel fuso orario UTC.
Con JPA abbiamo questa volta con
@Column(name = "DATE_COLUMN")
private java.sql.Timestamp dateValue;
E poiché dobbiamo passare questa volta tramite il servizio Web in UTC (Jax-ws 2.0) abbiamo i metodi getDate e setDate.
Siamo interessati a getDate.
public Calendar getDate()
{
Calendar calendar = Calendar.getInstance(utcTimeZone);
calendar.setTimeInMillis(dateValue.getTime());
return calendar;
}
Questo non funziona come potresti pensare.
E questo perché il fuso orario predefinito dell'applicazione non è "UTC".
Ecco un esempio per chiarimenti.
Il valore in DATE_COLUMN è uguale a "30.11.09 16: 34: 48,833045000", quando lo traduco in UTC ricevo "2009-11-30T14: 34: 48.833Z".
La differenza è di 2 ore. E questo perché il mio fuso orario predefinito è "Europa / Helsinki".
Stesso problema se si desidera solo mappare 'DATE_COLUMN' su Calendar
@Column(name = "DATE_COLUMN")
@Temporal(TemporalType.TIMESTAMP)
private Calendar dateValue;
public Calendar getDate()
{
calendar.setTimeZone(utcTimeZone);
return calendar;
}
Non voglio cambiare il fuso orario dell'applicazione perché non sembra la soluzione.
Ormai abbiamo solo due opzioni.
prima . Calcola l'offset tra fuso orario dell'applicazione e UTC e aggiungilo manualmente dopo la sottrazione automatica in calendar.setTimeZone.
public Calendar getDate()
{
Calendar calendar = Calendar.getInstance(utcTimeZone);
calendar.setTimeInMillis(dateValue.getTime());
int offset = TimeZone.getDefault().getOffset(dateValue.getTime());
calendar.add(Calendar.MILLISECOND, offset);
return calendar;
}
Secondo . Passare dateValue come Long
tramite il servizio Web. Il che non è male se non che perdiamo il tipo reale del campo in wsdl.
La mia soluzione immaginaria è
@Column(name = "DATE_COLUMN")
@Temporal(type = TemporalType.TIMESTAMP, timezone = 'UTC')
private Calendar dateValue;
Ma tendo a pensare che ci sia quello vero da qualche parte. E spero che tu possa indicarlo.
Soluzione
Abbiamo deciso di utilizzare la seguente soluzione.
Utilizzare Date
per recuperare la data dal database. È perché Date
è di tipo senza fuso orario.
@Column(name = "DATE_COLUMN")
@Temporal(TemporalType.TIMESTAMP)
private Date dateValue;
public Date getDate()
{
return dateValue;
}
E per inviarlo via WebService in UTC (jax-ws) abbiamo creato UtcTimestampAdapter
per cambiare la zona dall'impostazione predefinita dell'applicazione a UTC nella fase di marshalling.
public class UtcTimestampAdapter extends XmlAdapter<XMLGregorianCalendar, Date>
{
@Override
public XMLGregorianCalendar marshal(Date date) throws Exception
{
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(date);
DatatypeFactory dataTypeFactory = DatatypeFactory.newInstance();
XMLGregorianCalendar xmlCalendar =
dataTypeFactory.newXMLGregorianCalendar(calendar);
//Reset time zone to UTC
xmlCalendar.setTimezone(0);
return xmlCalendar;
}
@Override
public Date unmarshal(XMLGregorianCalendar calendar) throws Exception
{
return calendar.toGregorianCalendar().getTime();
}
}
Quindi per abilitare questa regola a tutti i campi Data
nel modulo abbiamo aggiunto impostazioni specifiche del pacchetto in questo modo.
@XmlJavaTypeAdapter(value = UtcTimestampAdapter.class, type = Date.class)
@XmlSchemaType(name = "dateTime", type = XMLGregorianCalendar.class)
package com.companyname.modulename;
Questo è tutto. Ora abbiamo una soluzione generica che incapsula tutta la logica in un unico posto. E se vogliamo inviare date senza fuso orario come UTC tramite il servizio web in qualche altro modulo, annoteremo semplicemente un determinato pacchetto.
Altri suggerimenti
Se è necessario eseguire il processo java nel fuso orario UTC, il modo più semplice per farlo è aggiungendo il seguente parametro JVM:
-Duser.timezone=UTC
TimeZone.setDefault (TimeZone.getTimeZone (" UTC "));
sembra influenzare l'intera JVM.
Ciò causerà il fallimento di altre applicazioni se si aspettavano l'ora locale
Un'altra soluzione è impostare il fuso orario predefinito per l'applicazione solo in un @StartupBean
:
import java.util.TimeZone;
import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.ejb.Startup;
@Startup
@Singleton
public class StartupBean {
@PostConstruct
public void initializeTheServer() {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}
}
Da quel momento in poi, tutta l'interpretazione degli oggetti Date sarà basata su UTC. Ciò include il marshalling XML.