I have a query that would be fairly simple using SQL databases. However, in MongoDB, I'm not sure on how to do it. I am building a notification system. I have a collection that stores notifications, and another that creates a document when the user has seen the notification, as such:
notifications collection:
_id: 1
content: "some content"
targetGroup: "somegroup"
seen-notification collection
_id: 1
notificationId: 1
userName: "johndoe"
I'm building an endpoint that should return all notifications in a specific group, that the user has not already seen. In a SQL pseudo-code, I'm trying to do:
SELECT
*
FROM
notifications AS n
INNER JOIN
seen-notification AS sn
ON n._id = sn.notificationId
WHERE
sn.notificationId IS NULL
AND n.targetGroup = "somegroup"
So, basically, a right join with a where clause. I'm using spring data with a MongoRepository. Right now, i'm making two different selects and iterating through them to remove the already seen. I've looked into mongo's $lookup and several SO questions regarding it, but I just couldn't get it work with spring data and MongoRepository. I'm fairly new to mongo with spring data, so maybe there's a way to accomplish this with $lookup and I just didn't figure out how.
try
https://mongoplayground.net/p/-dZHmP9yj-c
expects collection names of notifications and seenNotifications
#Aggregation(pipeline = {
"{$match: { targetGroup: ?0 } }",
"{$lookup: { from: \"seenNotifications\", localField: \"_id\", foreignField: \"notificationId\", as: \"seenNotificationsDocs\"}}",
"{$match: { seenNotificationsDocs: { $size: 0 } } }",
"{$project: { seenNotificationsDocs: 0 } }",
})
List<Notifications> findUnseenNotificationByTargetGroup(String targetGroup);
#Document("notifications")
public class Notifications {
#Id
private Integer id;
private String content;
private String targetGroup;
//getter & setter
}
Related
I am new to MongoDB and have trouble. I would be very grateful if someone could help me with it.
I have the entity like:
class FileRecord {
private ObjectId id;
private String fileId;
private EnumStatus status;
private Long userId;
private Integer year;
etc.
> }
A file can have many records but only one in the year.
I need to get all the last documents by criteria.
I don't understand how to make it right in Spring, but in the mongo query too if to be honest)
I understand how to make criteria for it:
var match = match(Criteria.where("userId").in(userIds).andOperator(Criteria.where("status").in(EnumStatus.availableStatues())));
And group by max:
var group = group("fileId", "year").max("year").as("max_year");
But how to make it to query in one that will return for me Colletion<FileRecord> after aggregation?
If I try it make it this way:
var aggregation = newAggregation(filter, group);
AggregationResults<FileRecord> aggregateResult =
mongoOperations.aggregate(aggregation, FileRecord.class, FileRecord.class);
Collection<FileRecord> records = aggregateResult.getMappedResults()
I get an exception:
readObjectId can only be called when CurrentBSONType is OBJECT_ID, not when CurrentBSONType is DOCUMENT.;
If someone has an idea I'm interested
Thank you in advance
I found the answer)
var group = group("fileId", "year").max("year").as("max_year").first("_id").as("enityId");
I needed to create the projection and after again make request to the DB and get the data that I needed by id. I don't have many of them, so the solution worked for me. But, as I understand, you can add a lot "first", and add the fields you need. And get all by one request
How can we select specific fields in Spring Data Mongo. I tried the following but I got cast exception from Foo to String.
Using #Query
#Query(value="{path : ?0}", fields="{path : 0}")
String findPathByPath(String path);
Non #Query
String findPathByPath(String path);
Here is the document model
#Document(collection = "foo")
public class Foo {
String name, path;
…
}
MongoDB only returns JSON documents for standard queries. What you'd like to see can be achieved by still returning a List<Foo>. The fields property in #Query will cause only the fields set to 1 being returned.
#Query(value="{ path : ?0}", fields="{ path : 0 }")
List<Foo> findByPath(String path);
We usually recommend introducing a dedicted DTO for that so that you prevent the partially filled Foo instance from being handed to save(…) in turn.
Another option is using the aggreation framework but that's more involved.
You can use
Query query = new Query();
query.fields().include("path");
You can use
public interface PersonRepository extends MongoRepository<Person, String>
#Query(value="{ 'firstname' : ?0 }",fields="{ 'firstname' : 1, 'lastname' : 1}")
List<Person> findByThePersonsFirstname(String firstname);
}
More information in spring data documentation
You can use below query to get specific fields.
#Query(fields="{path : 1}")
Foo findPathByPath(String path);
Records present in DB
{
"name" : "name2",
"path" : "path2"
},
{
"name" : "name3",
"path" : "path3"
}
Below query will return Foo object if path=Path3
{
"name": null,
"path": "path3"
}
we need to specify required fields with fieldName:1 and if don't require then specify it with 0.
I found this question while trying to get the value of a field from a specific object in my collection. From what my research shows, Mongo doesn't provide a way to natively return just a specific field's value from an object. (Disappointing since it seems pretty basic to be able to return just a specific value from a field like I would do in SQL or JSONPath).
To get around this, I wrote the following method using Spring MongoDB with Java 11:
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.MongoTemplate; //not used, just showing which type the template is
import java.util.Arrays;
import static java.util.Objects.requireNonNull;
/**
* Use this method to get a specific field from an object saved in Mongo. The objectId will be
* the id of the object to fetch, and the fieldValueToReturn will be the field to return.
*
* #return the value of the provided field-path converted to the class type provided
*/
public <T> T getFieldValueById(String objectId, String fieldValueToReturn, String collectionName, Class<T> classTypeToReturn) {
var query = new Query().addCriteria(Criteria.where("_id").is(objectId));
query.fields().include(fieldValueToReturn);
var result = mongoTemplate.findOne(query, org.bson.Document.class, collectionName);
requireNonNull(result, "Did not find any documents with id '" + objectId + "' in collection: " + collectionName);
return result.getEmbedded(Arrays.asList(fieldValueToReturn.split("\\.")), classTypeToReturn);
}
The getEmbedded call allows us to get the value of the nested field within the returned Bson document.
To use the method, just call it like this:
getFieldValueById("A1234", "field.nestedfield.nestedfield", "collectionName", String.class);
Hopefully this helps out someone else looking on how to do this.
As a side note, I'm not sure how to extend this to return a list of objects - if I get to that dilemma and solve it, I will try to update this answer. I'm also not sure if this is slower than running a Mongo aggregate query - I haven't tried running any performance comparisons between the two methods.
EDIT 2022-09-30: To return a list of a custom Pojo, it looks like you'll have to use an aggregate query via spring-data-mongodb. Also it seems basic queries are faster than aggregate queries, so use basic queries where possible.
You can directly pass your json query with #Query annotation, for example:
#Query("{ 'firstname' : 'john' }")
Here is the link to all json based queries in Spring Data MongoDb - https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mongodb.repositories.queries.json-based
You can do the following.
In your repository, you have the method:
String findPathByPath(String path);
If the document looks like this (below), and you want to only return path
#Document(collection = "foo")
public class Foo {
String name;
String path;
String type;
…
}
Then create a Projection interface, e.g.
#Projection(name = "flattenedFoo", types = Foo.class)
public interface FlattenedInvoice {
String getPath(); // This returns the path value in Foo class
}
You can use the getter methods to get the fields from Foo that you are interested in.
Then in your get request, you would have to specify the projectionName.
e.g. with (#Resource path)
#RestResource(path = "findByPath", rel = "findByPath")
String findPathByPath(String path);
You could then say (In a get request):
..../findByPath?path=target_path&projection=flattenedFoo
this would then return a json with only the fields specifies in FlattenedFoo interface.
I use querydsl to query on a mongodb. As allowed by mongodb, in several cases I store objects of different types in the same collection.
For instance, in my data model I have:
interface Notification {
NotificationType getType(); // EMAIL, SMS etc.
}
interface EmailNotification extends Notification {
Set<User> getRecipients();
}
Now I want to query for Notifications of any kind (not only EmailNotification), but in the case I have EmailNotifications, I want to filter on some recipient.
I tried this code (doesn't work):
final QNotification notification = QNotification.notification;
final BooleanBuilder typesPredicate = new BooleanBuilder();
// "recipientEmails" and "recipientPhones" are provided parameters (lists of String)
typesPredicate.or(notification.type.eq(NotificationType.EMAIL)
.and(notification.as(QEmailNotification.class).recipients
.any().email.in(recipientEmails)));
typesPredicate.or(notification.type.eq(NotificationType.SMS)
.and(notification.as(QSMSNotification.class).recipients
.any().phoneNumber.in(recipientPhones)));
notificationPersister.query(Notification.class).where(typesPredicate);
It doesn't throw any error or exception, but I don't get the expected result (actually I don't get any result) because the generated mongo query is wrong, and I can't get how to make it right.
Generated query is like:
{
"$and":[
// ...
{
"$or":[
{
"type":"EMAIL",
"notification.recipients.email":{
"$in":[
"a#b.com"
]
}
},
// ...
]
}
]
}
And the issue lies in the key "notification.recipients.email": it should be just "recipients.email".
Why does "notification.as(QEmailNotification.class).recipients" translates to "notification.recipients", and how can I make it as expected ?
Note that the following would work:
notificationPersister.query(EmailNotification.class).where(
QEmailNotification.eMailNotification.recipients.any().email.in(recipientEmails));
But then I'm forced to run 1 query per inherited class, which is not efficient.
As Timo said: Fixed in querydsl via github.com/querydsl/querydsl/pull/1428
I am using hibernate with restful interface, I have one-to-many relationship between three tables, when I want to retrieve the records in those tables from mysql database (using curl command) I also get their ids (which were auto-generated) but I don't want them to be retrieved, is there any way I can do this?
Here is result I retrieve:
[
{
"deviceID": "no id",
"deviceName": "no name",
"deviceType": "no_type",
***"id": 1,***
"newMeasurements": [
{
"measurementT": "type",
"measurementU": "unit",
"measurementV": "value",
"measurementDateTime": "15.09.9898",
***"id": 1***
}
],
"Oldmeasurement": [
{
"measurementTime": "12.02.1586",
"sampleR": "rate",
"sampleU": "Unit",
"sampleT": "Type",
"sampleU": "Unit",
"sampleV": "noValues",
***"id": 1***
}
],
***"patientID": 1***
}
]
Before answering to your question I would like to give you a glance/background on few things.
In case of OOP, Almost everything is object and how to identify, compare two instance of same type plays a key role here. There is a concept called equality and identity which is use to identify and compare objects(I hope you are aware of it).
Similarly there is another mechanism in hibernate called database identity to identify/compare two same type instance. Meaning it is a way to determine that the two same type instances representing/pointing to same row. This can be achieved only through identifier which you want to ignore during retrieval.
The bottom line is Id is really mandatory to identify objects since hibernate compares objects based on values but not by its memory location (identity)
Recommended way to create your entity is
Class MyEntity
{
#id
#column(name=”id”)
//Id generation strategy goes here.
Private int id;
#column(name=”name”)
Private String name;
Public int getId()
{
return this.id;
}
//Getter and setters of name
}
You might have noticed the absence of setter method of ID. Reason is changing of identity is not allowed/recommended. Hope this is helpful!
I want to build in a select with the possibility of marking more elements. So multiple="true". I use mysql and the Dao Technology of spring. I get the values for the select from database successfully. But now I have a problem when inserting the selected values to my database.
The important tables for that are:
The table demo.instrumente is filled with data like guitar, piano, etc. and an id. These values (i.e. guitar, piano) are displayed in the multiple select.
A user is able to select maybe 2 or 3 instruments. So I need to add the following instruments to the students. I do this with the table schueler_instrumente. Every student and instrument has an id. So i need to create data like this:
student_id 1 and instrument_id 2
student_id 1 and instrument_id 5
Here is my code for the instrument model class:
public class Instrumente {
private Integer instrumentid;
private String instrumentname;
//...getters and setters
}
This code is part of my controller:
#RequestMapping(method = RequestMethod.GET)
public String showUserForm(ModelMap model) {
model.put("instrumentListe", schuelerManager.getinstrumente());
return "usercenter";
}
And here's the relevant part of my schuelerManager
public Map<String, String> getinstrumente() {
Map<String,String> result = new LinkedHashMap<String,String>();
for (Instrumente instrument : instrumentDao.getInstrumente()) {
result.put(instrument.getInstrumentid().toString(),
instrument.getInstrumentname());
}
return result;
}
And here's how I get the data from my database:
public List<Instrumente> getInstrumente() {
return getJdbcTemplate().query("SELECT * FROM instrumente",
new RowMapper<Instrumente>() {
public Instrumente mapRow(ResultSet rs,
int rowNum)
throws SQLException {
Instrumente instrument = new Instrumente();
instrument.setInstrumentid
(rs.getInt("Instrument_ID"));
instrument.setInstrumentname
(rs.getString("Instrumentenbezeichnung"));
return instrument;
}
});
}
I do now know what I need to do in order to get the selected values from the select list. What do I have to write to the path="?" in the jsp.
I think that I can get a list of values back but how can I insert this list to my table schueler_instrument. Did I need to make a while or for repeat and make an insert everytime?
I can't find any nice example on the Internet. I hope someone can show me how to do this maybe with some code snippets.
In your controller's showUserForm() you are adding correctly your date to the ModelMap (or you could use its Java-5 counterpart Model). Now you'll need to use Spring's form tags in your view to represent the options in a dropdown/list way and receive onSubmit back in your controller the results that you will further persist in your db.
Have a look for a full example here.
Something that is not showcased in this example and I suggest you take a look is the #ModelAttribute annotation which is a nice way to communicate objects and values between your controller and your jsp view. For an example have a look in this tutorial.