【问题标题】:JPA EclipseLink PostgreSQL Insert java.time.LocalDate as java.sql.Date inserts wrong dateJPA EclipseLink PostgreSQL Insert java.time.LocalDate as java.sql.Date 插入错误的日期
【发布时间】:2017-04-03 15:04:43
【问题描述】:

我的行为很奇怪。我正在创建一个具有 LocalDate 字段的实体,并在数据库中插入,但数据库中的值在某些情况下会发生更改。这是它使用调试器记录的 SQL:

[EL Fine]: sql: 2016-11-19 21:31:57.979
--ClientSession(1809129176)--Connection(1261635736)
--Thread(Thread[main,5,main])
--INSERT INTO vcc_task (creator_user, execution_end_time, execution_start_time, last_edit_time, last_edit_user, server_name, state, thread_name, version, task_type, cc_service_id, vcc, start_time, end_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
bind => [user@vccModel1, null, null, 2016-11-20 00:31:46.016, user@vccModel1, , 0, , 1, 0, c1, vccModel1, 2017-05-06, 2017-05-07]

注意“2017-05-06, 2017-05-07”。 但是如果我查询 postgreSQL:

SELECT * FROM vcc_task

我编写的其他测试没有表现出错误。他们将正确的日期保存在数据库中。所以,我很困惑。

如果我使用与存储 taskEntity 相同的 EntityManager 查询数据库,我会得到一个具有正确日期的实体。但是,如果我使用不同的 EntityManager 进行查询,我会从数据库中获取日期,即正确日期的前一天。这是有道理的,因为 EM 正在从其缓存中返回。但我仍然不明白是谁在改变 Date,为什么有时会,有时不会。

例如,使用两个 LocalDate 构造函数通过以下测试:

  // private final LocalDate startTime = LocalDate.parse("2017-05-06");
  // private final LocalDate endTime = LocalDate.parse("2017-06-06");
  private final LocalDate startTime = Instant.parse("2017-05-06T00:00:00Z").atZone(ZoneOffset.UTC).toLocalDate();
  private final LocalDate endTime = Instant.parse("2017-06-06T00:00:00Z").atZone(ZoneOffset.UTC).toLocalDate();

  em = MyEntityManager.getEntityManager();
  final TaskDT tdt = new TaskDT(taskType, ccService, startTime, endTime, userName);
  t1 = new TaskEntity(userName, vcc, tdt);
  PersistLogic.persist(em, t1);
  em.close();

  em = MyEntityManager.getEntityManager();
  final TaskEntity t2 = em.find(TaskEntity.class, tid);
  em.close();

  Assert.assertNotNull(t2, "Task does exist");      
  Assert.assertEquals(t2.getVersion(), 1, "Resource Version");
  Assert.assertEquals(t2.getLastEditUser(), userName, "Resource Version");

  Assert.assertEquals(t2.getId().getVcc(), vcc, "getVcc");
  Assert.assertEquals(t2.getId().getTaskType(), taskType, "getTaskType");
  Assert.assertEquals(t2.getId().getCcService(), ccService, "getCcService");
  Assert.assertEquals(t2.getId().getStartTime(), startTime, "getStartTime");
  Assert.assertEquals(t2.getId().getEndTime(), endTime, "getEndTime");
  ...

当我在代码中创建实体时出现问题:

// create the task
final LocalDate taskFromD = toLocalDate(tmpFrom);
final LocalDate taskToD = toLocalDate(tmpTo);
final TaskDT taskDT = new TaskDT(taskType, ccServiceName, taskFromD, taskToD, userName);
final TaskEntity newTask = new TaskEntity(userName, vccName, taskDT);

PersistLogic.persist(em, newTask);

它看起来一样,但行为不同。

更新

这就是我将 Instant 转换为 LocalDate 以传递给实体的方式:

private static LocalDate toLocalDate(Instant i) {
   return i.atZone(ZoneOffset.UTC).toLocalDate();
}

这是我将 LocalDate 转换为 Date 并返回的方式:

import java.sql.Date;
import java.time.LocalDate;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter(autoApply = true)
public class LocalDateAttributeConverter implements AttributeConverter<LocalDate, Date> {

    @Override
    public Date convertToDatabaseColumn(LocalDate locDate) {
        return (locDate == null ? null : Date.valueOf(locDate));
    }

    @Override
    public LocalDate convertToEntityAttribute(Date sqlDate) {
        return (sqlDate == null ? null : sqlDate.toLocalDate());
    }
}

任何帮助将不胜感激。 谢谢

【问题讨论】:

  • LocalDate 隐含相对于本地时区,无论发生什么。 Date 是较新的 Java Instant 的旧 Java API 模拟,在内部使用 UTC。您能否将您的LocalDate 实例转换为Instants?如果是,它们是否表现出您所看到的行为?
  • 我更新了将 LocalDate 转换为 Date 并返回的代码。通过的测试用例使用通过解析字符串构造的 LocalDate。但是在代码中,失败了,我从 Instant 创建了 LocalDate。会不会有区别?
  • 产生这个问题是因为我有这个设置:TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
  • 如果我删除该设置,日期会正确存储在数据库中。
  • 我遇到的问题是我需要保留设置以正确存储和读取 Instant 对象。

标签: java postgresql jpa eclipselink localdate


【解决方案1】:

我猜问题是convertToDatabaseColumn()方法中调用Date.valueOf(locDate),这里生成的瞬间是这个日期的午夜在你系统的默认时区

【讨论】:

  • 不,这不是问题所在。在 convertToDatabaseColumn 方法中,我直接从 java.time.LocalDate 转换为 java.sql.Date。我没有使用 Instant 类。并且在调试时,java.time.LocalDate 正确转换为 sql.Date。这就像一个框架问题。我不得不决定使用 Instant 而不是 LocalDate。使用 Instant 课程,我不会出现转换问题。
  • LocalDate 与时区无关,而 java.sql.Date 则有。如果您在时区设置为 UTC+4 的系统上执行此转换,则结果将是 UTC+4 中今天的 0:00,它等于 UTC+0 中前一天的 20:00 - 只是由于时区的性质。
猜你喜欢
  • 1970-01-01
  • 2014-10-24
  • 1970-01-01
  • 1970-01-01
  • 2018-08-02
  • 2013-01-29
  • 1970-01-01
  • 2012-11-08
  • 1970-01-01
相关资源
最近更新 更多