Spring Data JPA - Lazy loading and #Fetch( FetchMode.JOIN) - spring-boot

2 Entity ProductMaster and Category
#Entity
#Table(name = "product_master")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
#NamedQuery(name = "ProductMaster.findAll", query = "SELECT p FROM ProductMaster p")
public class ProductMaster implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String description;
//Many Product will have one categoary
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="category_id")
private Category category;
//get and set fn
}
#Entity
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
#Table(name = "category")
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "category_name")
private String categoryName;
#JsonIgnore
#OneToMany(cascade = CascadeType.ALL, mappedBy = "category")
#Fetch( FetchMode.JOIN)
private Set<ProductMaster> products = new HashSet<ProductMaster>();
//get and set fn
}
while fetchAll in JPA repository of Product. Join is performed and its expected
Hibernate:
select
productmas0_.id as id1_1_,
productmas0_.category_id as categor11_1_,
productmas0_.created_by as created_2_1_,
productmas0_.created_dt as created_3_1_,
productmas0_.description as descript4_1_,
productmas0_.image as image5_1_,
productmas0_.is_favorite as is_favor6_1_,
productmas0_.price as price7_1_,
productmas0_.title as title8_1_,
productmas0_.updated_by as updated_9_1_,
productmas0_.updated_dt as updated10_1_
from
product_master productmas0_ limit ?
Hibernate:
select
category0_.id as id1_0_0_,
category0_.category_name as category2_0_0_,
category0_.created_by as created_3_0_0_,
category0_.created_dt as created_4_0_0_,
category0_.category_desc as category5_0_0_,
category0_.updated_by as updated_6_0_0_,
category0_.updated_dt as updated_7_0_0_,
products1_.category_id as categor11_1_1_,
products1_.id as id1_1_1_,
products1_.id as id1_1_2_,
products1_.category_id as categor11_1_2_,
products1_.created_by as created_2_1_2_,
products1_.created_dt as created_3_1_2_,
products1_.description as descript4_1_2_,
products1_.image as image5_1_2_,
products1_.is_favorite as is_favor6_1_2_,
products1_.price as price7_1_2_,
products1_.title as title8_1_2_,
products1_.updated_by as updated_9_1_2_,
products1_.updated_dt as updated10_1_2_
from
category category0_
left outer join
product_master products1_
on category0_.id=products1_.category_id
where
category0_.id=?
but while fetchAll in JPA repository of category multiple query for product are fired. I want lazy loading behavior here for product while fetch all in Category
select
category0_.id as id1_0_,
category0_.category_name as category2_0_,
category0_.created_by as created_3_0_,
category0_.created_dt as created_4_0_,
category0_.category_desc as category5_0_,
category0_.updated_by as updated_6_0_,
category0_.updated_dt as updated_7_0_
from
category category0_ Hibernate:
select
products0_.category_id as categor11_1_0_,
products0_.id as id1_1_0_,
products0_.id as id1_1_1_,
products0_.category_id as categor11_1_1_,
products0_.created_by as created_2_1_1_,
products0_.created_dt as created_3_1_1_,
products0_.description as descript4_1_1_,
products0_.image as image5_1_1_,
products0_.is_favorite as is_favor6_1_1_,
products0_.price as price7_1_1_,
products0_.title as title8_1_1_,
products0_.updated_by as updated_9_1_1_,
products0_.updated_dt as updated10_1_1_
from
product_master products0_
where
products0_.category_id=? 2022-09-25 13:39:56.507 TRACE 14160 --- [nio-8080-exec-5] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [2] Hibernate:
select
products0_.category_id as categor11_1_0_,
products0_.id as id1_1_0_,
products0_.id as id1_1_1_,
products0_.category_id as categor11_1_1_,
products0_.created_by as created_2_1_1_,
products0_.created_dt as created_3_1_1_,
products0_.description as descript4_1_1_,
products0_.image as image5_1_1_,
products0_.is_favorite as is_favor6_1_1_,
products0_.price as price7_1_1_,
products0_.title as title8_1_1_,
products0_.updated_by as updated_9_1_1_,
products0_.updated_dt as updated10_1_1_
from
product_master products0_
where
products0_.category_id=?
Problem statement to resolved here is that Number of Product query will be number of row in category table. We want this to be lazy load and don't want to perform multiple query for product while selecting category.

If you want a single query you should use left join fetch :
select c from Category c left join fetch c.products
And for the problem of multiple categories read this article
https://vladmihalcea.com/jpql-distinct-jpa-hibernate/

OneToMany relationships are inherently lazy but setting Fetch( FetchMode.JOIN) will override this lazy behaviour so should be removed if you want lazy fetching of products

Related

Q: Transactional Code why does this work so well?

