How to store date-time in UTC into a database using EclipseLink and Joda-Time?
我一直在摸索下面的EclipseLink Joda-Time转换器很长一段时间,将UTC中的日期时间存储到MySQL数据库中,但根本没有成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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); } } |
客户端上已经有一个单独的转换器,它将日期时间的字符串表示形式转换为UTC中的
无论如何,应该根据UTC区域将日期时间插入MySQL。
最佳/理想的解决方案是,如果它处理类似于Hibernate的Joda的日期时间 sub>
编辑:
作为示例的类型
1 2 3 4 |
因此,除非明确强制MySQL JDBC驱动程序使用UTC区域或JVM本身设置为使用该区域,否则即使要配置MySQL本身,它也不会使用UTC将
Sets the designated parameter to the given
java.sql.Timestamp value,
using the given Calendar object. The driver uses the Calendar object
to construct an SQLTIMESTAMP value, which the driver then sends to
the database. With a Calendar object, the driver can calculate the
timestamp taking into account a custom timezone. If noCalendar object
is specified, the driver uses the default timezone, which is that of
the virtual machine running the application.Ok.
通过检查MySQL JDBC驱动程序实现的
请参阅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 /**
* 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());
}
如果使用
在由连接/ JNDI支持的应用程序服务器/ Servlet容器中使用连接池时访问或操作数据源,如
需要强制MySQL JDBC驱动程序使用我们感兴趣的所需时区(UTC),需要通过连接URL的查询字符串提供以下两个参数。
我不熟悉MySQL JDBC驱动程序的历史,但在相对较旧版本的MySQL驱动程序中,可能不需要此参数
例如,对于应用程序服务器GlassFish,可以在创建JDBC领域时将它们与服务器内部的JDBC连接池一起设置,也可以使用管理Web GUI工具或直接在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <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> |
对于WildFly,可以使用CLI命令或使用管理Web GUI工具(使用XA数据源)在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <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> |
同样的事情适用于非XA数据源。在这种情况下,它们可以直接附加到连接URL本身。 sub>
在两种情况下,这些所有提到的属性都将设置为JDBC驱动程序中提到的类,即
例如,如果直接使用核心JDBC API,或者在Tomcat中使用连接池,则可以直接将它们设置为连接URL(在
1 2 3 4 5 6 7 8 9 10 11 12 | <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> |
附加:
如果目标数据库服务器在DST敏感区域上运行且夏令时(DST)未关闭,则会导致问题。更好地配置数据库服务器也使用不受DST(如UTC或GMT)影响的标准时区。 UTC通常优于GMT,但在这方面两者都相似。直接从此链接引用。
If you really prefer to use a local timezone, I recommend at least
turning off Daylight Saving Time, because having ambiguous dates in
your database can be a real nightmare.Ok.
For example, if you are building a telephony service and you are using
Daylight Saving Time on your database server then you are asking for
trouble: there will be no way to tell whether a customer who called
from"2008-10-26 02:30:00" to"2008-10-26 02:35:00" actually called
for 5 minutes or for 1 hour and 5 minutes (supposing Daylight Saving
occurred on Oct. 26th at 3am)!Ok.
顺便说一句,我放弃了EclipseLink的专有转换器,因为JPA 2.1提供了自己的标准转换器,可以根据需要移植到不同的JPA提供程序,而不需要进行任何修改。它现在看起来像下面的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import java.sql.Timestamp; import javax.persistence.AttributeConverter; import javax.persistence.Converter; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @Converter(autoApply = true) public final class JodaDateTimeConverter implements AttributeConverter<DateTime, Timestamp> { @Override public Timestamp convertToDatabaseColumn(DateTime dateTime) { return dateTime == null ? null : new Timestamp(dateTime.withZone(DateTimeZone.UTC).getMillis()); } @Override public DateTime convertToEntityAttribute(Timestamp timestamp) { return timestamp == null ? null : new DateTime(timestamp, DateTimeZone.UTC); } } |
然后,完全由相关应用程序客户端(Servlet / JSP / JSF /远程桌面客户端等)负责根据适当用户的时区转换日期/时间,同时向最终用户显示或呈现日期/时间。为简洁起见,本答案未涉及,并且根据当前问题的性质偏离主题。
转换器中的那些空检查也是不需要的,因为它也只是相关应用程序客户端的责任,除非某些字段是可选的。 sub>
现在一切都很好。欢迎任何其他建议/建议。任何对我的无知的批评都是最受欢迎的。
好。
我没有得到问题,尤其是转换为java.util.Date将使用系统时区的声明。以下测试显示了不同且正确的行为:
1 2 3 4 |
当然,如果你打印日期变量d,那么它的toString() - 方法使用系统时区,但是对象
例如,
Fri Mar 14 01:00:00 CET 2014
但这不是结果的内部状态,也不会存储在数据库中,所以不要混淆或担心。顺便说一下,您需要将结果转换为java.sql.Date或java.sql.Timestamp,具体取决于数据库中的列类型。
编辑:
要确保UTC,您应该更改其他方法
否则(假设反向方法总是以你所说的UTC中的DateTime-对象)你可能会得到不对称(我现在还不知道JodaTime在没有DateTimeZone参数的构造函数中做了什么 - 没有那么好记录?)。
EDIT 2:
测试代码
1 2 3 |
清楚地表明没有第二个DateTimeZone参数的DateTime构造函数隐式使用系统时区(我不喜欢在Joda或java.util。*中这样的implicits相同)。如果从UTC-DateTime对象前后返回的整个转换不起作用,那么我假设你的DateTime-objects输入可能不是真正的UTC。我建议明确检查一下。否则,我们没有足够的信息来说明您的转换代码无效的原因。