Hibernate lazy loading isn't working in #OneToOne Mapping - java

Although Lazy loading is applied, still it is running all 4 queries,
but I'm fetching only:
System.out.println("Author's Name: " + b.getEmail());
public class Author {
#Id
#Column(name = "AUTHOR_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String email;
#OneToOne(mappedBy = "author", fetch = FetchType.LAZY)
private Book book;
#OneToOne(mappedBy = "author", fetch = FetchType.LAZY)
private Address address;
#OneToOne(mappedBy = "author", fetch = FetchType.LAZY)
private Location location;
}
public class BooksManager {
public static void main(String[] args) {
Author b = (Author) session.get(Author.class, 6L);
System.out.println("Author's Name: " + b.getEmail());
}
}
It's running all 4 queries for the above code:
Hibernate: select author0_.AUTHOR_ID as AUTHOR_I1_1_0_, author0_.email as email2_1_0_, author0_.name as name3_1_0_ from AUTHOR author0_ where author0_.AUTHOR_ID=?
Hibernate: select address0_.address_id as address_1_0_0_, address0_.description as descript2_0_0_, address0_.PUBLISHED as PUBLISHE3_0_0_, address0_.title as title4_0_0_ from ADDRESS address0_ where address0_.address_id=?
Hibernate: select book0_.book_id as book_id1_2_0_, book0_.description as descript2_2_0_, book0_.PUBLISHED as PUBLISHE3_2_0_, book0_.title as title4_2_0_ from BOOK book0_ where book0_.book_id=?
Hibernate: select location0_.location_id as location1_3_0_, location0_.description as descript2_3_0_, location0_.PUBLISHED as PUBLISHE3_3_0_, location0_.title as title4_3_0_ from LOCATION location0_ where location0_.location_id=?

In your definition it, all 3 OneToOne relationships are optional (nullable).
when hibernate loads the entity Author, it has to create 3 proxies for relationships. but if a property is null , hibernate should not create proxy for that property.
Now hibernate somehow has to know whether the property is null or not.
so it has to lookup all other 3 tables to detect that.
finally hibernate is intelligent enough to ignore the hint made for LAZY loading and load them eagerly.

Related

Hibernate and Criteria Api generates wrong Join condition

I got following tables. Lets ignore the fact that the relation is done wrong here. I cannot change that.
Each company can have multiple employes and each employe belongs to only one company.
Table: Company
ID
EMPLOYE_ID
10
100
Table: Employe
ID
NAME
100 (Same as EMPLOYE_ID)
John
Now i want to create a relation #OneToMany between Company -> Employe . My entities look as follow
class Company {
#Id
#Column(name = "id", unique = true, nullable = false)
private String id;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "EMPLOYE_ID", referencedColumnName = "ID")
private Set<Employe> employees;
}
No matter if i try to create a uniderectional, or biderection relationship by adding also #ManyToOne on my Employe class, when using Criteria api to select all Company entities and their Employes i always end up with a wrong generated SQL query at the point where it joines the tables. The above relation for example creates following:
FROM company company0
INNER JOIN employe employe0 ON company0.id = employe0.employe_id
I tried several approaches, but i end up almost with the same error. It tries either to access a column which does not exist on the table, or joins wrong columns (e.g. id = id). Or by the following exception
Caused by: org.hibernate.MappingException: Repeated column in mapping
for entity: com.Employe column: id (should be mapped with
insert="false" update="false")"}}
What is a simple approach to create a bidrectional relation with the above table structure?
Note: I finally ended up changing the DB schema. Still, it would be interesting if someone could provide an answer for such a case, even if it is based on a not well formed
The central problem is that the described table structures do not allow a 1:n relationship from Company to Employee. According to the table design (especially the design of PKs) above, a company can only have one employee.
However, if the DB design cannot be changed, the following approach using the JoinColumnOrFormula annotation may lead to partial success.
The #JoinColumnOrFormula annotation is used to customize the join between a child Foreign Key and a parent row Primary Key when we need to take into consideration a column value as well as a #JoinFormula.
See https://docs.jboss.org/hibernate/stable/orm/userguide/html_single/Hibernate_User_Guide.html#associations-JoinColumnOrFormula for details.
More concretely with these Entities
#Entity
#Table(name="t_company")
public class Company {
#Id
#Column(name="id")
private Integer id;
#Column(name="employee_id")
private Integer employeeId;
#OneToMany(mappedBy = "company")
private List<Employee> employees;
// ..
}
#Entity
#Table(name = "t_employee")
public class Employee {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumnOrFormula( column =
#JoinColumn(
name = "id",
referencedColumnName = "employee_id",
insertable = false,
updatable = false
)
)
private Company company;
// ..
}
and this custom repository
#Repository
public class EmployeeRepository {
#Autowired
EntityManager entityManager;
List<Employee> findAll() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
Join<Employee, Company> joinCompany = root.join("company");
TypedQuery<Employee> query = entityManager.createQuery(cq);
return query.getResultList();
}
}
you get the following query:
select
employee0_.id as id1_1_,
employee0_.name as name2_1_
from t_employee employee0_
inner join t_company company1_ on employee0_.id=company1_.employee

