JPA Hibernate :: Inheritance of an entity, with additional OneToMany Lists - java

I'm using JPA Hibernate/Spring boot to build a web server with MySQL database, and I'm trying to extend a POJO Entity that looks like this, with additional OneToMany Lists.
#Entity
#Table(name="user")
public class User {
#Id
#GeneratedValue
private Integer id;
#Column(nullable=false)
private String name;
....Constructors, getters and setters....
}
with this basic user entity, I just wanna make a UserInfo entity with additional information about the user's careers.
#Entity
public class UserInfo extends User {
#OneToMany(cascade= CascadeType.ALL, fetch= FetchType.EAGER)
#JoinColumn(name="user_id", referencedColumnName = "id")
private List<Career> careers;
....Constructors, getters, setters......
}
And I'm quite confused which inheritance strategy I should choose. I don't think its necessary to make another column or table for this.
Or should I just query twice..?
I'm kinda new to JPA so not sure which is considered as the best practice or design..
Edit:
This is how Career entity looks like. Just in case..
#Entity
#Table(name="career")
public class Career {
#Id
#GeneratedValue
private Integer id;
#Column(nullable=false)
private Integer user_id;
#Column(nullable=false)
private String name;
#Column(nullable=false)
private String description;
....Constructors, getters and setters....
}
Since extending User table was meaningless(just in my case), I changed the User class like this.
#Table(name="user")
public class User {
#Id
#GeneratedValue
private Integer id;
#Column(nullable=false)
private String name;
#OneToMany(fetch= FetchType.LAZY)
#JoinColumn(name="user_id", referencedColumnName = "id")
private List<Career> careers;
....Constructors, getters, setters......
}
Now I'm trying this with Spring Data JPA, and when I try to show the list of Users with their Careers, it is now querying more than 40 times taking about a minute to show the result.
Is this the N+1 problem..? how can I solve this?

In my opinion the error lies within the model itself. Why should UserInfo extend User? I cannot imagine which attributes or methods the UserInfo should inherit from a User. Typical inheritances would be "Developer" or "Administrator".
Why don't you add UserInfo as a 1:1 relation in your User entity? Another option is to omit UserInfo and put the Careers as a 1:n relation right into your User.
To prevent possible n+1 issues on a growing number of Careers you might want to change the fetch mode. See below
#OneToMany(fetch=FetchType.LAZY,mappedBy="user")
#Fetch(FetchMode.SUBSELECT)
private Set<Career> careers = new HashSet<>();

Related

Obtain column data from One-To-Many join relation in Spring data JPA

