Spring Data Elasticsearch Query Merge - spring

I have a application for get a location from given points' 10 km radius and I have two filters for query; first is query by customer no and the second one is query by filtered items location according to. I want to combine elastic custom query and spring data method name based query. How can I merge them?
My document:
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.GeoPointField;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
#Getter
#Setter
#Document(indexName = "customer", replicas = 0, refreshInterval = "-1")
public class Customer {
#GeneratedValue(strategy= GenerationType.AUTO)
#Id
private String id;
private Integer cifNo;
private String userId;
private String name;
private Integer locationCount;
private Date lastSeenDate;
#GeoPointField
private GeoPoint geoPoint;
}
Repository;
#Repository
public interface CustomerLocationRepository extends ElasticsearchRepository<CustomerLocation,Long> {
List<CustomerLocation> findByCifNoAndUserIdIsNullOrderByLastSeenDateDesc(Integer cifNo);
List<CustomerLocation> findByCifNoAndUserIdOrderByLastSeenDateDesc(Integer clientNo, String userId);
}
Service:
public List<CustomerLocation> getCustomersPlacesIndexWithinLocation(GenericRequest genericRequest) {
GeoDistanceQueryBuilder geoDistanceQueryBuilder = QueryBuilders
.geoDistanceQuery("geoPoint")
.point(genericRequest.getLatitude(),genericRequest.getLongitude())
.distance(10, DistanceUnit.KILOMETERS);
List<CustomerLocation> customerLocationList;
if(genericRequest.getUserId()!=null) {
customerLocationList = customerLocationRepository.findByCifNoAndUserIdOrderByLastSeenDateDesc(Integer.valueOf(genericRequest.getClientNo()),genericRequest.getUserId());
} else {
customerLocationList = customerLocationRepository.findByCifNoAndUserIdIsNullOrderByLastSeenDateDesc(Integer.valueOf(genericRequest.getClientNo()));
}
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withFilter(geoDistanceQueryBuilder)
.build();
return elasticsearchTemplate.queryForList(searchQuery,CustomerLocation.class);
}
How can I combine spring data elasticsearch query result with custom location search query?

Currently this is not possible with Spring Data Elasticsearch. I added a Jira issue for this to be implemented as a new feature.

Related

Spring Boot JPA returns correct count but no data

Evening,
I have a Spring application that is connected to a PostgresSQL db. I can connect to the database and see that the query is returning the correct number of elements for the array but nothing in them:
curl http://localhost:8080/books
[{},{},{}]%
My Book model looks like this:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.math.BigDecimal;
#Entity
public class Book {
#Id
#GeneratedValue
private Long id;
private String name;
private String author;
private BigDecimal price;
public Book() {}
public Book(String name, String author, BigDecimal price) {
this.name = name;
this.author = author;
this.price = price;
}
}
and the controller:
#RestController
public class BookController {
#Autowired
private BookRepository repository;
// Find
#GetMapping("/books")
List<Book> findAll() {
List<Book> books = repository.findAll();
System.out.println(books);
return repository.findAll();
}
}
I've looked at these questions here, here and here but those answers didn't fit with this.
What am I not doing to see data come back?
In order for your entity to be serialized by Spring the entity needs to have getters for its properties. You could use lombok to auto-generate getter/setters for you entity properties or just write them your own.

How do I update a product that is partially defined then later add additional product details to it

