将 SQL Server 日期数据获取到 Joda 对象的最佳方法?

发布于 2025-01-07 10:33:48 字数 7991 浏览 5 评论 0原文

TL;DR 版本:请确认(如果没有,请提供帮助)我是否正确地将 SQL Server datetimeoffset 数据获取到我的 Joda Time Java 对象中。


我正在计划将我们的数据库和 Java 代码迁移为时区感知型。为了实现这一目标,我已经仔细阅读了这篇文章并正在尝试实施最佳实践。请注意,所有代码都被视为“一次性”代码,因此我在这里并不真正关心效率;只是正确性。

我们的环境由 Microsoft SQL Server 2008 数据库和 Java 服务层组成,我们可以通过存储过程和 Spring SimpleJdbcCall 访问所有数据。

提到的最佳实践之一是使用 Joda Time 库。由于这对我来说是新的,就像 datetimeoffset SQL 数据类型一样,我想确保我正确执行此操作(因此不会丢失任何信息。)

在 SQL Server 内部,我创建了一个用于测试所有各种 SQL Server get-time-type 函数的表:

CREATE TABLE MIKE_TEMP (
    ID INT NOT NULL IDENTITY,
    BLAH NVARCHAR(255),

    DT_GET_DATE DATETIME DEFAULT GETDATE() NOT NULL,
    DT_GET_UTC_DATE DATETIME DEFAULT GETUTCDATE() NOT NULL,
    DT_SYS_DATE_TIME DATETIME DEFAULT sysdatetime() NOT NULL,
    DT_SYS_UTC_DATE_TIME DATETIME DEFAULT sysutcdatetime() NOT NULL,
    DT_SYS_DATE_TIME_OFFSET DATETIME DEFAULT sysdatetimeoffset() NOT NULL,

    DTO_GET_DATE DATETIMEOFFSET DEFAULT GETDATE() NOT NULL,
    DTO_GET_UTC_DATE DATETIMEOFFSET DEFAULT GETUTCDATE() NOT NULL,
    DTO_SYS_DATE_TIME DATETIMEOFFSET DEFAULT sysdatetime() NOT NULL,
    DTO_SYS_UTC_DATE_TIME DATETIMEOFFSET DEFAULT sysutcdatetime() NOT NULL,
    DTO_SYS_DATE_TIME_OFFSET DATETIMEOFFSET DEFAULT sysdatetimeoffset() NOT NULL
);

在该表中,我添加了一个值,blah = 'Hello World!'。结果数据是:

ID BLAH         DT_GET_DATE         DT_GET_UTC_DATE     DT_SYS_DATE_TIME    DT_SYS_UTC_DATE_TIME DT_SYS_DATE_TIME_OFFSET DTO_GET_DATE                       DTO_GET_UTC_DATE                   DTO_SYS_DATE_TIME                  DTO_SYS_UTC_DATE_TIME              DTO_SYS_DATE_TIME_OFFSET           
-- ------------ ------------------- ------------------- ------------------- -------------------- ----------------------- ---------------------------------- ---------------------------------- ---------------------------------- ---------------------------------- ---------------------------------- 
1  Hello World! 2012-02-15 08:58:41 2012-02-15 14:58:41 2012-02-15 08:58:41 2012-02-15 14:58:41  2012-02-15 08:58:41     2012-02-15 08:58:41.6000000 +00:00 2012-02-15 14:58:41.6000000 +00:00 2012-02-15 08:58:41.6005458 +00:00 2012-02-15 14:58:41.6005458 +00:00 2012-02-15 08:58:41.6005458 -06:00 

有一个相应的存储过程,它只需执行 select * from MIKE_TEMP 并返回所有数据作为输出参数。

访问此数据的 Java 代码是(为了清楚起见,仅包含“有趣的”导入):

import org.joda.time.DateTime;
import java.util.Date;

@Component
public class MikeTempDaoImpl {
    private static final Logger logger = LoggerFactory.getLogger(MikeTempDaoImpl.class);

    private DataSource dataSource;

    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public DataSource getDataSource() {
        return dataSource;
    }

