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.
Related
So I have this object saved to the database with one property being dateCreated which is, of course, saved as MySQL timestamp. But while sending the data to the client, I want to be in milliseconds. Right now, I've mapped it to the Date object and converting it to milliseconds further. But I thought, what if I could map my POJO in such a way that it retrieves values in milliseconds. Here is what I've tried.
OmsJob:
#Entity
#EntityListeners(PreventAnyUpdate.class)
#ConfigurationProperties("omsjob")
#Table(name = "OMSJob")
public class OmsJob {
#Id
#NotNull
#Column(name = "jobId")
private String id;
#NotNull
private Long dateCreated; // If I map this property to Date, it works fine
}
I thought I'll add a custom converter that'll convert java.util.Date or java.sql.Date to milliseconds. But it isn't working:
#Component
#ConfigurationPropertiesBinding
public class DateConverter implements Converter<Date, Long> {
#Override
public Long convert(Date date) {
return date.getTime();
}
}
The error I am getting is pretty obvious but is there any way to achieve what I am trying to?
ERROR 229770 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Unsupported conversion from TIMESTAMP to java.lang.Long
An attribute won't know about its converter until you declare it. Do it as follows:
#NotNull
#Convert (converter = DateConverter.class)
private Long dateCreated;
Also, change the converter as follows:
public class DateConverter implements AttributeConverter<Date, Long> {
#Override
public Date convertToDatabaseColumn(Long millis) {
retrun new Date(millis);
}
#Override
public Long convertToEntityAttribute(Date date) {
return date.getTime();
}
}
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;
}
}
}
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;
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();
}
}
I have a column in a MySQL table like:
createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP`.
I want it to be stored in a field like:
private Long createdAt;
How would I do this?
Best if you use the supported types in TemporalType enum. Longs would require a converter.
Here's a converter for longs:
import java.sql.Timestamp;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter(autoApply = true)
public class LongConverter implements AttributeConverter<Long, Timestamp> {
#Override
public Timestamp convertToDatabaseColumn(Long attribute) {
if (attribute == null)
return null;
return new Timestamp(attribute);
}
#Override
public Long convertToEntityAttribute(Timestamp dbData) {
if (dbData == null)
return null;
return dbData.getTime();
}
}
Simply use #Temporal(TemporalType.TIMESTAMP) above createdAt