Jpa 如何将java.util.Date转换为Java8 java.time.YearMonth

Jpa 如何将java.util.Date转换为Java8 java.time.YearMonth,jpa,java-8,java.util.date,Jpa,Java 8,Java.util.date,如何最好地将java.util.Date转换为Java8java.time.YearMonth 不幸的是,下面抛出了一个DateTimeException: YearMonth yearMonth = YearMonth.from(date.toInstant()); 结果: java.time.DateTimeException: Unable to obtain YearMonth from TemporalAccessor: 2015-01-08T14:28:39.183Z of typ

如何最好地将
java.util.Date
转换为Java8
java.time.YearMonth

不幸的是,下面抛出了一个
DateTimeException

YearMonth yearMonth = YearMonth.from(date.toInstant());
结果:

java.time.DateTimeException: Unable to obtain YearMonth from TemporalAccessor: 2015-01-08T14:28:39.183Z of type java.time.Instant
at java.time.YearMonth.from(YearMonth.java:264)
...
我需要这个功能,因为我想使用JPA在数据库中存储
YearMonth
值。目前JPA不支持
YearMonth
,因此我提出了以下YearMonthConverter(导入省略):

//TODO(未来):当下一版本的JPA(即Java 9?)支持YearMonth时删除。看见https://java.net/jira/browse/JPA_SPEC-63
@转换器(自动应用=真)
公共类YearMonthConverter实现AttributeConverter{
@凌驾
public Date convertToDatabaseColumn(YearMonth属性){
//使用默认区域,因为最后只需要日期
return attribute==null?null:Date.from(attribute.atDay(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
}
@凌驾
public YearMonth convertToEntityAttribute(日期数据){
//TODO:检查Date->YearMonth是否不能以更好的方式完成
if(dbData==null)返回null;
日历=Calendar.getInstance();
calendar.setTime(dbData);
返回YearMonth.of(calendar.get(calendar.YEAR)、calendar.get(calendar.MONTH)+1);
}
}

是否有更好(更干净、更短)的解决方案(适用于两个方向)?

简短回答:

// From Date to YearMonth
YearMonth yearMonth =
        YearMonth.from(date.toInstant()
                           .atZone(ZoneId.systemDefault())
                           .toLocalDate());

// From YearMonth to Date
// The same as the OP:s answer
final Date convertedFromYearMonth = 
        Date.from(yearMonth.atDay(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
说明:

// From Date to YearMonth
YearMonth yearMonth =
        YearMonth.from(date.toInstant()
                           .atZone(ZoneId.systemDefault())
                           .toLocalDate());

// From YearMonth to Date
// The same as the OP:s answer
final Date convertedFromYearMonth = 
        Date.from(yearMonth.atDay(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
方法的JavaDoc说:

转换将提取年中的年和月字段。仅当时态对象具有ISO年表或可以转换为LocalDate时,才允许提取

因此,您需要能够:

  • 提取
    年度
    年度月份
    字段,或
  • 您应该使用可以转换为
    LocalDate
    的内容 让我们试试看

    final Date date = new Date();
    final Instant instant = date.toInstant();
    instant.get(ChronoField.YEAR); // causes an error
    
    这是不可能的,将引发异常:

    java.time.temporal.UnsupportedTemporalTypeException:不支持的字段:年 at java.time.Instant.get(Instant.java:571)

    这意味着备选方案1将被淘汰。在这个关于的极好的回答中解释了这个问题的原因

    尽管名称不同,
    java.util.Date
    表示时间线上的一个瞬间,而不是“日期”。自1970-01-01T00:00Z(1970 GMT/UTC开始时的午夜)以来,存储在对象中的实际数据是一个很长的毫秒计数

    JSR-310中与
    java.util.Date
    等价的类是Instant,因此有一个方便的方法toInstant()来提供转换

    因此,
    日期
    可以转换为
    即时
    ,但这对我们没有帮助,是吗

    然而,备选方案2证明是成功的。将
    Instant
    转换为
    LocalDate
    ,然后使用
    YearMonth.from(TemporalAccessor)
    -方法

        Date date = new Date();
    
        LocalDate localDate = date.toInstant()
                                  .atZone(ZoneId.systemDefault())
                                  .toLocalDate();
    
        YearMonth yearMonth = YearMonth.from(localDate);
        System.out.println("YearMonth: " + yearMonth);
    
    输出为(自2015年1月执行代码以来;):

    年份月份:2015-01


    就个人而言,我会避免映射到
    java.util.Date
    ,因为a)它不是
    java.sql.
    -类型,b)包括隐式时区(性能差和系统时区使用模糊),c)sql没有为年-月定义此类类型。最好使用@MenoHochschild将
    YearMonth
    映射为整数:这是个好主意-唯一需要注意的是,在查看数据库本身时,它是不可读的。在我的例子中没有问题…好吧,对于直接查看db内容来说,仅仅一个整数是不可读的,但是请注意,由于时区转换和db/服务器设置,映射到j.u.Date可以更改可视化的日期和月份(正如db管理员所看到的)。我选择了该解决方案-没有回答我最初的问题,但是在我的特殊情况下帮助我。。。public Integer convertToDatabaseColumn(YearMonth YearMonth){return(YearMonth==null)?null:(int)YearMonth.getLong(ChronoField.PROLEPTIC_MONTH)}public YearMonth convertToEntityAttribute(Integer dbData){return(dbData==null)?null:YearMonth.from(YearMonth.now().with(ChronoField.PROLEPTIC_MONTH,dbData))}