Child entities fetches with lazy fetching

I've an issue while fetching data with Hibernate Spring. I'm trying to figure out why a specific Get request does not work as I expected.
Database lay out
The goal is to get a list of all countries in the table country with the following method:
> #GetMapping("/getCountry") public List <Country> getCountry(){
> return(List<Country>) countryRepo.findAll(); }
The country entity is:
#Entity
public class Country {
#Id
private Integer id;
private String name;
#OneToMany(mappedBy="country", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
#JsonManagedReference
private List<PortOfLoading> portOfLoadings;
public Country() {
}
//getters and setters omitted
The Port of Loading entity is:
#Entity
public class PortOfLoading {
#Id
private int id;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JsonBackReference
private Country country;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "port_of_loading_id", referencedColumnName = "id")
private List<Container> containers;
public PortOfLoading() {
} //getters and setters are omitted
I would expect hibernate to make a query like this:
Hibernate: select country0_.id as id1_4_, country0_.name as name2_4_ from country country0_
But instead of that, hibernate starts to fetch all port of loadings for that entity and after that also all the containers for each port of loading:
Hibernate: select portofload0_.country_id as country_3_5_0_, portofload0_.id as id1_5_0_, portofload0_.id as id1_5_1_, portofload0_.country_id as country_3_5_1_, portofload0_.name as name2_5_1_ from port_of_loading portofload0_ where portofload0_.country_id=?
Hibernate: select containers0_.port_of_loading_id as port_of_8_2_0_, containers0_.id as id1_2_0_, containers0_.id as id1_2_1_, containers0_.carrier_id as carrier_5_2_1_, containers0_.container_reference as containe2_2_1_, containers0_.container_sort_id as containe6_2_1_, containers0_.port_of_loading_country_id as port_of_7_2_1_, containers0_.ocean_freight_costs_invoiced as ocean_fr3_2_1_, containers0_.port_of_loading_id as port_of_8_2_1_, containers0_.terminal_handeling_costs_invoiced as terminal4_2_1_ from container containers0_ where containers0_.port_of_loading_id=?
Because of all the other queries, it takes a very long time to get the data and I've been struggling to figure out why it is.
Thanks in advance!
Solved! The function FindAll() fetches all the data with the right querie. But the return statement on the getCountry() method fetches all the other entities. Adding #JsonIgnore solved the problem with the return statement:
#OneToMany(mappedBy="country", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
#JsonIgnore
private List<PortOfLoading> portOfLoadings;

How to prevent additional query for OneToOne relation

I have tables:
users (id, name, email, password)
user_statuses (user_id, is_premium, is_advanced, user_rank_id)
user_ranks (id, name, ordinal)
So the relation between User and UserStatus is 1-1, and I have following entity clasess:
#Entity
#Table(name = "users")
#Getter
#Setter
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String email;
private String password;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private UserStatus status;
}
#Entity
#Table(name = "user_statuses")
#Getter
#Setter
#NoArgsConstructor
public class UserStatus {
#Id
private long id;
#MapsId
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
private boolean isPremium;
private boolean isAdvanced;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_rank_id")
private UserRank rank;
}
#Entity
#Table(name = "user_ranks")
#Getter
#Setter
#NoArgsConstructor
public class UserRank {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private int ordinal;
}
Then i created endpoint "/users/{id}" which should return user's email address as a string:
#GetMapping("/users/{id}")
public String getUserEmail(#PathVariable("id") long userId) {
User user = service.getUser(userId);
return user.getEmail();
}
When I call above endpoint I get user's email address as a response, however looking at the console log I see that hibernate executed 2 queries but noone asked him to do so:
First one for fetching the user:
SELECT
user0_.id AS id1_2_0_,
user0_.email AS email2_2_0_,
user0_.name AS name3_2_0_,
user0_.password AS password4_2_0_
FROM
users user0_
WHERE
user0_.id = 1;
And second one for fetching User Status that is associated with this User object:
SELECT
userstatus0_.user_id AS user_id1_1_0_,
userstatus0_.is_advanced AS is_advan2_1_0_,
userstatus0_.is_premium AS is_premi3_1_0_,
userstatus0_.user_rank_id AS user_ran4_1_0_
FROM
user_statuses userstatus0_
WHERE
userstatus0_.user_id = 1;
So I am confused: Why is hibernate running second query when I set fetch = FetchType.LAZY on each relation... It looks like that LAZY is ignored for #OneToOne annotation?
I do not use EntityGraph.
How to stop hibernate for running second query?
EDIT
So, it turns out Hibernate ignores my Lazy hint because it needs to decide should it initialize property with NULL or ProxyObject which makes sense. This link explains it well:
https://thorben-janssen.com/hibernate-tip-lazy-loading-one-to-one/
However this link also suggests that the best way to model this is Unidirectional One to One and it says that I can always fetch UserStatus based on User's ID (because both tables "shares" primary key)
However this confuses me a little bit, because I can fetch both rows using single query (SELECT * FROM users LEFT JOIN user_statuses ON users.id = user_statuses.user_id), but with approach described in the link I need 2 queries, and as far as I know (which I might be wrong) is 1 query is better than executing 2 queries, also if I want to fetch 25 users and their User Statuses, then I would also need 2 queries, one for fetching users and then fetching corespoinding user statuses and finally write nested for each loops to join these objects. I could have just executed one single query to fetch everything...
It is possible to make OTO lazy even if it's not the owning side. You just need to mark it as optional = false. This way Hibernate will know that it can safely a create proxy (and null is not possible) as the association always exists. Note, though it really must be non-optional - the 2nd entity must always exist. Otherwise you'll get an exception once Hibernate tries to load it lazily.
As for the number of queries, with native Hibernate (not JPA!) you can select org.hibernate.annotations.FetchMode. Which gives options to:
Use a separate select
Or use a join to load association
Alternatively, you can stay with JPA and write a JPQL query and use fetch join to keep it as a single query.
PS: before doing additional select Hibernate will check if the element already exists within the Session. If it is, then no select is going to be issued. But with fetch join or FetchMode.JOIN you won't have this luxury - join will always happen.
For one to one relation in hibernate it is always loading reference object whether you keep Fetch type Lazy or Eager. So alternate solution is select only those columns which are needed, it should not contain that reference column. So in this case hibernate will not fire another query.
Query for below class will be :
#Query("select new Example(id,field1) from Example")
#Entity
#Table(name = "example")
class Example implements Serializable {
private static final long serialVersionUID = 1L;
public Example(Long id, String field1) {
this.id = id;
this.field1 = field1;
}
#Id
#Column(name = "id", nullable = false, updatable = false)
private Long id;
#OneToOne(mappedBy = "example", fetch = LAZY, cascade = ALL)
private CustomerDetails customerDetails;
#Column(name = "field1", nullable = false, updatable = false)
private String field1;
}

