How properly annotate hibernate entities - java

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.

Related

Mapping entities in JPA/Hibernate without adding additional fields (just by using ids)

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;
}

JPA StackOverflow -- Many to Many and one to one mapping

I am trying to save a JPA entity which has ManytoMany Relationship (Consumer and Product table) and OnetoOne relation with ConsumerDetailstable.Below are my entities
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class)
#Entity
public class Consumer {
#Id
#GeneratedValue
private Long id;
private String firstName;
private String lastName;
#JsonManagedReference
#OnToMany(mappedBy = "consumer")
private Set<ConsumerProduct> consumerProducts;
#OneToOne
private CustomerDetails consumerDetails;
}
#Entity
public class Product {
#Id
#GeneratedValue
private Long productId;
private String productCode;
#OneToMany(mappedBy = "product")
private Set<ConsumerProduct> consumerProducts;
}
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class)
#Entity(the join table)
public class ConsumerProduct {
#EmbeddedId
ConsumerProductKey id;
#JsonBackReference
#ManyToOne
#MapsId("id")
#JoinColumn(name = "id")
private Consumer consumer;
#ManyToOne
#MapsId("productId")
#JoinColumn(name = "product_id")
private Product product;
}
#Embeddable (forgein keys combined as embeded id)
public class ConsumerProductKey implements Serializable {
#Column(name="id")
private Long id;
#Column(name = "product_id")
private Long productId;
}
#Enitity (one to one relation table)
public class CustomerDetails {
#Id
#GeneratedValue
private Long consumerDtlId;
#OneToOne
private Consumer consumer;
private String city;
private String state;
private String country;
}
To save the entity am have just extended JPARepository and called save method
public class ConsumerRepository<Consumer> Implements JPARepository<Consumer, Long> {
#Override
public Consumer S save(Consumer entity) {
return save(entity);
};
}
I get java.lang.StackOverFlowError at save method.
Anything wrong with my Mappings ?
Question: Since this will be save operation and since Consumer Id is yet to be generated how do I assign to below Entities
ConsumerProduct.ConsumerProductKey (how do i assign Id of consumer table once it is inserted to join table ? will JPA take care of it)
CustomerDetails (how do i assign Id of consumer table once it is inserted to join table ? will JPA take care of it)
EDIT: I have updated the entity with JsonManagedReference and JsonBackedReference but still i have am facing stackoverflow error
It is due to Consumer trying to access ConsumerProduct and ConsumerProduct trying to access consumer entity and end up with StackOverflow error.
You should use #JsonManagedReference and #JsonBackReference annotation in consumer and ConsumerProduct respectivly.

Spring boot JPA decide cascading order of entities

I have 3 entity class defined as below:
#Entity
public class Rimorchio {
#Id
#GeneratedValue
private long idrimorchio;
#Column(unique = true)
private String targa;
// .... Getters and Setters
}
#Entity
public class Atk {
#Id
#GeneratedValue
private long idatk;
#Column(unique = true)
private String codice;
#Column(unique = true)
private String targa;
#OneToOne
private Rimorchio suggerito;
// .... Getters and Setters
}
#Entity
public class Trasportatore {
#Id
#GeneratedValue
private long idtrasportatore;
#Column(unique = true)
private String nometrasportatore;
#OneToMany(cascade = {CascadeType.PERSIST})
private List<Rimorchio> listarimorchi;
#OneToMany(cascade = {CascadeType.PERSIST})
private List<Atk> listaatk;
#OneToMany(cascade = {CascadeType.PERSIST})
private List<Autista> listaautisti;
// .... Getters and Setters
}
When I try to save in the database the Trasportatore class, i receive the following error:
org.hibernate.TransientPropertyValueException: object references an unsaved transient instance -
save the transient instance before flushing :
com.petroli.gestionefasipetroli.entities.Atk.suggerito ->
com.petroli.gestionefasipetroli.entities.Rimorchio
So, I have to save the Rimorchio instance before the Atk instance.
I have a structure with a Repository that extends JpaRepository and a Service that simply call
repository.saveAndFlush(trasportatore);
How can I decide the order of insertion into DB to avoid this problem? Or there is another solution (possibly avoiding manually cascading operations) to achieve this goal?

How to create a 1:n relationship with hibernate?

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

How get relationship entity from findAll in SDN 5?

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.

Categories