Spring mongodb get by two params within box throw repository - spring

I try to get data from mongoDb throw repository by two parameters which both I would like to be in box bounds:
model
#Document(collection = "houses")
public class House {
#Id
private String id;
#TextIndexed
private String name;
private String address;
private Double longitude;
private Double latitude;
/* getters and setters ommited*/ }
Repository
public interface HouseRepository extends PagingAndSortingRepository<House, String> {
#Query("{'name': {'$regex': '?0','$options' : 'i'}}")
Set<Hotel> findWithNameContains(String name);
Set<Hotel> findByLatitudeAndLongitudeWithin(Box bounds);
Set<Hotel> findByLatitudeAndLongitudeWithin(Circle circle);
}
but when I call:
Box bounds = new Box(geoLocation.getLocationCordinates().getNortheastBounds(),geoLocation.getLocationCordinates().getSouthwestBounds());
houseRep.findByLatitudeAndLongitudeWithin(bounds);
I get :
org.springframework.data.repository.query.ParameterOutOfBoundsException: Invalid parameter index! You seem to have declare too little query method parameters!
org.springframework.data.repository.query.Parameters.getParameter(Parameters.java:178)
org.springframework.data.repository.query.Parameters.getBindableParameter(Parameters.java:248)
org.springframework.data.repository.query.ParametersParameterAccessor.getBindableValue(ParametersParameterAccessor.java:107)
org.springframework.data.repository.query.ParametersParameterAccessor$BindableParameterIterator.next(ParametersParameterAccessor.java:150)
org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor$ConvertingIterator.next(ConvertingParameterAccessor.java:176)
org.springframework.data.mongodb.repository.query.MongoQueryCreator.from(MongoQueryCreator.java:240)
org.springframework.data.mongodb.repository.query.MongoQueryCreator.and(MongoQueryCreator.java:133)
org.springframework.data.mongodb.repository.query.MongoQueryCreator.and(MongoQueryCreator.java:58)
org.springframework.data.repository.query.parser.AbstractQueryCreator.createCriteria(AbstractQueryCreator.java:109)
To say truth, do not where is problem, maybe someone can help.

The error results from lon/lat being individual properties. Using MongoDB you need to store location data either as array or in GeoJSON format.
loc : [ 37.7577 , -122.4376 ] //...legacy format
loc : { type : "Point", coordinates : [ 37.7577 , -122.4376 ] } //...GeoJSON
Using Spring Data you have to option to use a double [] or Point for legacy as well as GeoJsonPoint for GeoJSON representation (see. Reference: GeoSpatial Queries). This allows you to use derived query methods like
List<Hotel> findByLocationNear(Point point);
GeoResults<Hotel> findByLocationNear(Point point, Distance maxDistance);
List<Hotel> findByLocationWithin(Circle circle);
List<Hotel> findByLocationWithin(Box box);
List<Hotel> findByLocationWithin(Polygon polygon);

Related

Spring entity dynamically calculated field with parameter from native query

