LocalDate with JPA does not work correctly - java

I do request localhost:8080/history/world/2020-02-08
Entity:
public class DailyStatistic {
...
#Column(columnDefinition = "DATE")
private LocalDate date;
...
Controller:
#GetMapping("/world/{date}")
public ResponseEntity<List<DailyStatistic>> getWorldStatByDate(#PathVariable String date) {
List<DailyStatistic> worldStatList = null;
try {
worldStatList = dataProvider.getWorldStatByDate(LocalDate.parse(date));
...
Invoked dataProvider method:
public List<DailyStatistic> getWorldStatByDate(LocalDate date) throws NoDataException {
List<DailyStatistic> dailyStatisticList = repository.findAllByDate(date);
...
Invoked repository method:
#Repository
public interface DailyStatRepository extends JpaRepository<DailyStatistic, Long> {
List<DailyStatistic> findAllByDate(LocalDate date);
Json answer:
{
...
"date": "2020-02-07",
...
}
Remind input: localhost:8080/history/world/2020-02-08
So I get a wrong resultSet. Anybody knows why it happens and how it solve?

Try with this way in your entity
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.time.LocalDateTime;
#JsonSerialize(converter = LocalDateTimeToStringConverter.class)
#JsonDeserialize(converter = StringToLocalDatetimeConverter.class)
private LocalDateTime date;
See this
Or simply this way
#JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate date;

Related

JPA and searching by several dates

This is my entity class
#Data
#NoArgsConstructor
#Builder
#AllArgsConstructor
#Entity(name = "words")
public class Word {
#Id
private Long id;
#ManyToOne
private Group group;
#ManyToOne
private User user;
#NotBlank
private String englishWord;
#NotBlank
private String russianWord;
private LocalDate created = LocalDate.now();
private final LocalDate plusOneDay = created.plusDays(1);
private final LocalDate plusTwoDays = created.plusDays(2);
private final LocalDate plusFiveDays = created.plusDays(5);
private final LocalDate plusTenDays = created.plusDays(10);
private final LocalDate plusTwoWeeks = created.plusWeeks(2);
private final LocalDate plusSixWeeks = created.plusWeeks(6);
private final LocalDate plusThreeMonths = created.plusMonths(3);
private final LocalDate plusSixMonths = created.plusMonths(6);
private final LocalDate plusOneYear = created.plusYears(1);
}
As you see I have several LocalDate fields.
I need to check, is created, OR plusOneDay, OR plusTwoWeek, etc matches with today's day. If it matches - it must be got from a database. I can write something like that:
Set<Word> findAllByCreatedOrByPlusOneDayOrByPlusTwoDays(LocalDate date);
But the request will be too long, and it doesn't work.
Is there another way to check several fields by one date?
I think that you may use Specification. Doc: https://docs.spring.io/spring-data/jpa/docs/1.7.0.RELEASE/reference/html/#specifications
I made some code, that may help.
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.time.LocalDate;
public class WordSpecs {
public static Specification<Word> isOneDateEqual(final LocalDate date){
return new Specification<Word>() {
#Override
public Predicate toPredicate(Root<Word> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
final Predicate created = criteriaBuilder.equal(root.get("created"), date);
final Predicate plusOneDay = criteriaBuilder.equal(root.get("plusOneDay"), date);
return criteriaBuilder.or(created, plusOneDay);
}
};
}
}
Repository Class:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
#org.springframework.stereotype.Repository
public interface WordRepository extends JpaRepository<Word, Long>, JpaSpecificationExecutor {
}
Service class
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;
#Service
public class WordService {
#Autowired
private WordRepository repository;
public List<Word> findMatched(final LocalDate date){
return repository.findAll(WordSpecs.isOneDateEqual(date));
}
}
Edit:
Much easier, you may like:
#Query("SELECT word FROM Word word WHERE 1 = 1 AND word.user.userId = :userId AND ( word.created = :date OR word.plus1 = :date ) " )
List<Word> findMatched(#Param("date") final LocalDate date, #Param("userId") final Long userId);
Yes, you can pass a list of possible dates:
Set<Word> findAllByCreatedIn(List<LocalDate> dates);
and then invoke it as:
repo.findAllByCreatedIn(List.of(date, date.minusDays(1), date.minusDays(2)));

How to pass local date in path variable in Spring Boot?

I'm writing REST service.
I want to get all records by date that I pass in #Path variable.
How Can I do that?
What I tried to do:
Model Class:
#Entity
#Table(name = "test")
public class Test {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate beginDate;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate endDate;
private String activity;
}
Repository:
#Repository
public interface TestRepository extends JpaRepository<Test, Integer> {
List<Test> findAllByName(String name);
List<Test> findAllByBeginDate(LocalDate date);
}
Service:
#Service
public class TestService {
#Autowired
private final TestRepository testRepository;
public TestService(TestRepository testRepository) {
this.testRepository = testRepository;
}
public List<Test> getAllTestsByBeginDate(LocalDate date) {
return testRepository.findAllByBeginDate(date);
}
}
Controller:
#RestController
#RequestMapping("/api/v1/")
public class TestController {
#GetMapping("test/all/{date}")
public List<Test> getAllTestsByBeginDate(#PathVariable ("date") #DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) {
return testService.getAllTestsByBeginDate(date);
}
}
When I pass date like this, I get errors:
This should work
#RestController
#RequestMapping("/api/v1/")
public class TestController {
#GetMapping("test/all/{date}")
public List<Test> getAllTestsByBeginDate(#PathVariable #DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) {
return testService.getAllTestsByBeginDate(date);
}
}
or this link will help
You can global configure any datetime format in application properties. Like:
spring.mvc.format.date=yyyy-MM-dd
spring.mvc.format.date-time=yyyy-MM-dd HH:mm:ss
spring.mvc.format.time=HH:mm:ss

Spring Data Redis - Issue while storing Date

I am using Spring Boot + Spring data Redis example to save Date into the Redis Cache. Although I used #DateTimeFormat #JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd"), but still persistance happening is long value. Look like its a millisecond.
Can somebody guide if I need to set extra configurations to persist date like yyyy-MM-dd.
HGETALL users:1
1) "_class"
2) "com.XXX.entity.User"
3) "userId"
4) "1"
5) "name"
6) "John"
7) "createdDate"
8) "1542043247352"
Entity classes:
#Builder
#Data
#AllArgsConstructor
#NoArgsConstructor
#RedisHash("users")
public class User {
#Id
private Long userId;
private String name;
#DateTimeFormat
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private Date createdDate;
private List<Group> groups;
}
UPDATE-1:: As per suggestion I implemented, but still not working
CustomDateSerializer.java
#Component
public class CustomDateSerializer extends JsonSerializer<Date> {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy");
#Override
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider)
throws IOException, JsonProcessingException {
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
}
}
Custom Interface
#Retention(RetentionPolicy.RUNTIME)
public #interface MyJsonFormat {
String value();
}
Model class
#MyJsonFormat("dd.MM.yyyy")
#JsonSerialize(using = CustomDateSerializer.class)
private Date createdDate;
I'd advise using LocalDateTime (or LocalDate if you prefer) instead. You can then annotate your fields with
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime createdAt;
using jackson's jsr310 add on:
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
By Using Custom Serializer, this can be solved. Ref #https://kodejava.org/how-to-format-localdate-object-using-jackson/#comment-2027
public class LocalDateSerializer extends StdSerializer<LocalDate> {
private static final long serialVersionUID = 1L;
public LocalDateSerializer() {
super(LocalDate.class);
}
#Override
public void serialize(LocalDate value, JsonGenerator generator, SerializerProvider provider) throws IOException {
generator.writeString(value.format(DateTimeFormatter.ISO_LOCAL_DATE));
}
}
POJO:
#JsonDeserialize(using = LocalDateDeserializer.class)
#JsonSerialize(using = LocalDateSerializer.class)
private LocalDate createdDate;

