2009-10-31 1 views
0

Я подхожу против неожиданной проблемы с летним временем в коде, который, как я думал, был чисто UTC. Я использую Java 1.6, iBatis SQL mapper (2.3.3) и Oracle XE (эквивалентную версию Oracle 10.2) с тонким драйвером Oracle.Каков правильный способ обработки дат UTC с использованием Java, iBatis и Oracle?

База данных содержит таблицу, которая представляет собой расписание телевизионного вещания. Каждая «Asset» (программа) имеет время начала и окончания. Вот соответствующий фрагмент:

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) 
); 

Оракул asset_time ограничения стрельбы для программ, расположенных вдоль США сберегательных центрального дневного света регулировки времени это предстоящее воскресенье утром, 11/1/2009.

У меня есть эта передача данных объекта (Даты 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; } 
[...] 
} 

И на карте Ibatis SQL у меня есть это утверждение, что вставляет актив DTO в таблицу 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> 

на стороне Java я проверил, что я даю Ibatis правильного ввод даты UTC с помощью этого предварительной вставки утверждения, не брошенного:

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."); 
} 

Просто до отказа ограничения Oracle приведенный выше код печатает:

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

Я в центральном часовом поясе США, GMT -5: 00, так что эта программа начинается в 1:30 и заканчивается в 2:00 утра. Переход на летнее время меняется в 2 часа ночи и возвращает часы в 1:00 утра.

Ibatis сообщает отказ Oracle ограничение (отредактированный):

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 

Вы заметите, что на стороне Oracle, он видит start_time/END_TIME с регулировкой времени перехода на летнее, так что что-то в отображении Ibatis логика или драйвер Oracle не делают то, что я ожидал. Водитель ojdbc14.jar, тонкий водитель:

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

Что такое правильный способ, чтобы гарантировать, что этот код чисто UTC?

Заранее благодарен!

ответ

5

У меня есть решение, которое, кажется, делает трюк. Несмотря на то, что в приложении и в базе данных использовались типы, которые хранят временные смещения с полуночи 1/1/1970 в GMT, спецификация JDBC требует применения корректировки от/к часовому поясу JVM по умолчанию. И iBatis отображает даты с использованием JDBC по умолчанию.Корректировки всегда были симметричными и, следовательно, безвредными, если данные не пересекали границу летнего времени, или если машина или JVM были установлены по умолчанию по умолчанию.

В качестве эксперимента я переключился на JVM часовой пояс по умолчанию GMT:

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

и это решило проблему, хотя и в очень тяжелой рукой способом (другой код в JVM не может ожидать, что это).

Но iBatis позволяет переопределять обработку по умолчанию по умолчанию на любом уровне детализации. Я написал GMT сохраняющего обработчик типа и зарегистрировал его для всех моих java.util.Dates:

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

Моего обработчик типа выглядит следующим образом:

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

В каком файле вы добавляете этот тег handler? Поддерживается ли она в ibaits 2.3? – DDK

0

Обычно Oracle преобразует значения даты/времени из часового пояса клиента в часовой пояс сервера при хранении данных. И назад, когда читаешь это снова.

Если вы хотите, чтобы значения даты и времени сохранялись без изменений, вы можете использовать вариант типа данных часового пояса - «TIMESTAMP WITH TIME ZONE Datatype», который позволяет хранить часовой пояс с помощью стоимость. Вы можете найти информацию здесь, в Oracle SQL data type doc. Просто выполните поиск части с часовым поясом.

+0

Спасибо за помощь! Теперь я думаю, что этот вопрос находится на грани между iBatis и JDBC. http://java.sun.com/j2se/1.3/docs/guide/jdbc/spec2/jdbc2.1.frame10.html указывает, что JDBC делает отображение часового пояса по умолчанию, и если я заставляю часовой пояс по умолчанию JVM на GMT, проблема уходи. IBatis по умолчанию TypeHandlerCallback for Date полагается на это поведение по умолчанию, поэтому я считаю, что мне нужно переопределить его с помощью метода, который вызывает ResultSet.getDate (int, * Calendar *) и PreparedStatement.setDate (int, * Calendar *), чтобы заставить GMT. –

+0

@jhartelt: +1: Я думаю, что вы были близки к знаку, хотя сам Oracle не делает конверсии, а драйвер JDBC Oracle реализует правильное поведение JDBC. Еще раз спасибо! –