JPA/Hibernate: invalid COUNT query alias for JOINED inheritance - java

I'm using Spring boot 2.3.12.RELEASE and Hibernate 5.4.32.Final. I'm using JOINED inheritance strategy for several classes with parent entity mapped like this:
#Entity
#Table(name = "dos_t_requests")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Request {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_requests")
#SequenceGenerator(name = "sq_requests", sequenceName = "dos_sq_requests", allocationSize = 1)
private long id;
#Column(name = "req_num")
private String number;
#Column(name = "created_at")
private LocalDateTime createdAt;
#Column(name = "edited_at")
private LocalDateTime editedAt;
#Column(name = "executed_at")
private LocalDateTime executedAt;
#Column(name = "issued_at")
private LocalDateTime issuedAt;
#Lob
#Column(name = "message")
private String message;
...
}
And here is one of child entities:
#Entity
#Table(name = "dos_t_stud_reqs")
#PrimaryKeyJoinColumn(name = "id")
public class StudentRequest extends Request {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stud_id")
private Student student;
...
}
I need to count the total number of rows of child entities to paginate the results. So I have a method that does all the work:
public Page<RequestPartInfo> getAll(DbUser dbUser, int page, int pageSize) {
...
String cntQuery;
String entQuery;
if (dbUser.getDbType() == DbUserType.STUDENT) {
cntQuery = String.join("\n",
"select count(r.id)",
"from StudentRequest r",
"where r.student.id = :userId");
entQuery = String.join("\n",
"from StudentRequest r",
"join fetch r.document d",
"join fetch d.recipient rc",
"join fetch r.curator c",
"join fetch r.executor e",
"join fetch r.issueType it",
"join fetch r.status st",
"where r.student.id = :userId",
"order by r.createdAt desc");
}
...
long count = em.createQuery(cntQuery, Long.class).setParameter("userId", dbUser.getId()).getSingleResult();
List<RequestPartInfo> requests = em.createQuery(entQuery, Request.class).setParameter("userId", dbUser.getId())
.setFirstResult(page * pageSize).setMaxResults(pageSize).getResultList()
.stream().map(this::mapPartInfo).collect(Collectors.toList());
return PagingUtil.createPage(requests, page, pageSize, count);
}
When I call this method, Hibernate generates an incorrect database query:
select count(studentreq0_.id) as col_0_0_ from oksana.dos_t_stud_reqs studentreq0_ where studentreq0_.id=studentreq0_1_.id and studentreq0_.stud_id=?
The problem with this request is that the WHERE section uses an alias that is not present in the FROM section. So I get following error: java.sql.SQLSyntaxErrorException: ORA-00904: "STUDENTREQ0_1_"."ID": invalid identifier.
I tried to search for a solution to the problem on the internet and found a similar problem here. But it says that this bug has been fixed in Hibernate 5.4.19.

Related

I have created a data class for my table which is throwing error in named queries

Entity name is similar to name of the table.
#Table
#Entity(name = "CKYCUploadTransactions")
#Access(AccessType.FIELD)
#NamedQueries({
#NamedQuery(name = "CKYCUploadTransactions.byUserId", query = "select k from CKYCUploadTransactions k where" +
"k.userId = :user_id"),
#NamedQuery(name = "CKYCUploadTransactions.byUserIdAndTxnTypeAndStatus", query = "select k from CKYCUploadTransactions k where" +
"k.userId = :user_id AND k.txnType = :txn_type AND k.status = :status"),
#NamedQuery(name = "CKYCUploadTransactions.byUserIdAndContext", query = "select k from CKYCUploadTransactions k where" +
"k.userId = :user_id and k.context = :context")
})
#Data
public class CKYCUploadTransactions {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "user_id")
private String userId;
#Column(name = "context")
private String context;
#Column(name = "txn_type")
private String txnType;
#Enumerated(EnumType.STRING)
#Column(name = "status", length = 20)
private Status status;
public enum Status{
initiated,
success,
failed
}
}
I am not understanding why it is throwing this error, I have created another entity like this but not facing the issues there.
Exception: Errors in named queries: CKYCUploadTransactions.byUserIdAndTxnTypeAndStatus, CKYCUploadTransactions.byUserIdAndContext, CKYCUploadTransactions.byUserId
You can follow this JPA Named Queries
CKYCUploadTransactions.byUserId
should be
CKYCUploadTransactions.findByUserId