    public MikeVTemp getMikeTemp() {
        SimpleJdbcCall data = new SimpleJdbcCall(getDataSource());

        data.withProcedureName("get_MIKE_TEMP");
        data.withoutProcedureColumnMetaDataAccess();
        data.declareParameters(
                new SqlOutParameter("ID", Types.INTEGER),
                new SqlOutParameter("BLAH", Types.NVARCHAR),
                new SqlOutParameter("DT_GET_DATE", Types.TIMESTAMP),
                new SqlOutParameter("DT_GET_UTC_DATE", Types.TIMESTAMP),
                new SqlOutParameter("DT_SYS_DATE_TIME", Types.TIMESTAMP),
                new SqlOutParameter("DT_SYS_UTC_DATE_TIME", Types.TIMESTAMP),
                new SqlOutParameter("DT_SYS_DATE_TIME_OFFSET", Types.TIMESTAMP),
                new SqlOutParameter("DTO_GET_DATE", Types.TIMESTAMP),
                new SqlOutParameter("DTO_GET_UTC_DATE", Types.TIMESTAMP),
                new SqlOutParameter("DTO_SYS_DATE_TIME", Types.TIMESTAMP),
                new SqlOutParameter("DTO_SYS_UTC_DATE_TIME", Types.TIMESTAMP),
                new SqlOutParameter("DTO_SYS_DATE_TIME_OFFSET", Types.TIMESTAMP)
        );

        Map out;

        try {
            out = data.execute();
        } catch (Exception ex) {
            logger.error(ex.getMessage());
        }

        int id = (Integer) out.get("ID");
        String blah = (String) out.get("BLAH");
        DateTime dtGetDate = new DateTime((Date) out.get("DT_GET_DATE"));
        DateTime dtGetUtcDate = new DateTime((Date) out.get("DT_GET_UTC_DATE"));
        DateTime dtSysDateTime = new DateTime((Date) out.get("DT_SYS_DATE_TIME"));
        DateTime dtSysUtcDateTime = new DateTime((Date) out.get("DT_SYS_UTC_DATE_TIME"));
        DateTime dtSysDateTimeOffset = new DateTime((Date) out.get("DT_SYS_DATE_TIME_OFFSET"));
        DateTime dtoGetDate = new DateTime((Date) out.get("DTO_GET_DATE"));
        DateTime dtoGetUtcDate = new DateTime((Date) out.get("DTO_GET_UTC_DATE"));
        DateTime dtoSysDateTime = new DateTime((Date) out.get("DTO_SYS_DATE_TIME"));
        DateTime dtoSysUtcDateTime = new DateTime((Date) out.get("DTO_SYS_UTC_DATE_TIME"));
        DateTime dtoSysDateTimeOffset = new DateTime((Date) out.get("DTO_SYS_DATE_TIME_OFFSET"));

        MikeTemp mt = new MikeTemp.Builder()
                .id(id)
                .blah(blah)
                .dtGetDate(dtGetDate)
                .dtGetUtcDate(dtGetUtcDate)
                .dtSysDateTime(dtSysDateTime)
                .dtSysUtcDateTime(dtSysUtcDateTime)
                .dtSysDateTimeOffset(dtSysDateTimeOffset)
                .dtoGetDate(dtoGetDate)
                .dtoGetUtcDate(dtoGetUtcDate)
                .dtoSysDateTime(dtoSysDateTime)
                .dtoSysUtcDateTime(dtoSysUtcDateTime)
                .dtoSysDateTimeOffset(dtoSysDateTimeOffset)
                .build();

        System.out.println("id                   = [" + mt.getId() + "]");
        System.out.println("blah                 = [" + mt.getBlah() + "]");
        System.out.println("dtGetDate            = [" + mt.getDtGetDate() + "]");
        System.out.println("dtGetUtcDate         = [" + mt.getDtGetUtcDate() + "]");
        System.out.println("dtSysDateTime        = [" + mt.getDtSysDateTime() + "]");
        System.out.println("dtSysUtcDateTime     = [" + mt.getDtSysUtcDateTime() + "]");
        System.out.println("dtSysDateTimeOffset  = [" + mt.getDtSysDateTimeOffset() + "]");
        System.out.println("dtoGetDate           = [" + mt.getDtoGetDate() + "]");
        System.out.println("dtoGetUtcDate        = [" + mt.getDtoGetUtcDate() + "]");
        System.out.println("dtoSysDateTime       = [" + mt.getDtoSysDateTime() + "]");
        System.out.println("dtoSysUtcDateTime    = [" + mt.getDtoSysUtcDateTime() + "]");
        System.out.println("dtoSysDateTimeOffset = [" + mt.getDtoSysDateTimeOffset() + "]");

        return mvt;
    }
}

这是由 JUnit 测试执行的:

@Test
public void testDateData() throws Exception {
    MikeTemp mt = dao.getMikeTemp();

    assertNotNull("MT should not be null, but it is.", mt);
    assertEquals(1, mt.getId());
    assertEquals("Hello World!", mt.getBlah());
}

所有 println 的结果是:

