I am trying to use an AttributeConverter to store the new Java 8 ZonedDateTime values in a MySQL database (DATETIME field) using Hibernate 4.3.0.
When I try to execute an update I get this error:
...Data truncation: Incorrect datetime value: '\xAC\xED\x00\x05sr\x00\x0Djava.time.Ser\x95]\x84\xBA\x1B"H\xB2\x0C\x00\x00xpw\x0D\x06\x00\x00\x07\xE0\x02\x11\x0B&\xEE\xE0\x08\x' for column...
I have read many SO answers saying to use the converter approach, so I wrote one:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.sql.Date;
import java.time.LocalDate;
import java.time.ZonedDateTime;
#Converter(autoApply = true)
public class ZonedDateTimeConverter implements AttributeConverter<ZonedDateTime, Date> {
#Override
public Date convertToDatabaseColumn(ZonedDateTime zonedDateTime) {
if (zonedDateTime == null) return null;
return java.sql.Date.valueOf(zonedDateTime.toLocalDate());
}
#Override
public ZonedDateTime convertToEntityAttribute(Date date) {
if (date == null) return null;
LocalDate localDate = date.toLocalDate();
return ZonedDateTime.from(localDate);
}
}
... but it never gets called.
I have even added jpaProperties.put("hibernate.archive.autodetection", "class, hbm"); to my jpaProperties, but no luck. The ZonedDateTimeConverter class is in the same package as my Entities, so it should get scanned.
What am I missing here?
Reading JPA 2.1 spec :
"The conversion of all basic types is supported except for the following: Id attributes (including the
attributes of embedded ids and derived identities), version attributes, relationship attributes, and
attributes explicitly annotated as Enumerated or Temporal or designated as such in the XML descriptor."
any chance your ZonedDateTime field is annotated with #Id or #Temporal in your entity ?
OK, this was my mistake. Part of my application is managed by Hibernate, but part of it is NOT. The object I'm having problems with is NOT managed by Hibernate, so it makes sense that the JPA stuff isn't getting called.
Related
We are currently using SpringBoot and PostgreSQL and we have problem with date format.
When we save or edit(sending POST request from front-end) something we need a YYYY-MM-DD format since any other type of format wont save anything to database so #JSONformat on Entity or some type of annotation like that is not possible. But when we are fetching example all users it would be nicer that we get DD-MM-YYYY in a response from server, and im not sure how to do that.
We can do it from front-end but code isn't nice so back-end solution would be better.
Thanks in advance!
You can create a mapper and map your entity object to a DTO that will be sent to your front-end.
Since you don't specify the class you are using for your dates I will use LocalDate as an example, but the same logic can be applied to your date class.
Let's say your entity class looks like that:
public class SampleEntity {
private LocalDate localDate;
}
Your DTO class will look like that:
public class SampleDto {
// the Jackson annotation to format your date according to your needs.
#DateTimeFormat(pattern = "DD-MM-YYYY")
private LocalDate localDate;
}
Then you need to create the mapper to map from SampleEntity to SampleDto and vice versa.
#Component
public class SampleMapper {
public SampleDto mapTo(final SampleEntity sampleEntity){
// do the mapping
SampleDto sampleDto = new SampleDto();
sampleDto.setLocalDate(sampleEntity.getLocalDate());
return sampleDto;
}
public SampleEntity mapFrom(final SampleDto sampleDto){
// do the mapping
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setLocalDate(sampleDto.getLocalDate());
return sampleEntity;
}
}
You can use all those in your controller like that:
#GetMapping
public ResponseEntity<SampleDto> exampleMethod() {
// service call to fetch your entity
SampleEntity sampleEntity = new SampleEntity(); // lets say this is the fetched entity
sampleEntity.setLocalDate(LocalDate.now());
SampleDto sampleDto = sampleMapper.mapTo(sampleEntity);
return ResponseEntity.ok(sampleDto);
}
With this solution, you can avoid adding Jackson annotations to your entity. Also with this solution, you can control exactly what your front-end can access. More on that here What is the use of DTO instead of Entity?
I have a db entity with some Instant type fields.
org.springframework.data.r2dbc.core.DatabaseClient (which is now deprecated), had a method .as(..) to automatically map to a java entity and it honoured the timezones too. Not sure how that happened internally.
But using org.springframework.r2dbc.core.DatabaseClient which does not have an automatic mapper, I must use a .map(...) which provides the Rows and I map them like
row.get("blah", Instant.class)
But it just gives time in my local TZ, not UTC.
Anyone knows the root cause? Thanks.
Short Answer
I'm assuming that you are using the PostgresSQL database and appropriate driver. Also, I assume that you are using the TIMESTAMP DB type for your Instant filed. To get the correct timezone from org.springframework.r2dbc.core.DatabaseClient you can use TIMESTAMP WITH TIME ZONE as a data type.
Long Answer
I have created a test repo to test the behavior of R2dbcRepository and R2DBC DatabaseClient to retrieve timestamp and timestampz into Instant. To do so, I have created the following table:
CREATE TABLE test_table (id SERIAL PRIMARY KEY, timestamp_without_tz TIMESTAMP, timestamp_with_tz TIMESTAMP WITH TIME ZONE);
And implemented the following service:
package com.patotski.r2dbctimestamps.service;
import com.patotski.r2dbctimestamps.domain.TestEntity;
import com.patotski.r2dbctimestamps.repo.TestEntityRepo;
import lombok.RequiredArgsConstructor;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import java.time.Instant;
#Service
#RequiredArgsConstructor
public class TestEntityService {
private final TestEntityRepo testEntityRepo; // uses standard R2dbcRepository implementation
private final DatabaseClient databaseClient;
public Mono<TestEntity> saveUsingRepo(TestEntity entity) {
return testEntityRepo.save(entity);
}
public Mono<TestEntity> getByIdFromRepo(int id) {
return testEntityRepo.findById(id);
}
public Mono<TestEntity> saveUsingDbClient(TestEntity entity) {
return databaseClient.sql("INSERT INTO test_table (timestamp_without_tz, timestamp_with_tz) VALUES(:timestamp_without_tz, :timestamp_with_tz)")
.bind("timestamp_without_tz", entity.getTimestamp_without_tz())
.bind("timestamp_with_tz", entity.getTimestamp_with_tz())
.map(row -> TestEntity.builder()
.id(row.get("id", Integer.class))
.timestamp_with_tz(row.get("timestamp_with_tz", Instant.class))
.timestamp_without_tz(row.get("timestamp_without_tz", Instant.class))
.build()).first();
}
public Mono<TestEntity> getByIdFromDbClient(int id) {
return databaseClient.sql("SELECT * from test_table where id = :id")
.bind("id", id)
.map(row -> TestEntity.builder()
.id(row.get("id", Integer.class))
.timestamp_with_tz(row.get("timestamp_with_tz", Instant.class))
.timestamp_without_tz(row.get("timestamp_without_tz", Instant.class))
.build()).first();
}
}
And created tests that just store entity and retrieve asserting Timestamps:
#SpringBootTest
class TestEntityServiceTest {
#Autowired
TestEntityService testEntityService;
#Test
#DisplayName("Should store and retrieve Entity with both timestamp fields in correct timezones using R2DBC repo.")
void shouldStoreCorrectTimestampsAndRetriveWithRepo() {
Instant now = Instant.now();
TestEntity entity = TestEntity.builder()
.timestamp_with_tz(now)
.timestamp_without_tz(now)
.build();
TestEntity saved = testEntityService.saveUsingRepo(entity).block();
Assertions.assertThat(testEntityService.getByIdFromRepo(saved.getId()).block()).isNotNull()
.extracting(TestEntity::getId,
TestEntity::getTimestamp_without_tz,
TestEntity::getTimestamp_with_tz)
.containsExactly(saved.getId(), now, now);
}
#Test
#DisplayName("Should store and retrieve Entity with both timestamp fields in correct timezones using R2DBC DatabaseClient.")
void shouldStoreCorrectTimestampsAndRetriveWithDbClient() {
Instant now = Instant.now();
TestEntity entity = TestEntity.builder()
.timestamp_with_tz(now)
.timestamp_without_tz(now)
.build();
testEntityService.saveUsingDbClient(entity).block();
Assertions.assertThat(testEntityService.getByIdFromDbClient(1).block()).isNotNull()
.extracting(TestEntity::getId,
TestEntity::getTimestamp_without_tz,
TestEntity::getTimestamp_with_tz)
.containsExactly(1, now, now);
}
}
Results show that these 2 methods are not consistent with each other:
R2dbcRepository test always passes in all TZs
DatabaseClient test passes only when tests are running in UTC TZ and fails when not. The reason is that field defined as TIMESTAMP in DB gets TZ offset.
TIMESTAMPZ field works correctly in both cases always
Repo to reproduce: https://github.com/xp-vit/r2dbc-timestamps
Created and issue for Spring team to at least discuss: https://github.com/spring-projects/spring-data-r2dbc/issues/608
I'm using latest spring and hibernate. I need to get the date and time form jsp page and want to insert into mysql database. I'm using TIMESTAMP as dataType of one fields. When I try to save, there is no error, but showing "HTTP Status [400] – [Bad Request]".
Finally I find out, there is a problem in Date and Time format (may be in annotation or MySQL datatype or jsp page). Because I try to update the form without changing valuei of date time (path="startDateTime"). It was successfully updated. When I try to change the value in date time (path="startDateTime"), it shows the "HTTP Status [400] – [Bad Request]".
I need to update Date and Time to database. I tried a lot of ways, but failed.
Model class
public class Survey{
//other declaration
#Column(name="startDateTime",columnDefinition="TIMESTAMP")
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(pattern="yyyy-MM-dd'T'HH:mm:ss")
private Date startDateTime;
}
jsp page
<spring:url value="/survey/save" var="saveURL"></spring:url>
<form:form action="${saveURL}" method="POST" modelAttribute="surveyForm">
//other stuffs
<form:input type="datetime-local" path="startDateTime" />
</form:form>
Controller
public ModelAndView saveSurvey(#ModelAttribute("surveyForm") Survey survey) {
surveyService.saveOrUpdate(survey);
return new ModelAndView("redirect:/survey/list");
}
In order to adopt good practice, i suggest using JAVA 8 LocalDateTime class.
So modify your model class like this
public class Survey{
//other declaration
#Column(name="startDateTime",columnDefinition="TIMESTAMP")
private LocalDateTime startDateTime;
}
When using JAVA 8 LocalDateTime class, you no longer need to add #Temporal(TemporalType.TIMESTAMP)
After that,you need to create a LocalDateTimeConverter class by implement Converter interface like this
import org.springframework.core.convert.converter.Converter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public final class LocalDateTimeConverter implements Converter<String, LocalDateTime> {
private final DateTimeFormatter formatter;
public LocalDateTimeConverter(String dateFormat) {
this.formatter = DateTimeFormatter.ofPattern(dateFormat);
}
#Override
public LocalDateTime convert(String source) {
if (source == null || source.isEmpty()) {
return null;
}
return LocalDateTime.parse(source, formatter);
}
}
Then register the LocalDateTimeConverter class in your configuration class like this
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#Configuration
#EnableWebMvc
class WebMvcContext extends WebMvcConfigurerAdapter {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new LocalDateTimeConverter("yyyy-MM-dd'T'HH:mm:ss.SSS"));
}
}
That's all and i hope it solves your problem.
You can learn more here
Well, i will still like to suggest that instead of leaving the responsibility to your site user to do the date time update why not allow hibernate to do that for you by using the #CreationTimestamp and #UpdateTimestamp annotations of hibernate or preferably by using JPA #PrePersist and #PreUpdate annotations like this
#Column(name="startDateTime")
#PrePersist
private LocalDateTime startDateTime;
#Column(name="updatedDateTime")
#PreUpdate
private LocalDateTime updatedDateTime;
With this approach, you no longer need to add startDateTime field in your form because hibernate will automatically insert and update those columns for you.
Note: Form input type datetime-local is not supported in all browers.
The problem is the parameter send to spring mvc is String, your dateToday type is date. Spring mvc don't know how to convert it.
You need a #InitBinder
Consult below
#InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
binder.registerCustomEditor(Timestamp.class, new CustomDateEditor(dateFormat, false));
}
In your hibernate configuration file .xml, is it set to update? Try checking on it.
<property name="hbm2ddl.auto">update</property>
Use initbinder in your controller class
#InitBinder
public void dataBinding(WebDataBinder binder) {
SimpleDateFormat dateFormat = new
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
dateFormat.setLenient(false);
binder.registerCustomEditor(LocalDateTime.class, "startDateTime", new CustomDateEditor(dateFormat, true));
}
Hope this solves it
I have a Grails 3.1.2 application
One of my domain classes is Goal
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
class Goal {
String name
String description
LocalDateTime targetDate
Date dateCreated
Date lastUpdated
static constraints = {
name(nullable: false, blank: false)
}
}
When I call validate on my an instance of Goal I get:
Field error in object 'com.liftyourgame.model.Goal' on field 'targetDate': rejected value [2016-01-28T15:10:39.000Z]; codes [com.liftyourgame.model.Goal.targetDate.typeMismatch.error,com.liftyourgame.model.Goal.targetDate.typeMismatch,goal.targetDate.typeMismatch.error,goal.targetDate.typeMismatch,typeMismatch.com.liftyourgame.model.Goal.targetDate,typeMismatch.targetDate,typeMismatch.java.time.LocalDateTime,typeMismatch]; arguments [targetDate]; default message [Could not find matching constructor for: java.time.LocalDateTime(java.lang.String)]
How can I change the validation so it doesn't need this constructor?
How can I change the validation so it doesn't need this constructor?
You can't, but the validation isn't really the problem. The data binder is the problem. The default binder does not yet have built in support for LocalDateTime.
You can register your own converter like this:
Converter:
// src/main/groovy/demo/MyDateTimeConverter.groovy
package demo
import grails.databinding.converters.ValueConverter
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
class MyDateTimeConverter implements ValueConverter {
#Override
boolean canConvert(Object value) {
value instanceof String
}
#Override
Object convert(Object value) {
def fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
LocalDateTime.parse(value, fmt)
}
#Override
Class<?> getTargetType() {
LocalDateTime
}
}
Register it as a bean:
// grails-app/conf/spring/resources.groovy
beans = {
myConverter demo.MyDateTimeConverter
}
I tried to use the DataTime in my entity class. Adding #Temporal(TemporalType.DATE) above the field, I got the error saying "The persistent field or property for a Temporal type must be of type java.util.Date, java.util.Calendar or java.util.GregorianCalendar". I can introduce the conversion back and forth; using setters and getters as follows:
#Temporal(TemporalType.DATE)
private Calendar attendanceDate;
public DateTime getAttendanceDate() {
return new DateTime(this.attendanceDate);
}
public void setAttendanceDate(DateTime attendanceDate) {
this.attendanceDate = attendanceDate.toCalendar(Locale.getDefault());
}
but I hope eclipselink to do it for me. I have gone thro' Persist Joda-time's DateTime via Hibernate. The answer suggesting to use hibernate, but I have to use eclipselink. I can use the DateTime object in my entity class with DB representation as BLOB, but I need it as Date. Is there anything like jodatime-eclipselink? Or any other suggestion? Thanks for the help.
Basic the link defines an EclipseLink Converter to convert from Joda DateTime to java.sql.Timestamp or Date.
You could use it, or define your own converter and use it through #Convert, #Converter in EclipseLink.
For DDL creation, the converter should define the initialize method and set the type on the mapping's field to java.sql.Timestamp.
Please log a bug (or vote for the existing one) on this in EclipseLink, we should have support for Joda.
I Try use joda-time-eclipselink-integration, but don't work, problably I made something wrong,
So I made more researchs and i found this link http://jcodehelp.blogspot.com.br/2011/12/persist-joda-datetime-with-eclipselink.html, they use #Converter annotation to convert the Joda Date Time.
I Try and works for me, I hope, works for you too.
I wanted to do the same thing, and Googling around actually led me here. I was able to accomplish this using the #Access annotation. First, you annotate the class like this
#Access(AccessType.FIELD)
public class MyClass
{
....
This provides field access to everything so you don't have to annotate the fields individually. Then you create a getter and setter for the JPA to use.
#Column(name="my_date")
#Temporal(TemporalType.DATE)
#Access(AccessType.PROPERTY)
private Date getMyDateForDB()
{
return myDate.toDate();
}
private void setMyDateForDB(Date myDateFromDB)
{
myDate = new LocalDate(myDateFromDB);
}
The #Access(AccessType.PROPERTY) tells JPA to persist and retrieve through these methods.
Finally, you'll want to mark your member variable as transient as follows
#Transient
private LocalDate myDate = null;
This stops JPA from trying to persist from the field as well.
That should accomplish what you're trying to do. It worked great for me.
Ahamed, you mentioned it wasn't working for you. Additionally you need to override the initialize method of the converter to define the desired field type:
#Override
public void initialize(DatabaseMapping mapping, Session session) {
((AbstractDirectMapping) mapping)
.setFieldClassification(java.sql.Timestamp.class);
}
The following is a working example based on the answers available in the topic
Basically the easiest approach is to use EclipseLink #Converter for a DateTime field in your Entity.
The converter usage looks as follows:
import org.eclipse.persistence.annotations.Convert;
import org.eclipse.persistence.annotations.Converter;
import javax.persistence.*;
import org.joda.time.DateTime;
#Entity
public class YourEntity {
#Converter(name = "dateTimeConverter", converterClass = your.package.to.JodaDateTimeConverter.class)
#Convert("dateTimeConverter")
private DateTime date;
And the converter itself:
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.converters.Converter;
import org.eclipse.persistence.sessions.Session;
import org.joda.time.DateTime;
import java.sql.Timestamp;
public class JodaDateTimeConverter implements Converter {
private static final long serialVersionUID = 1L;
#Override
public Object convertDataValueToObjectValue(Object dataValue, Session session) {
return dataValue == null ? null : new DateTime(dataValue);
}
#Override
public Object convertObjectValueToDataValue(Object objectValue, Session session) {
return objectValue == null ? null : new Timestamp(((DateTime) objectValue).getMillis());
}
#Override
public void initialize(DatabaseMapping mapping, Session session) {
// this method can be empty if you are not creating DB from Entity classes
mapping.getField().setType(java.sql.Timestamp.class);
}
#Override
public boolean isMutable() {
return false;
}
}
I am adding this for the purpose of easy copy-and-paste solution.
Solution is here
joda-time-eclipselink-integration
Answer from Atais works well. Below an upgrade to it.
You can omit #converter annotation by registering it globally.
At persistance.xml in persitence-unit add:
<mapping-file>META-INF/xyz-orm.xml</mapping-file>
and file META-INF/xyz-orm.xml with content:
<?xml version="1.0"?>
<entity-mappings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/orm" version="2.1">
<converter class="pl.ds.wsc.storage.converter.JodaDateTimeConverter"/>
</entity-mappings>
If your config file is META-INF/orm.xml then you can omit even first step because it is default confing for all persitence units.