我的MySQL数据库中有一个表有一个日期列:
+-------------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+---------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| type | varchar(50) | NO | | NULL | |
| expiration | date | NO | | NULL | |
我正在使用MySQL和JPA来保存日期。我有一个功能,用户可以选择最终的日期范围,它将获得所有日期。
查看这段代码(带有一堆SYSO),试着看看最近发生了什么......
@Override
protected DateTime nextReference(DateTime reference) {
System.out.println("Reference: " + reference.toString("dd-MM-YYYY"));
DateTime plus = reference.plusMonths(1);
System.out.println("One month from now: " + plus.toString("dd-MM-YYYY"));
DateTime result = plus.withDayOfMonth(reference.getDayOfMonth());
System.out.println("Final: " + result.toString("dd-MM-YYYY"));
return result;
}
这部分,结果很好:
Reference: 10-01-2017
One month from now: 10-02-2017
Final: 10-02-2017
Reference: 10-02-2017
One month from now: 10-03-2017
Final: 10-03-2017
Reference: 10-03-2017
One month from now: 10-04-2017
Final: 10-04-2017
Reference: 10-04-2017
One month from now: 10-05-2017
Final: 10-05-2017
Reference: 10-05-2017
One month from now: 10-06-2017
Final: 10-06-2017
Reference: 10-06-2017
One month from now: 10-07-2017
Final: 10-07-2017
Reference: 10-07-2017
One month from now: 10-08-2017
Final: 10-08-2017
Reference: 10-08-2017
One month from now: 10-09-2017
Final: 10-09-2017
Reference: 10-09-2017
One month from now: 10-10-2017
Final: 10-10-2017
Reference: 10-10-2017
One month from now: 10-11-2017
Final: 10-11-2017
Reference: 10-11-2017
One month from now: 10-12-2017
Final: 10-12-2017
Reference: 10-12-2017
One month from now: 10-01-2018
Final: 10-01-2018
好的,现在让我们转到保存部分:
@Transactional
private void saveTransactions(List<Transaction> transactions) {
for (Transaction t : transactions) {
System.out.println("Saving: " + t.getExpiration().toString("dd-MM-YYYY"));
Transaction saved = dao.save(t);
System.out.println("Saved: " + saved.getExpiration().toString("dd-MM-YYYY"));
}
}
正如你所看到的,我也设置了一些线来调试它....在继续输出之前,请查看DAO:
public T save(T entity) {
entityManager.persist(entity);
return entity;
}
没什么大不了的...输出:
Saving: 10-02-2017
Saved: 10-02-2017
Saving: 10-03-2017
Saved: 10-03-2017
Saving: 10-04-2017
Saved: 10-04-2017
Saving: 10-05-2017
Saved: 10-05-2017
Saving: 10-06-2017
Saved: 10-06-2017
Saving: 10-07-2017
Saved: 10-07-2017
Saving: 10-08-2017
Saved: 10-08-2017
Saving: 10-09-2017
Saved: 10-09-2017
Saving: 10-10-2017
Saved: 10-10-2017
Saving: 10-11-2017
Saved: 10-11-2017
Saving: 10-12-2017
Saved: 10-12-2017
你可以看到......它应该没问题吧?一切都在十号。
在我再次继续之前,检查模型和转换器:
//Attribute
@Convert(converter = JpaDateConverter.class)
private DateTime expiration;
//Converter
public class JpaDateConverter implements AttributeConverter<DateTime, Date> {
@Override
public Date convertToDatabaseColumn(DateTime objectValue) {
return objectValue == null ? null : new Date(objectValue.getMillis());
}
@Override
public DateTime convertToEntityAttribute(Date dataValue) {
return dataValue == null ? null : new DateTime(dataValue);
}
}
现在看看我的数据库:
mysql> select expiration from tb_transaction where notes = 3 and year(expiration
) = 2017;
+------------+
| expiration |
+------------+
| 2017-01-10 |
| 2017-02-10 |
| 2017-03-09 |
| 2017-04-09 |
| 2017-05-09 |
| 2017-06-09 |
| 2017-07-09 |
| 2017-08-09 |
| 2017-09-09 |
| 2017-10-09 |
| 2017-11-10 |
| 2017-12-10 |
+------------+
12 rows in set (0.00 sec)
对于一些,奇怪的,神秘的原因,一些日期保存在9日而不是10日!
没有警告,MySQL驱动程序没有错误或什么都没有。
请帮助伙计!
编辑交易类:
@Entity
@Table(name = "tb_transaction")
public class Transaction implements Cloneable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated(STRING)
private TransactionType type;
@Convert(converter = JpaDateConverter.class)
private DateTime expiration;
使用JPA 2.2支持java.time。
在Java中使用仅限日期的类来处理SQL中的仅日期值。
LocalDate // Represent a date-only, without a time-of-day and without a time zone.
.now( // Get today's date…
ZoneId.of( "Africa/Tunis" ) // …as seen in the wall-clock time used by the people of a particular region.
) // Returns a `LocalDate` object.
.plusMonths( 1 ) // Returns another `LocalDate` object, per immutable objects pattern.
JPA 2.2现在支持现代java.time类。无需再使用Joda-Time。
不要使用java.sql.Date
。那个班假装代表一个仅限日期,但实际上有一个时间设置为UTC,因为从java.util.Date
继承的可怕的设计决定(尽管名称代表一个日期和一天的时间和偏移量UTC本身为零)。这些遗留类是一个可怕的可怜的混乱。几年前Sun,Oracle和JCP社区都采用了JSR 310,所以你们都应该放弃这些课程。
LocalDate
LocalDate
类表示没有时间且没有time zone或offset-from-UTC的仅日期值。
时区对于确定日期至关重要。对于任何给定的时刻,日期在全球范围内因地区而异。例如,在Paris France午夜过后几分钟是新的一天,而在Montréal Québec仍然是“昨天”。
如果未指定时区,则JVM会隐式应用其当前的默认时区。在运行时(!)期间,该默认值可能是change at any moment,因此您的结果可能会有所不同。最好明确指定您期望/预期的时区作为参数。如果关键,请与您的用户确认该区域。
以proper time zone name的格式指定Continent/Region
,例如America/Montreal
,Africa/Casablanca
或Pacific/Auckland
。切勿使用2-4字母缩写,如EST
或IST
,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ;
如果要使用JVM的当前默认时区,请求它并作为参数传递。如果省略,代码变得模糊不清,因为我们不确定您是否打算使用默认值,或者如果您像许多程序员一样,不知道这个问题。
ZoneId z = ZoneId.systemDefault() ; // Get JVM’s current default time zone.
或指定日期。您可以将月份设置为一个数字,1月至12月的数字为1-12。
LocalDate ld = LocalDate.of( 1986 , 2 , 23 ) ; // Years use sane direct numbering (1986 means year 1986). Months use sane numbering, 1-12 for January-December.
或者,更好的是,使用预定义的Month
枚举对象,一年中的每个月一个。提示:在整个代码库中使用这些Month
对象而不仅仅是整数,以使您的代码更加自我记录,确保有效值,并提供type-safety。同样适用于Year
和YearMonth
。
LocalDate ld = LocalDate.of( 1986 , Month.FEBRUARY , 23 ) ;
显然你想从一个日期开始,一个月后作为日期范围。
LocalDate monthLater = ld.plusMonths( 1 ) ;
从JDBC 4.2开始,您的JDBC驱动程序需要支持一些关键的java.time类,例如LocalDate
。
请注意下面的ThreeTen-Extra链接。如果你在日期范围内做很多工作,你可能会发现那里的LocalDateRange
课程很方便。
java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,如java.util.Date
,Calendar
和SimpleDateFormat
。
要了解更多信息,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规格是JSR 310。
现在在Joda-Time的maintenance mode项目建议迁移到java.time班。
您可以直接与数据库交换java.time对象。使用符合JDBC driver或更高版本的JDBC 4.2。不需要字符串,不需要java.sql.*
类。
从哪里获取java.time类?
ThreeTen-Extra项目使用其他类扩展了java.time。该项目是未来可能添加到java.time的试验场。你可能会在这里找到一些有用的类,如Interval
,YearWeek
,YearQuarter
和more。
感谢@RickS,我们解决了这个问题。
PS:我很久以前就解决了这个问题,2年后再次遇到这个问题。大声笑
解决方案是在转换器:JpaDateConverter,当你将DateTime Objetivo转换为java.sql.Date时,根据文档:
如果给定的毫秒值包含时间信息,则驱动程序将时间组件设置为默认时区(运行应用程序的Java虚拟机的时区)中与零GMT对应的时间。
所以为了解决这个问题,我更改了服务器时区:
dpkg-reconfigure tzdata
在MySQL中:
SET @@global.time_zone = '+00:00';