Spring Data Neo4j not mapping Class fields to node properties - java

I do have a Repository
#Repository
public interface PointOfInterestRepository extends GraphRepository<Poi> {
// currently empty
}
with no custom methods defined. So I use the like of save(T... entities) which are predefined.
And I have my Poi class as follows
#NodeEntity(label = "PointOfInterest")
public class Poi {
#JsonIgnore
#GraphId
Long neo4jId;
#JsonManagedReference("node-poi")
#JsonProperty("node")
#Relationship(type = "BELONGS_TO", direction = Relationship.UNDIRECTED)
private Node node;
#JsonProperty("id")
#Property(name = "poiID")
private final String id;
#JsonProperty("uris")
#Property(name = "uris")
private final Set<URI> correspondingURIs = new HashSet<>();
/* Some more stuff I skip here*/
}
with getters for the fields.
Currently I am able to save such Pois to neo4j and retrieve them back, but when I try to work with those Nodes in the database via cypher it appears that the fields aren't mapped to neo4j properties.
I thought spring-data-neo4j would convert my class fields to neo4j graph properties. Am I wrong with that?
Note: The save calls seems to work very well. After that I can see the Nodes in the database and calling findAll() afterwards will return me all the saved Nodes (Pois) properly with all the correct values. But somehow, within the database, I cannot see any properties/fields.

The problem is the final fields. SDN would be unable to write values back to the entity when loaded from the graph because these fields are final (and SDN will use only the default no-args constructor), and as such, final fields are not supported.
Removing the final should fix this.

Related

MongoDB complex object versioning with Spring Boot

I'm trying to identify the best way to do mongodb object versioning.
Based on the mongodb document versioning pattern, storing revisions in a history collection and having current version in main collection is recommended. According to that, each revision contains the complete object instead of storing diffs.
Then I went through ways to implement data versioning in mongoDB where it recommends a method to store a single document containing all the revisions inside it having a separate history collection.
Therefore, I'm trying to implement my own object versioning implementation for the following document model due its complexity.
Invoice.java
public class Invoice {
private long invoiceId;
private String userName;
private String userId;
private LocalDateTime createdDate;
private LocalDateTime lastModifiedDate;
private List<String> operationalUnits;
private List<BodyModel> body;
private List<ReviewModel> reviews;
private BigDecimal changedPrice;
private String submitterId;
private LocalDateTime submittedTime;
private String approverId;
private LocalDateTime approvedTime;
}
BodyModel.java
public class BodyModel {
private String packageId;
private List<ItemModel> items;
private List<String> reviews;
}
ReviewModel.java
public class ReviewModel {
private String publishedTime;
private String authorName;
private String authorId;
private String text;
}
ItemModel.java
public class ItemModel {
private String itemNumber;
private String description;
private String brandId;
private String packId;
private List<String> reviews;
}
ER Diagram (Simplified)
At the moment, I'm using Javers library. But, Javers keeps the Invoice model as the main entity and other models such as BodyModel, ReviewModel, ItemModel as separated valueObjects. As a result, instead of creating a single revision document, it creates separated documents for the valueObjects. Additionally, it always constructs the current objects from the base version plus all changes which leads to huge read time. Addtionally, I identified a valueObjects issue that comes with javers. Refer this question for more info: MongoDB document version update issue with JaVers
Following are the issues, I've if I'm going to create my own implementation using spring boot.
If I'm going to put revisionId in each of the revisions (as shown in the below object) when mongoDB save(), how do I find the current revisionId to be included ?
{
_Id: <ObjectId>,
invoiceId: <InvoiceId>,
invoice: <Invoice>,
revisionId: <revisionId>
}
For this I can keep a field for revisionId in InvoiceModel which can be updated when saving to the main collection. And at the same time, it can be used to save the revision into history collection. Is it the best possible way to do it in this case ?
If I'm only going to store diffs, then how do I obtain the current version of the object ?
For this, it feels essential to fetch the current object from the main collection and then compare it with new version (I already have the new version, because that's what I'm going to store in main collection) to create the diff. Then diff can be stored in history collection and new version in main collection. Is it the best possible way to store diffs ?
In both scnearios, aspects coming from AOP can be used to intercept the save() method of the base repository to accomplish the task. But I don't mainly consider about coding implementation details, I would like to know which method or methods would be efficient in storing revisions for a data model such as given above (would like to discuss about methods I've mentioned as well) ?

