Accessing nested data in MongoDB through REST, Spring - java

Akin to my earlier question, I'm trying to access data in MongoDB using Spring REST.
I have collections of simple Key-Value Pairs and can access those fine.
{
"_id" : ObjectId("5874ab4a19b38fb91fbb234f"),
"roID" : "7ed3f9a6-bb9b-4d16-8d1a-001b7ec40b51",
"Name" : "[REDACTED]"
}
The problem is, these objects are used in another collection that displays a relationship with properties between them, like this:
{
"_id" : ObjectId("5874ab4f19b38fb91fbb6180"),
"[OBJECT CATEGORY A]" : {
"_id" : ObjectId("5874ab4a19b38fb91fbb257b"),
"roID" : "72f8a8b5-71a7-40ac-b1ac-1ffc98a507ba",
"Name" : "[REDACTED]"
},
"[OBJECT CATEGORY B]" : {
"_id" : ObjectId("5874ab4b19b38fb91fbb32a3"),
"roID" : "919446ab-1898-419f-a704-e8c34985f945",
"Name" : "[REDACTED]"
},
"[RELATIONSHIP INFORMATION]" : [
{
"[PROPERTY A]" : [
{
"[VALUE A]" : 5.0
},
{
"[VALUE B]" : 0.0
}
]
},
Properties are somewhere between 8 and 20.
The definition of the first (plain) object in Java looks like this:
#Document(collection="OBJ")
public class Obj {
public Obj(){};
#Id
public String id;
#Field("roID")
public String roID;
#Field("Name")
public String name;
}
The repository class:
#RepositoryRestResource(collectionResourceRel = "OBJ", path = "OBJ")
public interface ObjRepo extends MongoRepository<Obj, String> {
List<Obj> findByName(#Param("name") String name);
}
The question is: how do I access the nested objects? I've tried using LinkedHashMap in place of the Strings for the complex collection, curl only returns "null" when I try to access them. I tried defining a class
public class BITS {
#Id
private String _id;
#Field("roID")
private String roID;
#Field("Name")
private String name;
public BITS(){}
public BITS(String _id,String roID, String name){
this._id = _id;
this.roID = roID;
this.name = name;
}
}
to access these objects, unsuccessfully.

Turns out the class approach was correct, just not well executed.
I've created a plain JSON Collection for testing purposes:
#Document(collection="JSON")
public class JSON {
#Id
public String id;
#Field("squares")
public Square square;
#Field("dots")
public Dot dot;
public JSON(){};
public JSON(String id, Square square,Dot dot){
this.id = id;
this.square = square;
this.dot = dot;
};
}
Square.java
public class Square {
private String id;
private int x;
private int y;
public Square(){};
public Square(String id,int x, int y){
this.id = id;
this.x = x;
this.y = y;
};
public Map<String, Integer> getSquare()
{
Map<String, Integer> res = new HashMap<>();
res.put("x", x);
res.put("y", y);
return res;
}
}
(Dot is the same, just for the test)
So it's just about remodeling the desired response exactly, despite it being in that format in the database already.
If anyone could point me to where I can remove the clutter from the response tho, that would be nice. Currently looks like this:
"_embedded" : {
"JSON" : [ {
"square" : null,
"dot" : {
"dot" : {
"x" : 4,
"y" : 3
}
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/JSON/58ac466160fb39e5e8dc8b70"
},
"jSON" : {
"href" : "http://localhost:8080/JSON/58ac466160fb39e5e8dc8b70"
}
}
}, {
"square" : {
"square" : {
"x" : 12,
"y" : 2
}
},
"dot" : null,
"_links" : {
"self" : {
"href" : "http://localhost:8080/JSON/58ac468060fb39e5e8dc8b7e"
},
"jSON" : {
"href" : "http://localhost:8080/JSON/58ac468060fb39e5e8dc8b7e"
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/JSON"
},
"profile" : {
"href" : "http://localhost:8080/profile/JSON"
}
},
"page" : {
"size" : 20,
"totalElements" : 2,
"totalPages" : 1,
"number" : 0
}
}

Related

Problem creating compound index in Spring Data MongoDB

Creating index via MongoShell
db.car.createIndex({brand:1 , model:1 , colour:1 ,fuelTypes:1},{unique:true})
Creating CompoundIndex via spring application
#Document
#CompoundIndex(def = "{ 'brand':1 , 'model':1 , 'colour':1 , 'fuelTypes':1 }",unique = true)
public class Car {
private String brand;
private String model;
private List<FuelType> fuelTypes;
private String colour;
}
I was able to create via Mongo shell but not thourgh spring application.What's wrong in the above code?Are n't they equivalent?
I checked After inserting atleast one document.
Thanks in advance.
Here is a working example I tried (creates a new collection, document and the compound index):
The Car POJO class:
#CompoundIndex(name = "car-cmp-idx", def = "{'brand': 1, 'model': 1}", unique = true)
#Document
public class Car {
private String brand;
private String model;
private String colour;
public Car() {
}
public Car(String brand, String model, String colour) {
this.brand = brand;
this.model = model;
this.colour = colour;
}
// get/set methods. etc...
}
The application code to create a document in the (new) car: collection:
MongoOperations ops = new MongoTemplate(MongoClients.create(), "test");
Car car = new Car("Ford", "Model T", "Black");
ops.insert(car);
The result document verified from the mongo shell:
{
"_id" : ObjectId("5ed46f4960c3f13e5edf43b6"),
"brand" : "Ford",
"model" : "Model T",
"colour" : "Black",
"_class" : "com.example.demo.Car"
}
The indexes:
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "test.car"
},
{
"v" : 2,
"unique" : true,
"key" : {
"brand" : 1,
"model" : 1
},
"name" : "car-cmp-idx",
"ns" : "test.car"
}
]

How to filter in mongo repository for array of objects

Here i'm using MongoRepository and i need to query a list of objects that includes certain id in an array of objects inside.
The document structure :
{
"_id" : ObjectId("5ccc1c54a3d5eed9a6b8015a"),
"email" : "sineth3#gmail.com",
"name" : "edward3",
"businessName" : "aroma3",
"phone" : "07177222233",
"address" : "no 100 NY",
"bookletSignups" : [
{
"bookletId" : "sample-booklet",
"contactName" : "john doe"
},
{
"bookletId" : "sample-booklet1",
"contactName" : "john doe1"
}
],
"eventSignups" : [
{
"eventId" : "sample-event",
"contactName" : "john doe2"
},
{
"eventId" : "sample-event 1",
"contactName" : "john doe3"
}
],
"infoSignups" : [
{
"infoRequestId" : "sample-info ",
"contactName" : "john doe4"
},
{
"infoRequestId" : "sample-event 1",
"contactName" : "john doe5"
}
],
"webinarSignups" : [
{
"webinarId" : "sample-webinar ",
"contactName" : "john doe6"
},
{
"webinarId" : "sample-webinar 1",
"contactName" : "john doe7"
}
],
"updatedTime" : ISODate("2016-03-03T08:00:00Z")
}
The Repository :
#Repository
public interface UserRepository extends MongoRepository<User,String> {
#org.springframework.data.mongodb.repository.Query(value = "{ 'bookletSignups': { $elemMatch: { 'bookletSignups.bookletId' : ?0 } }}")
List<User> findByBookletId(String id);
}
User model class:
#Id
private String id;
private String email;
private String name;
private String businessName;
private String phone;
private String address;
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private Date createdTime;
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private Date updatedTime;
#Field("bookletSignups")
#DBRef
private List<BookletSignUp> bookletSignups;
#Field("eventSignups")
#DBRef
private List<EventSignUp> eventSignups;
#Field("infoSignups")
#DBRef
private List<InfoSignUp> infoSignups;
#Field("webinarSignups")
#DBRef
private List<WebinarSignUp> webinarSignups;
So im trying to retrieve User objects that includes a bookletSignups object with the passing bookletId value. But the result is empty. What has gone wrong here?
I would say you need to modify your query to looks like this:
#Repository
public interface UserRepository extends MongoRepository<User,String> {
#org.springframework.data.mongodb.repository.Query(value = "{ 'bookletSignups': { $elemMatch: { 'bookletId' : ?0 } }}")
List<User> findByBookletId(String id);
}
If you check MongoDB documentation for $elemMatch, link to documentation, you can see that basically in $elemMatch operator you are using field in embedded object, so you don't need to specify again name of array in which you are searching for objects.

Aggregation lookup not working in spring

I am running the following aggregation lookup query in spring but it doesn't seems to output the same result as one i run in mongo shell. I am guessing mongo shell knows that from:"testModel" collection exists but how do spring know "testModel" exists as we are only pass strings in Aggregation.lookup("testModel"). I can run all other stages fine for example match or project..
// code running in Mongo Shell
db.demoModel.aggregate([{
$lookup:
{
from: "testModel",
localField: "_id",
foreignField: "makeId",
as: "cool"
}
}]
)
// code from spring
public List<DemoModel> getSomeAutos() throws Exception {
LookupOperation lookupStage = Aggregation.lookup(
"testModel",
"_id",
"makeId",
"cool"
);
Aggregation aggregation = Aggregation.newAggregation(lookupStage);
List<DemoModel> demomode = mongoTemplate.aggregate(aggregation, "demoModel", DemoModel.class).getMappedResults();
return demomode;
}
// DemoModel.class
public class DemoModel {
#Id
private String id;
private String value;
private String label;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
}
//Mongo demoModel Collection
{
"_id" : ObjectId("5a8ee0d815dc17aa32f90f4b"),
"value" : "Mitsubishi",
"label" : "Mitsubishi"
}
{
"_id" : ObjectId("5a8ee0d815dc17aa32f90f4c"),
"value" : "BMW",
"label" : "BMW"
}
// Mongo testModel Collection
{
"_id" : ObjectId("5a8ee393b80c346266f25aba"),
"make" : "Mitsubishi",
"label" : "3000GT",
"value" : "3000GT",
"makeId" : ObjectId("5a8ee0d815dc17aa32f90f4b")
}
{
"_id" : ObjectId("5a8ee393b80c346266f25af8"),
"make" : "BMW",
"label" : "Alpina A2",
"value" : "Alpina A2",
"makeId" : ObjectId("5a8ee0d815dc17aa32f90f4c")
}
// Response i am getting
[
{
"id": "5a8ee0d815dc17aa32f90f4b",
"value": "Mitsubishi",
"label": "Mitsubishi"
},
{
"id": "5a8ee0d815dc17aa32f90f4c",
"value": "BMW",
"label": "BMW"
}
]
// expected response in this format
{
"_id" : ObjectId("5a8ee0d815dc17aa32f90f4b"),
"value" : "Mitsubishi",
"label" : "Mitsubishi",
"cool" : [
{
"_id" : ObjectId("5a8ee393b80c346266f25aba"),
"make" : "Mitsubishi",
"label" : "3000GT",
"value" : "3000GT",
"makeId" : ObjectId("5a8ee0d815dc17aa32f90f4b")
}]
}

Self-referencing JSON with Spring Data REST

Self-referencing JSON with Spring Data REST
In a Spring Data REST data model with Lombok, how can we export and import data to self-referencing JSON without creating a parallel entities or DTOs?
For example, a self-referencing JSON using JSONPath for a simple music manager might look like:
{ "id" : 1,
"albums" : [ {
"id" : 1,
"title" : "Kind Of Blue",
"artist" : "$..artists[?(#.id=1)]",
"tracks" : [ {
"id" : 1,
"title" : "So What",
"duration" : "PT9M5S",
"musicians" : [ {
"musician" : "$..artists[?(#.id=1)]",
}, {
"musician" : "$..artists[?(#.id=2)]",
} ]
}, {
"id" : 3,
"title" : "Blue in Green",
"duration" : "PT5M29S",
"musicians" : [ {
"musician" : "$..artists[?(#.id=1)]",
}, {
"musician" : "$..artists[?(#.id=2)]",
} ]
} ]
} ],
"artists" : [ {
"id" : 1,
"firstName" : "Miles",
"lastName" : "Davis",
"birthDate" : "1926-05-26"
}, {
"id" : 2,
"firstName" : "Bill",
"lastName" : "Evans",
"birthDate" : "1929-09-16"
} ]
}
How can we create the import and export functionality for this representation while retaining the Spring Data REST HATEOAS functionality? The musicians container in an export/import has an array of string JSONPath expressions and in the REST APIs the musicians is an array of Person objects (see below) -- so how can Jackson be configured to select the correct serializer and deserializer the export and import operations?
Details: music manager example
Here's a Spring Boot 1.5, Spring Data Rest, Lombok, and Jackson implementation (GitHub).
Music
#Data
#NoArgsConstructor
#Entity
public class Music {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToMany(cascade = { CascadeType.ALL }, orphanRemoval = true)
private List<Album> albums;
#OneToMany(cascade = { CascadeType.ALL }, orphanRemoval = true)
private List<Person> artists;
}
Album
#Data
#NoArgsConstructor
#Entity
public class Album {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String title;
#ManyToOne(cascade = { CascadeType.ALL })
private Person artist;
#OneToMany(cascade = { CascadeType.ALL }, orphanRemoval = true)
private List<Track> tracks;
}
Track
#Data
#NoArgsConstructor
#Entity
public class Track {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String title;
#JsonSerialize(using = MyDurationSerializer.class)
private Duration duration;
#ManyToMany(cascade = { CascadeType.ALL })
private List<Person> musicians;
public Track(String title, String duration, List<Person> musicians) {
this.title = title;
this.duration = Duration.parse(duration);
this.musicians = musicians;
}
}
Person
#Data
#NoArgsConstructor
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
#JsonSerialize(using = MyLocalDateSerializer.class)
private LocalDate birthDate;
public Person(String firstName, String lastName, String birthDate) {
this.firstName = firstName;
this.lastName = lastName;
this.birthDate = LocalDate.parse(birthDate);
}
}
MyDurationSerializer
public class MyDurationSerializer extends StdSerializer<Duration> {
private static final long serialVersionUID = 1L;
protected MyDurationSerializer() {
super(Duration.class);
}
#Override
public void serialize(Duration value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(value.toString());
}
}
MyLocalDateSerializer
public class MyLocalDateSerializer extends StdSerializer<LocalDate> {
private static final long serialVersionUID = 1L;
private DateTimeFormatter FORMATTER = ofPattern("yyyy-MM-dd");
protected MyLocalDateSerializer() {
super(LocalDate.class);
}
#Override
public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(value.format(FORMATTER));
}
}
Spring Data REST HATEOAS representation
curl http://localhost:4000/albums/1/tracks
{ "id" : 1,
"albums" : [ {
"id" : 1,
"title" : "Kind Of Blue",
"artist" : "#{
"id" : 1,
"firstName" : "Miles",
"lastName" : "Davis",
"birthDate" : "1926-05-26"
},
"tracks" : [ {
"id" : 1,
"title" : "So What",
"duration" : "PT9M5S",
"musicians" : [ {
"id" : 1,
"firstName" : "Miles",
"lastName" : "Davis",
"birthDate" : "1926-05-26"
}, {
"id" : 2,
"firstName" : "Bill",
"lastName" : "Evans",
"birthDate" : "1929-09-16"
} ]
}, {
"id" : 3,
"title" : "Blue in Green",
"duration" : "PT5M29S",
"musicians" : [ {
"id" : 1,
"firstName" : "Miles",
"lastName" : "Davis",
"birthDate" : "1926-05-26"
}, {
"id" : 2,
"firstName" : "Bill",
"lastName" : "Evans",
"birthDate" : "1929-09-16"
} ]
} ]
} ],
"artists" : [ {
"id" : 15,
"firstName" : "Miles",
"lastName" : "Davis",
"birthDate" : "1926-05-26"
}, {
"id" : 16,
"firstName" : "Bill",
"lastName" : "Evans",
"birthDate" : "1929-09-16"
} ]
}