id                   = [1]
blah                 = [Hello World!]
dtGetDate            = [2012-02-15T08:58:41.577-06:00]
dtGetUtcDate         = [2012-02-15T14:58:41.577-06:00]
dtSysDateTime        = [2012-02-15T08:58:41.580-06:00]
dtSysUtcDateTime     = [2012-02-15T14:58:41.600-06:00]
dtSysDateTimeOffset  = [2012-02-15T08:58:41.600-06:00]
dtoGetDate           = [2012-02-15T08:58:41.600-06:00]
dtoGetUtcDate        = [2012-02-15T14:58:41.600-06:00]
dtoSysDateTime       = [2012-02-15T08:58:41.600-06:00]
dtoSysUtcDateTime    = [2012-02-15T14:58:41.600-06:00]
dtoSysDateTimeOffset = [2012-02-15T08:58:41.600-06:00]

由于该服务器位于美国中部时区,我绝对希望看到 -06:00 的部分结果,但绝对不是全部结果。我是不是在路上错过了什么?在这种情况下,使用 java.util.Date 对象调用 Joda DateTime(Object) 构造函数是否正确?还有什么是我可以/应该做而我没有做的?

谢谢!

TL;DR version: Please confirm (or if not, provide assistance) that I'm getting SQL Server datetimeoffset data into my Joda Time Java objects correctly.


I'm in the middle of planning moving our database and Java code to be time-zone-aware. To accomplish this, I have been all over this post and am attempting to implement the best practices. Note that all code is considered "throw-away" code, so I'm not really concerned with efficiency here; just correctness.

Our environment consists of a Microsoft SQL Server 2008 database and a Java service layer whereby we access all data through stored procedures and Spring SimpleJdbcCall's.

One of the best practices mentioned is to use the Joda Time library. Since this is new to me, as is the datetimeoffset SQL datatype, I'd like to ensure that I'm doing this correctly (and thus not losing any information.)

Inside SQL Server, I created a table for testing all of the various SQL Server get-time-type functions:

CREATE TABLE MIKE_TEMP (
    ID INT NOT NULL IDENTITY,
    BLAH NVARCHAR(255),

    DT_GET_DATE DATETIME DEFAULT GETDATE() NOT NULL,
    DT_GET_UTC_DATE DATETIME DEFAULT GETUTCDATE() NOT NULL,
    DT_SYS_DATE_TIME DATETIME DEFAULT sysdatetime() NOT NULL,
    DT_SYS_UTC_DATE_TIME DATETIME DEFAULT sysutcdatetime() NOT NULL,
    DT_SYS_DATE_TIME_OFFSET DATETIME DEFAULT sysdatetimeoffset() NOT NULL,

    DTO_GET_DATE DATETIMEOFFSET DEFAULT GETDATE() NOT NULL,
    DTO_GET_UTC_DATE DATETIMEOFFSET DEFAULT GETUTCDATE() NOT NULL,
    DTO_SYS_DATE_TIME DATETIMEOFFSET DEFAULT sysdatetime() NOT NULL,
    DTO_SYS_UTC_DATE_TIME DATETIMEOFFSET DEFAULT sysutcdatetime() NOT NULL,
    DTO_SYS_DATE_TIME_OFFSET DATETIMEOFFSET DEFAULT sysdatetimeoffset() NOT NULL
);

Into this table, I added one value, blah = 'Hello World!'. The resulting data is:

ID BLAH         DT_GET_DATE         DT_GET_UTC_DATE     DT_SYS_DATE_TIME    DT_SYS_UTC_DATE_TIME DT_SYS_DATE_TIME_OFFSET DTO_GET_DATE                       DTO_GET_UTC_DATE                   DTO_SYS_DATE_TIME                  DTO_SYS_UTC_DATE_TIME              DTO_SYS_DATE_TIME_OFFSET           
-- ------------ ------------------- ------------------- ------------------- -------------------- ----------------------- ---------------------------------- ---------------------------------- ---------------------------------- ---------------------------------- ---------------------------------- 
1  Hello World! 2012-02-15 08:58:41 2012-02-15 14:58:41 2012-02-15 08:58:41 2012-02-15 14:58:41  2012-02-15 08:58:41     2012-02-15 08:58:41.6000000 +00:00 2012-02-15 14:58:41.6000000 +00:00 2012-02-15 08:58:41.6005458 +00:00 2012-02-15 14:58:41.6005458 +00:00 2012-02-15 08:58:41.6005458 -06:00 

There is a corresponding stored procedure that simply does a select * from MIKE_TEMP and returns all data as output parameters.

The Java code that accesses this data is (only "interesting" imports included for clarity):

import org.joda.time.DateTime;
import java.util.Date;

