Spring Mongo DB #DBREF - java

I am having trouble writing code that would allow get a user and claim details in a straightforward way. This is my MongoDB structure,
db.user.find();
user:
{
"name" : "KSK",
"claim" : [objectId("52ffc4a5d85242602e000000"),objectId("52ffc4a5d85242602e000001")]
}
claim:
[
{
"_id" : "52ffc4a5d85242602e000001",
"claimName" :"XXXX"
},
{
"_id" : "52ffc4a5d85242602e000000",
"claimName" :"YYY"
}
]
My Entity class is:
#Document(collection="user")
public class User{
#Id
private String id;
private String name;
#DBRef
private List<Claim> claim;
// setter and getter
}
Claim Class:
#Document(collection="Claim")
public class Claim{
#Id
private String id;
private String claimName;
}
I have a method to get the users by name like below,
public User findByName(String name);
If I try to hit this method am getting an error that,
No converter found capable of converting from type org.bson.types.ObjectId to type java.lang.String
So I changed my User entity class as like below,
Instead of private List<Claim> claim;
Changed as Private List<ObjectId> claim;
Now if I execute a method(findByName), I get a user object that has both claimed object ids ("52ffc4a5d85242602e000001","52ffc4a5d85242602e000000"), then iterate the claim list and get the claim details corresponding to the claim object Id.
Instead of doing this, when I execute findByName method I want to get a user and claim details. How can I achieve this functionality?

If you reference your Claims in the User class with #DBRef, your JSON should not only contain the ID but the reference to the collection where to find the ID as well, like this:
{
"name" : "KSK",
"claim" : [
{
"$ref" : "claim", // the target collection
"$id" : ObjectId("52ffc4a5d85242602e000000")
}
]
}
That is how Spring-Data maps your Java objects to MongoDB. If you start with a blank database and let Spring create and save the relations, you should have no problems using
#DBRef List<Claim> claims;

My suggestion is not to set that Claim class into separate #Document or just switch back to Relational Databases, because it's not a Mongo approach.
Also, if you insist on current architecture you can try using #DBRef above that List in User.class into smth like this:
public class ParentModel {
#Id
private String id;
private String name;
private ParentType parentType;
private SubType subType;
#DBRef
private List<Model> models;
....
}

as an alternative to #DBRef, take a look at RelMongo (link)
which provides a powerfull way to manage relations, in your case it will be like this :
#OneToMany(fetch = FetchType.LAZY)
private list<Claim> claims;

Related

Setting value to non DB attribute while fetching documents from Mongo using Morphia

This is one sample document (row) from a collection (table) called product which is in MongoDB:
{
"_id" : ObjectId("5cb39171528a28f831f"),
"seller" : "Product Seller 1",
"title" : "Product Title 1",
"price" : "159.75",
"brand" : "Product Brand 1",
"productId" : NumberInt(247)
}
The Java Model for this collection looks like this:
#Entity(value = "product", noClassnameStored = true)
#Data
public class Product {
#Id
#Property("_id")
private ObjectId objectId;
private String seller;
private String title;
private String price;
private String brand;
private Long productId;
}
I am using Morphia as a MongoDB Java Driver here. And I am using the Lombok #Data annotation which provides the constructor, getters and setters.
The model works perfectly and is able to fetch data from the DB. My new requirement is to add an additional field in the model. The name of the new field would be stringObjectId, which will store the value of the objectId in String format.
Please note that there will not be any changes in the DB document. That is, no new field is getting added to the DB document.
I want that when I am fetching data from the collection in the form of list or a single row
query.asList();
the value in the stringObjectId should also get populated. Something like this:
stringObjectId = objectId.toString()
No far I have tried to override the setters and the constructor of this model to set the value in the stringObjectId variable but nothing seems to work. Is there any way this can be achieved?
Check out Morphia LifeCycle events: https://www.playframework.com/modules/morphia-1.2.4/lifecycle-def
The #PostLoad one will be helpful here. You probably will need #Transient as well.

