playframework:在播放中将java8 java.time类型LocalDate与hibernate保持一致

playframework: persist java8 java.time type LocalDate with hibernate in Play

对于新的java.time。*类型,我无法获得任何类型的转换或兼容性。或至少是LocalDate。

我看到了例外情况:

1
2
3
4
5
6
play.api.http.HttpErrorHandlerExceptions$$anon$1: Execution exception[[IllegalStateException: Error(s) binding form: {"dateOfBirth":["Invalid value"]}]]
    at play.api.http.HttpErrorHandlerExceptions$.throwableToUsefulException(HttpErrorHandler.scala:265) ~[play_2.11-2.4.4.jar:2.4.4]

Caused by: java.lang.IllegalStateException: Error(s) binding form: {"dateOfBirth":["Invalid value"]}
    at play.data.Form.get(Form.java:592) ~[play-java_2.11-2.4.4.jar:2.4.4]
    at controllers.Application.addPatient(Application.java:49) ~[classes/:na]

我发现有几种方法在原则上应该可以使JPAhibernate,但是,如果这又是Play的问题,还是什么?我不知道。

第一种方法:

提供您自己的自定义转换器:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Converter(autoApply = true)
public class LocalDateAttributeConverter implements AttributeConverter<LocalDate, Date>
{
    @Override
    public Date convertToDatabaseColumn(LocalDate locDate) {
        return (locDate == null ? null : Date.valueOf(locDate));
    }

    @Override
    public LocalDate convertToEntityAttribute(Date sqlDate) {
        return (sqlDate == null ? null : sqlDate.toLocalDate());
    }
}

然后在字段上需要使用以下内容:

1
2
@Convert(converter = LocalDateAttributeConverter.class)
public LocalDate dateOfBirth;

http://www.thoughts-on-java.org/persist-localdate-localdatetime-jpa/

当然,据我所知,原则上,我不需要@Convert注释,因为转换器本身是使用@Converter(autoApply = true)注释进行注释的。但是,因为我找不到有关成功使用Play播放器(无Google命中)的文档,因此我尝试了使用和不使用转化器,但均未成功。

下一个方法:

实际上,据我所知,自hibernate5以来,现在应该支持较新的java8类型……。并且我已经使用了5.0.5,并且在类路径中包括了必要的库:

1
"org.hibernate" %"hibernate-java8" %"5.0.5.Final",

https://hibernate.atlassian.net/browse/HHH-8844

那根本没有帮助。相同的stacktrace。

为了很好,我添加了特定于hibernate的注释

1
@Type(type="java.time.LocalDate")

到我的领域。

那真是丑陋。但是,如果它有所帮助,我会忍受的。没有。同样的例外。将其与转换器一起使用会导致其他错误。这很有趣。

我正在使用Play 2.4和Hibernate 5.0.5。
有没有人设法做到这一点?


好的,所以我终于弄明白了。我应该更仔细地(慢慢地)看一下堆栈跟踪。

实际上,我最终受到三个问题的影响。全部以三种不同的方式解决。但是,所有这些都与对较新的java.time类的"挂起"支持有关。公平地说,底层库的支持只是最近才引入的。

第一个问题:表单绑定验证失败。

我在控制器中调试了表单绑定工作:

1
2
3
4
    Form<Patient> form = Form.form(Patient.class);
    form = form.bindFromRequest();
    System.out.println(form.toString());
    Patient patient = form.get();

在调用form.get()时引起堆栈跟踪的同时,实际上在调用form.bindFromRequest()时已经发生了错误。错误是在Form.errors映射中注册的,该映射来自org.springframework.validation.DataBinder

进一步的Google搜索,我遇到了这个问题:

https://groups.google.com/forum/#!topic/play-framework/Wl_ip56111c

实施此操作解决了我的第一个问题,然后进行了表单绑定。

第二个问题:持久性

我正在使用Hibernate来保持对象的持久性。
将hibernate-java8添加到我的类路径中可启用新数据类型的持久性。对于播放2,这将放入您的build.sbt文件中。

1
2
3
4
5
6
7
8
libraryDependencies ++= Seq(
  javaJdbc,
  cache,
  javaWs,
  javaJpa,
 "org.hibernate" %"hibernate-entitymanager" %"5.0.5.Final",
 "org.hibernate" %"hibernate-java8" %"5.0.5.Final",
)

https://hibernate.atlassian.net/browse/HHH-8844

第三个问题:Json支持

我无法将我的模型实例化呈现为Json对象。应该支持它,但是在某种程度上,它不是安装程序。

我发现这篇文章向我展示了如何解决该问题:

1
2
    // http://stackoverflow.com/questions/32872474/how-to-use-java-time-localdate-on-a-play-framework-json-rest/32891177#comment56814598_32891177
    // https://groups.google.com/forum/?hl=en.#!searchin/play-framework/java.time.localdate/play-framework/Dv-IpvBqWgo/l6NTp3e0BQAJ

为方便起见,我在此处发布了此修复程序:

1
2
3
4
5
    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new Jdk8Module());
    mapper.registerModule(new JSR310Module());
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    Json.setObjectMapper(mapper);

在应用程序启动时需要执行第一和第三修订。对于播放2.4,GlobalSettings对象已被弃用,因此我不确定他们希望您如何进行这种初始化(有人吗?)。但是,幸运的是它仍然可以工作。我当然没有Globals类(默认情况下不再创建),所以我创建了该类,并使用以下键(通过application.conf文件)将我的应用程序指向该类:

1
2
# Minimal global settings to fix form binding and json support of java.time.LocalDate
application.global=GlobalFixes

为方便起见,我将整个课程发布在这里,供仍在学习Playframework方式的人使用。

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
45
46
47
48
49
50
51
52
53
54
55
56
import play.*;
import play.data.format.Formatters;
import play.libs.Json;

import java.time.LocalDate;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JSR310Module;

import java.text.ParseException;

/**
 * Fixes for play application. TODO remove this once Play has fixed support for LocalDate
 * @author Sean van Buggenum
 *
 */

public class GlobalFixes extends GlobalSettings
{   // One can see from the javadoc of java.time.LocalDate that the toString guarantees this format, regardless of locale!
    private static final Pattern datePattern = Pattern.compile("(\\\\d\\\\d\\\\d\\\\d)-(\\\\d\\\\d)-(\\\\d\\\\d)");

    public void onStart(Application app)
    {
        //https://groups.google.com/forum/#!topic/play-framework/Wl_ip56111c
        Formatters.register(LocalDate.class, new Formatters.SimpleFormatter<LocalDate>()
        {
            @Override
            public LocalDate parse(String input, Locale l) throws ParseException
            {
                Matcher m = datePattern.matcher(input);
                if (!m.matches())
                    throw new ParseException("No valid Input for date text:" + input, 0);
                return LocalDate.of(Integer.valueOf(m.group(1)), Integer.valueOf(m.group(2)), Integer.valueOf(m.group(3)));
            }

            @Override
            public String print(LocalDate localTime, Locale l)
            {
                return localTime.toString();
            }
        });

        // Add Json's java 8 support
        // http://stackoverflow.com/questions/32872474/how-to-use-java-time-localdate-on-a-play-framework-json-rest/32891177#comment56814598_32891177
        // https://groups.google.com/forum/?hl=en.#!searchin/play-framework/java.time.localdate/play-framework/Dv-IpvBqWgo/l6NTp3e0BQAJ
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new Jdk8Module());
        mapper.registerModule(new JSR310Module());
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        Json.setObjectMapper(mapper);
    }
}

我希望这对某些人有所帮助。