Mapa coluna timestamp banco de dados para UTC Calendar (JPA) e passá-lo como data UTC via WebService (JAX-WS)
Pergunta
Isso soa como uma tarefa simples.
Obter valor timestamp UTC de DB e passá-lo como data UTC via Web Service.
Temos timestamp coluna_data coluna e armazene há tempo no fuso horário UTC.
Com JPA temos desta vez com
@Column(name = "DATE_COLUMN")
private java.sql.Timestamp dateValue;
E como nós temos que passar desta vez via Web Service em UTC (JAX-WS 2.0), temos getDate e métodos SetDate.
Estamos interessados ??em getDate.
public Calendar getDate()
{
Calendar calendar = Calendar.getInstance(utcTimeZone);
calendar.setTimeInMillis(dateValue.getTime());
return calendar;
}
Isto não funciona como você pode pensar que deveria.
E isso é porque fuso horário padrão do aplicativo não é 'UTC'.
Aqui está um exemplo de esclarecimento.
Valor no coluna_data igual a "30.11.09 16: 34: 48,833045000", quando eu traduzi-lo para UTC eu chegar. "2009-11-30T14: 34: 48.833Z"
A diferença é de 2 horas. E isso é porque o meu fuso horário padrão é "Europa / Helsínquia".
Mesmo problema se você quiser apenas para mapear 'coluna_data' para Calendar
@Column(name = "DATE_COLUMN")
@Temporal(TemporalType.TIMESTAMP)
private Calendar dateValue;
public Calendar getDate()
{
calendar.setTimeZone(utcTimeZone);
return calendar;
}
Eu não quero o fuso horário da aplicação de mudança porque ele não se parece com a solução.
Até agora temos apenas duas opções.
Primeiro . Calcule deslocamento entre o fuso horário da aplicação e UTC e adicioná-lo manualmente após a subtração automática no 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;
}
Segundo . Passe DATEVALUE como Long
via Web Service. O que não é ruim, exceto que perdemos tipo real do campo no WSDL.
A minha solução imaginária é
@Column(name = "DATE_COLUMN")
@Temporal(type = TemporalType.TIMESTAMP, timezone = 'UTC')
private Calendar dateValue;
Mas eu tendem a pensar que não é aquele em algum lugar real. E eu espero que você pode indicá-lo.
Solução
Nós decidimos usar seguinte solução.
Use Date
para recuperar data a partir da base de dados. É porque Date
é tipo timezoneless.
@Column(name = "DATE_COLUMN")
@Temporal(TemporalType.TIMESTAMP)
private Date dateValue;
public Date getDate()
{
return dateValue;
}
E para enviá-lo via WebService em UTC (JAX-WS) criamos UtcTimestampAdapter
a zona de mudança do padrão do aplicativo para UTC na fase de empacotamento.
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();
}
}
Em seguida, para activar esta regra a todos os Data
s campos no módulo nós adicionamos definição pacote específico como tal.
@XmlJavaTypeAdapter(value = UtcTimestampAdapter.class, type = Date.class)
@XmlSchemaType(name = "dateTime", type = XMLGregorianCalendar.class)
package com.companyname.modulename;
É isso. Agora temos solução genérica que encapsular toda a lógica em um só lugar. E se queremos enviar datas timezoneless como UTC através do serviço web em algum outro módulo iremos certo pacote apenas anotar.
Outras dicas
Se você precisa o processo java a correr no fuso horário UTC, a maneira mais fácil de fazer isso é adicionando o seguinte parâmetro JVM:
-Duser.timezone=UTC
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
parece estar afetando toda a JVM.
Isto fará com que outras aplicações falhar se eles estavam esperando, hora local
Outra solução é definir o fuso horário padrão para o aplicativo somente em um @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"));
}
}
A partir de então, toda interpretação de objetos Data será baseado no UTC. Isto inclui triagem XML.