2009-10-31 9 views
0

Je me heurte à un problème d'heure d'été inattendu dans le code que je pensais être purement UTC. J'utilise Java 1.6, le mappeur SQL iBatis (2.3.3) et Oracle XE (une version eval d'Oracle 10.2) avec le pilote thin Oracle.Quelle est la bonne façon de gérer les dates UTC en utilisant Java, iBatis et Oracle?

La base de données contient une table qui représente un programme de diffusion télévisée. Chaque "actif" (programme) a une heure de début et une heure de fin. Voici la tranche pertinente:

create table Asset 
(
asset_id  integer not null, -- The unique id of the Asset. 
[...] 
start_time timestamp,  -- The start time. 
end_time  timestamp,  -- The end time. 
[...] 

constraint asset_primary_key primary key (asset_id), 
constraint asset_time   check (end_time >= start_time) 
); 

L'oracle asset_time contrainte de tir pour des programmes qui chevauchent le réglage de l'heure de l'heure d'été centrale américaine ce dimanche matin à venir, 11/1/2009.

J'ai cet objet de transfert de données (les dates sont java.util.Dates):

public class Asset 
{ 
protected Long asset_id; 
[...] 
protected Date start_time; 
protected Date end_time; 

public Date  getStart_time()  { return start_time; } 
public Date  getEnd_time()  { return end_time; } 

public void setStart_time(Date start_time) { this.start_time = start_time; } 
public void setEnd_time(Date end_time)  { this.end_time = end_time; } 
[...] 
} 

Et dans la carte iBatis SQL J'ai cette déclaration qui insère un actif DTO dans la table Oracle Asset:

