I have entity Article I'm trying to retrieve one random record from database collection.
This is entity Article:
#Data
#Document(value = "article")
public class Article {
#Id
private String articleId;
private String title;
private String description;
private String fullArticle;
This is service to save it:
#Override
public Article save(Article article) {
return articleRepository.save(article);
}
And repository:
#Repository
public interface ArticleRepository extends MongoRepository<Article, String> {
}
So, now I'm trying to create a method that will get me one random record from my collection Article also, I want to create a controller so when I go to some endpoint and submit some get method to retrieve one record from the collection and so I can check it in postman or with Swagger. I find some answers to similar question to mine but no one solved my problem, I want to have API for something like that.
You can use $sample in an aggregation query to get a random document:
db.collection.aggregate([
{
"$sample": {
"size": 1
}
}
])
Example here
I've tested the code and it works as expected.
You can create add this method into repository:
#Aggregation(pipeline={"{$sample:{size:1}}"})
AggregationResults<Article> random();
And call from service like this:
#Override
public Article random(){
return articleRepository.random().getMappedResults().stream().findFirst().orElse(null);
// also you can use .orElseThrow() or whatever you want
}
Related
Working on a SpringBoot application using MongoDB as a persistent store.
Using spring data and MongoRepository to access MongoDB.
Using Javers to provide auditting.
If I use mongoRepository.insert(document) followed later by a mongoRepository.save(document) and then use javers to query the changes to that document, javers does not detect the differences between the object inserted and the object saved. It reports only a single change as if the save call was the original object persisted.
If I replace the insert call with a save and let spring data handle whether or not to insert or update, javers reports the expected change.
Example:
Consider the following:
#JaversSpringDataAuditable
public interface SomeDocumentRepository extends MongoRepository<SomeDocument, String> {
}
#Builder
#Data
#Document(collection = "someDocuments")
public class SomeDocument {
#Id
private String id;
private String status;
}
#Service
public class SomeDocumentService {
#Autowired
private SomeDocumentRepository someDocumentRepository;
public SomeDocument insert(SomeDocument doc) {
return someDocumentRepository.insert(doc);
}
public SomeDocument save(SomeDocument doc) {
return someDocumentRepository.save(doc);
}
}
#Service
public class AuditService {
#Autowired
private Javers javers;
public List<Change> getStatusChangesById(String documentId) {
JqlQuery query = QueryBuilder
.byInstanceId(documentId, SomeDocument.class)
.withChangedProperty("status")
.build();
return javers.findChanges(query);
}
}
If I call my service as follows:
var doc = SomeDocument.builder().status("new").build();
doc = someDocumentService.insert(doc);
doc.setStatus("change1");
doc = someDocumentService.save(doc);
and then call the audit service to get the changes:
auditService.getStatusChangesById(doc.getId());
I get a single change with "left" set to a blank and "right" set to "change1".
If I call "save" instead of "insert" like:
var doc = SomeDocument.builder().status("new").build();
doc = someDocumentService.save(doc);
doc.setStatus("change1");
doc = someDocumentService.save(doc);
and then call the audit service to get the changes I get 2 changes, the first being the most recent change with "left" set to "new", and "right" set to "change1" and a second change with "left" set to "" and "right" set to "new".
Is this a bug?
That's a good point. In case of Mongo, Javers covers only the methods from the CrudRepository interface. See https://github.com/javers/javers/blob/master/javers-spring/src/main/java/org/javers/spring/auditable/aspect/springdata/JaversSpringDataAuditableRepositoryAspect.java
Looks like MongoRepository#insert() should be also covered by the aspect.
Feel free to contribute a PR to javers, I will merge it. If you want to discuss the design first - please create a discussion here https://github.com/javers/javers/discussions
Like/dislike system.
App has entity Post. Post has field List likes and it joins table, which has columns post_id, user_id.
When User presses button "like" app will add authenticated user in List in PostService. But I need to have the "isLiked" boolean field. This will define what the Like button will look like in frontend.
I can get value for field countLike just call method size() from field likes.
But I don't know now I can get value for field "isLiked".
Help me with it, please.
#Entity
public class Post {
//some fields...
//there I saved users, who has posed "like"
private List<User> likes;
#Transient
private int countLike;
//there I want to save status - liked/disliked;
#Transient
private boolean isLiked;
}
#Service
public class PostService {
//some fields and methods...
public void createLike(int postId, User authenticatedUser) {
Post post = postRepository.getOne(postId);
post.getLikes().add(authenticatedUser);
this.update(post);
}
}
While the approach hinted to by yourself and the comment by "Lino - Vote don't say Thanks" will work, I don't think they are a very good idea.
Working but wastful.
You can create methods like the following:
public int likeCount{}{
return getLikes().size();
}
public boolean isLiked(){
return getLikes().size() > 0;
}
The problem with that is, it will load a lot of data just for providing a single number of even just a single bit of information.
More efficient in most scenarios
Instead I recommend loading the information from the database.
Assuming you are using Hibernate as the JPA implementation you can do that with the #Formula annotation. With this the relevant code looks like this:
#Entity
public class Post {
#Formula("(select count(user_id) from Likes l where l.post_id = post_id)")
private int countLike;
public boolean isLiked(){
return getCountLike() > 0;
}
}
I'm using Spring Data MongoDB and Spring Data Rest to create a REST API which allows GET, POST, PUT and DELETE operations on my MongoDB database and it's all working fine except for the update operations (PUT). It only works if I send the full object in the request body.
For example I have the following entity:
#Document
public class User {
#Id
private String id;
private String email;
private String lastName;
private String firstName;
private String password;
...
}
To update the lastName field, I have to send all of the user object, including the password ! which is obviously very wrong.
If I only send the field to update, all the others are set to null in my database. I even tried to add a #NotNull constraints on those fields and now the update won't even happens unless I send all of the user object's fields.
I tried searching for a solution here but I only found the following post but with no solution: How to update particular field in mongo db by using MongoRepository Interface?
Is there a way to implement this ?
Spring Data Rest uses Spring Data repositories to automatically retrieve and manipulate persistent data using Rest calls (check out https://docs.spring.io/spring-data/rest/docs/current/reference/html/#reference).
When using Spring Data MongoDB, you have the MongoOperations interface which is used as a repository for your Rest endpoints.
However MongoOperations currently does not supports specific fields updates !
PS: It will be awesome if they add this feature like #DynamicUpdate in Spring Data JPA
But this doesn't mean it can be done, here's the workaround I did when I had this issue.
Firstly let me explain what we're going to do:
We will create a controller which will override all the PUT operations so that we can implement our own update method.
Inside that update method, we will use MongoTemplate which do have the ability to update specific fields.
N.B. We don't want to re-do these steps for each model in our application, so we will retrieve which model to update dynamically. In order to do that we will create a utility class. [This is optional]
Let's start by adding the org.reflections api to our project dependency which allows us to get all the classes which have a specific annotation (#Document in our case):
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.12</version>
</dependency>
Then create a new class, called UpdateUtility and add the following methods and also replace the MODEL_PACKAGE attribute with your own package containing your entities:
public class UpdateUtility {
private static final String MODEL_PACKAGE = "com.mycompany.myproject.models";
private static boolean initialized = false;
private static HashMap<String, Class> classContext = new HashMap<>();
private static void init() {
if(!initialized) {
Reflections reflections = new Reflections(MODEL_PACKAGE);
Set<Class<?>> classes = reflections.getTypesAnnotatedWith(Document.class); // Get all the classes annotated with #Document in the specified package
for(Class<?> model : classes) {
classContext.put(model.getSimpleName().toLowerCase(), model);
}
initialized = true;
}
}
public static Class getClassFromType(String type) throws Exception{
init();
if(classContext.containsKey(type)) {
return classContext.get(type);
}
else {
throw new Exception("Type " + type + " does not exists !");
}
}
}
Using this utility class we can retreive the model class to update from it's type.
E.g: UpdateUtility.getClassFromType() will returns User.class
Now let's create our controller:
public class UpdateController {
#Autowired
private MongoTemplate mongoTemplate;
#PutMapping("/{type}/{id}")
public Object update(#RequestBody HashMap<String, Object> fields,
#PathVariable(name = "type") String type,
#PathVariable(name = "id") String id) {
try {
Class classType = UpdatorUtility.getClassFromType(type); // Get the domain class from the type in the request
Query query = new Query(Criteria.where("id").is(id)); // Update the document with the given ID
Update update = new Update();
// Iterate over the send fields and add them to the update object
Iterator iterator = fields.entrySet().iterator();
while(iterator.hasNext()) {
HashMap.Entry entry = (HashMap.Entry) iterator.next();
String key = (String) entry.getKey();
Object value = entry.getValue();
update.set(key, value);
}
mongoTemplate.updateFirst(query, update, classType); // Do the update
return mongoTemplate.findById(id, classType); // Return the updated document
} catch (Exception e) {
// Handle your exception
}
}
}
Now we're able to update the specified fields without changing the calls.
So in your case, the call would be:
PUT http://MY-DOMAIN/user/MY-USER-ID { lastName: "My new last name" }
PS: You can improve it by adding the possibility to update specific field in a nested objects...
Now, I have the next entity. This one is the m1 table of my database.
#Entity(name = "m1")
#Data
public class Information {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String date;
private Double weight_1;
private Double weight_2;
private Double weight_3;
private Double weight_4;
private int working;
}
So, when I do some call to the APIRest it returns me the information corresponding to the m1 table. The controller that I have is the next (simple controller that returns all the information):
#Controller
#RequestMapping(path = "/information")
public class InformationController {
#Autowired
private InformationRepository repository;
#GetMapping(path="/all")
public #ResponseBody List<Information> getAllInformations() {
// This returns a JSON or XML with the users
return repository.findAll();
}
}
The question is: There is any way to change the name of the m1 on runtime. For example can I put the name of the table in the call path and in the API Rest take it?
Maybe this is impossible and I am doing it the bad way I do not know.
EDIT: I mean, can I change the table that the API Rest is taking the data by putting the table that I want in the url/path that I call. For example: in my case the default table/entity that the APIRest take the data is m1, so can I call http://localhost:8080/information/especifictable/all/ where especific table is the table that I want the recieve the data of the database and in the API Rest take that url parameter and change the default m1 with the especifictable.
I do not know if I have explained it well, I do not know how to explain it well.
Such a design would only make sense, if there are two tables in DB, which look the same. if that is the case there is something wrong with your DB design.
Basically it is not possible, to the best of my knowledge.
In old version, SDN3, I can use findById(List id), but after upgrade to SDN4, I cannot use this function again, always return empty.
This is my sample class :
#NodeEntity
public class Right{
#GraphId
Long graphId;
String id; //random generated UUID
String name;
//Properties & Constructor
}
And then I have RightRepository that contain these code :
public interface RightRepository extends GraphRepository<Right> {
List<Right> findById(List<String> id);
}
Instead of use Loop to get per ID, I need to call repository only once, and get the List (without using findAll())
Is SDN4 already not support it? Is there any other solution?
As I post in a comment and after a further investigation, I think that a custom query is the only way to accomplish your requirement at the moment. This works:
#Query("MATCH (n:Right) WHERE n.id IN {rightIds} RETURN n")
List<Right> findRightById(#Param("rightIds") List<String> rightIds);
Hope it helps