I have a somewhat complex entity like following (Notice the super-class with many more fields):
public class Question extends Entry {
#OneToMany(orphanRemoval = true, mappedBy = "question")
#JsonManagedReference
private List<Answer> answers = new ArrayList<>();
private Long viewCount = 0L;
private Category category;
#OneToMany(mappedBy = "question", fetch = FetchType.LAZY,
cascade = CascadeType.ALL, orphanRemoval = true)
private List<QuestionTranslation> translations = new ArrayList<>();
#Transient
private double distance;
}
distance should be calculated from the DB when retrieving the result set from a native query.
E.g.
SELECT q.*, ST_Distance_Sphere(cast(q.location as geometry), ST_MakePoint(cast(?1 as double precision), cast(?2 as double precision))) as distance from question q
I cannot use #Formula to annotate my field distance since the query has to take parameters.
How can I map the field distance from the SQL query result to my entity field distance while leaving all the other mappings to be done by Hibernate?
Edit
Based on #gmotux suggestion I created a wrapper entity.
#Entity
#SqlResultSetMapping(
name="MappingQ",
entities={
#EntityResult(
entityClass = QuestionWithDistance.class,
fields={
#FieldResult(name="distance",column="distance"),
#FieldResult(name="question",column="question")})})
public class QuestionWithDistance{
#Id
#GeneratedValue
private String id;
#OneToOne
private Question question;
private double distance;
}
Query
Query query = entityManager.createNativeQuery("SELECT q.*, 222.22 as distance from question q", "MappingQ");
But it always fails with
org.postgresql.util.PSQLException: The column name id1_15_0_ was not found in this ResultSet.
Since you need extra parameters to calculate your field, you indeed cannot use #Formula, or even a getter to calculate the field.
Unfortunately for your case the only thing that comes to mind, assuming you are using an EntityManager based configuration for Hibernate, is leveraging its #PostLoad event listener, which you can use for calculating field values upon entity loading, like :
public class Question extends Entry {
#PostLoad
private void postLoad() {
this.distance = DistanceCalculator.calculateDistance(Double param1,Double param2);
//other calculations
}
}
That of-course is only a workaround and it means that you must have a static method somewhere execute native queries.
I would suggest detaching the "distance" notion from your Question entity, if possible in your requirements and calculate it, when required, with either a native SQL function call or a service method.

Cannot Query Neo4j Repositories

Hey everyone I am fairly new to Neo4j and am having an issue querying my repositories.
Repository is the follow:
public interface NodeOneRepository extends GraphRepository<NodeOne> {
List<NodeOne> findByNodeTwoNodeThreeAndActiveTrue(NodeThree nodeThree);
}
My entities are the following:
#NodeEntity(label = "NodeOne")
public class NodeOne {
#GraphId
private Long id;
private Boolean active = TRUE;
#Relationship(type = "IS_ON")
private NodeTwo nodeTwo;
}
#NodeEntity(label = "NodeTwo")
public class NodeTwo {
#GraphId
private Long id;
#Relationship(type = "CONTAINS", direction = "INCOMING")
private NodeThree nodeThree;
#Relationship(type = "IS_ON", direction = "INCOMING")
private List<NodeOne> nodeOnes = new ArrayList<>();
}
#NodeEntity(label = "NodeThree")
public class NodeThree {
#GraphId
private Long id;
#Relationship(type = "CONTAINS")
private List<NodeTwo> nodeTwos = new ArrayList<>();
}
Getters & Setters omitted. When I call the method I get an empty list. Is there something I am doing incorrectly?
You didn't describe exactly what you wanted to achieve, but I can see two problems:
Problem 1:
The current version of Spring Data Neo4j and OGM only allow nested finders, that is, finders that specify a relationship property, to one depth.
Supported
findByNodeTwoSomePropertyAndActiveTrue(String relatedNodePropertyValue)
Not Supported
findByNodeTwoNodeThree //Nesting relationships in finders is not supported
Problem 2:
Derived Finders Allow Matching Properties and Nested Properties. Not a whole instance of that class.
You can probably achieve what you would like using a custom query.
#Query("custom query here")
List<NodeOne> findByNodeTwoNodeThreeAndActiveTrue(NodeThree nodeThree);
If you need help to write a custom query, you can post another question or join the neo4j-users public slack channel.

spring jpa projection nested bean

is it possible to have a projection with nested collection with Spring JPA?
I have the following 2 simple entity (to explain the problem)
#Entity
#Table(name = "person")
public class Person implements Serializable {
private Integer id;
private String name;
#OneToMany
private List<Address> addressList = new ArrayList<>();
}
#Entity
#Table(name = "address")
public class Address implements Serializable {
private Integer id;
private String city;
private String street;
}
Is it possible to have a projection of Person with following attributes filled in ? {person.name, address.city}
I might be wrong in semantics of word Projection. but the problem is what i need to achieve. Maybe it is not possible with Projection, but is there another way to achieve the end goal? Named Entity graph perhaps ?
P.S. please suggest a solution for Spring JPA not Spring Jpa REST
thanks in advance
You're right, Entity Graphs serve this exact purpose - control field loading.
Create entity graphs dynamically from the code or annotate target entities with Named Entity Graphs and then just use their name.
Here is how to modify your Person class to use Named Entity Graphs:
#Entity
#Table(name = "person")
#NamedEntityGraph(name = "persion.name.with.city",
attributeNodes = #NamedAttributeNode(value = "addressList", subgraph = "addresses.city"),
subgraphs = #NamedSubgraph(name = "addresses.city", attributeNodes = #NamedAttributeNode("city")))
public class Person implements Serializable {
private Integer id;
private String name;
#OneToMany
private List<Address> addressList;
}
And then when loading your person:
EntityGraph graph = em.getEntityGraph("person.name.with.city");
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
return em.find(Person.class, personId, hints);
The same applies for queries, not only em.find method.
Look this tutorial for more details.
I think that that's not usual scenario of Data JPA usage. But you can achieve your goal with pure JPQL:
SELECT a.street, a.person.name FROM Address a WHERE …
This solution has 2 drawbacks:
It forces you to have bidirectional relationship Address ←→ Person
It returns List
Another solution (and that's preferred JPA way) is to create DTO like this:
class MyPersonDTO {
private String personName;
private List<String> cities;
public MyPersonDTO(String personName, List<Address> adresses) {
this.personName = personName;
cities = adresses
.stream()
.map(Address::getCity)
.collect(Collectors.toList());
}
}
And the execute JPQL query like this:
SELECT NEW package.MyPersonDTO(p.name, p.addressList) FROM Person p WHERE …
Return type will be List<MyPersonDTO> in that case.
Of course you can use any of this solutions inside #Query annotation and it should work.

Spring data elasticSearch : Update entity using alias

I'm currently fighting with the spring-data-elasticsearch API. I need it to work on an alias with several indexes pointing on it.
Each indexes have the same types stored, but are juste day to day storage (1rst index are monday's resulsts, second are tuesday's resulsts....).
Some of the ElasticsearchRepository methods don't work because of the alias. I currently managed to do a search (findOne() equivalent) but I am not able to update an entity.
I don't know how to achieve that, I looked to the documentation and samples.. but I'm stuck.
My repository
public interface EsUserRepository extends ElasticsearchRepository<User, String>
{
#Query("{\"bool\" : {\"must\" : {\"term\" : {\"id_str\" : \"?0\"}}}}")
User findByIdStr(String idStr);
}
My Entity
#Document(indexName = "osintlab", type = "users")
public class User
{
// Elasticsearch internal id
#Id
private String id;
// Just a test to get the real object index (_index field), in order to save it
#Field(index = FieldIndex.analyzed, type = FieldType.String)
private String indexName;
// Real id, saved under the "id_str" field
#Field(type = FieldType.String)
private String id_str;
#Field(type = FieldType.String)
private List<String> tag_user;
}
What I tested
final IndexQuery indexQuery = new IndexQuery();
indexQuery.setId(user.getId());
indexQuery.setObject(user);
esTemplate.index(indexQuery);
userRepository.index(user));
userRepository.save(user))

Spring Data MongoDB: Accessing and updating sub documents

First experiments with Spring Data and MongoDB were great. Now I've got the following structure (simplified):
public class Letter {
#Id
private String id;
private List<Section> sections;
}
public class Section {
private String id;
private String content;
}
Loading and saving entire Letter objects/documents works like a charm. (I use ObjectId to generate unique IDs for the Section.id field.)
Letter letter1 = mongoTemplate.findById(id, Letter.class)
mongoTemplate.insert(letter2);
mongoTemplate.save(letter3);
As documents are big (200K) and sometimes only sub-parts are needed by the application: Is there a possibility to query for a sub-document (section), modify and save it?
I'd like to implement a method like
Section s = findLetterSection(letterId, sectionId);
s.setText("blubb");
replaceLetterSection(letterId, sectionId, s);
And of course methods like:
addLetterSection(letterId, s); // add after last section
insertLetterSection(letterId, sectionId, s); // insert before given section
deleteLetterSection(letterId, sectionId); // delete given section
I see that the last three methods are somewhat "strange", i.e. loading the entire document, modifying the collection and saving it again may be the better approach from an object-oriented point of view; but the first use case ("navigating" to a sub-document/sub-object and working in the scope of this object) seems natural.
I think MongoDB can update sub-documents, but can SpringData be used for object mapping? Thanks for any pointers.
I figured out the following approach for slicing and loading only one subobject. Does it seem ok? I am aware of problems with concurrent modifications.
Query query1 = Query.query(Criteria.where("_id").is(instance));
query1.fields().include("sections._id");
LetterInstance letter1 = mongoTemplate.findOne(query1, LetterInstance.class);
LetterSection emptySection = letter1.findSectionById(sectionId);
int index = letter1.getSections().indexOf(emptySection);
Query query2 = Query.query(Criteria.where("_id").is(instance));
query2.fields().include("sections").slice("sections", index, 1);
LetterInstance letter2 = mongoTemplate.findOne(query2, LetterInstance.class);
LetterSection section = letter2.getSections().get(0);
This is an alternative solution loading all sections, but omitting the other (large) fields.
Query query = Query.query(Criteria.where("_id").is(instance));
query.fields().include("sections");
LetterInstance letter = mongoTemplate.findOne(query, LetterInstance.class);
LetterSection section = letter.findSectionById(sectionId);
This is the code I use for storing only a single collection element:
MongoConverter converter = mongoTemplate.getConverter();
DBObject newSectionRec = (DBObject)converter.convertToMongoType(newSection);
Query query = Query.query(Criteria.where("_id").is(instance).and("sections._id").is(new ObjectId(newSection.getSectionId())));
Update update = new Update().set("sections.$", newSectionRec);
mongoTemplate.updateFirst(query, update, LetterInstance.class);
It is nice to see how Spring Data can be used with "partial results" from MongoDB.
Any comments highly appreciated!
I think Matthias Wuttke's answer is great, for anyone looking for a generic version of his answer see code below:
#Service
public class MongoUtils {
#Autowired
private MongoTemplate mongo;
public <D, N extends Domain> N findNestedDocument(Class<D> docClass, String collectionName, UUID outerId, UUID innerId,
Function<D, List<N>> collectionGetter) {
// get index of subdocument in array
Query query = new Query(Criteria.where("_id").is(outerId).and(collectionName + "._id").is(innerId));
query.fields().include(collectionName + "._id");
D obj = mongo.findOne(query, docClass);
if (obj == null) {
return null;
}
List<UUID> itemIds = collectionGetter.apply(obj).stream().map(N::getId).collect(Collectors.toList());
int index = itemIds.indexOf(innerId);
if (index == -1) {
return null;
}
// retrieve subdocument at index using slice operator
Query query2 = new Query(Criteria.where("_id").is(outerId).and(collectionName + "._id").is(innerId));
query2.fields().include(collectionName).slice(collectionName, index, 1);
D obj2 = mongo.findOne(query2, docClass);
if (obj2 == null) {
return null;
}
return collectionGetter.apply(obj2).get(0);
}
public void removeNestedDocument(UUID outerId, UUID innerId, String collectionName, Class<?> outerClass) {
Update update = new Update();
update.pull(collectionName, new Query(Criteria.where("_id").is(innerId)));
mongo.updateFirst(new Query(Criteria.where("_id").is(outerId)), update, outerClass);
}
}
This could for example be called using
mongoUtils.findNestedDocument(Shop.class, "items", shopId, itemId, Shop::getItems);
mongoUtils.removeNestedDocument(shopId, itemId, "items", Shop.class);
The Domain interface looks like this:
public interface Domain {
UUID getId();
}
Notice: If the nested document's constructor contains elements with primitive datatype, it is important for the nested document to have a default (empty) constructor, which may be protected, in order for the class to be instantiatable with null arguments.
Solution
Thats my solution for this problem:
The object should be updated
#Getter
#Setter
#Document(collection = "projectchild")
public class ProjectChild {
#Id
private String _id;
private String name;
private String code;
#Field("desc")
private String description;
private String startDate;
private String endDate;
#Field("cost")
private long estimatedCost;
private List<String> countryList;
private List<Task> tasks;
#Version
private Long version;
}
Coding the Solution
public Mono<ProjectChild> UpdateCritTemplChild(
String id, String idch, String ownername) {
Query query = new Query();
query.addCriteria(Criteria.where("_id")
.is(id)); // find the parent
query.addCriteria(Criteria.where("tasks._id")
.is(idch)); // find the child which will be changed
Update update = new Update();
update.set("tasks.$.ownername", ownername); // change the field inside the child that must be updated
return template
// findAndModify:
// Find/modify/get the "new object" from a single operation.
.findAndModify(
query, update,
new FindAndModifyOptions().returnNew(true), ProjectChild.class
)
;
}

Resources