JPA and searching by several dates - java

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)));

Related

Java Stream - group by mutiple field

I have a List of ServerPricing Object, which i want to group by offeringClass and payment option.
Tried below code , but failing. Advice please
Map<String, List<ServerPricing>> groupByOfferingClassPaymentOption =
serverPricingList.stream().collect(
p.getOfferingClass().getOfferingClassMapped()+ p.getPaymentOption().getPaymentOptionMapped(),
Collectors.groupingBy(
p -> Arrays.asList(p.getOfferingClass().getOfferingClassMapped(), p.getPaymentOption().getPaymentOptionMapped())));
and below is the server pricing class
public class ServerPricing implements Serializable {
#JsonIgnore
private Date createdDate;
private LeaseTerm leaseTerm;
private OfferingClass offeringClass;
private PaymentOption paymentOption;
private int serverAttributesId;
#Transient
private float price;
}
given
import org.apache.commons.lang3.tuple.Pair;
you could have the following
public static Map<Pair<OfferingClass, PaymentOption>, List<ServerPricing>> getServerPricingMap(
List<ServerPricing> serverPricingList) {
return serverPricingList.stream()
.collect(Collectors.groupingBy(serverPricing
-> Pair.of(serverPricing.getOfferingClass(), serverPricing.getPaymentOption())));
}

LocalDate with JPA does not work correctly

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;

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

Query for mongoRepository list

I have a MongoDB collection with documents with the following fields:
date (Date obj)
offerType (str)
I want to write a method with MongoRepository to find all the documents that has date in a range of dates and offerType contains one of the string provided in a list.
Example
Documents:
date: 10-04-2019, offerType: offer1
date: 11-04-2019, offerType: offer3
date: 15-04-2019, offerType: offer2
date: 15-04-2019, offerType: offer1
I want:
dates between 9-04-2019 and 12-04-2019
following offers: offer1, offer3
In the previous example I would obtain documents 1 and 2.
My code
I use MongoRepository and a custom Object which contains the fields I require:
import java.util.Date;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface ReportVocalOrderRepository extends MongoRepository<ReportVocalOrder, String> {
List<ReportVocalOrder> findByDateBetween(Date startDate, Date endDate, Pageable pageable);
List<ReportVocalOrder> findByDateBetweenAndOfferTypeContaining(Date startDate, Date endDate, List<String> offers, Pageable pageable);
}
Here is the document class:
#JsonInclude(Include.NON_NULL)
#Document(collection = Constants.Mongo.Collections.VOCAL_ORDER_REPORT)
#ApiModel
public class ReportVocalOrder {
#Id
private String id;
private Date date;
private String offerType;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getOfferType() {
return offerType;
}
public void setOfferType(String offerType) {
this.offerType = offerType;
}
}
The first method of the MongoRepository works fine; the second one instead returns an empty list.
The problem is to query the mongoRepository to search a field that can contain one of the values of the list passed as an argument.
What's wrong with this implementation? There's a better way to implement this query?
It was simpler than I thought. I post an answer for whom is interested:
import java.util.Date;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface ReportVocalOrderRepository extends MongoRepository<ReportVocalOrder, String> {
List<ReportVocalOrder> findByDateBetweenAndOfferTypeIn(Date startDate, Date endDate, List<String> offers, Pageable pageable);
}
So the point is to use the keyword In.
We have another option to using Query
#Query(value = "{ 'groupId' : ?0 , 'user.$id' : {$in: ?1} }", delete = true)
List<GroupUser> deleteUserInGroup(String groupId, List<ObjectId> userId);
You can try it.

How to return objects in Jersey 2.x