@Component
public class MikeTempDaoImpl {
    private static final Logger logger = LoggerFactory.getLogger(MikeTempDaoImpl.class);

    private DataSource dataSource;

    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public DataSource getDataSource() {
        return dataSource;
    }

    public MikeVTemp getMikeTemp() {
        SimpleJdbcCall data = new SimpleJdbcCall(getDataSource());

        data.withProcedureName("get_MIKE_TEMP");
        data.withoutProcedureColumnMetaDataAccess();
        data.declareParameters(
                new SqlOutParameter("ID", Types.INTEGER),
                new SqlOutParameter("BLAH", Types.NVARCHAR),
                new SqlOutParameter("DT_GET_DATE", Types.TIMESTAMP),
                new SqlOutParameter("DT_GET_UTC_DATE", Types.TIMESTAMP),
                new SqlOutParameter("DT_SYS_DATE_TIME", Types.TIMESTAMP),
                new SqlOutParameter("DT_SYS_UTC_DATE_TIME", Types.TIMESTAMP),
                new SqlOutParameter("DT_SYS_DATE_TIME_OFFSET", Types.TIMESTAMP),
                new SqlOutParameter("DTO_GET_DATE", Types.TIMESTAMP),
                new SqlOutParameter("DTO_GET_UTC_DATE", Types.TIMESTAMP),
                new SqlOutParameter("DTO_SYS_DATE_TIME", Types.TIMESTAMP),
                new SqlOutParameter("DTO_SYS_UTC_DATE_TIME", Types.TIMESTAMP),
                new SqlOutParameter("DTO_SYS_DATE_TIME_OFFSET", Types.TIMESTAMP)
        );

        Map out;

        try {
            out = data.execute();
        } catch (Exception ex) {
            logger.error(ex.getMessage());
        }

        int id = (Integer) out.get("ID");
        String blah = (String) out.get("BLAH");
        DateTime dtGetDate = new DateTime((Date) out.get("DT_GET_DATE"));
        DateTime dtGetUtcDate = new DateTime((Date) out.get("DT_GET_UTC_DATE"));
        DateTime dtSysDateTime = new DateTime((Date) out.get("DT_SYS_DATE_TIME"));
        DateTime dtSysUtcDateTime = new DateTime((Date) out.get("DT_SYS_UTC_DATE_TIME"));
        DateTime dtSysDateTimeOffset = new DateTime((Date) out.get("DT_SYS_DATE_TIME_OFFSET"));
        DateTime dtoGetDate = new DateTime((Date) out.get("DTO_GET_DATE"));
        DateTime dtoGetUtcDate = new DateTime((Date) out.get("DTO_GET_UTC_DATE"));
        DateTime dtoSysDateTime = new DateTime((Date) out.get("DTO_SYS_DATE_TIME"));
        DateTime dtoSysUtcDateTime = new DateTime((Date) out.get("DTO_SYS_UTC_DATE_TIME"));
        DateTime dtoSysDateTimeOffset = new DateTime((Date) out.get("DTO_SYS_DATE_TIME_OFFSET"));

        MikeTemp mt = new MikeTemp.Builder()
                .id(id)
                .blah(blah)
                .dtGetDate(dtGetDate)
                .dtGetUtcDate(dtGetUtcDate)
                .dtSysDateTime(dtSysDateTime)
                .dtSysUtcDateTime(dtSysUtcDateTime)
                .dtSysDateTimeOffset(dtSysDateTimeOffset)
                .dtoGetDate(dtoGetDate)
                .dtoGetUtcDate(dtoGetUtcDate)
                .dtoSysDateTime(dtoSysDateTime)
                .dtoSysUtcDateTime(dtoSysUtcDateTime)
                .dtoSysDateTimeOffset(dtoSysDateTimeOffset)
                .build();

        System.out.println("id                   = [" + mt.getId() + "]");
        System.out.println("blah                 = [" + mt.getBlah() + "]");
        System.out.println("dtGetDate            = [" + mt.getDtGetDate() + "]");
        System.out.println("dtGetUtcDate         = [" + mt.getDtGetUtcDate() + "]");
        System.out.println("dtSysDateTime        = [" + mt.getDtSysDateTime() + "]");
        System.out.println("dtSysUtcDateTime     = [" + mt.getDtSysUtcDateTime() + "]");
        System.out.println("dtSysDateTimeOffset  = [" + mt.getDtSysDateTimeOffset() + "]");
        System.out.println("dtoGetDate           = [" + mt.getDtoGetDate() + "]");
        System.out.println("dtoGetUtcDate        = [" + mt.getDtoGetUtcDate() + "]");
        System.out.println("dtoSysDateTime       = [" + mt.getDtoSysDateTime() + "]");
        System.out.println("dtoSysUtcDateTime    = [" + mt.getDtoSysUtcDateTime() + "]");
        System.out.println("dtoSysDateTimeOffset = [" + mt.getDtoSysDateTimeOffset() + "]");

        return mvt;
    }
}