I have a product that is partially defined when it is first created. It is assigned a product code and a category to which it belongs. Later when the marketing group provide details, these details need to be updated by adding the details to the product. These are done by different groups. A batch job obtains the product partial product details from one database and the product details from a relational database and then updates the product with the product details in a MongoDb database. Here is the objects as they exist initially. The Product details get assigned a product code and category and written to the MongoDB database. The batch job runs nightly checking for product details for the Product and then should update the Product with the ProductDetails when they become available in the relational database. Here are the objects:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Data
#NoArgsConstructor
#AllArgsConstructor
#Document
public class Product {
#Id
private long productCode;
private String category;
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ProductDetail {
private long productCode; // matches the product code in Product
private String description;
private int quantityOnHold;
private BigDecimal price;
private String warehouseLocationId;
private float discountFactor;
private String orderDescCode;
private String vendorId;
}
I am wondering what's the way to update the Product with the Product details. Do I create the equivalent Product document and add the Product details as a nested document? Is there a way to simply update the existing document by first modeling it as it is i.e. just the Product with its 2 fields and then add the ProductDetail? I'm relatively new to using Spring Data Mongo, so I don't know what the approach should be that makes the most sense, please help.
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Data
#NoArgsConstructor
#AllArgsConstructor
#Document(collection="product_collection")
public class Product {
#Id
private long productCode;
private String category;
private ProductDetail productDetail;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ProductDetail {
private String description;
private int quantityOnHold;
private BigDecimal price;
private String warehouseLocationId;
private float discountFactor;
private String orderDescCode;
private String vendorId;
}
You can use spring-data-mongodb to do that.
You can use the following methods to update documents.
save – Update the whole object, if “_id” is present, perform an update, else insert it. Notice that an "_id" field is generated by spring-data-mongo and mapped to the field annotated with #Id.
updateFirst – Updates the first document that matches the query.
updateMulti – Updates all documents that match the query.
Upserting – If no document that matches the query, a new document is created by combining the query and update object.
findAndModify – Same with updateMulti, but it has an extra option to return either the old or newly updated document.
The easiest way to find and update an document in my opinion is to use mongoRepository :
import com.globallogic.spring.mongodb.model.Book;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface ProductRepo
extends MongoRepository<Product, Long> {
}
And then inject your mongoRepository in a service class:
#Service
public interface ProductService {
ProductRepo productRepo;
//injecting productRepo into you service
public ProductService(ProductRepo productRepo) {
this.productRepo = productRepo;
}
public void updateproduct(Long productCode, ProductDetail productDetail ) {
Product pFromMongo = productRepo.findOne(productCode);
//set whatever you want on pFromMongo
pFromMongo.setDetail(productDetail);
....
//And then save the productCode. This will add or update product detail
productRepo.save(pFromMongo);
}
}
The resulting product in MongoDB will look like :
{
productCode : 1,
category: "pCateg",
productDetail : {
description : "description",
quantityOnHold : 11,
price : 12.33,
warehouseLocationId : "warehouseLocationId",
discountFactor : 1.0,
orderDescCode : "orderDescCode",
vendorId: "vendorId"
}
}
You can take a look at this presentation https://www.youtube.com/watch?v=ReqMU6bmPNM&ab_channel=JavaTechie

Problem Parsing request body of type json, containing a list of string to Flux of string in Spring reactive

I have a DTO as below:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import reactor.core.publisher.Flux;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class InternetPackageDto {
private String id;
private String name;
private String termsAndConditions;
private String price;
private Flux<String> packageAttributes;
private Flux<String> extras;
}
And a Database Object as below:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import reactor.core.publisher.Flux;
#Document("internet_packages")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class InternetPackage {
#Id
private String id;
private String name;
private String termsAndConditions;
private String price;
private Flux<StoreableAttribute> attributes;
private Flux<StoreableAttribute> extras;
}
The StorableAttribute Database Model like so:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Document("package_attributes")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class StoreableAttribute {
#Id
private String id;
private String name;
private String description;
}
On the Data Object the fields: Flux<StoreableAttribute> attributes and Flux<StoreableAttribute> extras are stored in a separate collection alongside the Package Object. And is handled by the mapper as below:
public InternetPackage fromDto(InternetPackageDto dto) {
var internetPackage = new InternetPackage();
internetPackage.setName(dto.getName());
internetPackage.setPrice(dto.getPrice());
internetPackage.setId(dto.getId());
internetPackage.setExtras(this.resolePackageExtras(dto));
internetPackage.setAttributes(this.resolePackageAttributes(dto));
return internetPackage;
}
private Flux<StoreableAttribute> resolePackageExtras(InternetPackageDto dto) {
return this.storeableAttributeService.resolveAttributes(dto.getExtras());
}
for the extra and similarly for the attributes also.
And a simple controller method as below:
#PostMapping(produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
public Mono<InternetPackageDto> update(#RequestBody InternetPackageDto incomingPackageDto) {
return this.packageService
.updatePackage(this.dtoMapper.fromDto(incomingPackageDto))
.map(this.dtoMapper::toDto);
}
And when I make a post request I get an error stating
org.springframework.core.codec.CodecException: Type definition error: [simple type, class reactor.core.publisher.Flux]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `reactor.core.publisher.Flux` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (io.netty.buffer.ByteBufInputStream); line: 2, column: 13] (through reference chain: com.example.api.dto.InternetPackageDto["extras"])
Some more information:
I am using the class InternetPackageDto as a request object as well as a response object.
I am using Flux<String> and not List<String> since I wasn't sure if doing blocking resolution to list was a good idea.
The attributes are stored and managed separately.
And during the time of updating or inserting the package those; if a new extra or attribute is included the attributes collection in db will be updated with the insertion of new incoming extras and attributes.
It seems like I might have made a stupid mistake because I cannot find much information about this problem, or I am doing it completely wrong.
Any help would be greatly appreciated.
I think you should do smth like this
public Mono<InternetPackageDto> toDto(InternetPackage entity) {
var internetPackage = new InternetPackageDto();
internetPackage.setName(entity.getName());
internetPackage.setPrice(entity.getPrice());
internetPackage.setId(entity.getId());
return Mono.zip(Mono.just(internetPackage), entity.getExtras().collectList(), entity.getAttributes().collectList())
.flatMap(tu->{
var dto = tu.getT1();
dto.setExtras(tu.getT2()); //To make it work in my local i made entity.getAttributes() as Flux<String> so here you will probably need to use .stream().map(dbItem->dbItem.getPropertyName())
dto.setPackageAttributes(tu.getT2());
return Mono.just(dto);
});
}

