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
Related
I'm trying to map those three entities to each other without adding any additional fields to any of them. They should only contain the fields that already exist. I'm also trying to only get columns in the tables that represent the currently existing entity fields- and no additional columns.
#Entity
public class Order {
#Id
private Integer orderId;
private String title;
private Customer customer;
private List<Comment> comments;
}
#Entity
public class Customer {
#Id
private Integer customerId;
private String name;
}
#Entity
public class Comment {
#Id
private Integer commentId;
private Integer orderId;
private String details;
}
My understanding is that I can't simply use #OneToOne, #OneToMany and #ManyToOne mappings, because neither Customer nor Comment has a reference to Order . I'm trying to somehow reference the ids of Customer and Comment directly from Order.
I've tried using #MapsId and #JoinColumn but either I don't know how to properly use them, or they don't do what I think they do.
Is this task at all possible? If so, how to map them to each other?
For the reference to Comment you must use #JoinColum
The Customer reference assumes that there is a customer_id on the order table.
#Entity
public class Order {
#Id
private Integer orderId;
private String title;
#ManyToOne
#JoinColumn(name = "customer_id")
private Customer customer;
#OneToMany
#JoinColumn(name = "comment_id")
private List<Comment> comments;
}
I'm trying to create tables using hibernate but all the entities seems to be mapped as tables except for one. Even the manytomany containing this table's id is created but not the table itself.
Service.java
#Entity
#Table(name="service")
public class Service {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="title")
private String title;
#Column(name="description")
private String desc;
#Column(name="price")
private int price;
#Column(name="limit")
private int limit;
#Column(name="status")
private boolean available;
#Column(name="tags")
private String tags;
#ManyToOne
#JoinColumn(name="provider_id")
private User provider;
#ManyToOne(cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH})
#JoinColumn(name="category_id")
private Category category;
#ManyToMany(cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH})
#JoinTable(
name="service_clients",
joinColumns = #JoinColumn(name="service_id"),
inverseJoinColumns = #JoinColumn(name="client_id")
)
private List<User> clients;
//GETTERS,SETTERS
}
I can update the post if you need more information.
PS: I don't get any error in my logs.
"limit" is reserved keyword for some databases. You can change the name of the property or you can create the table manualy.
On top of Buraks awnser, you could try encoding the column name:
#Column(name="\"limit\"")
Unfortunatly Hibernate isn't doing this by itself, for many vendors and keywords (like "order", "user") it's needed.
I am trying to solve JPA problem. I have 2 main entities - CameraItem and Chain (which represents ordered list of cameras)
Now there have to be 2 #ManyToMany relationships between CameraItem and Chain.
Each CameraItem has at least one parent Chain. As one CameraItem can belong to different Chains, and each Chain can have multiple CameraItems this is the first simple direct #ManyToMany relationship.
Chains can be connected with each other via CameraItem. In other words, CameraItem is holding the connection between Chains. But this is not simple #ManyToMany relationship, because we also need information about direction of the Chains connection. So it is #ManyToMany relationship with new Entity as Baeldung describes here https://www.baeldung.com/jpa-many-to-many. Entity ConnectionPoint is holding the information about the direction as a String.
I paste the classes here:
CHAIN CLASS:
#Entity
#Table(name = "chain")
public class Chain {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#NotBlank(message = "Chain name is mandatory")
private String name;
#Column(name = "PLANT_NAME")
private String plantName;
private String description;
private String status;
private Boolean hasPlant;
#CreationTimestamp
#Column(name = "creation_time")
private LocalDateTime creationTime;
#OneToMany(mappedBy = "camera_item")
private List<CameraItem> cameraItems = new ArrayList<>();
#OneToMany(mappedBy = "chain")
Set<ConnectionPoint> connectionPoints;
CAMERA ITEM CLASS:
#Entity
#Table(name = "camera_item")
public class CameraItem {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn
private Camera camera;
private String name;
private Integer positionInChain;
#ManyToMany(mappedBy = "cameraItems", fetch = FetchType.LAZY)
private List<Chain> parentChainIds;
#OneToMany(mappedBy = "cameraItem")
Set<ConnectionPoint> connectionPoints;
CONNECTION POINT CLASS:
#Entity
#Table(name = "connection_point")
public class ConnectionPoint {
#Id
private Long id;
#Column(name = "direction")
private String direction;
#ManyToOne
#JoinColumn(name = "chain")
private Chain chain;
#ManyToOne
#JoinColumn(name = "camera_item")
private CameraItem cameraItem;
When I run the application I get this error:
org.hibernate.AnnotationException: mappedBy reference an unknown
target entity property:
no.trafsys.videodashboard.model.entity.CameraItem.camera_item in
no.trafsys.videodashboard.model.entity.Chain.cameraItems
Does somebody know where the problem can be?
I use #OneToMany annotations in Chain and CameraItem entities and #ManyToOne in ConnectionPoint like Baeldung in his tutorial.
Thank you in advance for any help
I don't think there is issue in ConnectionPoint. I think the issue is that:
In Chain class,
#OneToMany(mappedBy = "camera_item") // One-to-Many defined here
private List<CameraItem> cameraItems = new ArrayList<>();
while in CameraItem class, corresponding property is defined as follow:
#ManyToMany(mappedBy = "cameraItems", fetch = FetchType.LAZY) // Many-To-Many
private List<Chain> parentChainIds;
Try changing the mapping type to #ManyToMany in Chain class as well. It might work.
PS: I am not entirely sure of this, but this feels like the issue[incorrect mapping type]. Wanted to add this as a comment, but due to space issues, adding it as an answer.
#Entity
#Table(name = "chain")
public class Chain {
//..
#OneToMany(mappedBy = "camera_item")
private List<CameraItem> cameraItems = new ArrayList<>();
//..
}
mappedBy parameter can only be in one side of the relation. I suspect camera_item is database table column name. So your cameraItems needs #JoinTable(name = "camera_item"... annotation
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.
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.