Where condition not working on related entity

I'm trying to apply the where condition on the related entity, but the result set contains all the related entity data. It appears like the filter is ignored.
I have the following entities:
Entity Audit:
#Entity
#Table(name = "entity_audit")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#org.springframework.data.elasticsearch.annotations.Document(indexName = "entityaudit")
public class EntityAudit implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#org.springframework.data.elasticsearch.annotations.Field(type = FieldType.Keyword)
private Long id;
#NotNull
#Column(name = "entity_id", nullable = false)
private Long entityId;
#NotNull
#Column(name = "entity_class_name", nullable = false)
private String entityClassName;
#NotNull
#Column(name = "entity_name", nullable = false)
private String entityName;
#NotNull
#Enumerated(EnumType.STRING)
#Column(name = "action_type", nullable = false)
private EntityAuditType actionType;
#NotNull
#Column(name = "timestamp", nullable = false)
private Instant timestamp;
#NotNull
#Column(name = "user", nullable = false)
private String user;
#NotNull
#Column(name = "transaction_uuid", nullable = false)
private String transactionUuid;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "entity_audit_id")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<EntityAuditUpdateData> entityAuditUpdateData = new HashSet<>();
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "entity_audit_id")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<EntityAuditStatus> entityAuditStatuses = new HashSet<>();
Getters and setters...
Entity Audit Status
#Entity
#Table(name = "entity_audit_status")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#org.springframework.data.elasticsearch.annotations.Document(indexName = "entityauditstatus")
public class EntityAuditStatus implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#org.springframework.data.elasticsearch.annotations.Field(type = FieldType.Keyword)
private Long id;
#NotNull
#Column(name = "user_login", nullable = false)
private String userLogin;
#NotNull
#Column(name = "jhi_read", nullable = false)
private Boolean read;
#ManyToOne
private EntityAudit entityAudit;
Getters and setters...
I'm trying to achieve this query:
#Query("select distinct entityAudit from EntityAudit entityAudit " +
"join entityAudit.entityAuditStatuses entityAuditStatus " +
"where entityAuditStatus.userLogin =:userLogin " +
"order by entityAudit.timestamp desc")
Page<EntityAudit> retrieveAllByUserLogin(#Param(value = "userLogin") String userLogin, Pageable pageable);
But when I retrieve the data the EntityAuditStatuses are not filtered. I don't understand where the problem is.
Note: I removed the date property from the minimum reproducible example.
Use left join fetch instead of left join to make sure the dependent entityAuditStatuses are fetched as part of the join query itself, and not as multiple queries after finding the entityAudit. And since the result needs to be paginated, an additional countQuery will need to be specified (without the fetch). Working Query -
#Query(value = "select entityAudit from EntityAudit entityAudit " +
"left join fetch entityAudit.entityAuditStatuses entityAuditStatus " +
"where entityAuditStatus.userLogin = :userLogin ",
countQuery = "select entityAudit from EntityAudit entityAudit " +
"left join entityAudit.entityAuditStatuses entityAuditStatus " +
"where entityAuditStatus.userLogin = :userLogin ")
Without left join fetch, three queries are being generated - one which fetches the entityAuditId 1 (based on the userLogin 1) and then two more to fetch the entityAuditStatuses (from the entity_audit_status table only without the join) given the entityAuditId 1.
That is why, when you ask for userLogin = '1' - you retrieve the EntityAudit 1 which brings with it - entityAuditStatus 1 - entityAuditStatus 3 (which has userLogin = '2')
After adding left join fetch, there is only one query using join as per the defined entity relationships. So the results are correctly fetched.

How to Inner Join Two Independent Entities in Hibernate