Suppose I have two database tables, Product and ProductDetails.
create table Product
{
product_id int not null,
product_name varchar(100) not null,
PRIMARY KEY (product_id)
}
create table ProductDetails
{
detail_id int not null,
product_id int not null,
description varchar(100) not null,
PRIMARY KEY (detail_id,product_id),
FOREIGN KEY (product_id) REFERENCES Product(product_id)
}
Each product can have multiple product detail entries, but each product detail can only belong to one product. In SQL, I want to be able to retrieve each product detail but with the product name as well, and I would do that with a join statement.
select p.product_id,pd.detail_id,p.product_name,pd.description
from Product p join ProductDetails pd on p.product_id=pd.product_id
Now I need to have that concept in Spring data JPA form. My current understanding is the following:
#Table(name = "Product")
public class ProductClass
{
private int productID;
private String productName;
}
#Table(name = "ProductDetails")
public class ProductDetailsClass
{
private int detailID;
private int productID;
// this is the part I don't know how to set. #OneToMany? #ManyToOne? #JoinTable? #JoinColumn?
private String productName;
private String description;
}
(I didn't include any attributes such as #Id to keep the code minimal)
What do I need to write to get this private String productName; working?
My research on the #JoinTable and #OneToMany and other attributes just confuses me more.
P.S. This is a legacy Java program I inherited. The private String productName; part wasn't in the original code, but now I need the ProductDetails class to have the productName available.
P.P.S. I want to have a clear understanding of what I'm doing before trying anything and deploying. This is a legacy program deployed to production, and from what I understand, any code changes here can change the database structure as well, and no amount of money is enough to make me want to restore the Java program, the Spring Framework, the Apache server and MySQL database to a working order if anything catastrophic happens. Also I don't really have a development environment to test this. Help...
You research already goes in the right direction: You would need a #OneToMany relationship. The best descriptions for Hibernate has Vlad Mihalcea. On his webpage you could also find a good explanation of those relationships: The best way to map a #OneToMany relationship with JPA and Hibernate.
Firstly, you would have to create the entities correctly (an entity is represented by a table in a relational database).
Unidirectional (#OneToMany)
#Entity
#Table(name = "product")
public class Product
{
#Id
#GeneratedValue
private Long productID;
private String productName;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<ProductDetail> productDetails;
//Constructors, getters and setters...
}
#Entity
#Table(name = "product_details")
public class ProductDetail
{
#Id
#GeneratedValue
private Long detailID;
private String description;
//Constructors, getters and setters...
}
This is based on a unidirectional relationship. Therefore, each Product knows all the allocated ProductDetails. But the ProductDetails do not have a link to its Products. However, this unidirectional implementation is not recommended. It results in an increase of the size of the database, even its optimisation with #JoinColumn is not ideal because of more SQL calls.
Unidirectional (#ManyToOne)
#Entity
#Table(name = "product")
public class Product
{
#Id
#GeneratedValue
private Long productID;
private String productName;
//Constructors, getters and setters...
}
#Entity
#Table(name = "product_details")
public class ProductDetail
{
#Id
#GeneratedValue
private Long detailID;
private String description;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = product_id)
private Product product;
//Constructors, getters and setters...
}
In this unidirectional relationship only the ProductDetails know which Product is assigned to them. Consider this for a huge number of ProductDetail objects for each Product.
The #JoinColumn annotation specifies the name of the column of the table product_details in which the foreign key to the Product (its id) is saved. It also works without but it is more efficient with this annotation.
Bidirectional (#OneToMany and #ManyToOne)
#Entity
#Table(name = "product")
public class Product
{
#Id
#GeneratedValue
private Long productID;
private String productName;
#OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ProductDetail> productDetails;
//Constructors, add, remove method, getters and setters...
}
#Entity
#Table(name = "product_details")
public class ProductDetail
{
#Id
#GeneratedValue
private Long detailID;
private String description;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = product_id)
private Product product;
//Constructors, getters and setters...
}
With a bidirectional relationship objects of both sides (Product and ProductDetail) know which other objects got assigned to them.
But according to Vlad Mihalcea, this should not be used if too many ProductDetails exist per Product.
Also remember to implement proper add and remove methods for list entries (see article again, otherwise weird exceptions).
Miscellaneous
With the cascading, changes in a Product also get applied to its ProductDetails. OrphanRemoval avoids having ProductDetails without a Product.
Product product = new Product("Interesting Product");
product.getProductDetails().add(
new ProductDetails("Funny description")
);
product.getProductDetails().add(
new ProductDetails("Different description")
);
entityManager.persist(product);
Often the question about the correct equals and hashCode methods is a complex puzzle in your head. Especially for bidirectional relationships but also in other situations relying on a database connection it is recommendable to implement them quite simply as described by Vlad.
It is good practice to use objects for primitive data types as well. This gives you the option to retrieve a proper null when calling the getter.
Avoiding eager fetching should be quite clear...
When you now try to retrieve a Product out of the database, the object automatically has a list of all the ProductDetails assigned to it. To achieve this, JPA repositories in Spring could be used. Simple methods do not have to be implemented. When you have the need to customise the functionality more, have a look at this article by Baeldung.

How to stop Hibernate from eagerly fetching a relationship when it is mapped using a column (referencedColumnName) different than the primary key?

I'm mapping a relationship that does not use the entity's primary key. Using "referencedColumnName" with a column different than the primary key causes hibernate to eagerly fetch the association, by issuing an extra select, even when it's tagged with FetchType.LAZY.
My goal is to make it behave like a regular mapping, meaning it wouldn't issue an extra query every time I need to query the main entity.
I have already tried using #LazyToOne(LazyToOneOption.NO_PROXY), which sorts out the problem, but it does not operate well with Jackson's (JSON parsing library) module "jackson-datatype-hibernate5", which skips hibernate lazy proxies when serializing the results.
Here is a scenario almost like the one I have that causes the problem:
Entities:
#Entity(name = "Book")
#Table(name = "book")
public class Book
implements Serializable {
#Id
#GeneratedValue
private Long id;
private String title;
private String author;
#NaturalId
private String isbn;
//Getters and setters omitted for brevity
}
#Entity(name = "Publication")
#Table(name = "publication")
public class Publication {
#Id
#GeneratedValue
private Long id;
private String publisher;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "isbn",
referencedColumnName = "isbn"
)
private Book book;
#Column(
name = "price_in_cents",
nullable = false
)
private Integer priceCents;
private String currency;
//Getters and setters omitted for brevity
}
Repository (Spring-Data, but you could try directly with the EntityManager):
#Repository
public interface PublicationRepository extends JpaReadRepository <Publication, Long>
{
#Query ("SELECT d FROM Publication d WHERE d.publisher = ?1 ")
Optional <Publication> findByPublisher (String isbn);
}
Thanks
The only way to achieve what you are looking for is by moving the annotatation #Id to the isbn property.
You can leave the #GeneratedValue on the autoincrement property.
Notes:
1 - Make sure that your equals/hc are following the OID(Object ID) on your domain case the "NaturalId" ISBN.
2 - It will be good to ensure if possible on DB level that your natural ID has unique contraint on it.

