For sure, a simple question but I can't find my answer.
How can i get the entities from relationships using Neo4JRepository ?
Java 8 // Spring Boot 2 // SDN 5.0.9 // OGM 3
There is my code:
#NodeEntity(label = "category")
public class Category {
private #Id #GeneratedValue(strategy = InternalIdStrategy.class) Long id;
private String name;
#Relationship(type = "MEMBER_OF", direction = Relationship.INCOMING)
private Set<Sport> sports;
}
#NodeEntity(label = "sport")
public class Sport {
private #Id #GeneratedValue(strategy = InternalIdStrategy.class) Long id;
private String name;
private String descrition;
#Relationship(type = "MEMBER_OF")
private Set<Category> categories;
}
#RelationshipEntity(type = "MEMBER_OF")
public class Membership {
#Id
#GeneratedValue
private Long id;
#StartNode
Sport sport;
#EndNode
Category category;
}
A simple findAll from my Neo4jRepository return all nodes Sport but the set categories is null
So, can you tell me what did I wrong ?
Thanks.
Edit 21/08/2018
I changed my classes like this:
#NodeEntity(label = "sport")
public class Sport {
private #Id #GeneratedValue(strategy = InternalIdStrategy.class) Long id;
private String name;
private String descrition;
private Set<Membership> memberships;
}
#NodeEntity(label = "category")
public class Category {
private #Id #GeneratedValue(strategy = InternalIdStrategy.class) Long id;
private String name;
private Set<Membership> memberships;
}
#RelationshipEntity(type = "MEMBER_OF")
public class Membership {
#Id
#GeneratedValue
private Long id;
#StartNode
Sport sport;
#EndNode
Category category;
}
Now i've got this result:
In neo4j browser, the relationship is called merberships. Why OGM didn't use the RelationshipEntity's type ?
In my Rest service, using findAll, i still get null on this set.
Nope, it's ok here :) I've just forgot to keep #Relationship on my nodes
Another Question: How do I work with this Optional given by Neo4jRepository.findById, did someone have a good article for me ?
You are declaring a direct relationship between Category and Sport called MEMBER_OF but also define a rich relationship (#RelationshipEntity) with the same name.
Neo4j-OGM does not know what to map in this case.
From what I see in the sample code it is not necessary to add the rich relationship class at all because there are no additional properties defined and this would be the only reason to create such a class.
If you have properties defined but just not listed in the example, you should change the type of your collections and set it to Membership in both classes.
Related
I'm a beginner at JPA and table relationships and I've watched a few tutorials and consider the below to be correct(?). But I'd appreciate if a more experienced eye took a look at the below if it seems right.
I have a Cart, CartItem and an Item class. A Cart can have many CartItems. A CartItem can have one Item and also is part of one Cart. An Item can be in many CartItems.
I'm trying to get the relationship between the tables right but I would appreciate some help since it doesn't seem to work. I'm getting an error Cannot invoke "java.util.List.iterator()" because the return value of "...Cart.getItems()" is null. I'm assuming it's because I've set up the relationship of my tables incorrectly?
Cart:
#Entity
#Table(name="my_cart")
public class Cart {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="cart_id")
private String cartId;
#OneToMany(targetEntity = CartItem.class, cascade = CascadeType.ALL)
#JoinColumn(name="cart_item_foreign_key")
#Column(name="cart_items")
private List<CartItem> cartItems;
#Column(name="cart_total_number_of_items")
private long totalNumberOfItems;
#Column(name="cart_total_price")
private double totalPrice;
// + getters and setters and constructor
CartItem:
#Entity
#Table(name="cart_items")
public class CartItem {
#Id
#Column(name="cartitem_id")
private String itemId;
#Column(name="cart_item_name")
private String productName;
#Column(name="cart_item_description")
private String itemDescription;
#Column(name="cart_item_quantity")
private int itemQuantity;
#Column(name="cart_item_price")
private double itemPrice;
#ManyToOne
Cart cart;
#OneToOne
private Item product;
Item:
#Entity
#Table(name="my_items")
public class Item {
#Id
#Column(name="item_id")
private String itemId;
#Column(name="item_name", nullable = false)
private String name;
#Column(name="item_description", nullable = false)
private String description;
#Column(name="item_price", nullable = false)
private Double price;
#OneToOne(mappedBy = "item")
CartItem cartItem;
Could someone with more experience please point me in the right direction with the above table relationships? Thank you
In the Cart class, when you use #JoinColumn, you can omit #Column, in #JoinColumn there is a name attribute that is the column name.
I am developing a SpringBoot API that has 2 domain JPA entities. One of these entities is 'Player' which stores information about the player of the game and includes their 'id' which is autogenerated and autoincrementing.
The 'Game' entity stores information about previous games that have been played, and has an autogenerated and autoincremeneting id also 'gameId'.
However I also want to store all of the 'Players' 'id' who participated in that game within the 'Game' entity.
Therefore, when a Game object is created, it will require the 'id' of 2 seperate Player entities.
Would a OneToMany relationship work in this case? Where the Player is annotated with #OneToMany above the 'id' field, and the Game is annotated with #ManyToOne above the 'playerId' field?
Kind of like this:
public class Game {
#Id
#GeneratedValue
private Long gameId;
#ManyToOne()
private Long playerId;
//Should this be multiple different fields like below?
#ManyToOne()
private Long player1Id;
#ManyToOne()
private Long player2Id;
#ManyToOne()
private Long player3Id;
#ManyToOne()
private Long player4Id;
}
public class Player {
#Id
#GeneratedValue
#OneToMany()
private Long id;
private String firstName;
private String lastName;
private String email;
}
Does this answer your question?
This make you able to get id of player who joined.
#Entity
public class Game {
#Id
#GeneratedValue
private Long gameId;
#OneToMany(cascade = CascadeType.ALL)
private List<Player> players = new ArrayList<>();
public Game(Long gameId, List<Player> players){
...
}
}
#Entity
public class Player {
#Id
#GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
}
But I also recommend you reading a tutorial on this topic.
This could be easy way to solve, but unidirectional one-to-many mapping might not be a best practice. It should be used carefully.
https://thorben-janssen.com/best-practices-many-one-one-many-associations-mappings/
For that reason I recommend you to use many-to-one relation instead of one-to-many like bellow.
#Entity
public class Game {
#Id
#GeneratedValue
private Long gameId;
}
#Entity
public class Player {
#Id
#GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
#ManyToOne
private Game joinedGame;
}
Or if player can be joined in multiple games, consider middle-entity to loosen many-to-many relation, like Game - JoinPlayers - Player.
I am using hibernate to represent a database with the three major Entities User, Project and Comment. User and Project inherit from Base class. The Project also holds an unlimited amount of comments.
In the POJO i tried to represent the collection of comments associated by a project by with a List<Comment>.
My major problem is, when i i go and take a project which holds a number of comment references within the list java will throw an IllegalArgumentException saying, that it cant access the id field of comment, as it only gets an ArrayList.
Caused by: java.lang.IllegalArgumentException: Can not set int field com.project.objects.Comment.id to java.util.ArrayList
My classes are as followed - without Constructor/Setter/Getter as these are plain simple:
#MappedSuperclass
public abstract class Base {
#Id
#Column
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column
private String name;
#Column
private String longDesc;
#Column
private String briefDesc;
#Column
#ElementCollection(targetClass=String.class)
private List<String> goals;
#Column
private String picture;
#Column
private int cType;
#Entity(name = "Project")
#Table(name = "project")
public class Project extends Base {
#Column
private String start;
#Column
private String end;
#Column
private String manager;
#ElementCollection(targetClass=Comment.class)
#ManyToOne(targetEntity = Comment.class, fetch = FetchType.EAGER)
#JoinColumn(name = "comment_id")
private List<Comment> comments;
#Entity(name = "Comment")
#Table(name = "comment")
public class Comment {
#Id
#Column(name="comment_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column
private String comment;
#Column
private int rating;
#Column
private int pcuser;
#Column
private int cType;
Your 1:N association is wrong, as it is actually a N:1 right now. The correct would be:
Entity(name = "Project")
#Table(name = "project")
public class Project extends Base {
#Column
private String start;
#Column
private String end;
#Column
private String manager;
#OneToMany(mappedBy = "project", fetch = FetchType.EAGER)
private List<Comment> comments;
And in your Comment class:
#Entity(name = "Comment")
#Table(name = "comment")
public class Comment {
#Id
#Column(name="comment_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column
private String comment;
#Column
private int rating;
#Column
private int pcuser;
#Column
private int cType;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "id_project", nullable = false)
private Project project;
// THIS is the required and obrigatory mapping that you forgot.
// It's the foreing key itself
Disclaimer
I've never actually used Hibernate with inheritance before (usually, it's desnecessarily complex and also inefficient for a relational database) but check `https://www.baeldung.com/hibernate-inheritance` and `https://marcin-chwedczuk.github.io/mapping-inheritance-in-hibernate` for more information.
You're using a #ManyToOne annotation for comments but it should be #OneToMany.
In order to use #OneToMany you would have to have a column called something like project_id in the comment table, which you would reference from the #OneToMany field. Do you have that?
If not, how are you linking comments to projects in your database?
By the way, it's really easy to create poorly-performing systems with Hibernate, because it tends to obscure the cost of hitting the database. You've said that there can be any number of comments associated with a project. Do you really want to load them all every time the code loads a project? Let's say you just want a list of projects, for example to populate a selection list. Simply loading that list will also load every comment in the system, even though you don't actually need them.
Comment is an entity and should not be used with the #ElementCollection inside the Project entity.
Your relationship is a project to many comments. #OneToMany
Write some usual tests for my MVC webapp and stopped at findById() testing.
My model classes:
#Entity
public class Product {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
private double purchasePrice;
private double retailPrice;
private double quantity;
#ManyToOne
#JoinColumn (name = "supplier_id")
private Supplier supplier;
#ManyToOne
#JoinColumn (name = "category_id")
private Category category;
#Entity
public class Category {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany
#Cascade(org.hibernate.annotations.CascadeType.ALL)
private List<Product> products;
#Entity
public class Supplier {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#LazyCollection(LazyCollectionOption.FALSE)
#Cascade(org.hibernate.annotations.CascadeType.ALL)
#OneToOne
private Contact contact;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany
private List<Product> products;
And my test code:
private Product productTest;
private Category categoryTest;
private Supplier supplierTest;
#Before
public void setUp() throws Exception {
categoryTest = new Category("Test category", "", null);
supplierTest = new Supplier("Test supplier", null, null);
productTest = new Product("Test product","", 10, 20, 5, supplierTest, categoryTest);
categoryService.save(categoryTest);
supplierService.save(supplierTest);
productService.save(productTest);
}
#Test
public void findById() throws Exception {
Product retrieved = productService.findById(productTest.getId());
assertEquals(productTest, retrieved);
}
Well, assertion failed, because of difference product.category.products and product.supplier.products properties, as you can see on pic:
One product have it as null, another as {PersistentBag}.
Sure I can easy hack it by writing custom equals method (which will ignore these properties), but sure it's not the best way.
So, why these fields are different?
I'm sure solution in properly annotation of entities fields.
Two pointers :
you use #LazyCollection(LazyCollectionOption.FALSE) in your relationship fields, so fields with that annotation are dynamically loaded by your ORM when you retrieve your entity while entites created in your fixture of your unit test are created outside from your ORM and you don't value these fields.
Even if you remove #LazyCollection(LazyCollectionOption.FALSE), you may have other differences if you want to do assertEquals() with a retrieved entity and a entity created by the hand. For example, with Hibernate, your lazy List will not be null but instance of PersistentList.
So, you should perform some works to perform assertions.
You may check properties individually or you may use Reflection to assert fields and ignore comparison for null fields in the expected object.
check http://www.unitils.org/tutorial-reflectionassert.html, it may help you.
#NodeEntity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Employee {
#GraphId
private Long graphId;
#Indexed
private Long id;
private String name;
private String password;
#RelatedTo(type = "REPORT_TO", direction = Direction.OUTGOING)
private Department department;
}
I know spring-data-neo4j can use #Fetch to get Department data when get Employee entity, but not every time I need Department when get Employee, so how can I fetching department manully? The #Fetch annotation is not flexible
You can use Neo4jTemplate#fetch (T) to do manually what #Fetch does automagically.
See api doc here.