我想使用 JPA 在 Spring 应用程序中验证表单,但我在现场生产年份遇到问题。 当我使用正好 4 位数字的年份时,验证成功,例如 2010。 但是,当我输入小于 1000 或大于 9999(不是 4 位数字的值(的表单值时,我收到此异常:
Failed to convert property value of type java.lang.String to
required type java.time.Year for property productionYear;
nested exception is org.springframework.core.convert.ConversionFailedException:
Failed to convert from type [java.lang.String] to
type [@javax.persistence.Convert @javax.persistence.Column
@javax.validation.constraints.NotNull @com.kucharek.motorcycleshop.data.ProductionYearConstraint
java.time.Year] for value 12345;
nested exception is java.lang.IllegalArgumentException:
Parse attempt failed for value [12345]
该字段的 SQL 声明:
production_year year not null
check (production_year between
year("1885-01-01") and year(current_date())
)
实体类中的 Java 声明:
@Convert(converter = YearAttributeConverter.class)
@Column(name = "production_year")
@NotNull(message = "Production year is obligatory")
@ProductionYearConstraint(message = "Production year should be between 1885 and current year")
private Year productionYear;
转换器类别:
@Converter(autoApply = true)
public class YearAttributeConverter
implements AttributeConverter<Year, Short> {
@Override
public Short convertToDatabaseColumn(Year attribute) {
return (short) attribute.getValue();
}
@Override
public Year convertToEntityAttribute(Short dbData) {
return Year.of(dbData);
}
}
验证注释:
@Constraint(validatedBy = ProductionYearValidator.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ProductionYearConstraint {
String message() default "Invalid production year";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
验证器类:
public class ProductionYearValidator
implements ConstraintValidator<ProductionYearConstraint, Year> {
@Override
public boolean isValid(Year year, ConstraintValidatorContext context) {
return year != null && year.getValue() >= 1885 &&
year.getValue() < Year.now().getValue() ;
}
}
我做错了什么? 我应该更改数据库还是模型中的数据类型? 如何处理异常并向用户打印我的自定义错误消息而不是打印堆栈跟踪?
我已经用自定义消息文件解决了这个问题。
用于配置自定义消息文件消息的配置类.yml:
@Configuration
public class MessageSourceConfiguration {
@Bean
public MessageSource messageSource() throws IOException {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setCommonMessages(yamlProperties());
return messageSource;
}
@Bean
public Properties yamlProperties() throws IOException {
YamlPropertiesFactoryBean bean = new YamlPropertiesFactoryBean();
bean.setResources(new ClassPathResource("messages.yml"));
return bean.getObject();
}
}
文件资源/消息.yml:
typeMismatch:
motorcycle:
productionYear: Production year should be number between 1885 and current year
如果你阅读文档,即Year.parse(text)
的 javadoc ,你会发现:
该字符串必须表示有效的年份。0000 到 9999 范围之外的年份必须以加号或减号为前缀。
真正的问题是,你真的想支持0000到9999范围之外的年份吗?
数据库真的可以存储这样的年份值吗?实际上并不重要,因为check (production_year between year("1885-01-01") and year(current_date()))
无论如何都将年份限制在 1885 到 2020 年的范围,那么为什么解析问题对您来说是个问题呢?
但是,如果您确实想支持这样的年份值,则需要提供String
到Time
转换器,使用:
Year.parse(yearString, DateTimeFormatter.ofPattern("u"))
一种解决方案是将应用程序升级到 Java 16 或更高版本。
在 Java 16 之前,Year.parse(CharSequence text)
方法要求年份文本的长度正好是四位数(前面允许零填充(,即介于 0000 和 9999 之间。
该字符串必须表示有效的年份。0000 到 9999 范围之外的年份必须以加号或减号为前缀。
在确定错误消息时,底层库似乎在输入Year
上使用此方法,这导致了您遇到的异常。
从 Java 16 开始,这个四位数的限制已被取消,如parse
方法 Javadocs 所示:
该字符串必须表示有效的年份。
此更改是根据 Java 错误跟踪器中的 JDK-8245448 进行的。