What is the best way to relate this entity

in my spring boot application I have a user entity that relates from many to many with the entity courses (one user can enroll in many courses and one course can have multiple users)
And the entity courses relates one to many with the entity class (one course has several classes, but one class only has one course)
I'm doing my application with spring boot and for me to say that the user has enrolled in a course I just have to do:
user.setCourse(course)
userRepository.save(user)
when relating a course to the user I can say that he enrolled in the course. But how to relate the user to a class to know for example what classes he completed? I can take advantage of this user relationship too much for a lot of course one for many class or do I have to create a many relationship for many user with class?
Here is a small example of entities to better exemplify how the relationship is:
#Entity
public class User{
private long id;
private String name;
private String password;
#ManyToMany(mappedBy = “enrolledUser”, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable
private List enrolledCourse;
//getters and setters
}
#Entity
public class Course{
private long id;
private String name;
private String description;
#ManyToMany(mappedBy = “enrolledCourse”, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable
private List enrolledUser;
#OnetoMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List aulas;
//getters and setters
}
#Entity
public class Class{
private long id;
private String name;
private String content;
#ManyToOne(mappedBy = “class”, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable
private Course course;
//getters and setters
}
There are many things wrong here. First of all you cannot do this:
user.setCourse(course)
userRepository.save(user)
because we are talking about a many-to-many relationship. You would do something like this:
user.courses.add(course)
and vice versa
course.users.add(user)
Then you can save the user. Please follow the tutorial here:
https://www.callicoder.com/hibernate-spring-boot-jpa-many-to-many-mapping-example/
And use generic lists like List<Course> instead of just List. Actually I recommend using Set<Course> in this case. Just follow the tutorial above.
You can define the Class as a join table with extra columns that you need. Have a look at this post for defining those extra columns:
JPA 2.0 many-to-many with extra column

Hibernate Mapping Only one class

