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(); |
在调用
进一步的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); } } |
我希望这对某些人有所帮助。