Hello my professionals I have a simple question here that I would like to beg to solve this..
this is an Entity of Member
#Entity
#Getter
#Builder
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#AllArgsConstructor
/*#ToString(of = {"id", "username", "age"})*/
public class Member {
#Id
/*#GeneratedValue(strategy = GenerationType.IDENTITY)*/
#Column(name = "member_id")
private Long id;
private String username;
private int age;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "member")
private List<Team> teams;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "member")
private List<Coach> coachs;
}
And this is an Entity of Coach
#Entity
#AllArgsConstructor
#Getter
#Builder
#Setter
#NoArgsConstructor
#ToString(of = {"id","name","career"})
public class Coach {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name= "coach_id")
private Long id;
#Column
private String name;
#Column
private String career;
#ManyToOne(fetch = FetchType.LAZY,cascade = ALL)
#JoinColumn(name = "member_id")
private Member member;
#OneToOne(fetch = FetchType.LAZY,cascade = ALL)
#JoinColumn(name = "team_id")
private Team team;
}
and This is Controller Code
#GetMapping("/member")
public void createUser(){
Member m = memberService.createMember();
Coach c = m.getCoachs().get(0);
log.info(c.getName());
}
and This is Service Code
private final MemberRepository memberRepository;
#Transactional
public Member createMember(){
return memberRepository.findMemberById(3L);
}
and the last this is RepositoryCode
Member findMemberById(Long id);
So my question is that when i printed out Coach's name at the controller on console
it printed out so well.
but what I know the Transaction is over from the service So the persistence container is closed that means coach name can't be imported cause it's LAZY loading and persistence container is closed but it was printed out well
I want to know the reason why ...
here are the console results Thanks !!
[2022-01-10 23:27:46.835] [http-nio-9000-exec-2] [] INFO o.a.c.c.C.[.[.[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
[2022-01-10 23:27:46.835] [http-nio-9000-exec-2] [] INFO o.s.w.s.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
[2022-01-10 23:27:46.855] [http-nio-9000-exec-2] [] INFO o.s.w.s.DispatcherServlet - Completed initialization in 19 ms
Hibernate:
/* select
generatedAlias0
from
Member as generatedAlias0
where
generatedAlias0.id=:param0 */ select
member0_.member_id as member_i1_1_,
member0_.age as age2_1_,
member0_.username as username3_1_
from
member member0_
where
member0_.member_id=?
[2022-01-10 23:27:47.007] [http-nio-9000-exec-2] [4c0222d3] INFO p6spy - #1641824867007 | took 15ms | statement | connection 1| url jdbc:mariadb://patrick-lab.cjeq2ffynlc2.ap-northeast-2.rds.amazonaws.com:3306/patricklab?characterEncoding=UTF-8&serverTimezone=UTC
/* select generatedAlias0 from Member as generatedAlias0 where generatedAlias0.id=:param0 */ select member0_.member_id as member_i1_1_, member0_.age as age2_1_, member0_.username as username3_1_ from member member0_ where member0_.member_id=?
/* select generatedAlias0 from Member as generatedAlias0 where generatedAlias0.id=:param0 */ select member0_.member_id as member_i1_1_, member0_.age as age2_1_, member0_.username as username3_1_ from member member0_ where member0_.member_id=3;
[2022-01-10 23:27:47.170] [http-nio-9000-exec-2] [4c0222d3] INFO p6spy - #1641824867170 | took 12ms | commit | connection 1| url jdbc:mariadb://patrick-lab.cjeq2ffynlc2.ap-northeast-2.rds.amazonaws.com:3306/patricklab?characterEncoding=UTF-8&serverTimezone=UTC
;
Hibernate:
select
coachs0_.member_id as member_i4_0_0_,
coachs0_.coach_id as coach_id1_0_0_,
coachs0_.coach_id as coach_id1_0_1_,
coachs0_.career as career2_0_1_,
coachs0_.member_id as member_i4_0_1_,
coachs0_.name as name3_0_1_,
coachs0_.team_id as team_id5_0_1_
from
coach coachs0_
where
coachs0_.member_id=?
[2022-01-10 23:27:47.200] [http-nio-9000-exec-2] [4c0222d3] INFO p6spy - #1641824867200 | took 12ms | statement | connection 1| url jdbc:mariadb://patrick-lab.cjeq2ffynlc2.ap-northeast-2.rds.amazonaws.com:3306/patricklab?characterEncoding=UTF-8&serverTimezone=UTC
select coachs0_.member_id as member_i4_0_0_, coachs0_.coach_id as coach_id1_0_0_, coachs0_.coach_id as coach_id1_0_1_, coachs0_.career as career2_0_1_, coachs0_.member_id as member_i4_0_1_, coachs0_.name as name3_0_1_, coachs0_.team_id as team_id5_0_1_ from coach coachs0_ where coachs0_.member_id=?
select coachs0_.member_id as member_i4_0_0_, coachs0_.coach_id as coach_id1_0_0_, coachs0_.coach_id as coach_id1_0_1_, coachs0_.career as career2_0_1_, coachs0_.member_id as member_i4_0_1_, coachs0_.name as name3_0_1_, coachs0_.team_id as team_id5_0_1_ from coach coachs0_ where coachs0_.member_id=3;
[2022-01-10 23:27:47.213] [http-nio-9000-exec-2] [4c0222d3] INFO m.p.l.m.c.MemberController - Coach1
I believe it is because you are using the spring-boot default setting which the spring.jpa.open-in-view is set to true .
This property enables OpenSessionInView pattern which you can simply think that a transaction will be opened automatically for you at the very first beginning when processing any HTTP request (e.g. in the Servlet Filter etc). Because of this , a transaction is actually already open before your service method executes and it is still active after your service method completes. Hence you will not experience any LazyInitializationException even after you access non-initialized properties outside the service method as the transaction is still active.
There is a strong debate about whether or not spring-boot should enable it by default in the past . You can refer this for more details if you are interested. I personally would recommend to turn it off.

Order by #oneToMany field using JPA Specification

Consider the entities below -
#Entity
public class Employee {
#Id
#GeneratedValue
private long id;
private String name;
#OneToMany(mappedBy = "employee", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Phone> phones; //contains both "active" & "inactive" phones
}
#Entity
public class Phone {
#Id
#GeneratedValue
private long id;
private boolean active;
private String number;
#ManyToOne(fetch = FetchType.LAZY)
private Employee employee;
}
I need to pull all the employees and sort them depending on the count of "active" phones they have.
Please note that the employee can have active as well as inactive phones. So the query I am trying to achieve is
ORDER BY (SELECT
COUNT(phone4_.employee_id)
FROM
phone phone4_
WHERE
employee4_.id = phone4_.employee_id
AND phone4_.active = true
) DESC
I am stuck with specification here because of some reason and below is the code I have used -
List<Order> orders = new ArrayList<>();
orders.add(cb.desc(cb.size(employee.get("phones"))));
cq.orderBy(orders);
When I run the code the query that's get generated is
ORDER BY (SELECT
COUNT(phone4_.employee_id)
FROM
phone phone4_
WHERE
employee4_.id = phone4_.employee_id) DESC
I am unable to add an extra AND condition to the logic. Please suggest
As specified in the Persistence API specification:
4.6.16 Subqueries
Subqueries may be used in the WHERE or HAVING clause.
JPA doesn't support subqueries in the order by clause, nor in the select clause.
Hibernate ORM, though, supports them in the SELECT and WHERE clauses.
So you cannot write that query and being JPA compliant.
This HQL should work though and it's covered by Hibernate ORM:
SELECT e1, (SELECT count(p)
FROM Phone p
WHERE p.active = true AND p.employee = e1) as activeCount
FROM Employee e1
ORDER BY activeCount DESC
Surprisingly, writing this query with criteria doesn't work:
CriteriaBuilder builder = ormSession.getCriteriaBuilder();
CriteriaQuery<Object> criteria = builder.createQuery();
Root<Employee> root = criteria.from( Employee.class );
Subquery<Long> activePhonesQuery = criteria.subquery( Long.class );
Root<Phone> phoneRoot = activePhonesQuery.from( Phone.class );
Subquery<Long> phonesCount = activePhonesQuery
.select( builder.count( phoneRoot ) )
.where( builder.and( builder.isTrue( phoneRoot.get( "active" ) ), builder.equal( phoneRoot.get( "employee" ), root ) ) );
criteria.multiselect( root, phonesCount )
.orderBy( builder.desc( phonesCount ) );
The reason is that, Hibernate ORM tries to expand the subquery in the order by clause instead to refer to an alias. And as I mentioned before, this is not supported.
I think the HQL is the easiest option if you don't want to use native queries.

Hibernate performs update and delete on custom JPQL

I am trying to update the fields of an entity that has a ManyToMany relationship, however, as I just want to update the table fields and ignore the ManyToMany relationship. The relationship is between the Company and UserSystem entities, it was defined in the relationship that company_user_system is the union table of the entities. The problem is that when executing my update in Company, always before my update, Hibernate makes an update in company and the relationship delete in user_system_company and this erases the relationship between Company and UserSystem and I don't understand why these two queries occur if I don't execut.
These are the queries, the first and second are not executed by my code:
Hibernate: update company set active=?, email=?, identification_code=?, trading_name=?, update_on=? where id=?
Hibernate: delete from company_user_system where company_id=?
Hibernate: update company set email=?, phone=?, corporate_name=?, trading_name=?, identification_code=?, email=?, phone2=? where id=?
Hibernate: select company0_.id as id1_0_, company0_.active as active2_0_, company0_.corporate_name as corporat3_0_, company0_.created_on as created_4_0_, company0_.email as email5_0_, company0_.email2 as email6_0_, company0_.identification_code as identifi7_0_, company0_.phone as phone8_0_, company0_.phone2 as phone9_0_, company0_.trading_name as trading10_0_, company0_.update_on as update_11_0_ from company company0_ where company0_.id=?
Following is the update implementation code:
public class CompanyRepositoryImpl implements CompanyRepositoryCustom {
#PersistenceContext
private EntityManager entityManager;
public Company updateCompanyFields(Company company) {
// ... fieldSql implementation omitted
String sql = "UPDATE Company SET "+ fieldsSql +" WHERE id = :id ";
Query query = entityManager.createQuery(sql);
// set the values for the fields
for (Method method : getMethods) {
query.setParameter(lowercaseFirstCharInString(cutGetInMethods(method.getName())), method.invoke(company));
}
// set id
query.setParameter("id", company.getId());
// execute update and search the database to return the updated object
if (query.executeUpdate() == 1) {
query = entityManager.createQuery("SELECT c FROM Company c WHERE c.id = :id");
query.setParameter("id", company.getId());
Company getCompany = (Company) query.getResultList().get(0);
return getCompany;
}
return null;
}
// ... Other methods omitted
}
Repository Code:
#Repository
public interface CompanyRepository extends JpaRepository<Company, Long>, JpaSpecificationExecutor<Company> , CompanyRepositoryCustom {
#Modifying
Company updateCompanyFields(Company company);
}
Company entity code, I just added the attributes that I think may contain something useful to try to solve the problem:
#Entity
#DynamicUpdate
#Table(name = "company")
public class Company implements Serializable {
#CreationTimestamp
#Column(name = "created_on", nullable = false)
private Instant createdOn;
#UpdateTimestamp
#Column(name = "update_on")
private Instant updateOn;
#ManyToMany
#JoinTable(
name = "company_user_system",
joinColumns = #JoinColumn(
name = "company_id", referencedColumnName = "id"
),
inverseJoinColumns = #JoinColumn(
name = "user_system_id", referencedColumnName = "id"
)
)
private Set<UserSystem> userSystems = new HashSet<>();
}
The UserSystem class defines the relationship as follows:
#ManyToMany(mappedBy = "userSystems")
private Set<Company> companies = new HashSet<>();
What may be causing this update and delete before my update?
This happens because you changed somewhere the value(s) of your relationship. EntityManager tracks such changes and marks the entity as dirty. When you execute a custom SQL query Hibernate will perform all the pending queries (submit any dirty entities).
You may prevent it by calling EntityManager.clear().

