I want to fetch records from database without the primary key, for a table that i have created. Here is the model class for it.
#Entity
#Table(name = "DOCTORS_INFORMATION")
public class DoctorInformation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "DOCTOR_ID")
private int ID;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "ADDRESS_LINE1")
private String addressLine1;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "CITY")
private Location location;
//along with setters and getters
It is very difficult to remember the Doctor_Id for every doctor, and logically First_Name or anything cannot be made a primary key. All the tutorials and lecture videos that i have gone through fetch records using session.get where session is an object of class Session. In the source code of Session class all the overloaded methods of get mandatory take id as a parameter...
Is their a workaround for the above problem?
There are multiple options. All of the following examples search for a doctor whose lastName contains Johnson.
Criteria
String lastNameToSearchFor = "Johnson";
List doctors = session.createCriteria(DoctorInformation.class)
.add( Restrictions.like("lastName", "%"+lastNameToSearchFor+"%") ) // the leading wild card can become a problem since it cannot be indexed by the DB!
.addOrder( Order.asc("lastName") )
.list();
http://docs.jboss.org/hibernate/orm/3.5/javadocs/org/hibernate/Criteria.html
HQL
String lastNameToSearchFor = "Johnson";
String hql = "FROM " + DoctorInformation.class.getName() + " di WHERE di.lastName LIKE :lastName ORDER BY di.lastName ASC";
Query query = session.createQuery(hql);
query.setParameter("lastName", "%" + lastNameToSearchFor + "%"); // again, the leading wild card may be a problem
List doctors = query.list();
http://www.tutorialspoint.com/hibernate/hibernate_query_language.htm
Native SQL
String lastNameToSearchFor = "Johnson";
String sql = "SELECT * FROM DOCTORS_INFORMATION WHERE lastName LIKE :lastName ORDER BY lastName ASC";
Query query = session.createSQLQuery(sql).addEntity(DoctorInformation.class);
query.setString("lastName", "%" + lastNameToSearchFor + "%"); // again, the leading wild card may be a problem
List doctors = query.list();
If you want to add capability to search with, let's say firstName, then you might want to look into Hibernate's Disjunction: Creating a hibernate disjunction query programatically
Related
I have the following entity (some columns omitted for brevity):
#Entity
#Table(name = "INSTRUCTION")
public class Instruction {
#Id
#Column(name = "ID", nullable = false, unique = true)
public Long id;
#Column(name = "CURRENT_STATUS", nullable = false)
private InstructionState currentStatus;
#Column(name = "SUBTYPE", nullable = false)
private InstructionAction subtype;
//Getters & Setters
}
I want to write the following query in JPA to retrieve a count of the instructions grouped by their CURRENT_STATUS and SUBTYPE. I know that the following SQL works:
SELECT CURRENT_STATUS, SUBTYPE, COUNT(*) count
FROM (SELECT ID, CURRENT_STATUS, SUBTYPE
FROM INSTRUCTION
WHERE VALUE_DATE= '17-JUN-21'
AND LAST_UPDATED >= '16-JUN-21'
AND LAST_UPDATED < '17-JUN-21'
GROUP BY ID, CURRENT_STATUS, SUBTYPE)
GROUP BY CURRENT_STATUS, SUBTYPE;
I want to take the result from this query and map it to a new object call InstructionCount:
public class InstructionCount {
private InstructionState status;
private InstructionAction subType;
private Integer count;
public InstructionCount(final InstructionState status, final InstructionAction subType, final Integer count) {
this.status = status;
this.subType = subType;
this.count = count;
}
//Getters and setters
}
The Problem
I have come up with the following query in JPA for this in my repository class:
#Query(value = "SELECT new com.modles.InstructionCount(CURRENT_STATUS status, SUBTYPE subType, COUNT(*) count) \n" +
"FROM (SELECT ID, CURRENT_STATUS, SUBTYPE \n" +
"\t\tFROM LTD_RTGS_CASHINSTRUCTION \n" +
"\t\tWHERE VALUE_DATE= :valueDate \n" +
"\t\tAND LAST_UPDATED >= :lastUpdatedFrom \n" +
"\t\tAND LAST_UPDATED < :lastUpdatedTo \n" +
"\t\tGROUP BY ID, CURRENT_STATUS, SUBTYPE)\n" +
"GROUP BY CURRENT_STATUS, SUBTYPE", nativeQuery = true)
List<InstructionCount> findInstructionCounts(#Param("valueDate") LocalDate valueDate, #Param("lastUpdatedFrom") LocalDateTime lastUpdatedFrom, #Param("lastUpdatedTo") LocalDateTime lastUpdatedTo);
The issue is that this does not work, and I have found it is because I cannot use the approach of mapping it to a new object using the SELECT new com.modles.InstructionCount with nativeQuery = true. However when I try to remove the nativeQuery = true part and run my test I get the following error:
I also notice that the SQL gets syntax errors in the IDE, highlighted on the second SELECT statement, so I presume there is an issue with this format when not using nativeQuery.
Can anyone help with how I can resolve this issue? I know this can work, because when I remove the SELECT new com.modles.InstructionCount part, it will just return a list of Object[] with the correct values I'm expecting, but I would much prefer to map this to the correct object as part of the query. Alternatively, if there is a way to write this query using the Specification API, I would also use that approach!
Below query should just work just fine and there is no need of your inner/sub query
SELECT CURRENT_STATUS, SUBTYPE, COUNT(ID)
FROM INSTRUCTION
WHERE VALUE_DATE= '17-JUN-21'
AND LAST_UPDATED >= '16-JUN-21'
AND LAST_UPDATED < '17-JUN-21'
GROUP BY CURRENT_STATUS, SUBTYPE;
This should works seemlessly while returning the result as
InstructionCount
------->Edit------------->
#Query(value = "SELECT new com.modles.InstructionCount(currentStatus, subtype, count(id)) FROM Instruction WHERE valueDate= :valueDate AND lastUpdatedFrom >= :lastUpdatedFrom AND lastUpdatedTo < :lastUpdatedTo GROUP BY id, currentStatus, subtype")
List<InstructionCount> findInstructionCounts(#Param("valueDate") LocalDate valueDate, #Param("lastUpdatedFrom") LocalDateTime lastUpdatedFrom, #Param("lastUpdatedTo") LocalDateTime lastUpdatedTo);
The below code snippet does not work:
#Id
private Long id;
#ManyToMany(fetch = FetchType.LAZY)
#JoinFormula("""
(SELECT DISTINCT ON (product11.id, text26.language, textsourced28.source) text26.id
FROM product AS product11
JOIN producttexttitle AS producttexttitle27 ON product11.id = producttexttitle27.product
JOIN text AS text26 ON producttexttitle27.text = text26.id
JOIN textsourced AS textsourced28 ON text26.id = textsourced28.text
WHERE product11.id=id
ORDER BY product11.id, text26.language, textsourced28.source, textsourced28.time DESC)
""")
private List<Text> titles;
However this does,
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinFormula("""
(SELECT DISTINCT ON (product11.id, text26.language, textsourced28.source) text26.id
FROM product AS product11
JOIN producttexttitle AS producttexttitle27 ON product11.id = producttexttitle27.product
JOIN text AS text26 ON producttexttitle27.text = text26.id
JOIN textsourced AS textsourced28 ON text26.id = textsourced28.text
WHERE product11.id=id
ORDER BY product11.id, text26.language, textsourced28.source, textsourced28.time DESC
LIMIT 1)
""")
private Text titles;
The only problem is that the latter one only returns one instance, where I am looking for several.
Can figure it out.
#CollectionTable or #ElementCollection usage perhaps?
I need to supply a custom sql.
I have a User entity with skills property as a type List. I want to query the User table against a list of skills in such a way that if all the skills are present in skill column then only a match is found unless no.
I have used JPQL for this but it matches each element in the list one by one using the IN clause.
User Class
#Entity(name = "App_User")
//table name "user" is not allowed in postgres
public class User {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "user_id", updatable = false, nullable = false)
#Setter(AccessLevel.NONE)
private UUID id;
#Column(name = "user_name")
#NotBlank(message = "Name is mandatory")
private String name;
#Column(name = "user_email")
#NotBlank(message = "Email is mandatory")
private String email;
// Current point balance of the user
#Column(name = "points")
private int points;
#ElementCollection(fetch = FetchType.EAGER)
#Column(name = "skills")
#NotEmpty
private List<String> skills = new ArrayList();
}
JPA query that I have used is
SELECT u FROM App_User u JOIN u.skills skill where skill in :skillList
If I want to match a list of skills like this Arrays.asList("skill1","skill2","skill3")then I want only those users in the result who have all of these skills, not one or two. Above used IN clause return the same result.
I have read that it is not possible to compare two lists in JPQL so how can I achieve this using CriteriaBuilder CriteriaQueryAPI?
You can do this
#Query(value = "SELECT u FROM User u LEFT JOIN u.skills sk WHERE sk IN :skillList"
+ " GROUP BY u HAVING COUNT( sk) = :skillListSize")
List<User> findBySkills(#Param("skillList") List<String> skills,
#Param("skillListSize") long skillListSize);
Here, group by user and then check group having all skills or not using size. So it will fetch all user having all skills those are given.
Or use this way
#Query(value = "SELECT u FROM User u LEFT JOIN u.skills sk GROUP BY u"
+ " HAVING SUM(CASE WHEN sk IN (:skillList) THEN 1 ELSE 0 END) = :skillListSize")
List<User> findBySkills(#Param("skillList") List<String> skills,
#Param("skillListSize") long skillListSize);
And if you want a solution for user having exact same skill not more than the given list then see this solution.
The problem that you want to solve is called Relational Division.
SELECT u.* FROM App_User u
INNER JOIN
(
SELECT skills FROM App_User WHERE skills IN (list values)
GROUP BY skills
HAVING COUNT(DISTINCT skills) = (size of list)
) w ON u.user_name = w.user_name
I have an Table and Pojo in my Spring Boot application like below.
#Entity
#Table(name = "attendance_summary")
public class AttendanceSummary implements Serializable {
#Id
#SequenceGenerator(name = "attendance_summary_id_seq",
sequenceName = "attendance_summary_id_seq",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "attendance_summary_id_seq")
#Column(name = "id", updatable = false)
public Integer id;
#Column(name = "emp_id", nullable = false)
public Integer empId;
#Column(name = "designation_id")
public Integer designationId;
#Column(name = "designation_category_id")
public Integer designationCategoryId;
#Column(name = "department_id")
public Integer departmentId;
......
}
Now I want have dynamic inputs for these fields. Meaning user might select
a list of empIds, designationIds.... or any combinations of them or even none of them.
If they select none of the fields I need to return all the rows from the table in the database.
But in jpa when we write methods in repository we have to specify the field names like
public interface AttendanceSummaryRepository extends JpaRepository<Integer,AttendanceSummary>{
List<AttendanceSummary> findByEmpIdAndDesignationId....(List<Integer> empIdList,List<Integer> designationIdList ... );
}
Which means if any of these parameters are null I will get an error or an exception and as a result I will miss some data.
where as in PHP or some other language like that I can just check the value of the desired filters and just dynamically add a where clause in the query.
query= "Select * from attendance_summary where ";
if(empIdList != null)
query = query + " emp_id in empIdList "
if(designationIdList != null)
query = query + " designation_id in designationIdList "
.....
//You get the idea.
Is there any way to do so with jpaDataMethods and if yes how. Any detailed explanation / link to resources are much appreciated.
Sorry for poor english and if I couldn't explain my problem properly.
Take a look at Criteria API. It allows you to create dynamic queries.
In your example, something similar to this could work:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<AttendanceSummary> query = cb.createQuery(AttendanceSummary.class);
Root<AttendanceSummary> root = query.from(AttendanceSummary.class);
List<Predicate> predList = new ArrayList<>();
if (empIdList != null) {
predList.add(root.get('empId').in(empIdList));
}
if (designationIdList != null) {
predList.add(root.get('designationId').in(designationIdList));
}
// ... You get the idea.
Predicate[] predicates = new Predicate[predList.size()];
predList.toArray(predicates);
query.where(predicates);
return entityManager.createQuery(query).getResultList();
You can achieve this by using #Query annotation. Please refer spring guide for more details.
#Query(value = "from attendance_summary where (emp_id in (?1) or ?1 is null) and (designation_id in (?2) or ?2 is null)" )
Query Detail:
SELECT * #implicit so removed
FROM attendance_summary
WHERE ( emp_id IN (?1) #true when IDs are not null, thus apply filter
OR ?1 IS NULL #true when user input null, return all rows )
AND ( designation_id IN (?2) #true when IDs are not null, thus apply filter
OR ?2 IS NULL #true user input null, return all rows)
Example Project on github with spring-boot, jpa & h2. Look for SchoolController & SchoolRepo classes, applying the same logic, the endpoints \school will filter the result for input Ids & \allschool will return everything as input is null.
I am trying to map an Entity from 3 tables, using #Entity, #Table, #SecondaryTables and #Column annotations as illustrated in the following example.
The Tables are:
1. employee (primary)
2. employee_detail
3. job_description
#Entity
#Table(name="EMPLOYEE")
#SecondaryTables({
#SecondaryTable(name="EMPLOYEE_DETAIL",
pkJoinColumns = #PrimaryKeyJoinColumn(name="EMPLOYEE_ID")),
#SecondaryTable(name="JOB_DESCRIPTION",
pkJoinColumns = #PrimaryKeyJoinColumn(name="JD_ID"))
})
public class Employee {
#Id
#Column(name = "ID", table= "EMPLOYEE")
private int id;
#Column(name = "FIRST_NAME", table = "EMPLOYEE")
private String firstname;
#Column(name = "LAST_NAME", table = "EMPLOYEE")
private String lastname;
#Column(name = "BANK_ACCOUNT_NO", table = "EMPLOYEE_DETAIL")
private String bankacctnumber;
#Column(name = "JOB_SUMMARY", table = "JOB_DESCRIPTION")
private String jobsummary;
#Column(name = " ??? ", table = " ?? ")
private String uniqueid;
//getters and setters for above fields
...
}
My question is, if I would like to create the field "uniqueid" by concatenating
column "ID" in table "EMPLOYEE" AND column "JOB_CODE" in table "JOB_DESCRIPTION"
This entity corresponds with the following sql query string (I used string builder for clarity):-
StringBuilder sql = new StringBuilder();
sql.append("SELECT");
sql.append(" e.FIRST_NAME AS firstname,");
sql.append(" e.LAST_NAME AS lastname,");
sql.append(" d.BANK_ACCT_NUMBER AS bankacctnumber,");
sql.append(" j.JOB_SUMMARY AS jobsummary,");
sql.append(" CONCAT(e.ID,SUBSTR(j.JOB_CODE,3,8)) AS uniqueid");
sql.append(" FROM employee e");
sql.append(" LEFT JOIN employee_detail d ON d.EMPLOYEE_ID = e.ID");
sql.append(" LEFT JOIN job_description j ON j.JD_ID = e.JD_ID ");
sql.append(" WHERE e.ID = 1 ");
exactly how should the mapping of the columns be done for the field "uniqueid"? Is this possible?
No this is not possible because JPA is using the mapped columns for reading AND writing. So a column can not be concatenated.
But you can write a simple method in your class that concatenates the fields.
Another possibility to could be a view that uses your SQL statement but then you can only read and now write because the view contains fields of more than one table.