How to correctly add records to jpa onetomany join table

I'm developing a filing system where I have 3 tables. PROJECTS table consists of projectid, project name and other details (see below). This is an existing class and populated schema and I do not want to modify this part of the application if possible.
Folders table (Called ProjectClassification) consists of folderid and foldername and is the owning side of a unidirectional onetomany relationship.
Project_Folders is a join table. I'm using JPA 2.0 (EclipseLink) and JSF 2.0 as my web framework.
My basic problem is I can't add duplicate records to the join table using a merge operation. MERGE is good for adding records until the owning key already exists, after which point it will only update the join table. I know this is the way it's supposed to work but I need to add new records even if there's a duplicate of the owning key. This will allow me to store different projects in the same folder.
I've looked through some other questions here such as:
onetomany unidirectional with jointable setup using jpa
This says what is needed to add one entity to the other in a join table but i need to know more about how to correctly persist or merge the added entity to the database.
The folder entity class:
#Entity
#Table(name = "PROJECTCLASSIFICATIONS")
public class ProjectClassifications implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int proclassid;
private int projectid;
private String classification;
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "PROJECT_CLASSF_JOIN",
joinColumns = #JoinColumn(name = "proclassid", referencedColumnName = "proclassid"),
inverseJoinColumns = #JoinColumn(name = "projectid", referencedColumnName = "projectid", unique = true))
private Collection<Projects> projects;
public ProjectClassifications() {
}
public ProjectClassifications(String classification) {
this.classification = classification;
}
public ProjectClassifications(int proclassid, int projectid) {
this.proclassid = proclassid;
projects = new ArrayList<Projects>();
}
public ProjectClassifications(Projects newProject) {
projects = new ArrayList<Projects>();
}
public void addProject(Projects newProject) {
if(!getProjects().contains(newProject))
getProjects().add(newProject);
}
....
....
The Project entity class is a pre existing code and I do not want to modify at all if possible:
#Entity
#Table(name = "PROJECTS")
public class Projects {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int projectid;
private String projectName;
private String projectDescription;
#Temporal(javax.persistence.TemporalType.DATE)
private Date startDate;
#Temporal(javax.persistence.TemporalType.DATE)
private Date endDate;
private String commnts;
// foreign keys to parent tables
private int fk_countryid;
private int fk_companyid;
private int fk_employeeid;
#ManyToOne(optional = true)
#JoinColumn(name = "countryid")
private Country country;
....
....
I then use two html select lists to select values for projectid and proclassid which call the following methoid using a JSF managed bean:
public String makeProClassRecord() {
newProClass = new ProjectClassifications(proclassid, projectid);
newProject = proServ.findByProjectId(projectid);
newProClass.addProject(newProject);
facade.update(newProClass);
//facade.save(newProClass);
return showProclass();
}
My questions are:
1) Is MERGE the correct operation used to add records into a join table?
2) Is there a way to add records that contain duplicate keys (foreign keys represented as new records in the join table) using MERGE?
3) Should PERSIST be used to achieve question 2?
4) Would it be better to create an entity for the join table itself and simply use a PERSIST method to insert the records?
Many thanks
So I solved this myself a couple of weeks ago and thought of sharing the answer. Instead of doing merge or persist operations on any of the target entities, I created a Join table and unidirectional OneToMany relationship from the Project entity to the below ProjectFileSystem join table entity and simply did the persist operation using that entity. I need to add duplicate folders for different projects (or store many projects under a single folder item) so it seems more efficient to do the CRUD operations in the actual join table entity rather than from the target entity. Hope this helps:
#Entity
#Table(name = "PROFOLDERSYS_JOIN")
public class ProjectFileSystem implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int foldersysid;
private int proclassid;
private int projectid;
private String projectName;
private String folderName;
public ProjectFileSystem() {
}
public ProjectFileSystem(int proclassid, int projectid,
String projectName, String folderName) {
this.proclassid = proclassid;
this.projectid = projectid;
this.projectName = projectName;
this.folderName = folderName;
}
// getters and setters
}
The method in the bean would be:
public String makeProSys() {
newProSys = new ProjectFileSystem(proclassid, projectid, classification, projectName);
newProject = proServ.findByProjectId(projectid);
projectName = newProject.getProjectName();
newProSys.setProjectName(projectName);
newProClass = facade.findByContactId(proclassid);
classification = newProClass.getClassification();
newProSys.setFolderName(classification);
profilFacade.save(newProSys);
return showProSys();
}