This is being exercised by a JUnit test:

@Test
public void testDateData() throws Exception {
    MikeTemp mt = dao.getMikeTemp();

    assertNotNull("MT should not be null, but it is.", mt);
    assertEquals(1, mt.getId());
    assertEquals("Hello World!", mt.getBlah());
}

And the results from all of the println's are:

id                   = [1]
blah                 = [Hello World!]
dtGetDate            = [2012-02-15T08:58:41.577-06:00]
dtGetUtcDate         = [2012-02-15T14:58:41.577-06:00]
dtSysDateTime        = [2012-02-15T08:58:41.580-06:00]
dtSysUtcDateTime     = [2012-02-15T14:58:41.600-06:00]
dtSysDateTimeOffset  = [2012-02-15T08:58:41.600-06:00]
dtoGetDate           = [2012-02-15T08:58:41.600-06:00]
dtoGetUtcDate        = [2012-02-15T14:58:41.600-06:00]
dtoSysDateTime       = [2012-02-15T08:58:41.600-06:00]
dtoSysUtcDateTime    = [2012-02-15T14:58:41.600-06:00]
dtoSysDateTimeOffset = [2012-02-15T08:58:41.600-06:00]

Being as this server is in the US Central Time Zone, I definitely expect to see -06:00 for some of the results, but rather definitely not all of them. Have I missed something somewhere along the way? Is calling the Joda DateTime(Object) ctor with a java.util.Date object the correct thing to do in this situation? What else could/should I be doing that I'm not?

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

云醉月微眠 2025-01-14 10:33:49

如果你想完全避免java.util.Date/Calendar,这就是我在

SQL服务器端所做的:

SELECT CONVERT(NVARCHAR, DateFieldName, 126) AS DateFieldIsoString

其中DateFieldName是一个datetimeoffset > 字段,查询返回一个 java.lang.String

String 解析为 Joda DateTime

DateTime datetime = ISODateTimeFormat.dateTimeParser().withOffsetParsed()
          .parse(dateFieldAsString)

If you want to avoid java.util.Date/Calendar completely, this is what I've done

SQL server side:

SELECT CONVERT(NVARCHAR, DateFieldName, 126) AS DateFieldIsoString

where DateFieldName is a datetimeoffset field, and the query returns a java.lang.String

Parsing the String into a Joda DateTime:

DateTime datetime = ISODateTimeFormat.dateTimeParser().withOffsetParsed()
          .parse(dateFieldAsString)
美煞众生 2025-01-14 10:33:48

不,这不是这样做的方法。您正在使用 DateTime(Object) 构造函数,带有 Date知道时间上的某个时刻,并使用系统默认时间区。

正如 BalusC 所写,如果您想自己指定时区,可以将 Calendar 传递到 ResultSet.getTimestamp() - 但这不是与保留数据库中已存在的信息相同。

目前尚不清楚您正在使用哪个 JDBC 驱动程序,并且您可能必须放弃 SimpleJdbcCall,但 Microsoft JDBC 驱动程序 v3.0 具有 SQLServerCallableStatement.getDateTimeOffset (同上SQLServerResultSet)这可能会做正确的事情。鉴于您的数据类型并不是真正可移植的,这意味着您的代码也不能真正是可移植的:(

您确实需要保留偏移量吗?如果不需要,您可能可以集中精力使用“正常”来获取正确的即时值JDBC 调用 - 不幸的是,目前它似乎没有执行此操作。

No, this isn't the way to do it. You're using the DateTime(Object) constructor with a Date which only knows about an instant in time, and uses the system default time zone.

As BalusC wrote, you could pass a Calendar into ResultSet.getTimestamp() if you want to specify the time zone yourself - but that's not the same as preserving the information that's already present in the database.

It's not clear which JDBC driver you're using, and you'd probably have to move away from SimpleJdbcCall, but the Microsoft JDBC driver v3.0 has SQLServerCallableStatement.getDateTimeOffset (and ditto for SQLServerResultSet) which would presumably do the right thing. Given that your data type isn't really portable, it means your code can't really be either :(

Do you definitely need to preserve the offset? If not, you could probably concentrate on getting the right instant out using the "normal" JDBC calls - which unfortunately it looks like it's not doing at the moment.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文