Using Jackson for (De)serialization of Directed Graphs

I have these three classes to represent a directed Graph (for Finite State Machines):
public class Graph {
public Set<Node> nodes;
public Graph(#JsonProperty("nodes") Set<Node> nodes) {
this.nodes = nodes;
}
}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "name")
public class Node {
public String name;
public Set<Edge> edges;
public Node(final String name, final Set<Edge> edges) {
this.edges = edges;
this.name = name;
}
public Node(#JsonProperty("name") String name) {
this(name, new HashSet<Edge>());
}
}
public class Edge {
public String name;
public Node successor;
public Edge(#JsonProperty("name") String name,
#JsonProperty("successor") Node successor) {
this.name = name;
this.successor = successor;
}
I want to map them to a JSON Object with help of Jackson 2.3.3
The serialization of a simple Graph works flawlessly without infinite backwards references:
{
"nodes" : [ {
"name" : "Z1",
"edges" : [ {
"name" : "0",
"successor" : {
"name" : "Z0",
"edges" : [ {
"name" : "0",
"successor" : "Z0"
}, {
"name" : "1",
"successor" : "Z1"
} ]
}
}, {
"name" : "1",
"successor" : "Z1"
} ]
}, "Z0" ]
}
But when I want to deserialize this exact JSON, Jackson is giving me an error:
com.fasterxml.jackson.databind.JsonMappingException: Could not resolve Object Id
[Z0] (for [simple type, class graph.Node]) --
unresolved forward-reference? (through reference chain:
graph.Node["edges"]->graph.Node["edges"])
I can see from that, that node Z0 can't be referenced because it is still "under construction", so how do I tell Jackson that it has to construct the nodes first and then add the references in the edges?

Categories

Resources