Complex query for hibernate

I have three tables with entities in hibernate. DB - MySQL. I need to get fields from entity "Item" where ModelsMm.id has some value. At first I tried to do separate queries, it was huge amount of requests in sum. So, i tried to do complex query, but it became a very long run.
I think there is a simpler way, but I do not know what.
My query and entities.
List<Item> itemIds = session.createQuery("select it from Item it where :id in elements(it.mmPrice.modelsMm)");
#Entity (name = "MODELS_MM")
public class ModelsMm {
#Id
private int Id;
#ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name="parth_mm", joinColumns={#JoinColumn(name="MODEL_ID", referencedColumnName="ID")}, inverseJoinColumns={#JoinColumn(name="PART_ID", referencedColumnName="ID")})
private List<MmPrice> mmPrices;
#Entity (name = "MM_PRICE")
public class MmPrice {
#Id
private int id;
private String article;
#OneToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "article", referencedColumnName = "article",insertable = false, updatable = false)
private Item item;
#ManyToMany
#JoinTable(name="parth_mm", joinColumns={#JoinColumn(name="PART_ID", referencedColumnName="ID")}, inverseJoinColumns={#JoinColumn(name="MODEL_ID", referencedColumnName="ID")})
private List<ModelsMm> modelsMm;
#Entity
#Table(name="SHOP_ITEMS")
public class Item implements Serializable {
#Id
private int id;
private String article;
#OneToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "article", referencedColumnName = "article",insertable = false, updatable = false)
private MmPrice mmPrice;
In console i have that query
Hibernate: select item0_.ID as ID0_, item0_.ARTICLE as ARTICLE0_, item0_.article as article0_ from SHOP_ITEMS item0_ cross join MM_PRICE mmprice1_ where item0_.article=mmprice1_.article and (? in (select modelsmm2_.MODEL_ID from parth_mm modelsmm2_ where mmprice1_.ID=modelsmm2_.PART_ID))
Thanks.
First, you'll have to fix your mapping. In a bidirectional association, one side MUST be the inverse side, and thus use the mappedBy attribute. For example, if you choose ModelsMm to be the inverse side, then its mmPrices attribute should be declared as
#ManyToMany(mappedBy = "modelsMm")
private List<MmPrice> mmPrices;
You should also forget about CascadeType.ALL on ManyToMany associations: it makes no sense. You don't want to delete all the courses of a student when you delete a student, since the course is also followed by several other students.
Now, regarding your query, it's not very clear what you want to do. If you want to select all the items which have a price which have at least one model whose ID is in a collection of IDs, then you simply need the following query:
select distinct i from Item i
join i.mmPrice p
join p.modelsMm m
where m.id in :modelIds
Side note: please fix your naming. This inconsistent and unnecessary usage of mm as a prefix or suffix makes the code unreadable. Name your class Price, the fields of type Price price, and the collections of prices prices. Just as you would do in English: an Item has a price, and a price has models.

Categories