Sending date to REST service in JSON

I'm developing a CRUD application with a REST interface in Java-EE.
I have some entities which contains Date fields. When I want to create an instance of such an entity with a POST request with the JSON in the request body, jax-rs (or the underlying deserializer) complains about parsing the date part.
This is the exception I got:
Servlet.service() for servlet service.JAXRSConfiguration threw
exception java.time.format.DateTimeParseException: Text '2011-11-11'
could not be parsed at index 10
I tried to send this in the request:
{
"title": "testTitle",
"description": "testDescription",
"playtime": 50,
"creationDate": "2011-11-11"
}
How should I define the date in the json to get it parsed successfully? Which is the correct format?
Here is the entity class:
#Entity
#NamedQueries({
#NamedQuery(name = "Movie.findAll", query = "SELECT m FROM Movie m")
})
public class Movie extends AbstractDao{
private String title;
private String description;
#ManyToMany(mappedBy = "movies")
private List<Actor> actors;
#ManyToMany(mappedBy = "movies")
private List<Director> directors;
private int playtime;
#OneToOne(cascade = CascadeType.PERSIST)
private Trailer trailer;
#Temporal(value = TemporalType.DATE)
private Date creationDate;
getters,setters, etc
Here is the jax-rs service:
#Path("/movies")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class MovieRestService {
#Inject
private MovieService movieService;
#GET
public List<Movie> getMovies(){
return movieService.findAll();
}
#Path("{id}")
#GET
public Movie getMovie(#PathParam("id") long id){
return movieService.findById(id);
}
#POST
public Movie addMovie(Movie movie){
return movieService.create(movie);
}
#Path("{id}")
#PUT
public Movie updateMovie(Movie movie, #PathParam("id") long id){
return movieService.update(movie,id);
}
#Path("{id}")
#DELETE
public Movie deleteMovie(#PathParam("id") long id){
return movieService.delete(id);
}
}
Try to use a DateAdapter (XmlAdapter) like the following one:
And annotate:
#XmlJavaTypeAdapter(DateAdapter.class)
private Date creationDate;
You can also annotate at the getter of creationDate
Class:
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class DateAdapter extends XmlAdapter<String, Date> {
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
private static final String PATTERN_T_WITH_SEC = "yyyy-MM-dd'T'HH:mm:ss";
private SimpleDateFormat dateFormat = new impleDateFormat(PATTERN_T_WITH_SEC);
#Override
public String marshal(Date v) throws Exception {
dateFormat.setTimeZone(UTC);
String dateF = dateFormat.format(v);
return dateF;
}
#Override
public Date unmarshal(String v) throws Exception {
dateFormat.setTimeZone(UTC);
Date date = dateFormat.parse(v);
return date;
}
}

