Spring data elasticSearch returns null with findOne - spring

I'm testing Spring data with elasticSearch. The ES server is running on a remote server in tha same room.
I have one index created a day, under an alias. I'm trying to find a simple tweet. But when I try a findOne(), it doesn't seem to work because it returns always null.
Also, findAll(ids) doesn't work because I'm using the alias, but I can't find in the documentation how to handle this.
What do I want to achieve ?
For the moment, simply retrieve a tweet with a given id_str.
The count method works, the findOne doesn't
Here are my questions
What should I do to make findOne() to work ?
Which way should I use to search on multiple indexes in this alias ?
Here is how the datas looks like in ES
{
"id_str" : "135131315100051",
"..." : "...",
"user" : {
"id_str" : "15843643228"
"..." : "..."
}
}
My model
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 = "alias", type = "tweets")
public class Tweet
{
#Id
#Field(type = FieldType.String)
private String idStr;
public String getIdStr()
{
return idStr;
}
public void setIdStr(final String idStr)
{
this.idStr = idStr;
}
#Override
public String toString()
{
return "{ id_str : " + idStr + " }";
}
}
Alias is alias, and indexes are alias_dd-mm-yyyy
My repository
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import com.thales.communications.osintlab.bigdata.webservices.models.Tweet;
public interface EsTweetRepository extends ElasticsearchRepository<Tweet, String>
{
Tweet findByIdStr(String idStr);
}
My test
#Test
public void shouldReturnATweet()
{
//lets try to search same record in elasticsearch
final Tweet tweet1 = tweetRepository.findOne("593768150975512576");
//final Tweet tweet = tweetRepository.findByIdStr("593897683661824000");
System.out.println("Count is " + tweet1);
//System.out.println("Count is " + tweetRepository.count());
// System.out.println(tweet.toString());
}
Of course, the tweet with the tested Id exists :). And the count() is working fine.
Thanks for your help
EDIT
Here is a sample application of what I have : https://github.com/ogdabou/es-stackoverflow-sample
It seems that spring-data-elasticsearch is look for the field "_id" and not the field "id_str". Maybe because of method parsing (look there). I'm looking for a way to bind my json "id_str" attribute to my idStr java model.

What was the real issue
We set the _id field of our tweet in Elasticsearch with the id field given by twitter. But it saves it in another format ( eg 132 becomes 1.32E2)
When I'm going a findOne() it is searching for a match with the Elasticsearch _id field and not the id_str I needed.
Solution
There, you have 2 commits, the first is the issue, the second the solution.
New repository
public interface EsTweetRepository extends ElasticsearchRepository<Tweet, String>
{
#Query("{\"bool\" : {\"must\" : {\"term\" : {\"id_str\" : \"?0\"}}}}")
Tweet findByIdStr(String idStr);
}
The model
#Document(indexName = "my_index_01", type = "tweets")
public class Tweet
{
// Elasticsearch object internal id. Look at field "_id"
#Id
private String id;
// Twitter internal id, saved under the "id_str" field
#Field(type = FieldType.String)
private String id_str;
#Field(type = FieldType.String)
private String text;
public String getId_str()
{
return id_str;
}
public void setId_str(final String id_str)
{
this.id_str = id_str;
}
public String getText()
{
return text;
}
public void setText(final String text)
{
this.text = text;
}
public String getId()
{
return id;
}
public void setId(final String id)
{
this.id = id;
}
#Override
public String toString()
{
return "{ _id : " + id + ", id_str : " + id_str + ", text : " + text + " }";
}
}

Related

How can I update specific filed of my class by PUT method in SpringBoot rest api

