I have 2 JPA entities User and Course:
#Entity
public class User {
#Id
#GeneratedValue
private Integer id;
#ManyToMany
#JoinTable(name="UserCourse")
private Set<Course> courses;
// getters and setters
}
#Entity
#Cacheable
public class Course {
#Id
#GeneratedValue
private Integer id;
// getters and setters
}
Course is cacheable. hibernate.max_fetch_depth is set to 0.
What I need is when I iterate through a user's courses, hibernate should fire a simple SELECT query to fetch courses from join-table UserCourse and load Course entity from L2 cache.
But instead, hibernate always seem to be firing a join-query between UserCourse and Course.
So the question is -
In case of ManyToMany association, is there a way to force hibernate to fire a simple non-join SELECT on join-table and fetch the related cacheable entity from L2 cache?
Related
I'm trying to make an #OneToOne mapping following the https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/ the mapping itself works but its triggering an N+1 query problem.
The query is being made on the parent entity service and its triggering N+1 queries.
How can I improve this code to only make 1 query? We don't need to access the ParentDetails in this case.
EDIT: I've tried using JPQL and LEFT JOIN FETCH ParentDetails and didn't work either.
EDIT2: Just to try to add more information. I've put a breakpoint on the getParentDetails just to make sure I was not calling the getter anywhere and I'm not calling and double-checked and it seems a join problem on the repo call.
Let's go to the code:
Parent
#Entity
#DynamicUpdate
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
mappedBy = "parent")
private ParentDetails parentDetails;
// Getters, setters, etc omitted for brevity
}
ParentDetails
#Entity
public class ParentDetails {
#Id
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private Parent parent;
// Getters, setters, etc omitted for brevity
ParentDetailsRepository
#Repository
public interface ParentRepository extends JpaRepository<Parent, Long> {
Page<Parent>findByNameOrderByName(#Param("name") final String name,final Pageable pageable);
}
Hibernate executes the additional queries because the Parent entity doesn't map the foreign key column. Hibernate doesn't support lazy fetching for that end of the association. When Hibernate instantiates a Parent object, it needs to check if it needs to initialize the association with a proxy or a null object. And at some point, the team decided that they would fetch the associated entity if they are forced to perform a query anyways. I explained that in more detail here: https://thorben-janssen.com/hibernate-tip-lazy-loading-one-to-one
If you want to avoid the additional queries, you need to model a unidirectional association between ParentDetails and Parent. In your example, that would mean that you need to remove the parentDetails attribute from your Parent entity.
#Entity
#DynamicUpdate
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// Getters, setters, etc omitted for brevity
}
Because your ParentDetails entity uses the same id value as the Parent entity, you don't need a bidirectional association. If you want to get the ParentDetails entity for a Parent entity, you can get it with a call of the em.find(...) method
Parent p = // fetch the parent object ...
ParentDetails pd = em.find(p.getId(), ParentDetails.class);
Hello there is my model
#Entity
public class Group {
#Id
#GeneratedValue
private Long id;
#OneToMany(
cascade = CascadeType.ALL
)
private List<User> users;
//Constructors, getters and setters removed for brevity
}
#Entity
public class User{
#Id
#GeneratedValue
private Long id;
private boolean valid;
//Constructors, getters and setters removed for brevity
}
Is it possible to make a HSQL request to select a Group by id, but keeping in the User list, only the users with 'true' in the "valid" field ?
If you want to use HSQL to return JPA entity tree (simple way), the answer is no.
Though, you can write this:
select g from Group g
left join g.users u with u.valid = true
where g.id = 123
your users collection won't be initialized. If you add fetch keyword to the join, you get an error:
QuerySyntaxException: with-clause not allowed on fetched associations; use filters
Which leads you to other solution, using annotation #Filter. But you need to enable the filter, and then you have problem with updating the entity read with the filter turned on (collection synchronization).
(OpenJPA2.x) I have Parent->(linktable)->Category relation. If I remove category from parent's category array it gets properly deleted from the linktable (unlinked). Adding a new category to an array gets inserted to the linktable. However problem is category target entity is also deleted from Category table. I have debugged jdbc queries and it's performed by OpenJPA library, db tables don't have a cascaded delete constraint.
Parent(key=ServerId,Code)
ServerId|Code |Name
1 |code1|name1
1 |code2|name2
1 |code3|name3
2 |code1|name4
Category(key=ServerId,Code)
1 |cat1 |child1
1 |cat2 |child2
2 |cat2 |child3
LinkTable(key=ServerId,PCode,CCode)
ServerId|PCode|CCode
1 |code1|cat1
1 |code1|cat2
1 |code3|cat1
Parent->Categories are linked using OneToMany annotation. Category does not know where it was linked from so prefer keeping that entity class clean as possible without any link annotations.
#Entity #Table(name="Parent") #Access(AccessType.FIELD)
public class Parent {
#EmbeddedId Parent.PK pk; // contains serverId+code fields
private String name;
#OneToMany(fetch=FetchType.LAZY, orphanRemoval=true, cascade=CascadeType.PERSIST)
#JoinTable(name="LinkTable",
joinColumns={
#JoinColumn(name="ServerId", referencedColumnName="ServerId", nullable=false),
#JoinColumn(name="PCode", referencedColumnName="Code", nullable=false)
},
inverseJoinColumns={
#JoinColumn(name="ServerId", referencedColumnName="ServerId", nullable=false),
#JoinColumn(name="CCode", referencedColumnName="Code", nullable=false)
}
)
private List<Category> cats;
public List<Category> getCategories() { return cats; }
}
#Entity #Table(name="Category") #Access(AccessType.FIELD)
public class Category {
#EmbeddedId Category.PK pk; // serverId,Code fields
private String name;
// this entity don't have OneToMany,ManyToOne,etc.. links back to parent
}
This is a legacy application I must use a composited primary keys, but it should not be a JPA problem I guess, after all this is a valid sql schema pattern.
You annotated the association with orphanRemoval=true. That precisely means that categories which are removed from their parent, and are thus orphan, must be removed.
#Entity
public class Person {
#Id
#GeneratedValue
private int personId;
#OneToOne(cascade=CascadeType.ALL, mappedBy="person", fetch=FetchType.LAZY)
private PersonDetail personDetail;
//getters and setters
}
#Entity
public class PersonDetail {
#Id
#GeneratedValue
private int personDetailId;
#OneToOne(cascade=CascadeType.ALL,fetch=FetchType.LAZY)
private Person person;
//getters and setters
}
when i do
Person person1=(Person)session.get(Person.class, 1);
i see two queries being fired. one for fetching person data and another for person detail data.
As per my understanding only 1 query should have been fired that is for fetching person data not for person detail data as i have mentioned
lazy loading. Why personDetail data is getting fetched along with person data ?
Hibernate cannot proxy your own object as it does for Sets / Lists in a #ToMany relation, so Lazy loading does not work.
I think this link could be useful to understand your problem: http://justonjava.blogspot.co.uk/2010/09/lazy-one-to-one-and-one-to-many.html
Based on your comment and since the PersonDetail entity contains a foreign key column that references the Person entity, it looks like you only have 1 problem:
Entity relationships include the concept of a relationship owner (in this case PersonDetail), which means that you want to add a #JoinColumn annotation in the PersonDetail entity.
You have already correctly defined the inverse side of the relationship (the entity that is not the owner of the relationship) with the mappedBy attribute that was added to the association annotation (#OneToOne in your case) to make the relationship bi-directional, which means that the associated PersonDetail may be reached from a Person instance.
Given the relationship that is clarified in your comment, you should only have to make 1 change to your code as shown here:
#Entity
public class Person {
#Id
#GeneratedValue
private int personId;
//Retain the mappedBy attribute here:
#OneToOne(cascade=CascadeType.ALL, mappedBy="person",
fetch=FetchType.LAZY)
private PersonDetail personDetail;
//getters and setters...
}
#Entity
public class PersonDetail {
#Id
#GeneratedValue
private int personDetailId;
#OneToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
//Change: add the #JoinColumn annotation here:
#JoinColumn(name="PERSON_FK_COLUMN")
private Person person;
//getters and setters...
}
I have a ManyToMany relationship established with a join table that is exactly like the one described on the Java Persistence wiki Example of a ManyToMany relationship annotations. Using that wiki example of Employees & Projects as a reference, the code listed in the example works fine to create three tables: EMP, PROJ and the EMP_PROJ join table. What I would like to do is use that EMP_PROJ join table in a separate entity. I don't want to add additional columns to the EMP_PROJ join table. For example, suppose an administrator associates projects with an employee. That list is stored in EMP_PROJ. What I would like to do is create a separate entity called ManagerReport that will return, for an employee, the list of projects associated with that employee. The manager can then enter specific info regarding each project for the employee, like start date, end date, performance, etc.
Below are the tables and sample code pulled from the wiki page.
EMPLOYEE (table)
ID FIRSTNAME LASTNAME
1 Bob Way
2 Sarah Smith
EMP_PROJ (table)
EMP_ID PROJ_ID
1 1
1 2
2 1
PROJECT (table)
ID NAME
1 GIS
2 SIG
#Entity
public class Employee {
#Id
#Column(name="ID")
private long id;
...
#ManyToMany
#JoinTable(
name="EMP_PROJ",
joinColumns={#JoinColumn(name="EMP_ID", referencedColumnName="ID")},
inverseJoinColumns={#JoinColumn(name="PROJ_ID", referencedColumnName="ID")})
private List<Project> projects;
...
}
You will have to create a ManagerReportProject entity which maps to EMP_PROJ table with #Table annotation.
In Employee entity, instead of #ManyToMany mapping for a collection of Projects use #OneToMany mapping for collection mapping to ManagerReportProject entities.
You will still be able to get a list of employee's projects because each ManagerReportProject further points to Project. You can even create a helper getProjects() method inside Employee to get a list of projects. Method must be annotated with #Transient to mark getProjects() method as not JPA persitent (by default all properties or fields inside Entity mappings are persitent)
#Entity
public class Employee
{
#Id
#Column(name="ID")
private long id;
...
#OneToMany...
private List<ManagerReportProject> managerReportProjects;
...
/*
YOU DON'T NEED THIS ANYMORE:
#ManyToMany
#JoinTable(
name="EMP_PROJ",
joinColumns={#JoinColumn(name="EMP_ID", referencedColumnName="ID")},
inverseJoinColumns={#JoinColumn(name="PROJ_ID", referencedColumnName="ID")})
private List<Project> projects;
*/
#Transient
public List<Project> getProjects()
{
List<Project> projects = new ArrayList<Project>();
for(ManagerReportProject managerReportProject: managerReportProjects)
{
projects.add(managerReportProject.getProject());
}
return projects;
}
...
}
ManagerReportProject should point to Employee, Project and Manager entity with #ManyToOne association.
Put manager report specific columns into ManagerReportProject (start date, end date, performance, etc.).
ManagerReportProject maps to EMP_PROJ table with #Table annotation.
#Entity
#Table(name= "EMP_PROJ")
public class ManagerReportProject{
#Id
#Column(name="ID")
private long id;
//manager report columns
private Date startDate;
private Date endDate;
performance, etc.
#ManyToOne...
private Manager manager;
...
#ManyToOne...
private Employee employee;
#ManyToOne...
private Project project;
...
}
Create a Manager entity, use #OneToMany for collection mapping to ManagerReportProject entities:
#Entity
public class Manager {
#Id
#Column(name="ID")
private long id;
...
#OneToMany...
private List<ManagerReportProject> managerReportProjects;
...
}
Now you can enter specific info regarding each project for the employee, like start date, end date, performance, etc.
This is a sketch just to demonstrate an idea of how to edit existing manager report for a specific empolyee working for a specific manager:
Emyployee employee = ...
Manager manager = ...
List<ManagerReportProject> managerReportProjects= employee.getManagerReportProjects()
for(ManagerReportProject managerReportProject: managerReportProjects )
{
if(manager.equals(managerReportProject.getManager()))
{
Project project = managerReportProject.getProject();
managerReportProject.setStartDate(...);
managerReportProject.setEndDate(...);
managerReportProject.setperformance(...);
...
}
}