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.
Related
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;
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)));
This is how my entity looks like:
#Entity
public class Registration {
#Id
#GeneratedValue
private Integer id;
#org.springframework.format.annotation.DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate date;
}
This is how my repo could look like:
#Query(value = "SELECT * FROM registration WHERE MONTH(date) = ?1 AND YEAR(date) = ?2")
List<Registration> findAll(Integer month, Integer year);
And this will be service:
public List<Registration> getCurrentRegistration() {
LocalDate today = LocalDate.now();
return registrationRepository.findAll(today.getMonth().getValue(), today.getYear());
}
public List<Registration> getRegistrations(Integer month, Integer year) {
return registrationRepository.findAll(month, year);
}
How can I change my native query to be JPA query?
Will the JPA query able to work on postgresql and hsqldb?
And why JPA queries are the best for spring apps? (or why they are not)
Make a specification class and write the below specification method in it.
import javax.persistence.criteria.Predicate;
import org.springframework.data.jpa.domain.Specification;
public class RegistrationSpecification {
public static Specification<Registration > registrationSpecForDate(
LocalDate invoiceDate ) {
return (root, cq, cb) -> {
List<Predicate> predicates = new ArrayList<Predicate>();
if (invoiceDate!=(null)) {
predicates.add(cb.greaterThanOrEqualTo(root.get("date"),
invoiceDate));
}
return cb.and(predicates.toArray(new Predicate[0]));
};
}
Then in your repository inject this specification in JPA's findAll() method.
`public List<Registration> getRegistrations(LocalDate date) {
return
registrationRepository.findAll
(RegistrationSpecification.registrationSpecForDate(date));
`
You could do this using QueryDSL JPA (https://github.com/querydsl/querydsl/tree/master/querydsl-jpa) to define a predicate:
Predicate createPredicate(Integer month, Integer year) {
return QRegistration.date.year().eq(year).and(QRegistration.date.month().eq(month));
}
Then make your repo extend QueryDslPredicateExecutor:
public interface RegistrationRepository extends JpaRepository<Registration>, QueryDslPredicateExecutor {
// Your query methods here
}
This contains a List<T> findAll(Predicate predicate) method which you can pass your predicate in to obtain the items you were after, e.g.:
registrationRepository.findAll(createPredicate(1, 1970));
See here for more info about using QueryDSL with Spring: https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
In my browser debug, I can see that there is a date parameter inside my v object (Wed Mar 25 2015 03:00:00 GMT+0300 (Turkey Standard Time)),full text string format.
function saveVehicle(v) {
return $http.post('/shipment/vehicle/save', v).then(function(response) {
return response.data;
})
The problem is in my requestmapping debug, that date parameter comes with null. The server side coding is like this:
#RequestMapping(value="/vehicle/save", method = RequestMethod.POST)
public Vehicle saveVehicle(#RequestBody Vehicle v){
return vehicleRepository.save(v);
}
And my Vehicle model is like this:
#Entity
#Table(name = "VEHICLE", schema = "VV")
public class Vehicle {
#Column(name = "LOADING_DT")
#JsonSerialize(using = TfJsonDateSerializer.class)
#JsonDeserialize(using = TfJsonDateDeSerializer.class)
private Date loadingDate;
You need to map your object 'v' send from browser into the Java Object 'Vehicle'.
Usually using a json mapper or custom mapping from Map to your Vehicle pojo.
also try to POST a well formed object, that ressambles by parameter names your pojo.
v = {
"loading_date": new Date()
}
$http.post(..., v);
Additionally, I see that you're using custom (de)serializers, so please either post their code or be sure that they perform correctly, according to how the JS is serializing the date value
best
nas
It might be that the property into the request body doesn't have the same name of the java Vehicle#loadingDate attribute.
Assuming your request body has an attribute called loading_date, you have to map that name to the java attribute as follows:
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonProperty("loading_date")
private Date loadingDate;
Also, it might be a good idea to define a string conversion for dates:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
#JsonProperty("loading_date")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "UTC")
private Date loadingDate;
and add getters and setters in case you forgot:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
#JsonProperty("loading_date")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "UTC")
private Date loadingDate;
public Date getLoadingDate() {
return loadingDate;
}
public void setLoadingDate(Date loadingDate) {
this.loadingDate = loadingDate;
}
I have a hazelcast instance whose key is of type MyObject and value is an enum.
Let's say one of the attributes of MyObject class is date which is of type java.sql.Date.
class MyObject {
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date
}
}
public enum MyEnum {
TEST_ENUM;
}
Also I am using predicate to filter on the keys retrieve the enum value.
For ex:
EntryObject entryObject = new PredicateBuilder().getEntryObject();
PredicateBuiler predicateBuilder = entryObject.key.get(date).isNull;
This is how I am trying to add index:
IMap<MyObject, MyEnum> map = hazelcastInstance.getMap("test");
map.addIndex("date", true)
But as soon as this gets executed an exception is being thrown:
com.hazelcast.query.QueryException: java.lang.IllegalArgumentException: There is no suitable accessor for 'date' on class 'class com.main.constants.myEnum'
at com.hazelcast.query.impl.getters.ReflectionHelper.createGetter(ReflectionHelper.java:176)
at com.hazelcast.query.impl.getters.Extractors.instantiateGetter(Extractors.java:88)
at com.hazelcast.query.impl.getters.Extractors.getGetter(Extractors.java:73)
at com.hazelcast.query.impl.getters.Extractors.extract(Extractors.java:57)
at com.hazelcast.query.impl.QueryableEntry.extractAttributeValueFromTargetObject(QueryableEntry.java:156)
at com.hazelcast.query.impl.QueryableEntry.extractAttributeValue(QueryableEntry.java:82)
at com.hazelcast.query.impl.QueryableEntry.getAttributeValue(QueryableEntry.java:48)
at com.hazelcast.query.impl.QueryableEntry.getConverter(QueryableEntry.java:67)
at com.hazelcast.query.impl.IndexImpl.saveEntryIndex(IndexImpl.java:67)
at com.hazelcast.map.impl.operation.AddIndexOperation.run(AddIndexOperation.java:75)
I understand it's trying to find the index attribute in the value class
How do I get this thing working i.e. add the index on the Key rather than on the value.
While writing a test I actually found your problem :-)
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.query.EntryObject;
import com.hazelcast.query.PredicateBuilder;
import java.io.Serializable;
import java.sql.Date;
public class Test
{
public static void main(String[] args)
{
HazelcastInstance hz = Hazelcast.newHazelcastInstance();
IMap<MyObject, MyEnum> map = hz.getMap("test");
EntryObject entryObject = new PredicateBuilder().getEntryObject();
PredicateBuilder builder = entryObject.key().get("date").isNull();
map.addIndex("__key#date", true);
map.put(new MyObject(), MyEnum.TEST_ENUM);
}
public static class MyObject implements Serializable
{
private Date date;
public Date getDate()
{
return date;
}
public void setDate(Date date)
{
this.date = date;
}
}
public static enum MyEnum {
TEST_ENUM;
}
}
The trick is to create the index based on the map-key and not the value which is taken by default. You already did it in your query entryObject.key() but missed it on the index definition __key.date.