I used SpringBoot, and in the PUT method I check if the score exists then I want to update the score and also update the history by adding the latest score to it.
The Score Class:
package thesisMongoProject;
import java.util.Date;
import java.util.List;
import javax.validation.constraints.NotBlank;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import com.fasterxml.jackson.annotation.JsonView;
#Document(collection = "score")
public class Score {
#Id
#NotBlank
#JsonView(Views.class)
private String score;
#NotBlank
#JsonView(Views.class)
private String player;
#NotBlank
#JsonView(Views.class)
private String code;
#JsonView(Views.class)
private Date date;
private List<History> history;
public String getScore() {
return score;
}
public void setScore(String score) {
this.score = score;
}
public String getPlayer() {
return player;
}
public void setPlayer(String player) {
this.player = player;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public List<History> getHistory() {
return history;
}
public void setHistory(List<History> history) {
this.history = history;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
#Override
public String toString() {
return "Score [score=" + score + ", player=" + player + ", code=" + code + ", history=" + history + ", date="
+ date + "]";
}
}
The ScoreRepository:
package thesisMongoProject.Repository;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import thesisMongoProject.Score;
import thesisMongoProject.ScoreDto;
#Repository
public interface ScoreRepository extends MongoRepository<Score, String>{
public Score findByScore(String score);
public void save(ScoreDto scoredto, String score);
}
But the PUT method save a new instance into the MongoDB instead of updating the existing one
The PUT method:
//Update Score By ID
#PutMapping("/{score}")
public ResponseEntity<?> updatePlayerByID(
#PathVariable("score")String score,
#RequestBody #JsonView(Views.class) #Valid Score score1){
Score findscore = srepo.findByScore(score);
if(findscore == null)
return ResponseEntity.status(404).body("There is not Score!");
else {
history = new ArrayList<History>();
h = new History();
h.setScore(score1.getScore());
h.setDate(score1.getDate());
history.add(h);
score1.setHistory(history);
srepo.save(score1);
return ResponseEntity.ok(score1);
}
}
Also i tried to use ScoreDTO and #PatchMapping like this:
The ScoreDTo Class:
package thesisMongoProject;
import java.util.List;
public class ScoreDto {
private String score;
List<History> history;
public String getScore() {
return score;
}
public void setScore(String score) {
this.score = score;
}
public List<History> getHistory() {
return history;
}
public void setHistory(List<History> history) {
this.history = history;
}
}
And the PATCHMAPPING:
#PatchMapping("/{score}")
public ResponseEntity<?> updateByScore(
#PathVariable("score")String score,
#RequestBody ScoreDto score1){
Score findscore = srepo.findByScore(score);
if(findscore == null)
return ResponseEntity.status(404).body("There is not Score!");
else {
srepo.save(score1, score);
return ResponseEntity.ok(score1);
}
}
but in my console I have an error:
org.springframework.data.mapping.PropertyReferenceException: No property save found for type Score! Did you mean 'date'?
could you help me how can i update the existing field of score, please?!
The primary key of a database should not be mutable. If there are multiple players with the same score, the earlier players' data would be replaced.
Ideally, for updating an existing document where id and all its new fields are known, something like this can be done:
score1.setScore(score);
srepo.save(score1);
Assuming score is the id of the document that is to be updated and score1 contains all other fields correctly, this will replace the existing document with id score with the new one score1.
In the first code ( the PUT method ), score1 should have the same id as findscore, then it will update the existing document.
Score findscore = srepo.findByScore(score);
if(findscore == null)
return ResponseEntity.status(404).body("There is not Score!");
else {
history = new ArrayList<History>();
h = new History();
h.setScore(score1.getScore());
h.setDate(score1.getDate());
history.add(h);
Also, for the exception you are getting, this save method
public void save(ScoreDto scoredto, String score);
can't be handled by the spring data repository automatically, you will have to define its implementation. More on what kind of methods can be defined or not here. The Standard save method in the repository can be used to achieve the required.

spring-data-mongodb aggregation with composite ID

I'm having trouble reading documents from MongoDB using the aggregation framework: I always get null IDs in my results. This only happens for documents that have composite IDs. I tried various versions of spring-data-mongodb (1.10.12, 2.0.7), same result.
Entity definition class
#Document(collection="entities")
public class MyEntity {
static class CompositeKey implements Serializable {
private String stringKey;
private Integer intKey;
public CompositeKey(String stringKey, Integer intKey) {
this.stringKey = stringKey;
this.intKey = intKey;
}
public Integer getIntKey() {
return intKey;
}
public String getStringKey() {
return stringKey;
}
public String toString() {
return "{" + stringKey + " - " + intKey + "}";
}
}
#Id
private CompositeKey id;
private String param;
public MyEntity() {}
public MyEntity(String stringKey, Integer intKey) {
id = new CompositeKey(stringKey, intKey);
}
public CompositeKey getId(){
return id;
}
public void setId(CompositeKey id) {
this.id = id;
}
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
}
Testing code
public static void main(String[] args) {
MongoClient client = new MongoClient("127.0.0.1");
SimpleMongoDbFactory factory = new SimpleMongoDbFactory(client, "aggTest");
MongoTemplate mongoTemplate = new MongoTemplate(factory);
MyEntity entity = new MyEntity();
entity.setId(new MyEntity.CompositeKey("one", 1));
entity.setParam("param1");
mongoTemplate.save(entity);
entity = new MyEntity();
entity.setId(new MyEntity.CompositeKey("two", 2));
entity.setParam("param2");
mongoTemplate.save(entity);
Criteria crit = Criteria.where("param").ne("param3");
List<AggregationOperation> aggOpList = new ArrayList<AggregationOperation>();
aggOpList.add(Aggregation.match(crit));
System.out.println("Documents fetched with find: ");
for (MyEntity aggResult : mongoTemplate.find(new Query(crit), MyEntity.class).toArray(new MyEntity[0]))
System.out.println(aggResult.getId() + " - " + aggResult.getParam());
System.out.println("\nDocuments fetched with aggregate: ");
TypedAggregation<MyEntity> aggregation = new TypedAggregation<>(MyEntity.class, aggOpList);
AggregationResults<MyEntity> aggregate = mongoTemplate.aggregate(aggregation, MyEntity.class);
for (MyEntity aggResult : aggregate.getMappedResults())
System.out.println(aggResult.getId() + " - " + aggResult.getParam());
}
Output
Documents fetched with find:
{one - 1} - param1
{two - 2} - param2
Documents fetched with aggregate:
null - param1
null - param2
Debugging into the following method MappingMongoConverter.read(final MongoPersistentEntity entity, final Document bson, final ObjectPath path) I found that in the first case (find method) the documentAccessor variable has the following contents
Document{{_id=Document{{stringKey=one, intKey=1}}, param=param1, _class=MyEntity}}
whereas in the second case (aggregation query) it looks like
Document{{stringKey=one, intKey=1, param=param1, _class=MyEntity}}
The document gets flattened somehow, which makes it impossible for the converter to populate the ID field. I must be doing something wrong, but what?
Spring Data MongoDB lower than 3.x automatically flatten composite id (fields under composite id are unwrapped and place at root object). This is removed in version 3.0 onwards:
https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#new-features.3.0

Spring rest controller giving unsupported content type

Hello all here is what i have:
StockController.java
#RestController
public class StockController {
#Autowired
private StockRepository repository;
#RequestMapping(value = "stockmanagement/stock")
public ResponseEntity<?> addStock(#RequestBody String stock
) {
System.out.println(stock);
return new ResponseEntity<>(HttpStatus.OK);
}
when I make a request like so using chrome advanced rest extension :
Raw Headers
Content-Type: application/json
Raw Payload
{"stock": {"productId": 2, "expiryAndQuantity" : {}, "id": 0}}
It works fine in that out comes a string of json
However when i try to replace String stock with Stock stock where stock looks like this:
public class Stock {
#Id
private String id;
private String productId;
private Map<LocalDateTime, Integer> expiryAndQuantity;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public Map<LocalDateTime, Integer> getExpiryAndQuantity() {
return expiryAndQuantity;
}
public void setExpiryAndQuantity(Map<LocalDateTime, Integer> expiryAndQuantity) {
this.expiryAndQuantity = expiryAndQuantity;
}
#Override
public String toString() {
return String.format(
""
);
}
}
I get an error where by the following is fed back to me:
"status": 415
"error": "Unsupported Media Type"
"exception": "org.springframework.web.HttpMediaTypeNotSupportedException"
"message": "Content type 'application/json;charset=UTF-8' not supported"
"path": "/stockmanagement/stock"
My question is; how do i create a request which maps to my Stock object.
You can try with #JsonRootName annotation, by default Spring serialize using no root name value. like this:
{"productId": 2, "expiryAndQuantity" : {}, "id": 0}
But if you want that your serialization has a rootname you need to use #JsonRootName annotation.
#JsonRootName(value = "Stock")
And it'll produce something like this
{"Stock": {"productId": 2, "expiryAndQuantity" : {}, "id": 0}}
You can see more here
http://www.baeldung.com/jackson-annotations
instead of accepting a String Accept a Stock object.and accept it from a post request than having a get request
#RequestMapping(value = "stockmanagement/stock",method=RequestMethod.POST)
public ResponseEntity<?> addStock(#RequestBody Stock stock){
}
and your request should be sent like this
{
"productId": 2
,"expiryAndQuantity" : null
,"id": 0
}
all parameter names should be equal to the objects filed names,since spring has jackson binders on class path and object will be created inside the controller method. if you are planning on passing different parameters from the post request you can use
#JsonProperty("pid")
private String productId;
on the field name.

Upsert Mongo Document using spring data mongo

I have a Class
#Document
public class MyDocument {
#Id
private String id;
private String title;
private String description;
private String tagLine;
#CreatedDate
private Date createdDate;
#LastModifiedDate
private Date updatedDate;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getTagLine() {
return tagLine;
}
public void setTagLine(String tagLine) {
this.tagLine = tagLine;
}
}
i have added annotated application with #EnableMongoAuditing
i have created interface which implements mongorepository
public interface MyDocumentRepository extends MongoRepository<MyDocument, String> {
}
when i have created RestController with GET,POST,PATCH methods
in POST I'm sending
{'title':'first'}
Controller Class POST method is
#RequestMapping(value = "/", method = RequestMethod.POST)
public ResponseEntity<?> saveMyDocument(#RequestBody MyDocument myDocument) {
MyDocument doc = myDocumentRepo.save(myDocument);
return new ResponseEntity<MyDocument>(doc, HttpStatus.CREATED);
}
Its saving the data in mongo.
{
"_id" : ObjectId("56b3451f0364b03f3098f101"),
"_class" : "com.wiziq.service.course.model.MyDocument",
"title" : "test"
}
and PATCH request is like
#RequestMapping(value = "/{id}", method = RequestMethod.PATCH)
public ResponseEntity<MyDocument> updateCourse(#PathVariable(value = "id") String id,
#RequestBody MyDocument myDocument) {
myDocument.setId(id);
MyDocument doc = courseService.save(myDocument);
return ResponseEntity.ok(course);
}
when in make PATCH request with data {"description":"This is test"}
it update the docuent BUT it removes title field and createdDate form the document, its doing update which is ok. But i wanted to do an upsert, i can do its using mongoTemplate,
but there i have to set each property which i want to set.
Is there any generic way to that if i get a PATCH request i can update only not null properties.. properties which are coming in request
spring-data-rest seems to do it using #RepositoryRestResource. How can i achieve the same.
I don't want to code like this
Update update = new Update().set("title", myDocument.getTitle()).set("description", myDocument.getdescription());
Unfortunately its the behavior in MongoDB, you can verify the same using shell.
So to update create an Update Object and using
Query query = new Query(Criteria.where("id").is(ID));
Here ID is the document which you want to update.Based on your requirement set upsert after that using findAndModify update document.
mongoTemplate.findAndModify(query, update,
new FindAndModifyOptions().returnNew(true).upsert(false),
someclass.class);
If you have a model like MyModel.class and you need a smooth way to create an Update object from it there is no real clear way how to do this but you can use MongoConverter bean that is created in Spring Data Mongo auto configuration and then just use replaceOne method of MongoCollection.
#Autowired
private MongoTemplate template;
#Autowired
private MongoConverter mongoConverter;
...
#Override
public void upsertMyModel(MyModel model) {
Document documentToUpsert = new Document();
mongoConverter.write(model, documentToUpsert);
template.getCollection(collectionName).replaceOne(
Filters.eq("_id", model.getId()),
documentToUpsert,
new ReplaceOptions().upsert(true));
}
Upsert can be done in Spring data mongodb using BulkOperations.
Suppose there are two entities Entity1 and Entity2. Entity1 has foreginId which is primary id of Entity2. Both have a field title. Now, to upsert from entity2 to entity1, we can do it as follows:
Query query = new Query(Criteria.where("foreignId").is(entity2.getId()));
Update update = new Update();
update.set("title",entity2.getTitle());
List<Pair<Query, Update>> updates = new ArrayList<Pair<Query, Update>>();
updates.add(Pair.of(query, update););
BulkOperations bulkOps = this.mongoTemplate.bulkOps(BulkMode.UNORDERED, Entity1.class);
bulkOps.upsert(updates);
bulkOps.execute();

Store enum name, not value in database using EBean

I have this enum :
public enum DocumentTypes {
PDF("PDF Document"), JPG("Image files (JPG)"), DOC("Microsoft Word documents");
private final String displayName;
DocumentTypes(final String display) {
this.displayName = display;
}
#Override
public String toString() {
return this.displayName;
}
}
And a model like this :
#Entity
#Table(name = "documents")
public class Document extends Model {
#Id
public Long id;
#Constraints.Required
#Formats.NonEmpty
#Enumerated(EnumType.STRING)
#Column(length=20, nullable=false)
public DocumentTypes type;
#Constraints.Required
#Formats.NonEmpty
#Column(nullable=false)
public String document;
}
I match the enum using this in my controller :
DynamicForm form = form().bindFromRequest();
// ...
Document doc = new Document();
doc.type = DocumentTypes.valueOf(form.field("type").value());
doc.save();
The problem is that in database, it's stored as "Microsoft Word documents", but I would prefer to store it as DOC.
How can I do that?
You can define it very fine granular with the Anotation EnumMapping or EnumValue. This works with the old version org.avaje.ebean.
It seems that there was a complete rewrite of the code. In the actual version there is a different approach.

Resources