How to do JPA join in one to many situations?

I have two entities Outlet and products having one to many relationship as follows-
#Entity
public class Outlet{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
Long id;
#Fetch (FetchMode.SELECT)
#OneToMany(mappedBy = "outletProduct",fetch = FetchType.LAZY)
List<Product> products;
and
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
Boolean isActive;
#ManyToOne
#JoinColumn(name = "outletId")
Outlet outletProduct;
Now i want Outlets where products isactive is true.
I am writing follwing JPQL for-
#Query("Select op From Outlet op join op.products pro where pro.isActive=1 order by op.id")
List<Outlet> findAllByVarietiesPriceTimePeriodIn( );
But i am still getting outlets with prodcuts isactive false.
Please suggest.
I want to achieve result which will come from this sql query-
SELECT * FROM outlet join product on product.outlet_id= outlet.id where product.is_active=1 ;
I want Outlet where it has at least one Product but i dont want Products which is isactive false. Right now I am getting all Products in the List
It's not recommended, but will work using JOIN FETCH:
Select op From Outlet op join fetch op.products pro where pro.isActive = 1 order by op.id

spring data jpa findAll() not working properly

I am having below classes
#Entity
#Table(name = "USR_E_GROUPS")
public class GroupEntity {
#Id
#Column(name = "UIDUSERGROUP")
#GenericGenerator(name = "generator", strategy = "uuid2")
#GeneratedValue(generator = "generator")
private String id;
.........
#OneToMany(mappedBy = "group", cascade = CascadeType.PERSIST)
private List<UserGroupEntity> users;
same is for UserGroupEntity
now if I use groupRepoository.findAll()
It's is firing select query for every Group and inside different select query for UserGroupEntity. so it's taking too much time.
I want to make it to fire select with join so it will be a single query.
This is probably an n + 1 issue.
From the docs
By default, Hibernate3 uses lazy select fetching for collections and
lazy proxy fetching for single-valued associations. These defaults
make sense for most associations in the majority of applications.
By default the children are fetched lazily. Use JOIN FETCH to get the result in a single query.
In your GroupRepoository
#Query("SELECT g FROM GroupEntity g JOIN FETCH g.users gu")
List<GroupEntity> findAllEager();

Resources