<insert id="Asset.insert" parameterClass="com.acme.Asset"> 
    insert into Asset 
     (asset_id, [...] start_time, end_time) 
    values 
     (#asset_id#, [...] #start_time#, #end_time#) 
</insert> 

du côté Java j'ai vérifié que je donne iBatis l'entrée de la date UTC correcte via cette affirmation pré-insertion, qui n'est pas jeté:

System.err.println("Inserting asset " + program_id); 
System.err.println(" "+asset.getStart_time_str()+"--"+asset.getEnd_time_str()); 
if (!asset.getEnd_time().after(asset.getStart_time())) { 
System.err.println("Invalid datetime range in asset."); 
throw new AssertionError("Invalid datetime range in asset."); 
} 

Juste avant l'échec de contrainte Oracle les impressions de code ci-dessus:

Inserting asset EP011453960004 
    2009-11-01T06:30:00Z--2009-11-01T07:00:00Z 

Je suis dans le fuseau horaire centrale américaine, GMT -5: 00, donc ce programme commence à 01h30 et se termine à 02h00. L'heure d'été change à 2h00 du matin et revient à 1h00 du matin.

iBatis signale l'échec de contrainte Oracle (modifiée):

2009-10-30 22:58:42,238 [...] Executing Statement: 
    insert into Asset (asset_id, [...] start_time, end_time) 
     values  (?, [...] ?, ?) 
2009-10-30 22:58:42,238 [...] Parameters: 
    [EP011453960004, [...] 2009-11-01 01:30:00.0, 2009-11-01 01:00:00.0] 
2009-10-30 22:58:42,238 [..] Types: 
    [java.lang.Long, [...] java.sql.Timestamp, java.sql.Timestamp] 
2009-10-30 22:58:42,285 [...] - Failed with a SQLException: 
--- The error occurred in com/acme/data/dao/Asset-Write.xml. 
--- The error occurred while applying a parameter map. 
--- Check the Asset.insert-InlineParameterMap. 
--- Check the statement (update failed). 
--- Cause: java.sql.SQLException: ORA-02290: check constraint (ACME.ASSET_TIME) 
              violated 

Vous remarquerez que sur le côté Oracle, il est de voir le start_time/end_time avec le réglage de l'heure de l'heure d'été, donc quelque chose dans la cartographie iBatis logique ou le pilote Oracle ne fait pas ce que je m'attendais. Le pilote est ojdbc14.jar, le conducteur mince:

JDBCReadWrite.Driver  = oracle.jdbc.OracleDriver 
JDBCReadWrite.ConnectionURL = jdbc:oracle:thin:@localhost:1521:XE 

Quelle est la bonne façon de faire en sorte que ce code est purement UTC?

Merci d'avance!

Répondre

5

J'ai une solution qui semble faire l'affaire. Même si l'application et la base de données utilisaient des types qui stockent des décalages horaires à partir de minuit le 1/1/1970 en GMT, la spécification JDBC appelle l'application d'un ajustement de/vers le fuseau horaire par défaut de la JVM. Et iBatis mappe les dates en utilisant la valeur par défaut de JDBC.Les ajustements étaient toujours symétriques et donc inoffensifs tant que les données ne franchissaient pas les limites de l'heure d'été, ou si la machine ou la JVM étaient réglées sur GMT par défaut.

Comme une expérience que je commutées le fuseau horaire par défaut JVM GMT:

TimeZone.setDefault(TimeZone.getTimeZone("UTC")); 

ce qui a résolu le problème, mais d'une manière très lourde (autre code dans la machine virtuelle Java ne peut pas attendre à ce sujet).

Mais iBatis vous permet de remplacer la gestion des types par défaut, quel que soit le niveau de granularité. J'ai écrit un gestionnaire de type préserver GMT et enregistré pour tous mes java.util.Dates:

<typeHandler callback="com.acme.GMTDateTypeHandler" javaType="java.util.Date"/> 

Mon gestionnaire de type ressemble à ceci:

public class GMTDateTypeHandler implements TypeHandlerCallback 
{  
    @Override 
    public void setParameter(ParameterSetter setter, Object parameter) 
     throws SQLException 
    { 
     java.util.Date date = (java.util.Date) parameter; 
     if (date == null) 
      setter.setNull(Types.TIMESTAMP); 
     else 
     { 
      Timestamp timestamp = new Timestamp(date.getTime()); 
      Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); 
      setter.setTimestamp(timestamp, calendar); 
     } 
    } 

    @Override 
    public Object getResult(ResultGetter getter) throws SQLException 
    { 
     Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); 
     return getter.getTimestamp(calendar); 
    } 

    @Override 
    public Object valueOf(String s) 
    { 
     throw new UnsupportedOperationException(
      "GMTDateTypeHandler.valueOf() is not supported."); 
    } 
} 
+0

quel fichier ajoutez-vous ce tag de typehandler? Est-ce supporté par ibaits 2.3? – DDK

0

Généralement, Oracle convertit les valeurs date/heure du fuseau horaire du client en fuseau horaire du serveur lors du stockage des données. Et en arrière, en le relisant. Si vous souhaitez que les valeurs date/heure soient conservées, vous pouvez utiliser une variante du type de données fuseau horaire, le type de données "TIMESTAMP WITH TIME ZONE", qui vous permet de stocker le fuseau horaire avec le fuseau horaire. valeur. Vous pouvez trouver quelques informations ici dans le Oracle SQL data type doc. Recherchez simplement la partie "avec fuseau horaire".

+0

Merci pour l'aide! Je pense maintenant que cette question est à la limite entre iBatis et JDBC. http://java.sun.com/j2b/1.3/docs/guide/jdbc/spec2/jdbc2.1.frame10.html indique que JDBC effectue un mappage de fuseau horaire par défaut, et si je force le fuseau horaire JVM par défaut à l'heure GMT, le problème allez-vous en. Le TypeHandlerCallback par défaut d'iBatis pour Date repose sur ce comportement par défaut, donc je crois que je dois le remplacer par un qui appelle ResultSet.getDate (int, * Calendar *) et PreparedStatement.setDate (int, * Calendar *) pour forcer GMT. –

+0

@jhartelt: +1: Je pense que vous étiez proche de la cible, bien que ce ne soit pas Oracle lui-même qui effectue les conversions, mais le pilote Oracle JDBC implémentant un comportement correct de JDBC. Merci encore! –

Questions connexes