I have a query that uses a constructor expression. For the most part it works, but I wanted to reduce the number of queries on the database. So I tried FETCH and #EntityGraph, but I am getting
org.springframework.dao.InvalidDataAccessResourceUsageException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy ...
I tried removing the FETCH and just use JOIN to fix the problem, but it does not do the fetch operation.
Is this even allowed or possible with Hibernate/JPA?
Code wise it looks something like
#Query("select new example.BillingTargetAndStudent(bt,s) from Student s, BillingTarget bt where s.id = bt.targetID and bt.account.status = :accountStatus and bt.account.organization = :organization")
Stream<BillingTargetAndStudent> streamAllByOrganizationAndStatusForStudentList(Organization organization, AccountStatus accountStatus);
What I want to do is FETCH bt.account in the same query along with s.user.attributes. The bt.account isn't too hard to workaround, but the s.user.attributes would not be possible with the constructor expression as it is a collection
What I ended up doing is to use the Stream<Object[]> and select bt, s from ... then have a converter that changes Object[] to call the constructor expression
default Stream<BillingTargetAndStudent> streamAllByOrganizationAndStatusForStudentList(Organization organization, AccountStatus accountStatus) {
return streamAllByOrganizationAndStatusForStudentList0(organization, accountStatus)
.map(o -> new BillingTargetAndStudent((BillingTarget) o[0], (Student) o[1]));
}
#Query("select bt, s from Student s join fetch s.user u join fetch u.attributes, BillingTarget bt join fetch bt.account a where s.id = bt.targetID and bt.account.status = :accountStatus and bt.account.organization = :organization")
Stream<Object[]> streamAllByOrganizationAndStatusForStudentList0(Organization organization, AccountStatus accountStatus);
I can reduce the query slightly by putting in one #EntityGraph
#EntityGraph("Student.forStudentList")
#Query("select bt, s from Student s, BillingTarget bt left join fetch bt.billingContractTargets bct join fetch bt.account a where s.id = bt.targetID and bt.account.status = :accountStatus and bt.account.organization = :organization")
Stream<Object[]> streamAllByOrganizationAndStatusForStudentList0(Organization organization, AccountStatus accountStatus);
#NamedEntityGraph(
name = "Student.forStudentList",
attributeNodes = {
#NamedAttributeNode("schedules"),
#NamedAttributeNode(value = "user", subgraph = "user.attributes"),
},
subgraphs = {
#NamedSubgraph(name = "user.attributes",
attributeNodes = {#NamedAttributeNode("attributes")})
}
)
But I am limited to just one and it has to be the entity for the repository. I cannot say
#EntityGraph("BillingTarget.forStudentList")
#EntityGraph("Student.forStudentList")
Related
I have a Model called CommunityProfile. This model contains two child relationships; player (type User), and rank (type Rank).
The default spring boot JPA-generated query is taking approximately 9s to fetch 200 records, which is rather slow. By using the following MySQL query, I can return the data I need rather quickly:
SELECT cp.*, r.*, u.* FROM community_profiles cp
LEFT JOIN users u ON cp.player_id = u.id
LEFT JOIN ranks r ON cp.rank_id = r.id
WHERE cp.community_id = 1
How can I make my repository map the results to their correct Objects/Models?
I have tried using a non-native query, like this:
#Query("SELECT cp FROM CommunityProfile cp " +
"LEFT JOIN FETCH cp.player u " +
"LEFT JOIN FETCH cp.rank r " +
"WHERE cp.communityId = :communityId")
List<CommunityProfile> findByCommunityIdWithJoin(#Param("communityId") Integer communityId);
However, this is still quite slow in comparison, resulting in an 800-900ms response. For comparison, my current Laravel application can return the same data in a 400-ms cold start.
Any tips are appreciated, thank you
==UPDATE==
After trying the suggested #Index annotation, I still don't really see any performance gains. Did I implement correctly?
#Entity
#Table(name = "community_profiles", indexes = #Index(name = "cp_ci_idx", columnList = "community_id"))
public class CommunityProfile {
If your JPA query is working, and you are just asking about performance, you may add the following index:
CREATE INDEX idx ON community_profiles(community_id);
This index should allow MySQL to filter off records which are not part of the result set.
From JPA itself you may use:
#Table(indexes = #Index(name = "idx", columnList = "community_id"))
public class CommunityProfile {
// ...
}
Have you tried EntityManager
#PersistenceContext(type = PersistenceContextType.TRANSACTION)
private EntityManager entityManager;
List<CommunityProfile> findByCommunityIdWithJoin(Integer communityId){
String query = ""SELECT cp FROM CommunityProfile cp " +
"LEFT JOIN FETCH cp.player u " +
"LEFT JOIN FETCH cp.rank r " +
"WHERE cp.communityId = :communityId"
List<CommunityProfile> list = entityManager.createNativeQuery(query, CommunityProfile.class)
.setParameter("communityId",communityId)
.getResultList();
entityManager.clear();
return list
}
Once I used this kind of native query inside loop and it constantly returned cash values bu entityManagaer.clear() clears cash. This is for info only)
Or create an Index on specific columns when you are defining entity classes like:
#Table(indexes = {
#Index(columnList = "firstName"),
#Index(name = "fn_index", columnList = "firstName"),
#Index(name = "mulitIndex1", columnList = "firstName, lastName")
...
}
For Non-entity #Index you can check documentation
This is SQL Query I have tried to join all the tables using Spring Data JPA. Some please help me out.
SELECT op.id AS "Profile ID",
op.organization_name AS "Organization name",
pav.annotation AS "legacy-sams-acc-id",
ip.subnet AS "CIDR IP range"
FROM profile.profile p
JOIN profile.organization_profile op ON op.id = p.id
JOIN profile.profile_annotation_type pat ON pat.publisher_id = p.supplier_publisher_id
LEFT OUTER JOIN
(SELECT pa.id AS id,
pa.profile_id AS profile_id,
ftpa.annotation AS annotation,
ftpa.free_text_profile_annotation_type_id AS free_text_profile_annotation_type_id
FROM profile.profile_annotation pa
JOIN profile.free_text_profile_annotation ftpa ON ftpa.id = pa.id
) AS pav ON pav.free_text_profile_annotation_type_id = pat.id AND pav.profile_id = p.id
LEFT OUTER JOIN profile.ip_range_identifier ip ON ip.organization_profile_id = op.id
WHERE pat.description = 'legacy-sams-acc-id'
AND p.supplier_publisher_id = 66
ORDER BY op.id ASC
Following will be the corresponding Java class. I'm struck with converting SQL to Java.
public Specification<OrganizationProfile> organizationProfileReportCriteria(Long publisherId) {
return (root, criteriaQuery, criteriaBuilder) -> {
Subquery<Profile> profileSubquery = criteriaQuery.subquery(Profile.class);
Root<Profile> profileRoot = profileSubquery.from(Profile.class);
Join<Profile, OrganizationProfile> profileOrganizationProfileJoin = profileRoot.join("supplierPublisher");
profileSubquery.select(profileRoot)
.where(criteriaBuilder.equal(profileOrganizationProfileJoin, root),
criteriaBuilder.equal(profileRoot.get("supplierPublisher").get("id"), publisherId));
Subquery<ProfileAnnotationType> profileAnnotationTypeSubquery = criteriaQuery.subquery(ProfileAnnotationType.class);
Root<ProfileAnnotationType> profileAnnotationTypeRoot = profileAnnotationTypeSubquery.from(ProfileAnnotationType.class);
Join<ProfileAnnotationType, OrganizationProfile> organizationProfileAnnotationTypeJoin
= profileAnnotationTypeRoot.join("publisher");
profileAnnotationTypeSubquery.select(profileAnnotationTypeRoot)
.where(criteriaBuilder.equal(organizationProfileAnnotationTypeJoin, root),
criteriaBuilder.equal(profileAnnotationTypeRoot.get("publisher").get("id"), publisherId));
Subquery<ProfileAnnotation> profileAnnotationSubquery = criteriaQuery.subquery(ProfileAnnotation.class);
Root<ProfileAnnotation> profileAnnotationRoot = profileAnnotationTypeSubquery.from(ProfileAnnotation.class);
Join<ProfileAnnotation, OrganizationProfile> profileAnnotationOrganizationJoin
= profileAnnotationRoot.join("profile");
profileAnnotationSubquery.select(profileAnnotationRoot)
.where(criteriaBuilder.equal(profileAnnotationOrganizationJoin, root),
criteriaBuilder.equal(profileAnnotationRoot.get("profile").get("supplierPublisher").get("id"), publisherId));
return criteriaBuilder.or(criteriaBuilder.exists(profileSubquery));
};
}
Basically, I'm not sure how to join multiple tables using JPA. Someone, please explain to me how to convert multiple join and Subquery for converting SQL to Java. Basically confusion about profile and Organization profile table.
I have an #Entity class Company with several attributes, referencing a companies Table in my db. One of them represents a Map companyProperties where the companies table is extended by a company_properties table, and the properties are saved in key-value format.
#Entity
#Table(name = "companies")
public class Company extends AbstractEntity {
private static final String TABLE_NAME = "companies";
#Id
#GeneratedValue(generator = TABLE_NAME + SEQUENCE_SUFFIX)
#SequenceGenerator(name = TABLE_NAME + SEQUENCE_SUFFIX, sequenceName = TABLE_NAME + SEQUENCE_SUFFIX, allocationSize = SEQUENCE_ALLOCATION_SIZE)
private Long id;
//some attributes
#ElementCollection
#CollectionTable(name = "company_properties", joinColumns = #JoinColumn(name = "companyid"))
#MapKeyColumn(name = "propname")
#Column(name = "propvalue")
private Map<String, String> companyProperties;
//getters and setters
}
The entity manager is able to perform properly find clauses
Company company = entityManager.find(Company.class, companyId);
However, I am not able to perform JPQL Queries in this entity and retrieve the Map accordingly. Since the object is big, I just need to select some of the attributes in my entity class. I also do not want to filter by companyProperties but to retrieve all of them coming with the proper assigned companyid Foreign Key. What I have tried to do is the following:
TypedQuery<Company> query = entityManager.createQuery("SELECT c.id, c.name, c.companyProperties " +
"FROM Company as c where c.id = :id", Company.class);
query.setParameter("id", companyId);
Company result = query.getSingleResult();
The error I get is:
java.lang.IllegalArgumentException: An exception occurred while creating a query in EntityManager:
Exception Description: Problem compiling [SELECT c.id, c.name, c.companyProperties FROM Company as c where c.id = :id]. [21, 40] The state field path 'c.companyProperties' cannot be resolved to a collection type.
org.eclipse.persistence.internal.jpa.EntityManagerImpl.createQuery(EntityManagerImpl.java:1616)
org.eclipse.persistence.internal.jpa.EntityManagerImpl.createQuery(EntityManagerImpl.java:1636)
com.sun.enterprise.container.common.impl.EntityManagerWrapper.createQuery(EntityManagerWrapper.java:476)
Trying to do it with joins (the furthest point I got was with
Query query = entityManager.createQuery("SELECT c.id, c.name, p " +
"FROM Company c LEFT JOIN c.companyProperties p where c.id = :id");
does not give me either the correct results (it only returns the value of the property and not a list of them with key-value).
How can I define the right query to do this?
Your JPA syntax looks off to me. In your first query you were selecting individual fields in the Company entity. But this isn't how JPA works; when you query you get back the entire object, with which you can access any field you want. I propose the following code instead:
TypedQuery<Company> query = entityManager.createQuery("from Company as c where c.id = :id", Company.class);
query.setParameter("id", companyId);
Company result = query.getSingleResult();
Similarly, for the second join query I suggest the following code:
Query query = entityManager.createQuery("SELECT c" +
"FROM Company c LEFT JOIN c.companyProperties p WHERE c.id = :id");
query.setParameter("id", companyId);
List<Company> companies = query.getResultList();
The reason why only select a Company and not a property entity is that properties would appear as a collection inside the Company class. Assuming a one to many exists between companies and properties, you could access the propeties from each Company entity.
You are expecting to get a complete Company object when doing select only on particular fields, which is not possible. If you really want to save some memory (which in most cases would not be that much of a success) and select only some field, then you should expect a List<Object[]>:
List<Object[]> results = entityManager.createQuery("SELECT c.id, c.name, p " +
"FROM Company c LEFT JOIN c.companyProperties p where c.id = :id")
.setParameter("id", companyId)
.getResultList();
Here the results will contain a single array of the selected fields. You can use getSingleResult, but be aware that it will throw an exception if no results were found.
I am trying to use HQL fetching my entity along with sub-entities using JOIN FETCH, this is working fine if I want all the results but it is not the case if I want a Page
My entity is
#Entity
#Data
public class VisitEntity {
#Id
#Audited
private long id;
.
.
.
#OneToMany(cascade = CascadeType.ALL,)
private List<VisitCommentEntity> comments;
}
and because I have millions of visits I need to use Pageable and I want to Fetch the comments in a single database query like :
#Query("SELECT v FROM VisitEntity v LEFT JOIN FETCH v.comments WHERE v.venue.id = :venueId and ..." )
public Page<VisitEntity> getVenueVisits(#Param("venueId") long venueId,...,
Pageable pageable);
That HQL call throws the following exception:
Caused by: java.lang.IllegalArgumentException: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=null,role=com.ro.lib.visit.entity.VisitEntity.comments,tableName=visitdb.visit_comment,tableAlias=comments1_,origin=visitdb.visit visitentit0_,columns={visitentit0_.visit_id ,className=com.ro.lib.visit.entity.VisitCommentEntity}}] [select count(v) FROM com.ro.lib.visit.entity.VisitEntity v LEFT JOIN FETCH v.comments WHERE v.venue.id = :venueId and (v.actualArrival > :date or v.arrival > :date)]
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1374)
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1310)
at org.hibernate.ejb.AbstractEntityManagerImpl.createQuery(AbstractEntityManagerImpl.java:309)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
and once I remove the paging everything works fine
#Query("SELECT v FROM VisitEntity v LEFT JOIN FETCH v.comments WHERE v.venue.id = :venueId and ..." )
public List<VisitEntity> getVenueVisits(#Param("venueId") long venueId,...);
Obviously the problem is the count query from Spring-Data, but how can we fix it?
The easiest way is to use the countQuery attribute of the the #Query annotation to provide a custom query to be used.
#Query(value = "SELECT v FROM VisitEntity v LEFT JOIN FETCH v.comments …",
countQuery = "select count(v) from VisitEntity v where …")
List<VisitEntity> getVenueVisits(#Param("venueId") long venueId, …);
Alternatively in newest versions of Spring (supporting JPA 2.1 specification) you can use entity graph like this:
#EntityGraph(attributePaths = "roles")
#Query("FROM User user")
Page<User> findAllWithRoles(Pageable pageable);
Of course named entity graphs work as well.
You have to specify countQuery param for #Query and now you can use Page or List as return value.
#Query(value = "SELECT v FROM VisitEntity v LEFT JOIN FETCH v.comments WHERE v.venue.id = :venueId and ...",
countQuery = "SELECT count(v) FROM VisitEntity v LEFT JOIN v.comments WHERE v.venue.id = :venueId and ..." )
public Page<VisitEntity> getVenueVisits(#Param("venueId") long venueId,...,
Pageable pageable);
If you want completely control your query build by Specification with join fetch you can check CriteriaQuery return type and change join fetch logic according to query type like this:
public class ContactSpecification implements Specification<Contact> {
#Override
public Predicate toPredicate(Root<Contact> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
if(query.getResultType() == Long.class) {
root.join(Contact_.company);
} else {
root.fetch(Contact_.company);
}
return cb.equal(root.get(Contact_.company).get(Company_.name), "Company 123");
}
}
I was not able to find this info in documentation, but from SimpleJpaRepository.getCountQuery() method you can see query for count request first build for Long return type, and later fetch for expected class is running.
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Long> query = builder.createQuery(Long.class);
Root<S> root = applySpecificationToCriteria(spec, domainClass, query);
It can be not reliable since it is an implementation details which can be changed, but it works.
Try countProjection
#Query(value="SELECT v FROM VisitEntity v LEFT JOIN FETCH v.comments WHERE v.venue.id = :venueId and ..." ,
countProjection = "v.id")
public Page<VisitEntity> getVenueVisits(#Param("venueId") long venueId,...,
Pageable pageable);
I have a query
List<Integer> ids = sessionFactory.getCurrentSession()
.createSQLQuery("SELECT l.id FROM likes l INNER JOIN liketoanswers lta ON l.id = lta.id AND lta.answer_id = :a_id")
.setParameter("a_id",answer.getId())
.list();
List<Like> likes = new ArrayList<>();
for (Integer id : ids) {
likes.add(findById(id));
}
return likes;
I want to make this query like .createQuery("from Like l inner join fetch likes.answers la where la.id = answer.getId ..)
but word Like is registered in sql and a dont want to change entity class name.
I known that there a method to substitute class , but cannot find