Couchbase - Auto generate key with user defined suffix

I want to save an employee object in couchbase using spring boot java. I am using reactive couchbase driver. My requirement is to save the employee object with employeeId suffixed with hard coded string "-EMPLOYEETYPE".
Example:
Object to couchbase from Java Application:
{ "employeeId" : "12345", "lname" :"ltest", "fname" : "ftest"}
While saving to couch base, key supposed to be generated like
"12345-EMPLOYEETYPE"
Below code is not working, kindly guide me how to achieve it.
Note: I am using lombok so there are no getters and setters.
#Document
public final class Employee {
#Id #GeneratedValue(strategy = GenerationStrategy.USE_ATTRIBUTES,delimiter="-EMPLOYEETYPE")
private String id;
#IdAttribute
private String employeeId;
}
Found a solution. We need to create an instance variable with suffix string literal assigned to it, and annotate with #IdSuffix. (For prefix, #IdPrefix). This field will not be persisted into couchbase and only used to generate id for document.
#Document
public final class Employee {
#Id #GeneratedValue(strategy = GenerationStrategy.USE_ATTRIBUTES,delimiter="-")
private String id;
#IdAttribute
private String employeeId;
#IdSuffix
private String suffix = "EMPLOYEETYPE";
}
Reference Doc: https://docs.spring.io/spring-data/couchbase/docs/current/reference/html/#couchbase.autokeygeneration.configuration

Spring data MongoDB unable to map _id in group aggregation

I'm using Spring Data MongoDB to generate an aggregated query. At one point I do this:
// 5. Rejoin the array with group.
group("email", "name", "surname", "birthday", "creationTime", "updateTime", "technology")
.push(SCORES_FIELD).as(SCORES_FIELD));
The generated step (in the log) is this:
"$group" : {
"_id" : {
"email" : "$_id",
"name" : "$name" ,
"surname" : "$surname" ,
"birthday" : "$birthday" ,
"creationTime" : "$creationTime" ,
"updateTime" : "$updateTime" ,
"technology" : "$technology"
} ,
"scores" : { "$push" : "$scores"}
}
Which is perfectly fine, I've tested it on the Mongo shell and gives back exactly what I want.
The problem is that when I do the same with Spring Data, the email field (which is the _id field in Mongo) is mapped as null. There's probably something wrong in my mapping but I haven't been able to figure out what is it exactly. Here's the model:
#Document(collection = "user")
public class User implements UserDetails {
private static final long serialVersionUID = 1L;
private String name;
private String surname;
private LocalDate birthday;
#Id
#Field("_id")
private String email;
private Collection<? extends GrantedAuthority> authorities;
private String password;
private Set<Score> scores;
private LocalDateTime creationTime;
private LocalDateTime updateTime;
private String technology;
// Getters and setters, hashmap, equals and toString
}
I've done other queries and everything works out perfectly. I'm having this problem only on this one which is the only aggregation I do.
Promoting my comment to answer.
The _id can't be mapped into email because group stage returns multiple keys in _id document. previousOperation() is just a convenience method to return the _id from previous group operation. You can try changing to group("email").first("name").as("name").... and see if it helps.
I would expect spring to read the Field annotation from the model and map the _id field back to email now.

Spring-Data-Mongodb - How to look for a document, based on a field value of a document referenced within?

Here are my two classes :
#Document(collection="people")
public class Person {
// Data.
#Id
private ObjectId Id;
#Field("email")
private String email;
#DBRef
private List<Entity> entities;
/* Getters and setters removed to keep this question simple. */
}
And, here is my entity class.
#Document(collection = "entities")
public class Entity {
#Id
private ObjectId Id;
#DBRef
private List<Person> people;
/* Getters and setters removed to keep question simple. */
}
And here is my mongoRepository :
public interface EntityRepository extends MongoRepository<Entity, String>{
List<Entity> findByPeople_email(String email);
}
I think you can see what I want to do here - I want to query the mongo database, and get back all entities attached to a user where the email address matches the one I provide. I keep getting stuck in an SQL mindset here, and thinking I can just do something like :
SELECT * FROM people WHERE email="<email_here>"
But obviously that is not the case! Anyone know what I am missing?

