Neo4j save #Relationship data - java

Here's MVCE: https://github.com/neo4j-examples/movies-java-spring-data-neo4j
If you change one test to:
#Test
public void testFindByTitle() {
String title = "The Matrix";
Movie result = movieRepository.findByTitle(title);
Person p = personRepository.findByName("Keanu Reeves");
assertNotNull(result);
assertEquals(1999, result.getReleased());
}
You can see in debug mode that object p does not have any movies.
Person entity is:
#NodeEntity
public class Person {
#Id
#GeneratedValue
private Long id;
private String name;
private int born;
#Relationship(type = "ACTED_IN")
private List<Movie> movies = new ArrayList<>();
public Person() {
}
public Person(String name, int born) {
this.name = name;
this.born = born;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public int getBorn() {
return born;
}
public List<Movie> getMovies() {
return movies;
}
}
This is offical example from neo4j. How can i store entity Person with movies in database and also have Movie entity with roles ?
Edit: What i can do is add in Person entity method:
public void addMovie(Movie movie) {
if (this.movies == null) {
this.movies = new ArrayList<>();
}
this.movies.add(movie);
}
And in the test add:
p.addMovie(matrix);
personRepository.save(p);
But i don't like this - cause i setting it manually from two sites.

You do not need to set the references manually from two sides. Expand your code snippet slightly by a single line movie.setPerson(this); and you are done:
public void addMovie(#NotNull Movie movie) {
if (this.movies == null)
this.movies = new ArrayList<>();
this.movies.add(movie);
movie.setPerson(this);
}

Related

JUnit Testing One-To-Many HashSet

I am trying to learn testing in Spring Boot. I was trying to test my models but then I faced with NullPointer. Let's see the code.
Genre.java
public class Genre {
#Column(nullable = false)
#OneToMany(mappedBy = "genre", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Movie> movieSet;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Set<Movie> getMovie() {
return movieSet;
}
public void setMovie(Movie movie) {
if (movieSet.size() == 0) {
movieSet = new HashSet<>();
}
movieSet.add(movie);
}}
GenreTest.java
public class GenreTest {
#Test
public void IdGetterSetter() {
Genre genre = new Genre();
genre.setId(1);
assertThat(genre.getId()).isEqualTo(1);
}
#Test
public void MovieGetterSetter(){
Genre genre = new Genre();
Movie movie = new Movie();
genre.setMovie(movie);
assertThat(genre.getMovie()).isEqualTo(movie);
}}
genre.setMovie(movie) is getting NullPointer error. What should I need to do? By the way, I am testing my getter and setter functions in the same function. Do I need to seperate them or is it okay to use it like that? Give me some opinion please.
This:
public void setMovie(Movie movie) {
if (movieSet.size() == 0) {
movieSet = new HashSet<>();
}
movieSet.add(movie);
}}
movieSet is unitialized.
You either want to:
private Set<Movie> movieSet = new HashSet<>();
Or:
public void setMovie(Movie movie) {
if (movieSet == null) {
movieSet = new HashSet<>();
}
movieSet.add(movie);
}}

JPA ManyToMany: Adding existing object to another object