I have 2 entities: DocumentEntity (docNumber (primary key), dateOfFill, ...) and FileEntity (id, title, size, ...). I have an HQL query with inner join of 2, which should run on Oracle DB:
String queryStr = "SELECT docNumber " +
+ "FROM DocumentEntity d " +
+ "INNER JOIN FileEntity f " +
+ "ON d.docNumber = f.title " +
+ "WHERE d.date > to_date('01.01.2011','dd.mm.yyyy')"
Query query = em.createQuery(query_string);
return query.getResultList();
When I run the code snippet I'm getting an exception org.hibernate.hql.ast.QuerySyntaxException: Path expected for join!
I looked through
Hibernate 4.3.6 QuerySyntaxException: Path expected for join
HQL ERROR: Path expected for join
Path Expected for Join! Nhibernate Error
HQL Hibernate INNER JOIN
but none resolved my problem. The suggested paths cannot be used in this example (at least it gives wrong path error). The answer of the last link says that:
Joins can only be used when there is an association between entities.
The issue is that I cannot associate these 2 entities.
The question is:
How can I join these 2 entities?
UPDATE:
My entities are:
#Entity
#Table(name = "DOCUMENT")
public class DocumentEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "DOC_NUMBER", nullable = false)
private String docNumber;
#Basic(optional = false)
#Column(name = "DATE_OF_FILL")
#Temporal(TemporalType.DATE)
private Date dateOfFill;
...
}
and
#Entity
#Table(name = "FS_FILE")
public class FileEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "FS_FILE_SEQ", allocationSize = 1, sequenceName = "FS_FILE_SEQ")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "FS_FILE_SEQ")
#Column(name = "ID", nullable = false)
protected Long id;
#Column(name = "TITLE", nullable = false)
protected String title;
#Column(name = "MIMETYPE", nullable = false)
protected String mimetype;
#Column(name = "FILESIZE", nullable = false)
protected Long filesize;
#Column(name = "FILEPATH", nullable = false)
protected String filepath;
...
}
In this case, you don't need to do a join since you limit the result with the condition d.docNumber = f.title.
Just add the condition in the where clause and use a SQL query instead of a JPQL query since it seems more matching to your need.
String sqlString= "SELECT d.docNumber " +
+ "FROM DOCUMENT d, FS_FILE f " +
+ "WHERE d.docNumber = f.title " +
+ "AND d.date > to_date('01.01.2011','dd.mm.yyyy')"
Query query = em.createNativeQuery(sqlString);
return query.getResultList();

SQL Query Too Complex To Express In JPA Criteria API?