Nested property not found in a Spring Data query method declaration

I'm trying to use findBy... in my repository to get a Savingaccount object passing a nested attribute(name) as a parameter. Currently I'm using:
Mono<SavingAccount> findByOwnerName(String name);
but I'm getting this error: No property name found for type Owner! Traversed path: SavingAccount.owner.
My repository:
package com...SavingAccMS.Repository;
import com.everis.SavingAccMS.Model.Owner;
import com.everis.SavingAccMS.Model.SavingAccount;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import reactor.core.publisher.Mono;
public interface SavingAccountRepo extends ReactiveMongoRepository<SavingAccount, String>
{
Mono<SavingAccount> findByNumber(String number);
//This one is the problem
Mono<SavingAccount> findByOwnerName(String name);
Mono<SavingAccount> findByOwner(Owner owner);
}
My Entity:
package com...SavingAccMS.Model;
import java.security.acl.Owner;
import javax.validation.constraints.NotBlank;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Data;
#Data
#Document(collection = "SavingAccs")
public class SavingAccount
{
#Id
private String id;
#NotBlank
private String number;
#NotBlank
private Owner owner;
#NotBlank
private String currency;
#NotBlank
private double balance = 0.00;
#NotBlank
private String status;
}
package com...SavingAccMS.Model;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Data;
#Data
#Document(collection = "Owners")
public class Owner
{
public String dni;
public String name; //findBy this attribute is required.
}
According to your imports, the Owner in your SavingAccount refers to java.security.acl.Owner, not the one you defined yourself in om...SavingAccMS.Model.Owner. The former does not carry a name attribute.
I'm not certain but I don't think you can find the owner name in hibernate like that.
I would try the following!
#Query("SELECT * FROM SavingAccount where owner.name = :name")
Mono<SavingAccount> findByOwnerName(#Param("name") String name);

Spring Data ES query by nested object date field

Using Spring Data ES.
I have an index named ship with following definition:
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
#Document(indexName = "ship", type = "journey")
public class ShipJourney {
#Id
private String shipId;
#Field(type = FieldType.Nested)
private List<ShipLocation> shipLocation;
//getters and setters
}
ShipLocation is a nested object defined as:
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import java.time.LocalDateTime;
public class ShipLocation {
private double latitude;
private double longitude;
#Temporal(TemporalType.TIMESTAMP)
private LocalDateTime recordedOnTime;
//setters and getters
}
I want to find a ship location just before or equal to LocalDateTime parameter.
I tried this:
ShipJourney findTopByShipIdAndShipLocationRecordedOnTimeLessThanOrderByShipLocationRecordedOnTimeDesc(
String shipId, LocalDateTime recordedOnTime);
only to realise later that I was using Top on the ShipJourney itself which will be eventually one record only due to the shipId's uniqueness.
How can I limit the data I get for the nested element based on one of its attribute?
I'm not sure if it's possible with the Spring Data ElasticSearch repositories. If you can autowire ElasticSearchTemplate, your query could look something like this (untested):
String shipId = "A";
LocalDateTime recordedOnTime = LocalDateTime.now();
int maxShipLocations = 3; // <-- TOP
NestedQueryBuilder findByLocation = nestedQuery("shipLocation", rangeQuery("shipLocation.recordedOnTime").lt(recordedOnTime));
TermQueryBuilder findById = termQuery("id", shipId);
QueryBuilder findByIdAndLocation = QueryBuilders.boolQuery()
.filter(findById)
.filter(findByLocation.innerHit(new QueryInnerHitBuilder().setSize(maxShipLocations)));
SearchQuery query = new NativeSearchQueryBuilder()
.withIndices("ship")
.withTypes("journey")
.withQuery(findByIdAndLocation)
.withSort(SortBuilders.fieldSort("shipLocation.recordedOnTime").order(SortOrder.DESC))
.build();
List<ShipJourney> shipJourney = elasticSearchTemplate.queryForList(query, ShipJourney.class);

Resources