The domain that I'm describing is not the same that I use (with this in mind, there is no need to worry with the fact the the sample class doesn't use Enum for the type, and other things), but describe well my problem. I want to make a query that filter transactions by username and type.
public class Transaction {
private String userName;
private String type;
private BigDecimal value;
...
}
I would like to search all the transactions that match a list of [{'username', 'type'}], where the couple {"username", "type"} is chosen by the user, and the user can choose as many as he wants
For instance: Find all :
"Debit" transactions made by "Rafael"
"Credit" transactions made by "Daniel"
"Debit" transactions made by "Monica"
"Credit" transactions made by "Monica"
I have two possible solutions for my problem, but I don't like any
POSSIBLE SOLUTION 1
I could dynamically add n "OR" clauses to my query, like:
"WHERE (t.userName = 'Rafael' AND t.type = 'Debit')
OR (t.userName = 'Daniel' AND t.type = 'Credit')
... "
POSSIBLE SOLUTION 2
I could concatenate the "username" to the "type" and check if the result is contained by a list of concatenated values of "username" with "type" which was generated dynamically.
CONCAT(t.userName, '-', t.type) IN (list)
for the example, the variable "list" would have the following values:
['Rafael-Debit', 'Daniel-Credit', 'Monica-Debit', 'Monica-Credit']
I'm using JPA, does anyone have a suggestion?
Related
I have passed my query from postgres to mongodb, everything is correct. But in the ordering I do not see how to integrate it within #Query as in the example sql
// sql-postgres ( repository )
#Query("select c from ClienteElser c where c.securityDomainId IN (2,10) and c.deleted is null order by c.razonSocial")
//mongodb ( repository )
#Query("{security_domain_id: { $in: [2,10] },'deleted':null}")
public List<Cliente> findAllByOrderByRazonSocialAsc(Sort sort);
//service
Sort sort = new Sort(Sort.Direction.ASC, "razon_social");
List<Cliente> result = clienteRepository.findAllByOrderByRazonSocialAsc(sort);
At the moment I have fixed it like that, but I would prefer it to be inside #Query
And order giving priority to capital letters, I do not know how to avoid that. Example : "ACS" is before "Abalos"
Can someone help me to integrate the sort within #Query with mongodb and that does not differ between uppercase and lowercase
Thank you
This has been adressed in DATAMONGO-1979 for the Lovelace release (aka Spring Data MongoDB 2.1.0).
It will allow to set a default sort for repository query methods via the #Query annotation.
#Query(sort = "{ age : -1 }")
List<Person> findByFirstname(String firstname);
Using an explicit Sort parameter along with the annotated one allows to alter the defaults set via the annotation. The method argument Sort parameters will be added to / or override the annotated defaults depending on the fields used.
#Query(sort = "{ age : -1 }")
List<Person> findByFirstname(String firstname, Sort sort);
I'm trying to implement a rest api using RepositoryRestResource and RestTemplate
It all works rather well, except for loading #DBRef's
Consider this data model:
public class Order
{
#Id
String id;
#DBRef
Customer customer;
... other stuff
}
public class Customer
{
#Id
String id;
String name;
...
}
And the following repository (similar one for customer)
#RepositoryRestResource(excerptProjection = OrderSummary.class)
public interface OrderRestRepository extends MongoRepositor<Order,String>{}
The rest api returns the following JSON:
{
"id" : 4,
**other stuff**,
"_links" : {
"self" : {
"href" : "http://localhost:12345/api/orders/4"
},
"customer" : {
"href" : "http://localhost:12345/api/orders/4/customer"
}
}
}
Which if loaded correctly by the resttemplate will create a new Order instance with customer = null
Is it possible to eagerly resolve the customer on the repository end and embed the JSON?
Eagerly resolving dependent entities in this case will raise most probably N+1 database access problem.
I don't think there is a way to do that using default Spring Data REST/Mongo repositories implementation.
Here are some alternatives:
Construct an own custom #RestController method that would access the database and construct desired output
Use Projections to populate fields from related collection, e.g.
#Projection(name = "main", types = Order.class)
public interface OrderProjection {
...
// either
#Value("#{customerRepository.findById(target.customerId)}")
Customer getCustomer();
// or
#Value("#{customerService.getById(target.customerId)}")
Customer getCustomer();
// or
CustomerProjection getCustomer();
}
#Projection(name = "main", types = Customer.class)
public interface CustomerProjection {
...
}
The customerService.getById can employ caching (e.g. using Spring #Cachable annotation) to mitigate the performance penalty of accessing the database additionally for each result set record.
Add redundancy to your data model and store copies of the Customer object fields in the Order collection on creation/update.
This kind of problem arises, in my opinion, because MongoDB doesn't support joining different document collections very well (its "$lookup" operator has significant limitations in comparison to the common SQL JOINs).
MongoDB docs also do not recommend using #DBRef fields unless joining collections hosted in distinct servers:
Unless you have a compelling reason to use DBRefs, use manual references instead.
Here's also a similar question.
I have a OrientDB question. For the statement
INSERT INTO Account CONTENT
{ name : 'Luca',
vehicles : {
#class : 'Vehicle',
type : 'Car',
model : 'Maserati',
isItTrue: false
}
}
I get a result (see screenshot), but the class/table 'Vehicle' does not contain any entries. Is this a valid result?
I expected that automatically some values are written into 'Vehicle' and connected to 'Account'. Only the column 'vehicles' from 'Account' contains JSON-Data.
Thank you for your response.
Best regards
Karo
Screenshot OrientDB
With your query you are inserting embedded data into Account, if you want to insert some data into Account and Vehicle, you should do it in different queries and then if you want you can connect them with an edge. See example:
create class Account extends v
create class Vehicle extends v
create class owns extends e
create property Account.name String
create property Vehicle.type String
create property Vehicle.model String
create property Vehicle.isItTrue BOOLEAN
insert into Account(name) values ("Luca")
insert into Vehicle(type, model, isItTrue) values ("Car", "Maserati", false)
create edge owns from (select from Account where name = "Luca") to (select from Vehicle where model = "Maserati")
Alternatively, if you are not looking to create edges and you just want a link from document to document, the below will give you desired results in single insert / transaction while still keeping your json structure for the linked record
create class Account
create class Vehicle
create property Account.vehicles embedded Vehicle
INSERT INTO Account set name = "luca", vehicles = [ {
"#type": "d",
"#class" : "Vehicle",
"type" : "Car",
"model" : "Maserati",
"isItTrue": false
}]
I am new to both Morphia and MongoDB. Is there a way to check using Morphia that a certain field in my database is not null and also exists. For example from the following record of a user from a collection of users in database:
{ "_id" : ObjectId("51398e6e30044a944cc23e2e"),
"age" : 21 ,
"createdDate" : ISODate("2013-03-08T07:08:30.168Z"),
"name" : "Some name" }
How would I use a Morphia query to check if field "createdDate" is not null and exists.
EDIT:
I am looking for a solution in Morphia. So far I have come up with this:
query.and(query.criteria("createdDate").exists(),
query.criteria("createdDate").notEqual(null));
From documentation, I learnt Morphia does not store empty or null fields. Hence the justification for notEqual(null).
EDIT 2: From the answers I can see the problem needs more explanation. I cannot modify the createdDate. To elaborate: the example above is less complex than my actual problem. My real problem has sensitive fields which I cannot modify. Also to complicate things a bit more, I do not have control over the model otherwise I could have used #PrePersist as proposed in one of the answers.
Is there a way to check for null and non existing field when I have no control over the model and I am not allowed to modify fields?
From the documentation, Morphia does not store Null/Empty values (by default) so the query
query.and(
query.criteria("createdDate").exists(),
query.criteria("createdDate").notEqual(null)
);
will not work since it seems you are not able to query on null, but can query for a specific value.
However, since you can only query for a specific value, you can devise a workaround where you can update the createdDate field with a date value that is never used in your model. For example, if you initialize a Date object with 0, it will be set to the beginning of the epoch, Jan 1st 1970 00:00:00 UTC. The hours you get is the localized time offset. It will be sufficient if your update only involves modifying the matching element(s) in mongo shell, hence it would look similarly to this:
db.users.update(
{"createdDate": null },
{ "$set": {"createdDate": new Date(0)} }
)
You can then use the Fluent Interface to query on that specific value:
Query<User> query = mongoDataStore
.find(User.class)
.field("createdDate").exists()
.field("createdDate").hasThisOne(new Date(0));
It would be much simpler when defining your model to include a prePersist method that updates the createdDate field. The method is tagged with the #PrePersist annotation so that the date is set on the order prior to it being saved. Equivalent annotations exist for #PostPersist, #PreLoad and #PostLoad.
#Entity(value="users", noClassNameStored = true)
public class User {
// Properties
private Date createdDate;
...
// Getters and setters
..
#PrePersist
public void prePersist() {
this.createdDate = (createdDate == null) ? new Date() : createdDate;
}
}
When you first create your Morphia instance, before calling morphia.mapPackage() do this:
morphia.getMapper().getOptions().setStoreNulls(true);
to have Morphia store null values.
Anyway, you should be able to query non-null values with:
query.field("createdDate").notEqual(null);
In mongo you can use this query:
db.MyCollection.find({"myField" : {$ne : null}})
This query will return objects that have the field 'myField' and has a value that is not null.
I have two domain objects,
#Document
public class PracticeQuestion {
private int userId;
private List<Question> questions;
// Getters and setters
}
#Document
public class Question {
private int questionID;
private String type;
// Getters and setters
}
My JSON doc is like this,
{
"_id" : ObjectId("506d9c0ce4b005cb478c2e97"),
"userId" : 1,
"questions" : [
{
"questionID" : 1,
"type" : "optional"
},
{
"questionID" : 3,
"type" : "mandatory"
}
]
}
I have to update the "type" based on userId and questionId, so I have written a findBy query method inside the custom Repository interface,
public interface CustomRepository extends MongoRepository<PracticeQuestion, String> {
List<PracticeQuestion> findByUserIdAndQuestionsQuestionID(int userId,int questionID);
}
My problem is when I execute this method with userId as 1 and questionID as 3, it returns the entire questions list irrespective of the questionID. Is the query method name valid or how should I write the query for nested objects.
Thanks for any suggestion.
Just use the #Query annotation on that method.
public interface CustomRepository extends MongoRepository<PracticeQuestion, String> {
#Query(value = "{ 'userId' : ?0, 'questions.questionID' : ?1 }", fields = "{ 'questions.questionID' : 1 }")
List<PracticeQuestion> findByUserIdAndQuestionsQuestionID(int userId, int questionID);
}
By adding the fields part of the #Query annotation, you are telling Mongo to only return that part of the document. Beware though, it still returns the entire document in the same format - just missing everything you did not specify. So your code will still have to return List<PracticeQuestion> and you will have to do:
foreach (PracticeQuestion pq : practiceQuestions) {
Question q = pq.getQuestions().get(0); // This should be your question.
}
Property expressions
Property expressions can refer only to a direct property of the managed entity, as shown in the preceding example. At query creation time you already make sure that the parsed property is a property of the managed domain class. However, you can also define constraints by traversing nested properties. Assume Persons have Addresses with ZipCodes. In that case a method name of List<Person> findByAddressZipCode(ZipCode zipCode);
creates the property traversal x.address.zipCode. The resolution algorithm starts with interpreting the entire part (AddressZipCode) as the property and checks the domain class for a property with that name (uncapitalized). If the algorithm succeeds it uses that property. If not, the algorithm splits up the source at the camel case parts from the right side into a head and a tail and tries to find the corresponding property, in our example, AddressZip and Code. If the algorithm finds a property with that head it takes the tail and continue building the tree down from there, splitting the tail up in the way just described. If the first split does not match, the algorithm move the split point to the left (Address, ZipCode) and continues.
Although this should work for most cases, it is possible for the algorithm to select the wrong property. Suppose the Person class has an addressZip property as well. The algorithm would match in the first split round already and essentially choose the wrong property and finally fail (as the type of addressZip probably has no code property). To resolve this ambiguity you can use _ inside your method name to manually define traversal points. So our method name would end up like so:
UserDataRepository:
List<UserData> findByAddress_ZipCode(ZipCode zipCode);
UserData findByUserId(String userId);
ProfileRepository:
Profile findByProfileId(String profileId);
UserDataRepositoryImpl:
UserData userData = userDateRepository.findByUserId(userId);
Profile profile = profileRepository.findByProfileId(userData.getProfileId());
userData.setProfile(profile);
Sample Pojo :
public class UserData {
private String userId;
private String status;
private Address address;
private String profileId;
//New Property
private Profile profile;
//TODO:setter & getter
}
public class Profile {
private String email;
private String profileId;
}
For the above Document/POJO in your Repository Class:
UserData findByProfile_Email(String email);
For ref : http://docs.spring.io/spring-data/data-commons/docs/1.6.1.RELEASE/reference/html/repositories.html
You need to use Mongo Aggregation framework :
1) Create custom method for mongo repository : Add custom method to Repository
UnwindOperation unwind = Aggregation.unwind("questions");
MatchOperation match = Aggregation.match(Criteria.where("userId").is(userId).and("questions.questionId").is(questionID));
Aggregation aggregation = Aggregation.newAggregation(unwind,match);
AggregationResults<PracticeQuestionUnwind> results = mongoOperations.aggregate(aggregation, "PracticeQuestion",
PracticeQuestionUnwind.class);
return results.getMappedResults();
2) You need to cretae a class(Because unwind operation has changed the class structure) like below :
public class PracticeQuestionUnwind {
private String userId;
private Question questions;
This will give you only those result which matches the provide userId and questionId
Result for userId: 1 and questionId : 111 :
{
"userId": "1",
"questions": {
"questionId": "111",
"type": "optional"
}
}
i too had similar issue. for that i added $ before the nested class attributes.
try below query
#Query(value = "{ 'userId' : ?0, 'questions.$questionID' : ?1 }") List<PracticeQuestion> findPracticeQuestionByUserIdAndQuestionsQuestionID(int userId, int questionID);