I have an SQL query that gets me exactly the data I need. The problem is that we are trying to express all queries in JPA Criteria API to maintain portability, and I can't figure out how to map this particular query.
The problem is that the JPA Criteria API Subquery class lacks the multiselect() method that CriteriaQuery class has. As you can see in the SQL query, I have computed fields in the sub-query which don't exist in the entity. Thus, I have no way to retrieve these fields.
I would be quite appreciative if anyone knows a solution or could offer guidance, or even if someone could validate that what I am trying to achieve in JPA Criteria API is not possible.
The SQL:
SELECT w.NAME AS 'wave_name',
Count(*) AS 'num_lines',
Sum(qty_ordered) AS 'num_units',
Count(DISTINCT unit_of_work_id) AS 'num_units_of_work',
Sum(completed_units) AS 'completed_units',
( Sum(completed_units) + Sum(qty_scratched) ) / Sum(qty_ordered) AS 'perc_completed_units'
FROM (SELECT t.id,
t.wave_id,
t.quantity_requested AS 'qty_ordered',
t.quantity_scratched AS 'qty_scratched',
t.unit_of_work_id AS 'unit_of_work_id',
Ifnull(m.quantity, 0) AS 'qty_picked',
CASE
WHEN Ifnull(m.quantity, 0) > quantity_requested THEN
quantity_requested
ELSE Ifnull(m.quantity, 0)
END AS 'completed_units'
FROM task t
LEFT OUTER JOIN (SELECT move.task_id,
Sum(quantity) AS 'quantity'
FROM move
GROUP BY task_id) m
ON m.task_id = t.id) s
JOIN wave w
ON w.id = s.wave_id
GROUP BY w.name;
The entities:
#Entity
#Table(name = "task")
public class Task {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#ManyToOne (cascade = CascadeType.ALL)
#JoinColumn (name = "wave_id", nullable = false)
private Wave wave;
#ManyToOne (cascade = CascadeType.ALL)
#JoinColumn (name = "unit_of_work_id", nullable = false)
private UnitOfWork unitOfWork;
#OneToMany (cascade = CascadeType.ALL, mappedBy = "task")
private Set<Move> moves = new HashSet<Move>();
#Column (name = "quantity_requested")
private Long quantityRequested;
#Column (name = "quantity_scratched")
private Long quantityScratched;
}
#Entity
#Table(name = "wave")
public class Wave {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "wave", cascade = CascadeType.ALL)
private Set<Task> tasks = new HashSet<Task>();
}
#Entity
#Table(name = "unit_of_work")
public class UnitOfWork {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
#Column (name = "id")
private Long id;
#OneToMany(mappedBy = "unitOfWork", cascade = CascadeType.ALL)
private Set<Task> tasks = new HashSet<Task>();
}
#Entity
#Table(name = "move")
public class Move {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#ManyToOne (cascade = CascadeType.ALL)
#JoinColumn (name = "task_id", nullable = false)
private Task task;
#Column (name = "quantity")
private Long quantity;
}
I would say use named parameters or native query approach for this.
For example:
Named parameters:
public interface Repo extends JpaRepository<AEntity, String> {
#Query("select a from AEntity a where a.BEntity.name = :name")
public aMethod( #Param("name") String name)
}
OR
Native query approach:
public interface Repo extends JpaRepository<AEntity, String> {
#Query(value = "select * from Tablename t where t.name = :name", nativeQuery=true)
public aMethod(#Param("name") String name)
}
Check this link if you are using spring jpa
http://docs.spring.io/spring-data/data-jpa/docs/1.4.x/reference/htmlsingle/#jpa.named-parameters

JPA - Create or use Foreign Key

Okay I have 2 entities -:
Teacher
Department
One teacher can belong to one department and one department can have many teachers.
So the requirement is that Teacher entity contains a mappedBy attribute that maps the relation.
Now the problem that I am facing is that if a Department does not exist then it is created and if not a query fetches the department and that is used in the the teacher object.
Now the problem is that if the Department exists I have to use merge() if not I have to use persist() since the department will be newly inserted. Is there anything I can do that allows for dynamic use of merge or persist ?
The code is as follows-:
Department.java
#Entity
#Table (name = "DEPARTMENTS")
#NamedQueries({
#NamedQuery(name = "Department.findById",
query = "SELECT d FROM "
+ "Department d "
+ "WHERE d.deptId = :id")
})
public class Department extends STSEntity{
private static final long serialVersionUID = 1L;
#Column (name = "DEPT_ID", length = 10,
unique = true, nullable = false)
private String deptId;
#OneToMany (targetEntity = Teacher.class,
mappedBy = "dept")
private Collection<Teacher> teachers = new ArrayList<>();
Teacher.java
#Entity
#Table (name = "TEACHER_DATA")
#NamedQueries ( {
#NamedQuery(name = "Teacher.getByDept",
query = "SELECT t "
+ "FROM Teacher t "
+ "WHERE t.dept = :dept")
})
public class Teacher extends STSEntity {
private static final long serialVersionUID = 1L;
#Column (name = "TEACHER_ID", unique = true, nullable = false, length = 10)
private String teacherId;
#ManyToOne (targetEntity = Department.class,
fetch = FetchType.EAGER,
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
#JoinColumn (name = "DEPT_ID", nullable = false)
private Department dept;
Anyway I can use both ? Any help will be appreciated
Basically if the foreign key exists use it or otherwise create it.

Categories

Resources