I've got a web service which manages Parada objects. What I want to achieve seems straightforward: return lists of these objects:
List<Parada> list
This list is returned using a Service class which uses another DAO class, just commenting it out.
Besides, my common practise is that every web method return a Response using ResponseBuilder, as in here:
return Response.ok(obj, MediaType.APPLICATION_JSON).build();
This is an example of one of my web methods:
#GET
#Consumes(value = MediaType.TEXT_PLAIN)
#Produces(MediaType.APPLICATION_JSON)
#Path("{idParadaGtfs}")
public Response getParadasPorIdGtfs(
#PathParam(value = "idParadaGtfs") Integer pCodigoParadaEnGtfs
){
try{
getServiceIfNecessary();
List<Parada> paradas = service.getParadas(pCodigoParadaEnGtfs);
return Response.ok(paradas, MediaType.APPLICATION_JSON).build();
}catch(HibernateException e){
String msg = "Error HibernateException: " + e.getMessage();
LogHelper.logError(logger, msg, true);
e.printStackTrace();
return Response.serverError().tag(msg).build();
}catch(Exception e){
String msg = "Error Exception: " + e.getMessage();
LogHelper.logError(logger, msg, true);
e.printStackTrace();
return Response.serverError().tag(msg).build();
}
}
Unfortunately, I'm not receiving any object and I get the following error everytime I execute the web method described above:
nov 26, 2015 2:20:16 PM org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor aroundWriteTo
GRAVE: MessageBodyWriter not found for media type=application/json, type=class java.util.ArrayList, genericType=java.util.List<model.Parada>.
What do I have to implement to let my web methods build Responses using Lists?
Thank you!
EDIT:
I've been able to make it work by making some changes and additions, which I'll describe now.
First of all, I've added a Parada container class, ParadaContainer:
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import com.ingartek.ws.paradasasociadasws.model.Parada;
#XmlRootElement
public class ParadaContainer implements Serializable {
private static final long serialVersionUID = 6535386309072039406L;
private List<Parada> paradas;
public ParadaContainer(ArrayList<Parada> pParadas) {
this.setParadas(pParadas);
}
public List<Parada> getParadas() {
return paradas;
}
public void setParadas(List<Parada> paradas) {
this.paradas = paradas;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("ParadaContainer [");
if (paradas != null) {
builder.append("paradas=");
for(Parada p : paradas){
builder.append(p.toString());
}
}
builder.append("]");
return builder.toString();
}
}
Now, I'm not returning a List of Parada objects, instead I return a single ParadaContainer object:
ParadaContainer paradas = new ParadaContainer(new ArrayList<Parada>(service.getParadas()));
return Response
.ok(paradas)
.type(MediaType.APPLICATION_JSON)
.build();
I don't know whether they are mandatory or not, but I've created another class (MyObjectMapperProvider)...
import javax.ws.rs.ext.ContextResolver;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
final ObjectMapper defaultObjectMapper;
public MyObjectMapperProvider() {
defaultObjectMapper = createDefaultMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return defaultObjectMapper;
}
private static ObjectMapper createDefaultMapper() {
final ObjectMapper result = new ObjectMapper();
result.configure(SerializationFeature.INDENT_OUTPUT, true);
return result;
}
}
...and edited my Application class and added some lines (see as of *Jackson * comment until Clases de Servicios comment):
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.core.Application;
import org.glassfish.jersey.jackson.JacksonFeature;
import com.ingartek.ws.paradasasociadasws.ws.ParadasWS;
public class App extends Application {
private final Set<Class<?>> classes;
public App() {
HashSet<Class<?>> c = new HashSet<Class<?>>();
// Filtro CORS:
c.add(CORSFilter.class);
// Jackson
c.add(MyObjectMapperProvider.class);
c.add(JacksonFeature.class);
// Clases de Servicios:
c.add(ParadasWS.class);
classes = Collections.unmodifiableSet(c);
}
#Override
public Set<Class<?>> getClasses() {
return classes;
}
}
Afterwards, I've edited my class model by adding some annotations to them (#XmlRootElement and #JsonProperty; removed irrelevant getters, setters, hashCode, equals and toString methods):
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlRootElement;
import com.fasterxml.jackson.annotation.JsonProperty;
#XmlRootElement(name = "grupo")
#Entity
#Table(name = "grupos_cercania_exacta")
public class Grupo implements Serializable {
#Transient
private static final long serialVersionUID = -5679016396196675191L;
#JsonProperty("id")
#Id
#Column(name = "id_grupo")
private Integer id;
...
}
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlRootElement;
import com.fasterxml.jackson.annotation.JsonProperty;
#XmlRootElement(name = "operador")
#Entity
#Table(name = "operadores_asociados")
public class Operador implements Serializable {
#Transient
private static final long serialVersionUID = -7557099187432476588L;
/*
Atributos
*/
#JsonProperty("codigo")
#Id
#Column(name = "codigo_operador", insertable = false, updatable = false)
private Integer codigo;
#JsonProperty("nombre")
#Column(name = "descripcion_corta", insertable = false, updatable = false)
private String nombre;
#JsonProperty("descripcion")
#Column(name = "descripcion_larga", insertable = false, updatable = false)
private String descripcion;
#JsonProperty("web")
#Column(name = "direccion_web", insertable = false, updatable = false)
private String web;
#JsonProperty("telefono")
#Column(name = "telefono", insertable = false, updatable = false)
private String telefono;
...
}
import java.io.Serializable;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlRootElement;
import com.fasterxml.jackson.annotation.JsonProperty;
#XmlRootElement(name = "parada")
#Entity
#Table(name = "paradas_asociadas")
public class Parada implements Serializable {
#Transient
private static final long serialVersionUID = -3594254497389126197L;
#JsonProperty("id")
#Id
#Column(name = "id")
private UUID id;
#JsonProperty("codigoMunicipio")
#Column(name = "codigo_municipio")
private Integer codigoMunicipio;
#JsonProperty("nombre")
#Column(name = "nombre")
private String nombre;
#JsonProperty("descripcion")
#Column(name = "descripcion")
private String descripcion;
#JsonProperty("idGtfs")
#Column(name = "id_gtfs")
private Integer idGtfs;
#JsonProperty("idWs")
#Column(name = "id_ws")
private Integer idWs;
#JsonProperty("latitud")
#Column(name = "latitud")
private Double latitud;
#JsonProperty("longitud")
#Column(name = "longitud")
private Double longitud;
#JsonProperty("utmX")
#Column(name = "utm_x")
private Double utmX;
#JsonProperty("utmY")
#Column(name = "utm_y")
private Double utmY;
#JsonProperty("grupo")
#ManyToOne
#JoinColumn(name = "grupo_cercania_exacta_id")
private Grupo grupo;
#JsonProperty("operador")
#ManyToOne
#JoinColumn(name = "operador")
private Operador operador;
...
}
I've to admit that I've had some problems just after these changes. Sharp people could've realised that there is a missing attribute regarding the previous Parada class: the lack of Point attribute. This attribute was causing me some problems, this is, the absence of a Serializer and a Serializer was preventing me from creating a successful JSON. So I googled it out and found three options:
Remove the Point item. This was my ultimate choice, as Point was superfluous due to the existence of Latitude and Longitude elements and because it just could bother or confuse the final user.
Creating a custom Serializer and Deserializer. Fortunately I found the following link, which describes the process of creating them. The following is described in here:
Add these annotations to our coordinates field:
#JsonSerialize(using = PointToJsonSerializer.class)
#JsonDeserialize(using = JsonToPointDeserializer.class)
Create such serializer:
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.vividsolutions.jts.geom.Point;
public class PointToJsonSerializer extends JsonSerializer<Point> {
#Override
public void serialize(Point value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
String jsonValue = "null";
try
{
if(value != null) {
double lat = value.getY();
double lon = value.getX();
jsonValue = String.format("POINT (%s %s)", lat, lon);
}
}
catch(Exception e) {}
jgen.writeString(jsonValue);
}
}
Create such deserializer:
import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.PrecisionModel;
public class JsonToPointDeserializer extends JsonDeserializer<Point> {
private final static GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), 26910);
#Override
public Point deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
try {
String text = jp.getText();
if(text == null || text.length() <= 0)
return null;
String[] coordinates = text.replaceFirst("POINT ?\\(", "").replaceFirst("\\)", "").split(" ");
double lat = Double.parseDouble(coordinates[0]);
double lon = Double.parseDouble(coordinates[1]);
Point point = geometryFactory.createPoint(new Coordinate(lat, lon));
return point;
}
catch(Exception e){
return null;
}
}
}
The last option is to use Jackson Datatype JTS library, whose github repository lays here.
I lasted some hours so that I could find these solutions, but finally I got them. Hope it helps to someone. Thank you!
Either you don't have a JSON provider (I am guessing you do) or you are using MOXy. Under the latter assumption, with MOXy, it needs to know the type information in order to be able to serialize. When you return Response, you are wrapping the object, which takes away type information (because of type erasure), as opposed to if you were doing
#GET
public List<Parada> get() {}
Here the type information is known. But doing
#GET
public Response get() {
List<Parada> list..
return Response.ok(list)...
}
The type is hidden and erased by the time the entity reaches the serialization phase of the processing.
To get around this, GenericEntity was introduced
Normally type erasure removes generic type information such that a Response instance that contains, e.g., an entity of type List<String> appears to contain a raw List<?> at runtime. When the generic type is required to select a suitable MessageBodyWriter, this class may be used to wrap the entity and capture its generic type.
So you can do
List<Parada> paradas = ...
GenericEntity<List<Parada>> entity = new GenericEntity<List<Parada>>(paradas){};
return Response.ok(entity, ...)...
Second option, is to instead of using MOXy, use Jackson instead. With Jackson, the type info is not needed (in most cases), as the serializer just introspects and the bean bean properties to get the data.
It is not allowed to send a List back. Probably because List has no #XmlRootElement notation. You can create your own container:
#XmlRootElement
public class ParadaContainer implements Serializable {
private List<Parada> list;
public List<Parada> getList() {
return list;
}
public void setList(List<Parada> list) {
this.list = list;
}
}
You part will look like:
try{
getServiceIfNecessary();
List<Parada> paradas = service.getParadas(pCodigoParadaEnGtfs);
ParadaContainer paradaContainer = new ParadaContainer();
paradaContainer.setList(paradas);
return Response.ok(paradaContainer, MediaType.APPLICATION_JSON).build();
}

Categories