I am using JPA and wanted to figure out how the many-to-many relationship. Let's say I have a "Store" and a "Customer". These have a many to many relationship.
So my understanding is, a Store can have many Customers, and Customer can be associated with many Stores. So what I wanted to do is create two Stores and several customers. Then I wanted to have the same customer be a part of Store 1 and Store 2. However, when I saved Store 1 with the customer, and then took that same customer and associated it with Store 2 (let's say the customers shops at both stores), I get this error message: detached entity passed to persist.
Not sure how to resolve this. Any help and comments are appreciated. Thanks in advance!
#Entity
public class Store {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
private List<Item> items = new ArrayList<>();
#ManyToMany(cascade=CascadeType.ALL)
private List<Customer> customers = new ArrayList<>();
public List<Customer> getCustomers() {
return customers;
}
public void setCustomers(List<Customer> customers) {
this.customers = customers;
}
public Store() {
}
public Store(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Item> getItems() {
return items;
}
public void setItems(List<Item> items) {
this.items = items;
}
public Store addItem(Item item) {
items.add(item);
return this;
}
}
#Entity
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
#Entity
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private BigDecimal price;
public Item() { }
public Item(String name, BigDecimal price) {
this.name = name;
this.price = price;
}
public Item() {
}
public Item(String name, BigDecimal price) {
this.name = name;
this.price = price;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
}
This is the driver code using Spring Boot:
Store safeway = new Store("Safeway4");
safeway.addItem(new Item("Fuji Apple", new BigDecimal(1)));
safeway.addItem(new Item("Black Grapes", new BigDecimal(2)));
safeway.addItem(new Item("Cheese Pizza", new BigDecimal(10)));
Store bestBuy = new Store("Best Buy4");
bestBuy.addItem(new Item("55 inch TV", new BigDecimal(550)));
bestBuy.addItem(new Item("Bluray Player", new BigDecimal(85)));
bestBuy.addItem(new Item("Nikon SLR", new BigDecimal(1500)));
Customer elf = new Customer();
elf.setName("Elf");
Customer neo = new Customer();
neo.setName("Neo");
safeway.getCustomers().add(elf);
safeway.getCustomers().add(neo);
Customer yoda = new Customer();
yoda.setName("Yoda");
Customer crazy = new Customer();
crazy.setName("Crazy");
bestBuy.getCustomers().add(yoda);
bestBuy.getCustomers().add(crazy);
log.debug("adding neo to best buy");
bestBuy.getCustomers().add(neo); // Adding Neo to both stores!
log.debug("saving safeway 1");
storeRepository.save(safeway);
log.debug("saving safeway 1 done");
log.debug("saving bestBuy 1");
storeRepository.save(bestBuy); // error happens here <-----------
log.debug("saving bestBuy 1 done");
If you remove the CascadeType.ALL, you'll avoid this problem.
Logically, a Customer can exist without ever being associated to any Store. That means the lifecycle of a Customer should be independent of that of a Store entity, thus cascading any operation for a Customer from Store is wrong.
You save your Customer instances separately, associate the saved instances with the appropriate Store and then save it separately.

New Entity being added every time i Perform a ancestor load query?

So every time i perform the query api below instead of listing all the expenses created by the user it creates a new one?
#ApiMethod(
name = "getMyExpenses",
path = "getMyExpenses",
httpMethod = ApiMethod.HttpMethod.POST
)
public List<Expense> getMyExpenses(final User user) throws UnauthorizedException {
if (user == null) {
throw new UnauthorizedException("You need to authorisation");
}
String userId = user.getUserId();
Key<Person> personKey = Key.create(Person.class, userId);
return ofy().load().type(Expense.class).ancestor(personKey).list();
}
Im not sure why this is happening. Any help is appreciated.
Also the problem may be with how i tried to defined the parent relationship? here is my Expense model class.
#Entity
public class Expense {
#Id Long ExpenseId;
String name;
#Parent
Ref<Person> ExpenseCreator;
int amount;
private Expense() {}
public Expense(Long expenseId, String name, int amount) {
ExpenseId = expenseId;
this.name = name;
this.amount = amount;
}
public Long getExpenseId() {
return ExpenseId;
}
public String getName() {
return name;
}
public int getAmount() {
return amount;
}
}

Neo4j OGM RelationshipEntity when StartNode and EndNode are objects of same type

I am following the Neo4j OGM guide at - http://neo4j.com/docs/ogm/java/stable/
For Relationship entities, we need to have a start node and an end node. I've modified the example a little bit (to make it simpler like this) -
#NodeEntity
public class Student extends Entity {
private String name;
#Relationship(type= "ENROLLED")
private Enrollment enrollment = new Enrollment();
public String getName() {
return name;
}
public Enrollment getEnrollment() {
return enrollment;
}
public void setEnrollment(Enrollment enrollment) {
this.enrollment = enrollment;
}
public Student() {
}
public Student(String name) {
this.name = name;
}
}
#NodeEntity
public class Course extends Entity {
private String name;
public String getName() {
return name;
}
#Relationship(type= "ENROLLED", direction= Relationship.INCOMING)
private Enrollment enrollment = new Enrollment();
public Enrollment getEnrollment() {
return enrollment;
}
public void setEnrollment(Enrollment enrollment) {
this.enrollment = enrollment;
}
public Course() {
}
public Course(String name) {
this.name = name;
}
}
#RelationshipEntity(type = "ENROLLED")
public class Enrollment extends Entity {
#StartNode
private Student student;
#EndNode
private Course course;
private Date enrolledDate;
public Student getStudent() {
return student;
}
public Course getCourse() {
return course;
}
public Date getEnrolledDate() {
return enrolledDate;
}
public Enrollment() {
}
public Enrollment(Student student, Course course, Date enrolledDate) {
this.student = student;
this.course = course;
this.enrolledDate = enrolledDate;
}
}
Now when I try to save this in Neo4j, it works fine. However in my scenario, the type of StartNode and EndNode objects are the same -
#NodeEntity
public class MyObject extends Entity {
private String name;
#Relationship(type="CONNECTION")
private MyConnection startConnection = new MyConnection();
#Relationship(type="CONNECTION", direction= Relationship.INCOMING)
private MyConnection endConnection = new MyConnection();
public String getName() {
return name;
}
public MyConnection getStartConnection() {
return startConnection;
}
public void setStartConnection(MyConnection myConnection) {
this.startConnection = myConnection;
}
public MyConnection getEndConnection() {
return endConnection;
}
public void setEndConnection(MyConnection endConnection) {
this.endConnection = endConnection;
}
public MyObject() {
super();
}
public MyObject(String name) {
super();
this.name = name;
}
}
#RelationshipEntity(type="CONNECTION")
public class MyConnection extends Entity {
#StartNode
private MyObject start;
#EndNode
private MyObject end;
private String name;
public String getName() {
return name;
}
public MyConnection() {
super();
}
public MyConnection(MyObject start, MyObject end, String name) {
super();
this.start = start;
this.end = end;
this.name = name;
}
}
When I try to save these using -
public class Main {
public static void main(String[] args) {
Session session = Neo4jSessionFactory.getInstance().getNeo4jSession();
Student s = new Student("manoj");
Course c = new Course("physics");
Enrollment e = new Enrollment(s, c, new Date());
s.setEnrollment(e);
c.setEnrollment(e);
MyObject startObj = new MyObject("Start Object");
MyObject endObj = new MyObject("End Object");
MyConnection conn = new MyConnection(startObj, endObj, "Connection");
startObj.setStartConnection(conn);
endObj.setEndConnection(conn);
try(Transaction tx = session.beginTransaction()) {
session.save(s);
session.save(c);
session.save(e);
session.save(startObj);
session.save(endObj);
session.save(conn);
tx.commit();
}
}
}
The student, course and enrollments objects get saved, but the two MyObject and the MyConnection objects don't get saved and I get the following exception -
Exception in thread "main" java.lang.NullPointerException
at org.neo4j.ogm.metadata.MetaData.classInfo(MetaData.java:76)
at org.neo4j.ogm.mapper.EntityGraphMapper.mapRelationshipEntity(EntityGraphMapper.java:389)
at org.neo4j.ogm.mapper.EntityGraphMapper.link(EntityGraphMapper.java:319)
at org.neo4j.ogm.mapper.EntityGraphMapper.mapEntityReferences(EntityGraphMapper.java:262)
at org.neo4j.ogm.mapper.EntityGraphMapper.mapEntity(EntityGraphMapper.java:154)
at org.neo4j.ogm.mapper.EntityGraphMapper.mapRelatedEntity(EntityGraphMapper.java:528)
at org.neo4j.ogm.mapper.EntityGraphMapper.mapRelationshipEntity(EntityGraphMapper.java:420)
at org.neo4j.ogm.mapper.EntityGraphMapper.link(EntityGraphMapper.java:319)
at org.neo4j.ogm.mapper.EntityGraphMapper.mapEntityReferences(EntityGraphMapper.java:262)
at org.neo4j.ogm.mapper.EntityGraphMapper.mapEntity(EntityGraphMapper.java:154)
at org.neo4j.ogm.mapper.EntityGraphMapper.map(EntityGraphMapper.java:87)
at org.neo4j.ogm.session.delegates.SaveDelegate.save(SaveDelegate.java:65)
at org.neo4j.ogm.session.delegates.SaveDelegate.save(SaveDelegate.java:41)
at org.neo4j.ogm.session.Neo4jSession.save(Neo4jSession.java:370)
at neo4j.ogm.ex.Main.main(Main.java:37)
Can you please help me to solve this -
1) Is it necessary that the StartNode and EndNode objects be of different types?
2) Is there some problem with my code or is it a shortcoming of the Neo4j OGM?
Thanks in advance,
Manoj.
Update after trying Luanne's suggestion -
Thanks Luanne. I tried your suggestion, though I had to specify the URL differently. I used -
http://m2.neo4j.org/content/repositories/snapshots
because by default it used https and I was getting some security exception and this dependency was not getting downloaded.
Anyways, with the 1.1.1-SNAPSHOT version, I still get the below error -
Exception in thread "main" java.lang.NullPointerException
at org.neo4j.ogm.metadata.MetaData.classInfo(MetaData.java:80)
at org.neo4j.ogm.mapper.EntityGraphMapper.haveRelationEndsChanged(EntityGraphMapper.java:391)
at org.neo4j.ogm.mapper.EntityGraphMapper.getRelationshipBuilder(EntityGraphMapper.java:362)
at org.neo4j.ogm.mapper.EntityGraphMapper.link(EntityGraphMapper.java:325)
at org.neo4j.ogm.mapper.EntityGraphMapper.mapEntityReferences(EntityGraphMapper.java:276)
at org.neo4j.ogm.mapper.EntityGraphMapper.mapEntity(EntityGraphMapper.java:157)
at org.neo4j.ogm.mapper.EntityGraphMapper.mapRelatedEntity(EntityGraphMapper.java:571)
at org.neo4j.ogm.mapper.EntityGraphMapper.mapRelationshipEntity(EntityGraphMapper.java:473)
at org.neo4j.ogm.mapper.EntityGraphMapper.link(EntityGraphMapper.java:329)
at org.neo4j.ogm.mapper.EntityGraphMapper.mapEntityReferences(EntityGraphMapper.java:276)
at org.neo4j.ogm.mapper.EntityGraphMapper.mapEntity(EntityGraphMapper.java:157)
at org.neo4j.ogm.mapper.EntityGraphMapper.map(EntityGraphMapper.java:90)
at org.neo4j.ogm.session.delegates.SaveDelegate.save(SaveDelegate.java:67)
at org.neo4j.ogm.session.delegates.SaveDelegate.save(SaveDelegate.java:43)
at org.neo4j.ogm.session.Neo4jSession.save(Neo4jSession.java:376)
at neo4j.ogm.ex.Main.main(Main.java:37)
Is your intent to model MyObject to contain a single outgoing relationship type CONNECTION and a single incoming relationship type CONNECTION where each relationship has a property name?
So if we're looking at EndObject, then startConnection is the relationship with name conn2 and endConnection is the relationship with name conn1?
If so, we might have a problem here. https://jira.spring.io/browse/DATAGRAPH-728
Update:
This wasn't a bug after all. The problem is the initialization you have in MyObject:
#Relationship(type="CONNECTION")
private MyConnection startConnection = new MyConnection();
#Relationship(type="CONNECTION", direction= Relationship.INCOMING)
private MyConnection endConnection = new MyConnection();
You've initialized both startConnection and endConnection to an invalid relationship entity i.e. one with no start and no end node.
In your test, you set the startConnection on startObj but not the end. In effect, the endConnection is represented by the invalid relationship entity.
Remove the initialization and it should work as you expect.
Thanks Luanne for your answer / suggestion. I came across your excellent article The essence of Spring Data Neo4j 4 and realized that my need is very similar to the Ingredient-Pairing example (mentioned in your article). For me, there can be a connection (MyConnection) between two objects (MyObject). So my modified code looks like this -
package neo4j.ogm.ex.domain;
import java.util.HashSet;
import java.util.Set;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.Relationship;
#NodeEntity
public class MyObject extends Entity {
private String name;
#Relationship(type="CONNECTION", direction= Relationship.UNDIRECTED)
private Set<MyConnection> connections = new HashSet<>();
public String getName() {
return name;
}
public Set<MyConnection> getConnections() {
return connections;
}
public void addConnection(MyConnection myConnection) {
myConnection.getStart().getConnections().add(myConnection);
myConnection.getEnd().getConnections().add(myConnection);
}
public MyObject() {
super();
}
public MyObject(String name) {
super();
this.name = name;
}
}
package neo4j.ogm.ex.domain;
import org.neo4j.ogm.annotation.EndNode;
import org.neo4j.ogm.annotation.RelationshipEntity;
import org.neo4j.ogm.annotation.StartNode;
#RelationshipEntity(type="CONNECTION")
public class MyConnection extends Entity {
#StartNode
private MyObject start;
#EndNode
private MyObject end;
private String name;
public String getName() {
return name;
}
public MyObject getStart() {
return start;
}
public MyObject getEnd() {
return end;
}
public MyConnection() {
super();
}
public MyConnection(MyObject start, MyObject end, String name) {
super();
this.start = start;
this.end = end;
this.name = name;
}
}
And it works perfectly. I'll also try out your other suggestion of removing the invalid initialization.
Thanks again,
Manoj.

