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.
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
I'm using Quarkus and Hibernate / Panache.
For this example, I have 3 tables (table_a, table_b, table_c) that I am joining together using a native query. In the project I'm working on, it's around 5 JOIN tables to retrieve the information I'm looking for.
table_b is purely a mapping / join table for table_a and table_c:
SELECT
a.id,
a.name,
c.login_date
FROM
table_a a
JOIN table_b b ON b.a_id = a.id
JOIN table_c c ON b.c_id = c.id
WHERE
c.login_date > '01-MAY-21'
I'm porting the above to HQL. I've mapped all my #Entity classes with their respective #Table, along with their #Column names. We're good in that department.
SELECT
a.id,
a.name,
c.loginDate
FROM
TableA a
JOIN TableA b ON b.aId = a.id
JOIN TableB c ON b.cId = c.id
WHERE
c.loginDate > '01-MAY-21'
I'm only looking for name and login_date. There is a bunch of other information stored in table_a and table_c that I don't want for this specific query. So I created an entity for this call:
#Entity
#IdClass(LoginDetailsPk.class)
#NamedQuery(
name = "LoginDetails.findFromDate",
query = "FROM TableA a " +
"JOIN TableA b ON b.aId = a.id " +
"JOIN TableB c ON b.cId = c.id " +
"WHERE c.loginDate > '01-MAY-21'"
)
public class LoginDetails extends PanacheEntityBase {
#Id
private int id;
#Id
private String name;
#Id
private String loginDate;
public static List<LoginDetails> findFromDate(String fromDate) {
// Eventually pass fromDate into find()
return find("#LoginDetails.findFromDate").list();
}
}
I'm having a hard time trying to understand why the return even works. When I invoke LoginDetails.findFromDate(...) and store it in a List<LoginDetails>, it works fine. However, when I try to access the list, I get a ClassCastException error.
List<LoginDetails> details = LoginDetails.findFromDate(null);
for(LoginDetails detail : details) { // <------ Throws a class cast exception
//...
}
After debugging, I'm noticing that generic type stored in my List isn't even my LoginDetails class; rather, it's an array of objects (List<Object[]>) with all my #Entities and the irrelevant information I'm not looking for.
I'm lost. Would it make more sense to move back to a native query?
Your HQL is creating a Object[] for every row in the result, because you are not specifying any SELECT, and by default all the objects in the FROM clause are included in that Object array. If you want to return a LoginDetails object you need to create a constructor with all the attributes:
public LoginDetails(int id, String name, String loginDate) {
this.id = id;
this.name = name;
this.loginDate = loginDate;
}
And then change the query to:
query = "SELECT new LoginDetails(a.id, a.name, c.loginDate) "
"FROM TableA a " +
"JOIN TableA b ON b.aId = a.id " +
"JOIN TableB c ON b.cId = c.id " +
"WHERE c.loginDate > '01-MAY-21'"
See https://docs.jboss.org/hibernate/core/3.5/reference/en/html/queryhql.html#queryhql-select
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")
I have these following Classes:
class Person(){
#OneToMany(mappedBy="person")
private List<PersonRoles> roles;
}
class PersonRoles(){
#ManyToOne
#JoinColumn(name = "person_id", nullable = false)
private Person person;
#ManyToOne
#JoinColumn(name = "request_id")
private Request request;
}
class Request(){
#OneToMany(mappedBy="request")
private List<PersonRoles> roles;
}
Now I am going to fetch all person based on a given request id and his roles by using hibernate and inner join but my log is telling me that my table doesn't exist. This is my query so far:
sql = "SELECT p.* FROM person AS p INNER JOIN p.roles ON p.roles.personId = p.id
INNER JOIN request AS r ON p.roles.requestId = r.id AND p.roles.role like :item
AND r.id = :id";
query = session.createSQLQuery(sql);
query.addEntity(Person.class);
query.setParameter("item", "Members");
query.setParameter("id", id);
person = (Person) query.uniqueResult();
and this is what i received on the log:
Table 'p.roles' doesn't exist
Did i forget some hibernate annotation? or My query has something wrong?
Brief reason
your syntax of SQL is wrong
Detailed explanation
here is the syntax of inner join example
SELECT column_name(s)
FROM table1
INNER JOIN table2
ON table1.column_name = table2.column_name;
for multiple inner join
SELECT *
FROM table1
INNER JOIN table2
ON table1.primaryKey=table2.table1Id
INNER JOIN table3
ON table1.primaryKey=table3.table1Id
but you have used INNER JOIN p.roles there should be a table name after the INNER JOIN, not a column name.
that's why you got an error, moreover, use HQL instead of SQL in hibernate it is a good practice.
happy coding!
table Student
----------------
id | somecolumn with string| fk(which is self referencing ID of the same table)
how can i make hibernate query that will fetch all items where value of id will match value of fk.
here is what i have tried but it doesn't works (it returns only 1 result instead of result set)
List<Student> list = (List<Student>) sessionFactory
.getCurrentSession()
.createQuery("from Student p join p.studentFKs p2 where p2.id = :parentId")
.setParameter("parentId", parentId).list();
can someone help me solve the mistery? i know that it can be achieved with Hibernate criterias also.
UPDATE answer is to change query to(join was invalid.. ):
select p from Student p join p.student p2 where p2.id = :parentId
i use something like this and this is work
property of join inside entity Student :
#ManyToMany(cascade = CascadeType.REFRESH)
private Set<Student> testJoins = new HashSet<Student>();
HQL to get that set :
select a.testJoins from Student a where a.id = :parentId
maybe you can change your query from p2.id = :parentId to-->> p.id = :parentId