Update Specific Fields with Spring Data Rest and MongoDB

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...

Change table on runtime - Spring API Rest

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.

Neo4J OGM Session.load(ID) returns null object for existing ID

I am conducting some Neo4J tests and running into the following peculiar problem. I created a small model which I'm intending to use with OGM. The model has a superclass Entity and a child class Child. They're both in package persistence.model. Entity has the required Long id; with matching getId() getter.
public abstract class Entity {
private Long id;
public Long getId() {
return id;
}
}
#NodeEntity
Child extends Entity {
String name;
public Child() {
}
}
Creating Child objects and persisting them through OGM works fine. I'm basing myself on the examples found in the documentation and using a Neo4jSessionFactory object, which initialises the SessionFactory with the package persistence.model. The resulting database contains objects with proper ID's filled in.
The problem arises when I try to fetch a Child for a given ID. I'm trying it with three methods, using two connection systems (bolt and ogm):
boltSession.run("MATCH (a:Child) WHERE id(a) = {id} RETURN a", parameters("id", childId));
ogmSession.query("MATCH (a:Child) WHERE id(a) = $id RETURN a", params);
ogmSession.load(Child.class, childId, 1);
The first two methods actually return the correct data. The last one returns a null value. The last one, using OGM, has some obvious benefits, and I'd love to be able to use it properly. Can anyone point me in the right direction?
In your test code you are doing a lookup by id of type int.
private int someIdInYourDatabase = 34617;
The internal ids in Neo4j are of type Long.
If you change the type of the id to long or Long then it will work.
private long someIdInYourDatabase = 34617;

How to map entities to existing graph?

I got a graph which is described by the following Cypher expression:
CREATE
(BMW:Brand {name: "BMW", country: "Germany"}),
(X3:Model {name: "X3", acceleration: 7.1, maxSpeed: 227.5, displacement: 1997, consumption: 6}),
(lastGen:Generation {from: 2013}),
(xDrive20i:Modification {name: "xDrive20i", maxSpeed: 210, acceleration: 8.3, consumption: 7.9}),
(X3)-[:MODEL_OF]->(BMW),
(BMW)-[:MODEL]->(X3),
(lastGen)-[:GENERATION_OF]->(X3),
(X3)-[:GENERATION]->(lastGen),
(xDrive20i)-[:MODIFICATION_OF]->(X3),
(X3)-[:MODIFICATION]->(xDrive20i),
(lastGen)-[:MODIFICATION]->(xDrive20i),
(xDrive20i)-[:MODIFICATION_OF]->(lastGen);
I described a java class matching to Brand's data structure:
#NodeEntity
#TypeAlias("Brand")
public class Brand {
#GraphId
private Long id;
#Indexed(indexType = IndexType.FULLTEXT, indexName = "brand_name")
private String name;
private String origin;
private String owner;
#RelatedTo(type = "MODEL", direction = Direction.OUTGOING)
private Set<Model> models;
//getters and setters are ommited
}
and repository:
public interface BrandRepository extends GraphRepository<Brand>{
//method's signatures are ommited
}
When I call brandRepository.count() it returns 1 as I expect. But if I call brandRepository.getOne(2249L) I get an exception:
java.lang.IllegalStateException: No primary SDN label exists .. (i.e one with starting with __TYPE__)
As I understand reading LabelBasedNodeTypeRepresentationStrategy source, a node has to have at least one label with __TYPE__ prefix.
How do I map the entity to the graph given that I may not change the graph structure?
I wouldn't mind implementing my own custom LabelBasedNodeTypeRepresentationStrategy if there is no other way. But in this case could somebody let me know why it is implemented this way (I think it is not accidentally) and how should I bind custom solution to spring-data-neo4j use it?
I use neo4j-2.0.0-M06 and spring-data-neo4j-3.0.0.M1.
SDN adds additional metadata to your graph when you store entities, that metadata is missing in your case.
You can try to add that metadata yourself by calling
neo4jTemplate.postEntityCreation(node, Brand.class);
but that for instance doesn't index your name field (manual legacy index).

Categories