我一直在摸索以下EclipseLink Joda-Time转换器很长一段时间,以便将UTC中的日期时间存储到MySQL数据库中,但根本没有成功。
import java.util.Date;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.converters.Converter;
import org.eclipse.persistence.sessions.Session;
import org.joda.time.DateTime;
public final class JodaDateTimeConverter implements Converter {
private static final long serialVersionUID = 1L;
@Override
public Object convertObjectValueToDataValue(Object objectValue, Session session) {
//Code to convert org.joda.time.DateTime to java.util.Date in UTC.
//Currently dealing with the following line
//that always uses the system local time zone which is incorrect.
//It should be in the UTC zone.
return objectValue instanceof DateTime ? ((DateTime) objectValue).toDate() : null;
}
@Override
public Object convertDataValueToObjectValue(Object dataValue, Session session) {
return dataValue instanceof Date ? new DateTime((Date) dataValue) : null;
}
@Override
public boolean isMutable() {
return true;
}
@Override
public void initialize(DatabaseMapping databaseMapping, Session session) {
databaseMapping.getField().setType(java.util.Date.class);
}
}
objectValue
方法的convertObjectValueToDataValue()
参数是instanceOf
DateTime
,已根据UTC区域。因此,我避免使用.withZone(DateTimeZone.UTC)
。
客户端上已经有一个单独的转换器,它将日期时间的字符串表示形式转换为UTC中的org.joda.time.DateTime
,然后再发送给EJB。)
((DateTime) objectValue).toDate()
方法的return语句中的convertObjectValueToDataValue()
始终采用应位于UTC区域的系统本地时区。
无论如何,应根据UTC区域将日期时间插入MySQL。
最佳/理想的解决方案是,如果它处理Joda的日期时间,类似于Hibernate
修改
作为示例的org.joda.time.DateTime
类型的属性在模型类中指定如下。
@Column(name = "discount_start_date", columnDefinition = "DATETIME")
@Converter(name = "dateTimeConverter", converterClass = JodaDateTimeConverter.class)
@Convert("dateTimeConverter")
private DateTime discountStartDate; //Getter and setter.
答案 0 :(得分:2)
Date
在Java中与时区无关。它始终需要UTC(默认情况下始终为),但当Date
/ Timestamp
通过JDBC驱动程序传递到数据库时,它会根据JVM时区解释日期/时间,默认为系统时间反过来区域(本机操作系统区域)。
因此,除非明确强制MySQL JDBC驱动程序使用UTC区域或JVM本身设置为使用该区域,否则它不会使用UTC将Date
/ Timestamp
存储到目标数据库中虽然MySQL本身被配置为使用default_time_zone='+00:00'
中的my.ini
或my.cnf
部分中的[mysqld]
来使用UTC。像Oracle这样的某些数据库可能会支持带时区的时间戳,这可能是我不熟悉的例外情况(未经测试,因为我目前没有这种环境)。
void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException
将指定参数设置为给定的
java.sql.Timestamp
值, 使用给定的Calendar对象。驱动程序使用Calendar对象 构造一个SQLTIMESTAMP
值,然后驱动程序发送给它 数据库。使用Calendar对象,驱动程序可以计算 时间戳考虑到自定义时区。 如果没有Calendar
个对象 如果指定,则驱动程序使用默认时区,即时区 运行应用程序的虚拟机。
通过检查MySQL JDBC驱动程序实现的setTimestampInternal()
方法的调用,可以进一步澄清这一点。
在setTimestampInternal()
方法的两个重载版本中查看对setTimestamp()
方法的以下two次调用。
/** * Set a parameter to a java.sql.Timestamp value. The driver converts this * to a SQL TIMESTAMP value when it sends it to the database. * * @param parameterIndex the first parameter is 1... * @param x the parameter value * * @throws SQLException if a database access error occurs */ public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { setTimestampInternal(parameterIndex, x, this.connection.getDefaultTimeZone()); } /** * Set a parameter to a java.sql.Timestamp value. The driver converts this * to a SQL TIMESTAMP value when it sends it to the database. * * @param parameterIndex the first parameter is 1, the second is 2, ... * @param x the parameter value * @param cal the calendar specifying the timezone to use * * @throws SQLException if a database-access error occurs. */ public void setTimestamp(int parameterIndex, java.sql.Timestamp x,Calendar cal) throws SQLException { setTimestampInternal(parameterIndex, x, cal.getTimeZone()); }
如果使用Calendar
方法未指定PreparedStatement#setTimestamp()
实例,则将使用默认时区(this.connection.getDefaultTimeZone()
)。
在由连接/ JNDI支持的应用程序服务器/ Servlet容器中使用连接池时,访问或操作数据源,如
com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
(xa) com.mysql.jdbc.jdbc2.optional.MysqlDataSource
(non-xa) 需要强制MySQL JDBC驱动程序使用我们感兴趣的所需时区(UTC),需要通过连接URL的查询字符串提供以下两个参数。
我不熟悉MySQL JDBC驱动程序的历史,但在相对较旧版本的MySQL驱动程序中,可能不需要此参数useLegacyDatetimeCode
。因此,在这种情况下,可能需要调整自己。
例如,对于应用程序服务器GlassFish,可以在创建JDBC领域时设置它们,同时使用管理Web GUI工具或{{1直接。 domain.xml
如下所示(使用XA数据源)。
domain.xml
对于WildFly,可以使用CLI命令或使用管理Web GUI工具(使用XA数据源)在<jdbc-connection-pool datasource-classname="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"
name="jdbc_pool"
res-type="javax.sql.XADataSource">
<property name="password" value="password"></property>
<property name="databaseName" value="database_name"></property>
<property name="serverName" value="localhost"></property>
<property name="user" value="root"></property>
<property name="portNumber" value="3306"></property>
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="characterEncoding" value="UTF-8"></property>
<property name="useUnicode" value="true"></property>
<property name="characterSetResults" value="UTF-8"></property>
<!-- The following two of our interest -->
<property name="serverTimezone" value="UTC"></property>
<property name="useLegacyDatetimeCode" value="false"></property>
</jdbc-connection-pool>
<jdbc-resource pool-name="jdbc_pool"
description="description"
jndi-name="jdbc/pool">
</jdbc-resource>
中配置它们。
standalone-xx.yy.xml
同样的事情适用于非XA数据源。在这种情况下,它们可以直接附加到连接URL本身。
在这两种情况下,这些所有提到的属性都将被设置为JDBC驱动程序中提到的类<xa-datasource jndi-name="java:jboss/datasources/datasource_name"
pool-name="pool_name"
enabled="true"
use-ccm="true">
<xa-datasource-property name="DatabaseName">database_name</xa-datasource-property>
<xa-datasource-property name="ServerName">localhost</xa-datasource-property>
<xa-datasource-property name="PortNumber">3306</xa-datasource-property>
<xa-datasource-property name="UseUnicode">true</xa-datasource-property>
<xa-datasource-property name="CharacterEncoding">UTF-8</xa-datasource-property>
<!-- The following two of our interest -->
<xa-datasource-property name="UseLegacyDatetimeCode">false</xa-datasource-property>
<xa-datasource-property name="ServerTimezone">UTC</xa-datasource-property>
<xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class>
<driver>mysql</driver>
<transaction-isolation>TRANSACTION_READ_COMMITTED</transaction-isolation>
<xa-pool>
<min-pool-size>5</min-pool-size>
<max-pool-size>15</max-pool-size>
</xa-pool>
<security>
<user-name>root</user-name>
<password>password</password>
</security>
<validation>
<valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker"/>
<background-validation>true</background-validation>
<exception-sorter class-name="org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLExceptionSorter"/>
</validation>
<statement>
<share-prepared-statements>true</share-prepared-statements>
</statement>
</xa-datasource>
<drivers>
<driver name="mysql" module="com.mysql">
<driver-class>com.mysql.jdbc.Driver</driver-class>
</driver>
</drivers>
,在这个类中使用它们各自的setter方法。
例如,如果直接使用核心JDBC API,或者在Tomcat中使用连接池,则可以直接将它们设置为连接URL(com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
)
context.xml
其他:
如果目标数据库服务器在DST敏感区域上运行且夏令时(DST)未关闭,则会导致问题。更好地配置数据库服务器也使用不受DST(如UTC或GMT)影响的标准时区。 UTC通常优于GMT,但在这方面两者都相似。直接从this link引用。
如果你真的更喜欢使用当地时区,我建议至少 关闭夏令时,因为日期不明确 你的数据库可能是一场真正的噩梦。
例如,如果您正在构建电话服务并且正在使用 您要求的数据库服务器上的夏令时 麻烦:没有办法告诉客户是否打电话 来自&#34; 2008-10-26 02:30:00&#34;到&#34; 2008-10-26 02:35:00&#34;实际上叫 5分钟或1小时5分钟(假设夏令时) 发生在10月26日凌晨3点)!
顺便说一句,我删除了EclipseLink的专有转换器since JPA 2.1 provides its own standard converter,可以根据需要将其移植到不同的JPA提供程序,而不需要进行任何修改。现在看起来如下<Context antiJARLocking="true" path="/path">
<Resource name="jdbc/pool"
auth="Container"
type="javax.sql.DataSource"
maxActive="100"
maxIdle="30"
maxWait="10000"
username="root"
password="password"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/database_name?useEncoding=true&characterEncoding=UTF-8&useLegacyDatetimeCode=false&serverTimezone=UTC"/>
</Context>
也被java.util.Date
替换。
java.sql.Timestamp
完全由相关应用程序客户端(Servlet / JSP / JSF /远程桌面客户端等)负责在显示或显示日期时根据适当的用户时区转换日期/时间/最终用户的时间,为简洁起见未在本答复中涵盖,并且根据当前问题的性质偏离主题。
转换器中的那些空检查也是不需要的,因为它也只是相关应用程序客户端的责任,除非某些字段是可选的。
现在一切都很顺利。欢迎任何其他建议/建议。任何对我的无知的批评都是最受欢迎的。
答案 1 :(得分:1)
我没有得到问题,尤其是转换为java.util.Date会使用系统时区的声明。以下测试显示了不同且正确的行为:
DateTime joda = new DateTime(2014, 3, 14, 0, 0, DateTimeZone.UTC);
Date d = joda.toDate();
System.out.println(joda.getMillis()); // 1394755200000
System.out.println(d.getTime()); // 1394755200000
当然,如果你打印日期变量d,那么它的toString() - 方法使用系统时区,但对象joda
和d
都代表同一时刻你可以自UTC区域中的UNIX纪元以来的毫秒表示。
例如System.out.println(d);
在我的时区生成此字符串:
Fri Mar 14 01:00:00 CET 2014
但这不是结果的内部状态,也不会存储在数据库中,所以不要混淆或担心。顺便说一句,您需要将结果转换为java.sql.Date或java.sql.Timestamp,具体取决于数据库中的列类型。
修改强>
要确定UTC,您应该更改其他方法convertDataValueToObjectValue()
并使用如下的显式转换:
new DateTime((Date) dataValue, DateTimeZone.UTC)
否则(假设反向方法总是按照你所说的UTC中的DateTime-对象)你可能会得到不对称(我现在不知道JodaTime在没有DateTimeZone参数的构造函数中做了什么 - 不是这样的记录完好?)。
修改-2:强>
测试代码
DateTime reverse = new DateTime(d);
System.out.println(reverse); // 2014-03-14T01:00:00.000+01:00
System.out.println(reverse.getZone()); // Europe/Berlin
清楚地表明没有第二个DateTimeZone参数的DateTime构造函数隐式使用系统时区(我不喜欢在Joda或java.util。*中这样的含义相同)。如果从UTC-DateTime对象前后返回的整个转换不起作用,那么我假设你的DateTime-objects输入可能不是真正的UTC。我建议明确检查一下。否则,我们没有足够的信息来说明您的转换代码无效的原因。