Hibernate Mapping
How to implement such a code?
Each company has two properties, they are company name and estimated annual earnings.
There are two types of companies: 1- Main company, 2 - Subsidiary company.
The company can belong only to one company but can have a few child companies.
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String companyName;
private double estimatedAnnualEarnings;
private Company company; // here need to do a #OneToOne
private List<Company> subsidiaryCompany; // here need to do a #OneToMany
}
In your Implementation you should use :
The #Entity annotation in your class level, so the entity can be persisted to database.
The #Column annotation with the companyName and estimatedAnnualEarnings properties, so they can be persisted as columns in the database.
#ManyToOne annotation with the company field, so it can be mapped with a self-reference relationship.
The same goes with the subsidiaryCompany List which needs to be mapped with #OneToMany annotation to have a relationship too.
This is how should be your code:
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column
private String companyName;
#Column
private double estimatedAnnualEarnings;
#ManyToOne(cascade={CascadeType.ALL})
#JoinColumn(name="mainCompanyId")
private Company mainCompany;
#OneToMany(mappedBy="mainCompany")
private List<Company> subsidiaryCompanies;
//getters and setters goes here
}
Note
I changed the name of company field to mainCompany and
subsidiaryCompaniy to subsidiaryCompanies for better readability
and to make it fit the logic better.
If you want to give your entity a different name in the database you
should use #Table(name="differentName") in the class level with
#Entity annotation, the smae thing with the columns you can add
name property to the #Column annotation i.e
#Column(name="company_name") if you want different names.

Query dsl returning duplicate records on #one to many association(leftJoin vs leftJoin.fetch vs fetchAll)

Here is my scenario: i have person entity which looks like below.
#Entity
public class Person{
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<PhoneNumber> phoneNumbers = new HashSet<>(0);
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "AGENCY_ID")
private Agency agency;
I am unable to retrieve correct data,when i query for persons.
Problems i have :
1. duplicate records.
2. person with no agency not returning .
3. Bad performance
Here is what i tried, and see combination of above problems
query.from(qPerson).leftJoin(qPerson.phoneNumbers, telecommNumber).leftJoin(qPerson.agency,qAgency);
I have problem 1: which is obvious(in one-to-many relationship) and this can be solved in direct hibernate by using distinct(). I tried distinct in queryDsl and that doesnt seem to work well.
query.from(qPerson).leftJoin(qPerson.phoneNumbers, telecommNumber).fetch().leftJoin(qPerson.agency,qAgency).fetch();
I have problem 3 in this case: returns results correctly but performance is really bad.(Cartesian product problem, i guess).
query.from(qPerson).fetchAll();
I have problem 2 in this case :This one performs well, but doesnt return person without agency when i try to sort on agency field for example. But returns that person if i dont add below to the query.
query.orderBy(person.agency.agencyIdentifierDescription.asc());
I am trying to arrive at a solution that solves above three problems. Thanks for your help.
Well, you should define your entities as following:
"In JPA a ManyToOne relationship is always (well almost always) required to define a OneToMany relationship, the ManyToOne always defines the foreign key (JoinColumn) and the OneToMany must use a mappedBy to define its inverse ManyToOne."
from Wiki:
ManyToOne
OneToMany
example:
public class Person {
#ID
private Integer id;
#OneToMany(mappedBy = "person")
private Set<PhoneNumber> = phoneNumbers;
#ManyToOne
#JoinTable(name="agency_person", joinColumns={#JoinColumn(name="person_id", referencedColumnName="id")}, inverseJoinColumns={#JoinColumn(name="agency_id", referencedColumnName="id")})
private Agency agency;
//Getters & Setters
}
//---------------------------------------------------
public class PhoneNumber {
#ID
private Integer id;
#ManyToOne
#JoinTable(name="phonenumber_person", joinColumns={#JoinColumn(name="phone_id", referencedColumnName="id")}, inverseJoinColumns={#JoinColumn(name="person_id", referencedColumnName="id")})
private Person person;
//Getters & Setters
}
//---------------------------------------------------
public class Agency {
#ID
private Integer id;
#OneToMany(mappedBy = "agency")
private Set<Person> persons;
//Getters & Setters
}

Categories

Resources