I have entity Workflow which has #OneToMany relation with ValidationResults class. It's fetch Lazy but sometimes I would like to get all the Workflows and interate on them accessing the ValidationResults. In that moment I want jpa to get all the data eagerly not query each time I access ValidationResults. I use springDataJpa, How to do it, is there any way to do it with #Query ?
I try to achieve something like that but I don't know how
//here all the workflows has corresponding data eagerly
List<Workflow> workflows = workflowService.getAllWorkflowsWithValidationResultsEagerly();
//here validationResults ref is lazy, when I try to access it it does query
List<Workflow> workflows = workflowService.getAllWorkflowsUsually();
Here are my entities.
#Entity
#Table(name = "workflow")
public class Workflow {
..............
#OneToMany(fetch = FetchType.LAZY, mappedBy = "workflow", cascade = CascadeType.ALL)
private Set<ValidationResults> validationResultsSet = new HashSet<>();
public Set<ValidationResults> getValidationResultsSet(){return this.validationResultsSet;}
...............
}
And ValidationResult class
#Entity
#Table(name = "validation_results")
public class ValidationResults {
...
#ManyToOne
#JoinColumn(name = "workflow_id", insertable = false, updatable = false)
private Workflow workflow;
....
}
The spring boot-ish way of doing this is by using the #EntityGraph as described in the documentation.
You can use fetch join in order to do it on #Query https://www.logicbig.com/tutorials/java-ee-tutorial/jpa/fetch-join.html
#Query("SELECT DISTINCT e FROM Employee e INNER JOIN FETCH e.tasks t")
If you don't want to create another query, just call .size() of your list
Related
I am trying to make use of JPA query to fetch the data from DB using below query:
from Candidate c where c.id = :id
where, id = candidate_id(primary key of main table).
This query fetch the data from all of its associated tables as well, whereas my requirement is to fetch the data only from 2 of its associated tables. Because 3rd table contains heavy JSON data which degrades the response time of above query.
I tried using JOIN, Fetch JOIN of parent with its 2 associated tables but it didn't work. I was also looking into how we can skip just one column data(column of 3rd table with heavy JSON) while fetching the Candidate data but with no luck.
Is it possible to achieve it using JPA query or I need to try something else? I am good to save candidate and all its associated tables data in one go using repo.save(candidate) but don't want to fetch one of the related tables data.
This is how I have made the association in spring-boot:
CANDIDATE ENTITY:
#OneToOne(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "candidate")
#JsonManagedReference
private Address address;
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "candidate")
#JsonManagedReference
private Set<Skills> skills= new HashSet<>();
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "candidate")
#JsonManagedReference
private Set<Prefrence> prefrences = new HashSet<>();
ADDRESS ENTITY:
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "candidate_id")
#JsonBackReference
private Candidate candidate;
SKILLS ENTITY:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "candidate_id")
#JsonBackReference
private Candidate candidate;
//rest of the fields
PREFERENCE ENTITY:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "candidate_id")
#JsonBackReference
private Candidate candidate;
//rest of the fields
I don't want to fetch Preference entity data when using candidateRepo.findById(id) It should be always null in fetched Candidate
Attached the diagram
diagram
What you need here is a DTO and I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Candidate.class)
public interface CandidateDto {
#IdMapping
Long getId();
String getName();
AddressDto getAddress();
Set<SkillsDto> getSkills();
#EntityView(Address.class)
interface AddressDto {
#IdMapping
Long getId();
String getName();
}
#EntityView(Skills.class)
interface SkillsDto {
#IdMapping
Long getId();
String getName();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
CandidateDto a = entityViewManager.find(entityManager, CandidateDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<CandidateDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!
In my REST API project (Java 8, Spring Boot 2.3.1) I have a problem with some queries triggering massive query chains by loading lazy relations, even though the related objects are never accessed.
I have a UserEntity and a polymorphic CompanyEntity that are related with a ManyToMany relationship. I have an endpoint that returns all users and I include the IDs of the related companies in the JSON. I excpect a query to the user table and a query to the company table, however all related entities of one sub-entity of CompanyEntity are always loaded for each of those sub-entities resulting in large query chains.
Here are snippets of my classes:
User entity
#Entity(name = "USERS")
public class UserEntity {
#Id
#GeneratedValue
private UUID id;
#EqualsAndHashCode.Exclude
#Fetch(FetchMode.SUBSELECT)
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "users_company",
joinColumns = #JoinColumn(name = "USER_ID"),
inverseJoinColumns = #JoinColumn(name = "COMPANY_ID")
)
private Set<CompanyEntity> companies = new HashSet<>();
public List<UUID> getCompanyIds() {
return companies.stream()
.map(CompanyEntity::getId)
.collect(Collectors.toList());
}
}
Polymorphic company entity
#Entity(name = "COMPANY")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class CompanyEntity {
#Id
#GeneratedValue
private UUID id;
#Fetch(FetchMode.SUBSELECT)
#ManyToMany(mappedBy = "companies", fetch = FetchType.LAZY)
private Set<UserEntity> users = new HashSet<>();
}
Concrete company subclass that triggers the problem
#Entity(name = "CUSTOMER")
public class CustomerEntity extends CompanyEntity {
#NotNull
#OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
private ContactPersonEntity contactPerson;
#Fetch(FetchMode.SUBSELECT)
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY, mappedBy = "customer")
private Set<TransactionEntity> transactions = new HashSet<>();
public Set<UUID> getTransactionIds() {
return this.transactions.stream()
.map(TransactionEntity::getId)
.collect(Collectors.toSet());
}
}
In the REST controller I return the following mapping:
#GetMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE)
public List<UserReadModel> getUsers() {
return userRepository.findAll().stream()
.map(userEntity -> new UserReadModel(userEntity))
.collect(Collectors.toList());
}
Where the UserReadModel is a DTO:
#Data
public class UserReadModel {
private UUID id;
private List<UUID> companyIds;
}
Logging the database queries results in the following output:
// Expected
Hibernate: select userentity0_.id as id1_47_, ... from users userentity0_
Hibernate: select companies0_.user_id ... case when companyent1_1_.id is not null then 1 when companyent1_2_.id is not null then 2 when companyent1_.id is not null then 0 end as clazz_0_ from users_company companies0_ inner join company companyent1_ on companies0_.company_id=companyent1_.id left outer join customer companyent1_1_ on companyent1_.id=companyent1_1_.id left outer join external_editor companyent1_2_ on companyent1_.id=companyent1_2_.id where companies0_.user_id in (select userentity0_.id from users userentity0_)
// Unexpected as they are marked lazy and never accessed
Hibernate: select contactper0_.id ... from contact_person contactper0_ where contactper0_.id=?
Hibernate: select transactio0_.customer_id ... from transactions transactio0_ where transactio0_.customer_id=?
Hibernate: select contactper0_.id ... from contact_person contactper0_ where contactper0_.id=?
Hibernate: select transactio0_.customer_id ... from transactions transactio0_ where transactio0_.customer_id=?
...
I've read through loads of articles on entity mapping and lazy loading but I can't seem to find a reason why this behavior persists. Did anyone have this problem before?
You are accessing the collection, so Hibernate has to load the collection. Since you only need the ids and already have a DTO, I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(UserEntity.class)
public interface UserReadModel {
#IdMapping
UUID getId();
#Mapping("companies.id")
Set<UUID> getCompanyIds();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
UserReadModel a = entityViewManager.find(entityManager, UserReadModel.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<UserReadModel> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary! In your case, a query like the following will be generated:
select u.id, uc.company_id
from users u
left join users_company uc on uc.user_id = u.id
left join company c on c.id = uc.company_id
Depending on the Hibernate version, the join for the company might even be omitted.
I eventually figured out the solution and want to post it here, in case anyone stumbles upon this question. This was purely a mistake on my side and is not reproducible from the examples I posted.
I used lombok annotations to generate equals and hashcode methods on the customer entity (and all other entities for that matter) and forgot to annotate the contactPerson and transactions fields with #EqualsAndHashcode.Exclude. As the equals method was called somewhere along the execution, it triggered the lazy loading of those fields. Implementing equals and hashcode manually and using the guidelines from this article for that solved the problem.
I have a list of projects and a list of customers. A project can be for one customer and every customer can have many projects. So it's a simple 1:n relationship where the project is the owning side.
Simplified to the essential it is
#Entity
public class Project {
#Id
long id;
#ManyToOne(optional = true)
#JoinColumn(name = "customer", nullable = true, updatable = true)
Customer customer;
}
#Entity
public class Customer {
#Id
long id;
}
When I load a list of projects, I want to retrieve the customers efficiently at the same time. This is not the case. There is one single query for the projects and then for every distinct customer that is encountered a separate query is issued.
So say I have 100 projects that are assigned to 50 different customers. This would result in one query for the projects and 50 queries for the customers.
This quickly adds up and for large project/customer lists our application gets rather slow. Also this is just one example. All our entities with relationships are affected by this behavior.
I already tried #Fetch(FetchMode.JOIN) on the customers field as suggested here but it does nothing and FetchMode.SUBQUERY is not applicable according to Hibernate:
org.hibernate.AnnotationException: Use of FetchMode.SUBSELECT not allowed on ToOne associations
How can I fix this problem?
If you are using Spring Data JPA to implement your repositories, you can specify lazy fetching in the JPA entities:
#Entity
public class Project {
#Id
long id;
#ManyToOne(fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "customer", nullable = true, updatable = true)
Customer customer;
}
#Entity
public class Customer {
#Id
long id;
...
}
And add #EntityGraph to your Spring Data JPA-based repository:
#Repository
public interface ProjectDao extends JpaRepository<Project, Long> {
#EntityGraph(
type = EntityGraphType.FETCH,
attributePaths = {
"customer"
}
)
Optional<Project> findById(Long id);
...
}
My blog post at https://tech.asimio.net/2020/11/06/Preventing-N-plus-1-select-problem-using-Spring-Data-JPA-EntityGraph.html helps you preventing the N+1 select problem using Spring Data JPA and #EntityGraph.
Yes, it is a by-the-book example of the n+1 selects problem.
The approach I use in most cases is to make the association lazy and define a batch size.
Alternatively, you could use a JPQL query with [left] join fetch to initialize the association directly from the query result set:
select p from Project p left join fetch p.customer
Yes, it is a by-the-book example of the n+1 selects problem as #dragan-bozanovic said.
In Spring-Boot 2.1.3 #Fetch(FetchMode.JOIN) can be used to solve it:
#ManyToOne(optional = true)
#Fetch(FetchMode.JOIN)
#JoinColumn(name = "customer", nullable = true, updatable = true)
Customer customer;
Warning: If the relationship can be invalid, for example when marked with #NotFound(action = NotFoundAction.IGNORE), each invalid relationship will trigger another SELECT query.
I have 2 entities with #Where annotation. First one is Category;
#Where(clause = "DELETED = '0'")
public class Category extends AbstractEntity
and it has the following relation;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "category")
private Set<SubCategory> subCategories = Sets.newHashSet();
and second entity is SubCategory;
#Where(clause = "DELETED = '0'")
public class SubCategory extends AbstractEntity
and contains corresponding relation;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CATEGORY_ID")
private Category category;
Whenever I call the following Dao method;
#Query(value = "select distinct category from Category category join fetch category.subCategories subcategories")
public List<Category> findAllCategories();
I got the following sql query;
select
distinct category0_.id as id1_3_0_,
subcategor1_.id as id1_16_1_,
category0_.create_time as create2_3_0_,
category0_.create_user as create3_3_0_,
category0_.create_userip as create4_3_0_,
category0_.deleted as deleted5_3_0_,
category0_.update_time as update6_3_0_,
category0_.update_user as update7_3_0_,
category0_.update_userip as update8_3_0_,
category0_.version as version9_3_0_,
category0_.name as name10_3_0_,
subcategor1_.create_time as create2_16_1_,
subcategor1_.create_user as create3_16_1_,
subcategor1_.create_userip as create4_16_1_,
subcategor1_.deleted as deleted5_16_1_,
subcategor1_.update_time as update6_16_1_,
subcategor1_.update_user as update7_16_1_,
subcategor1_.update_userip as update8_16_1_,
subcategor1_.version as version9_16_1_,
subcategor1_.category_id as categor11_16_1_,
subcategor1_.name as name10_16_1_,
subcategor1_.category_id as categor11_3_0__,
subcategor1_.id as id1_16_0__
from
PUBLIC.t_category category0_
inner join
PUBLIC.t_sub_category subcategor1_
on category0_.id=subcategor1_.category_id
where
(
category0_.DELETED = '0'
)
Could you please tell me why the above query lacks
and subcategor1_.DELETED = '0'
inside its where block?
I have just solved a similar problem in my project.
It is possible to put #Where annotation not only on Entity, but on also on your child collection.
According to the javadoc:
Where clause to add to the element Entity or target entity of a collection
In your case, it would be like :
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "category")
#Where(clause = "DELETED = '0'")
private Set<SubCategory> subCategories = Sets.newHashSet();
Please find a similar issues resolved here
I believe thus solution is not as invasive compared to using Hibernate Filters.These filters are disabled by default and operate on Session level, thus enabling them each time new Session opens is extra work especially when your DAO works through abstractions like Spring Data
This is a quick reply;
#Where(clause = "DELETED = '0'")
public class SubCategory extends AbstractEntity
Where will effect when direct query for SubCategry.
To not get deleted sub categories use Hibernate Filters
as exampled on here
Consider the following model
#Entity
// JPA and JAXB annotations here
public class Employee implements Serializable {
// other fields, annotations, stuffs
...
#ElementCollection(fetch = FetchType.LAZY,
targetClass = Address.class)
#CollectionTable(name = "employee_address",
schema = "hris",
joinColumns = #JoinColumn(name = "employee_id",
nullable = false,
referencedColumnName = "employee_id",
foreignKey = #ForeignKey(ConstraintMode.CONSTRAINT)))
protected Set<Address> addresses;
// setters, getters
...
}
#Embeddable
// JAXB annotations here
public class Address implements Serializable {
// fields, setters, getters
}
The Address class is annotated with #Embeddable annotation, and the Employee class has an embedded element collection of addresses. The element collection's fetch is set to FetchType.LAZY. Now, I would like to create a #NamedQuery that would retrieve all employees with addresses eagerly initialized. Knowing that JOIN FETCH will only work with entity collections annotated with #OneToMany or #ManyToMany based on JPA 2.1, how would I create a valid JPQL query that would allow me to eagerly retrieve embedded element collections?
In the JPA 2.1 specification (JSR 338) I cannot find any hint that fetch joins only work on entity relationships (but not embeddables). JSR 338, section 4.4.5.3 even states:
A FETCH JOIN enables the fetching of an association or element collection as a side effect of the execution of a query.
As another hint the following minimal example (essentially resembling yours) executed with Hibernate 4.3.11 as JPA provider results in a single query:
Address embeddable:
#Embeddable public class Address { private String city; }
Employee entity:
#Entity public class Employee {
#Id private Long id;
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "address",
joinColumns = #JoinColumn(name="employee_id"))
private Set<Address> addresses;
}
JPQL Query:
em.createQuery("select e from Employee e join fetch e.addresses").getResultList();
Resulting SQL query:
select
employee0_.id as id1_1_,
addresses1_.employee_id as employee1_1_0__,
addresses1_.city as city2_5_0__
from
Employee employee0_
inner join
address addresses1_ on employee0_.id=addresses1_.employee_id
So the above JPQL query seems to solve your problem.
By the way, more effective way might be not to use join, but subselect
#Fetch(FetchMode.SUBSELECT)
#BatchSize(size=500)
it makes two selects, instead of one, but doesn't produce so much ambiguity.