Strange behaviour of MongoDB when querying by foreign key

I am writing some test code to learn spring-data with MongoDB. I can successfully create two Documents: Person and ADocument, where ADocument contains a reference to Person.
#Document
public class Person {
#Id
private ObjectId id;
#Indexed
private String name;
public ObjectId getId() {
return id;
}
public void setId(ObjectId id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
...
#Document
public class ADocument {
#Id
private ObjectId id;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
private String title;
private String text;
#DBRef
private Person docperson;
public Person getDocperson() {
return docperson;
}
public void setDocperson(Person docperson) {
this.docperson = docperson;
}
public ObjectId getId() {
return id;
}
public void setId(ObjectId id) {
this.id = id;
}
}
The problem arises when I try to get all the 'adocuments' related to a person by using the person's ID (once the person's name is provided):
public List<ADocument> loadDocumentsByPersonName(String pname) {
Query qPerson = new Query().addCriteria(Criteria.where("name").is(pname));
qPerson.fields().include("_id");
Person pers = mongoTemplate.findOne(qPerson, Person.class);
ObjectId persId = pers.getId();
Query qDoc = new Query().addCriteria(Criteria.where("person.$id").is(persId));
System.out.println(qDoc.toString());
List<ADocument> list2 = mongoTemplate.find(qDoc, ADocument.class);
return list2;
}
Everyting works fine except that list2 is always empty (while it shouldn't).
System.out.println(qDoc.toString()) gives something like:
Query: { "person.$id" : { "$oid" : "536a0d50e4b0d0c10297f2ab"}}, Fields: null, Sort: null
If I try to issue the query above on the Mongo shell I get the following:
db.adocument.find({ "person.$id" : { "$oid" : "536a0805e4b0af174d0b5871"}})
error: {
"$err" : "Can't canonicalize query: BadValue unknown operator: $oid",
"code" : 17287
}
While if I type
db.adocument.find({ "person.$id" : ObjectId("536a0805e4b0af174d0b5871")})
I actually get a result!
I am using MongoDB 2.6.0 and Spring Data 1.4.2.
I really can't figure out what's going on... Any help is extremely appreciated!
I got it!
For some reason, I had to explicit the collection name in the Query:
List list2 = mongoTemplate.find(qDoc, ADocument.class, COLLECTION_NAME);
where COLLECTION_NAME="adocument".
As for the shell behaviour, it seems that Query.toString() does never return a correct syntax to be cut and paste for shell execution.

Categories

Resources