Can not instantiate value of type [simple type, class java.time.LocalDate] from String value

I have a class like this:
#Data
#NoArgsConstructor(force = true)
#AllArgsConstructor(staticName = "of")
public class BusinessPeriodDTO {
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
LocalDate startDate;
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
LocalDate endDate;
}
And I used this class inside another class, let's call it PurchaseOrder
#Entity
#Data
#NoArgsConstructor(access = AccessLevel.PROTECTED, force = true)
public class PurchaseOrder {
#EmbeddedId
PurchaseOrderID id;
#Embedded
BusinessPeriod rentalPeriod;
public static PurchaseOrder of(PurchaseOrderID id, BusinessPeriod period) {
PurchaseOrder po = new PurchaseOrder();
po.id = id;
po.rentalPeriod = period;
return po;
}
And I'm trying to populate a purchaseOrder record using jakson and this JSON:
{
"_class": "com.rentit.sales.domain.model.PurchaseOrder",
"id": 1,
"rentalPeriod": {
"startDate": "2016-10-10",
"endDate": "2016-12-12"
}
}
But I have faced with an error:
java.lang.RuntimeException: com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class java.time.LocalDate] from String value ('2016-10-10');
I am sure jakson and popularization works correctly.
Include in your pom.xml:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.6</version>
</dependency>
Then in your BusinessPeriodDTO.java import LocalDateDeserializer as follows:
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
And finally, always in your BusinessPeriodDTO.java file, annotate the interested dates like this:
#JsonDeserialize(using = LocalDateDeserializer.class)
LocalDate startDate;
#JsonDeserialize(using = LocalDateDeserializer.class)
LocalDate endDate;
Old question but I recently had to answer it for myself.
There are different solutions (as commented by rapasoft, see for example here).
The quick solution I used involves adding a setDate(String) method for deserialization.
It might not be the prettiest solution, but it works without updating other classes.
Below a runnable class to demonstrate:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
/**
* Demonstrate Java 8 date/time (de)serialization for JSON with Jackson databind.
* Requires {#code com.fasterxml.jackson.core:jackson-databind:2.8.5}
* and {#code com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.8.5}
*/
public class JdateDto {
/** The pattern as specified by {#link java.text.SimpleDateFormat} */
public static final String ISO_LOCAL_DATE_PATTERN = "yyyy-MM-dd";
/* Used when serializing isoLocalDate. */
#JsonFormat(shape = Shape.STRING, pattern = ISO_LOCAL_DATE_PATTERN)
private LocalDate isoLocalDate;
public LocalDate getIsoLocalDate() {
return isoLocalDate;
}
/* Used when deserializing isoLocalDate. */
public void setIsoLocalDate(String date) {
setIsoLocalDate(LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE));
}
public void setIsoLocalDate(LocalDate isoDate) {
this.isoLocalDate = isoDate;
}
public static void main(String[] args) {
try {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
JdateDto dto = new JdateDto();
dto.setIsoLocalDate(LocalDate.now());
String json = mapper.writeValueAsString(dto);
System.out.println(json);
JdateDto dto2 = mapper.readValue(json, JdateDto.class);
if (dto.getIsoLocalDate().equals(dto2.getIsoLocalDate())) {
System.out.println("Dates match.");
} else {
System.out.println("Dates do not match!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

Categories