Mappare la colonna del timestamp del database sul calendario UTC (JPA) e passarlo come data UTC tramite WebService (jax-ws)

StackOverflow https://stackoverflow.com/questions/1821104

  •  10-07-2019
  •  | 
  •  

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.

È stato utile?

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top