spring-data-rest integration test fails with simple json request

My spring-data-rest integration test fails for a simple json request. Consider the below jpa models
Order.java
public class Order {
#Id #GeneratedValue//
private Long id;
#ManyToOne(fetch = FetchType.LAZY)//
private Person creator;
private String type;
public Order(Person creator) {
this.creator = creator;
}
// getters and setters
}
Person.java
ic class Person {
#Id #GeneratedValue private Long id;
#Description("A person's first name") //
private String firstName;
#Description("A person's last name") //
private String lastName;
#Description("A person's siblings") //
#ManyToMany //
private List<Person> siblings = new ArrayList<Person>();
#ManyToOne //
private Person father;
#Description("Timestamp this person object was created") //
private Date created;
#JsonIgnore //
private int age;
private int height, weight;
private Gender gender;
// ... getters and setters
}
In my test I created a person by using personRepository and inited order by passing person
Person creator = new Person();
creator.setFirstName("Joe");
creator.setLastName("Keith");
created.setCreated(new Date());
created.setAge("30");
creator = personRepository.save(creator);
Order order = new Order(creator);
String orderJson = new ObjectMapper().writeValueAsString(order);
mockMvc.perform(post("/orders").content(orderJson).andDoPrint());
Order is created but creator is not associated with the order. Also I want to pass request body as a json object. In this my json object should contain creator as follows
{
"type": "1",
"creator": {
"id": 1,
"firstName": "Joe",
"lastName": "Keith",
"age": 30
}
}
If I send request body with the following json, the call works fine
{
"type": "1",
"creator": "http://localhost/people/1"
}
But I don't want to send the second json. Any idea how to solve the issue. Because already my client is consuming the server response by sending first json. Now I migrated my server to use spring-data-rest. After that all my client code is not working.
How to solve this?
You are correctly associating order with the creator, however the Person is not associated with the orders. You are missing the List<Order> orders field in Person class. Add this, add annotations, add methods for adding order to person and then before sending JSON you should call something like this:
creator.addOrder(order);
order.setCreator(cretr);
Did you try using cascade = CascadeType.ALL in #ManyToOne annotation
public class Order {
#Id #GeneratedValue//
private Long id;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)//
private Person creator;
private String type;
public Order(Person creator) {
this.creator = creator;
}
// getters and setters
}
Both your Order and Person classes should implement Serializable to properly break them down into and rebuild them from JSON.
There are some ways to solve your problem, but I want give you a hint. You just can save only "id" of your person and get the person by "id" from your database, when you need this.
It solves your problem and it also saves the memory.
I believe you need to do two things to get this work.
Handle the deserialization properly. As you expect Jackson to populate the nested Person object via the constructor you need to annotate this with #JsonCreator. See here:
http://www.cowtowncoder.com/blog/archives/2011/07/entry_457.html
One of more powerful features of Jackson is its ability to use arbitrary >constructors for creating POJO instances, by indicating constructor to use with
#JsonCreator annotation
...........................................
Property-based creators are typically used to pass one or more
obligatory parameters into constructor (either directly or via factory
method). If a property is not found from JSON, null is passed instead
(or, in case of primitives, so-called default value; 0 for ints and so
on).
See also here on why Jackson may not be able to automatically work this out.
https://stackoverflow.com/a/22013603/1356423
Update your JPA mappings. If the associated Person is now populated correctly by the Jackson deserializer then by adding the necessary JPA cascade options to the relationship then both instances should be persisted.
I think then the following should work as expected:
public class Order {
#Id
#GeneratedValue(...)
private Long id;
#ManyToOne(fetch = FetchType.LAZY, cascade = cascadeType.ALL)
private Person creator;
private String type;
#JsonCreator
public Order(#JsonProperty("creator") Person creator) {
this.creator = creator;
}
}

Categories

Resources