This question already has answers here:
JPA support for Java 8 new date and time API
(8 answers)
Closed 5 years ago.
How can I use Date library for my POJO?
I can use this my code:
#Data
#Entity
#Table(name = "PERSON")
public class Person implements Serializable {
private static final long serialVersionUID = -8041031461422721556L;
#Id
#Column(name = "PERSON_ID")
private Long id;
#Column(name = "NAME")
private String name;
#Column(name = "DOB")
private LocalDate dob;
}
I'm using java.time.LocalDate type.
Right now Hibernate/JPA doesn't have a compatibility with Java 8 Date library, but you only need make a AttributeConverter to use this library:
For type TIMESTAMP you can use this converter:
#Converter(autoApply = true)
public class LocalDateTimeAttributeConverter implements AttributeConverter<LocalDateTime, Timestamp> {
#Override
public Timestamp convertToDatabaseColumn(LocalDateTime datetime) {
return datetime == null ? null : Timestamp.valueOf(datetime);
}
#Override
public LocalDateTime convertToEntityAttribute(Timestamp timestamp) {
return timestamp == null ? null : timestamp.toLocalDateTime();
}
}
For type DATE you can use this converter:
#Converter(autoApply = true)
public class LocalDateAttributeConverter implements AttributeConverter<LocalDate, Date> {
#Override
public Date convertToDatabaseColumn(LocalDate date) {
return date == null ? null : Date.valueOf(date);
}
#Override
public LocalDate convertToEntityAttribute(Date date) {
return date == null ? null : date.toLocalDate();
}
}
For type TIME you can use this converter:
#Converter(autoApply = true)
public class LocalTimeAttributeConverter implements AttributeConverter<LocalTime, Time> {
#Override
public Time convertToDatabaseColumn(LocalTime time) {
return time == null ? null : Time.valueOf(time);
}
#Override
public LocalTime convertToEntityAttribute(Time time) {
return time == null ? null : time.toLocalTime();
}
}
Related
I would like to know how to format the date time correctly? The result is Localdatetime yyyy-MM-ddTHH:mm.
Could you advise how to solve?
I'm using Java 11, and does it because #JsonFormat not support #RequestParam?
Controller:
#PostMapping("/checkFollowupDate")
public LocalDateTime updateCaseFollowup(#RequestParam("followupDate") #DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS") LocalDateTime followupDate) {
return followupDate;
}
Entity:
#Entity
#Table(name = "caseFollowup")
public class CaseFollowup {
#JsonFormat(pattern="yyyy-MM-dd HH:mm:ss.SSS")
private LocalDateTime followupDate;
Since you are using Spring-boot , I'm also assuming you are using java8 . In any case try using java8 time api for date like :
#JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
private LocalDateTime followupDate;
and if you are on JPA 2.1 which was released before java8 then in your entity class you could have a converter to convert it for sql timestamp like :
#Converter(autoApply = true)
public class LocalDateTimeAttributeConverter implements AttributeConverter<LocalDateTime, Timestamp> {
#Override
public Timestamp convertToDatabaseColumn(LocalDateTime locDateTime) {
return locDateTime == null ? null : Timestamp.valueOf(locDateTime);
}
#Override
public LocalDateTime convertToEntityAttribute(Timestamp sqlTimestamp) {
return sqlTimestamp == null ? null : sqlTimestamp.toLocalDateTime();
}
}
Remember that in newer version of Hibernate(Hibernate 5) and JPA the above conversion will be performed automatically and doesn't require you to provide the above method.
If your requirement is just to persist the Date read from the #RequestParam through the entity class in a particular format, you could always convert it manually into any format that you may choose before setting the value into your entity class like :
#PostMapping("/caseFollowup")
public Integer updateCaseFollowup(#RequestParam("followupDate")
LocalDateTime followupDate) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatDateTime = followupDate.format(formatter);
}
use this code in you model class :
#JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssZ", shape = JsonFormat.Shape.STRING)
private OffsetDateTime lastModifiedDate;
and create this class mapper :
import java.sql.Timestamp;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
#Component
public class DateMapper {
public OffsetDateTime asOffsetDateTime(Timestamp ts){
if (ts != null){
return OffsetDateTime.of(ts.toLocalDateTime().getYear(), ts.toLocalDateTime().getMonthValue(),
ts.toLocalDateTime().getDayOfMonth(), ts.toLocalDateTime().getHour(), ts.toLocalDateTime().getMinute(),
ts.toLocalDateTime().getSecond(), ts.toLocalDateTime().getNano(), ZoneOffset.UTC);
} else {
return null;
}
}
public Timestamp asTimestamp(OffsetDateTime offsetDateTime){
if(offsetDateTime != null) {
return Timestamp.valueOf(offsetDateTime.atZoneSameInstant(ZoneOffset.UTC).toLocalDateTime());
} else {
return null;
}
}
}
I am upgrading our application to a version of the framework that supports Java 8's Date/Time API. I've updated our entity models to reflect the correct type conversion (date -> LocalDate, timestamp -> LocalDateTime).
I'm now hitting an issue running my unit tests where the converters are attempting to cast java.sql.Date as java.sql.Timestamp. This is confusing because Timestamp shouldn't be used for these entities. The testing SQL script uses TO_DATE() to set the dates, like so:
INSERT INTO note (id, content, date, owner_id) VALUES (1, 'test content', TO_DATE('17/12/2017', 'DD/MM/YYYY'), 1);
And the exception returned is:
Caused by: java.lang.ClassCastException: java.sql.Timestamp cannot be cast to java.sql.Date
at com.<redacted>.model.jpa.LocalDateAttributeConverter.convertToEntityAttribute(LocalDateAttributeConverter.java:8)
at org.hibernate.metamodel.model.convert.internal.JpaAttributeConverterImpl.toDomainValue(JpaAttributeConverterImpl.java:45)
at org.hibernate.type.descriptor.converter.AttributeConverterSqlTypeDescriptorAdapter$2.doConversion(AttributeConverterSqlTypeDescriptorAdapter.java:140)
... 64 more
from a simple AttributeConverter<>:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.sql.Date;
import java.time.LocalDate;
#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());
}
}
I can understand it having an issue casting from Timestamp to Date but Timestamp shouldn't be involved at all here.
Entity for completeness:
#XmlRootElement
#Entity(name = "notes")
public class Note implements ModelObject {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(optional = false)
#JoinColumn(name = "owner_id", referencedColumnName = "id")
private User user;
private String content;
private LocalDate date;
// getters/setters omitted for brevity
}
Not sure if this affects it, but I have also created AttributeConverters for LocalTime and LocalDateTime.
#Converter(autoApply = true)
public class LocalTimeAttributeConverter implements AttributeConverter<LocalTime, Time> {
#Override
public Time convertToDatabaseColumn(LocalTime localTime) {
return (localTime == null ? null : Time.valueOf(localTime));
}
#Override
public LocalTime convertToEntityAttribute(Time sqlTime) {
return (sqlTime == null ? null : sqlTime.toLocalTime());
}
}
#Converter(autoApply = true)
public class LocalDateTimeAttributeConverter implements AttributeConverter<LocalDateTime, Timestamp> {
#Override
public Timestamp convertToDatabaseColumn(LocalDateTime locDateTime) {
return (locDateTime == null ? null : Timestamp.valueOf(locDateTime));
}
#Override
public LocalDateTime convertToEntityAttribute(Timestamp sqlTimestamp) {
return (sqlTimestamp == null ? null : sqlTimestamp.toLocalDateTime());
}
}
Using Hibernate 5.3.0.Final and H2 1.4.197 with hibernate-java8 library.
Current implementation:
#Getter(AccessLevel.NONE)
#Setter(AccessLevel.NONE)
#Column(name = "send_time", columnDefinition = "timestamp without time zone not null")
private LocalTime sendTime;
#Convert(converter=LocalTimeConverter.class)
public LocalTime getSendTime() {
return sendTime;
}
#Convert(converter=LocalTimeConverter.class)
public void setLocalTime(LocalTime time) {
this.sendTime = time;
}
#Converter(autoApply = true)
public class LocalTimeConverter implements AttributeConverter<LocalDateTime, Timestamp>{
#Override
public Timestamp convertToDatabaseColumn(LocalDateTime zonedDateTime) {
if(zonedDateTime == null) {
return null;
}
return Timestamp.valueOf(zonedDateTime);
}
#Override
public LocalDateTime convertToEntityAttribute(Timestamp sqlTime) {
if(sqlTime == null) {
return null;
}
return sqlTime.toLocalDateTime();
}
}
object.setSendTime(LocalTime.of(11, 00, 00));
The error I get all the time:
ERROR: column "send_time" is of type time without time zone but expression is of type bytea
Hint: You will need to rewrite or cast the expression.
You should use java.sql.Time instead. Are there a specific reason you are using LocalDateTime? Example with LocalTime:
#Converter
public class MyConverter implements AttributeConverter<LocalTime, Time> {
#Override
public Time convertToDatabaseColumn(LocalTime localTime) {
if(localTime == null){
return null;
}
// convert LocalTime to java.sql.Time
}
#Override
public LocalTime convertToEntityAttribute(Time time) {
if(time == null){
return null;
}
// convert java.sql.Time to LocalTime
}
}
You do not need a converter. You can use annotations (as per JPA 2.2). This works with Postgres.
#Column(nullable = false, columnDefinition = "TIMESTAMP WITH TIME ZONE")
private OffsetDateTime createdOn;
Instantiation can be done as per:
#PrePersist
private void prePersist() {
this.createdOn = OffsetDateTime.now(ZoneId.of("UTC"));
}
The example here uses OffsetDateTime but for LocalDateTime you can use
#Column(nullable = false, columnDefinition = "TIMESTAMP WITHOUT TIME ZONE")
private LocalDateTime createdOn;
ERROR: column "receipt_time" is of type time without time zone but
expression is of type bytea Hint: You will need to rewrite or cast
the expression. Position: 490
private LocalTime receiptTime;
#Column(name = "receipt_time")
public LocalTime getReceiptTime() {
return receiptTime;
}
public void setReceiptTime(LocalTime receiptTime) {
this.receiptTime = receiptTime;
}
If you want to use LocalTime then you could use a Converter:
#Converter
public class MyConverter implements AttributeConverter<LocalTime, Time> {
#Override
public Time convertToDatabaseColumn(LocalTime localTime) {
if(localTime == null){
return null;
}
// convert LocalTime to java.sql.Time
}
#Override
public LocalTime convertToEntityAttribute(Time time) {
if(time == null){
return null;
}
// convert java.sql.Time to LocalTime
}
}
Then in your entity you would use:
#Column(name = "receipt_time")
#Convert(converter = MyConverter.class)
public LocalTime getReceiptTime() {
return receiptTime;
}
Is there a better way of writing a Java validator which ensures that a start date is before an end date than writing a class level ConstraintValidator in the following manner:
// VALIDATOR IMPLEMENTATION
public class StartBeforeEndDateValidator implements ConstraintValidator<StartBeforeEndDateValid, Object> {
// cannot use LocalDate here...
private String start;
private String end;
#Override
public void initialize(final StartBeforeEndDateValid annotation) {
start = annotation.start();
end = annotation.end();
}
#Override
public boolean isValid(final Object bean, final ConstraintValidatorContext context) {
try {
final String startDateStr = BeanUtils.getProperty(bean, start);
final String endDateStr = BeanUtils.getProperty(bean, end);
final LocalDate startDate = new LocalDate(startDateStr);
final LocalDate endDate = new LocalDate(endDateStr);
return !startDate.isAfter(endDate);
} catch (final Exception e) {
return false;
}
}
}
// USAGE
#StartBeforeEndDateValid(start = "startDate", end = "endDate")
#Entity
public class MyBean {
#NotNull
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
private LocalDate startDate;
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
private LocalDate endDate;
...
}
I don't really like the fact that I have to use reflection to extract the 2 date objects from the bean. Unfortunately afaik the validation spec does not specify a way to set only the values you want to validate from the bean.
One way would be to add an interface to MyBean
public interface StartEndDateable {
public LocalDate getStartDate();
public LocalDate getEndDate();
}
public class MyBean implements StartEndDatable {
...
Then you can set the generic type on ConstraintValidator to the new interface instead of Object.
public class StartBeforeEndDateValidator implements ConstraintValidator<StartBeforeEndDateValid, StartEndDatable> {
#Override
public void initialize(StartBeforeEndDateValid annotation) {
}
#Override
public boolean isValid(StartEndDatable bean, ConstraintValidatorContext context) {
final LocalDate startDate = bean.getStartDate();
final LocalDate endDate = bean.getEndDate();
return !startDate.isAfter(endDate);
}
}
Obviously any class you then want to validate with the start and end date will have to implement the StartEndDateable (Not the best name, I know, but I'm sure you can think of something better) and define the getStartDate and getEndDate methods.