Write custom SQL query using JPA - java

I have a JPA repository for a DB entity as follows:
#Repository
public interface StudentRepository
extends JpaRepository<Student, String> {
}
Student entity is as follows:
#Entity
#Table(name = "student")
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(generator = "custom-uuid")
#GenericGenerator(name = "custom-uuid",
strategy = "generator.CustomUUIDGenerator")
private String id;
#Column(name = "roll_number")
private String rollNumber;
#Column(name = "name")
private String name;
#LastModifiedDate
#Column(name = "last_modified_date")
#JsonIgnore
private Instant lastModifiedDate = Instant.now();
Now, I would like to write the following SQL query using JPA as follows:
SELECT id, roll_number from student where id=<id> order by last_modified_date desc;
Being new to JPA/Hibernate, I don't have an idea how to achieve this using JPA/Hibernate.
Could anyone please help here ?

You could write something like:
String hql = "SELECT s.id, s.rollNumber FROM Student s WHERE s.id = :id ORDER BY s.lastModifiedDate DESC";
query.setParameter("id", 10);
Query query = session.createQuery(hql);
List results = query.list();
Refer: https://www.tutorialspoint.com/jpa/jpa_jpql.htm

Create a new method in Repository
#Repository
public interface StudentRepository extends JpaRepository<Student, String> {
/**
* List all student by criterias.
*
* #return
*/
#Query(value = "SELECT id, roll_number from student where id=?1 order by last_modified_date desc;", nativeQuery = true)
List<Student> listStudent(String id);
}
Reference document: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.at-query

Related

"Column not found" when joining table in SpringBoot

I'm new in spring boot and now I'm trying to joining two tables where one of them are composite table (idk if it's have any effect or not). Please take a look on my code and help me.
Employees.java (Model)
#Entity
#Table(name = "employees")
public class Employees {
#Id
#Column(name = "emp_no")
private long emp_no;
#Column(name = "name")
private String name;
}
Salaries.java (Model, The composite table)
#Entity
#Table(name = "salaries")
public class Salaries {
#Column(name = "emp_no") //Primary Key
private long emp_no;
#Column(name = "salary")
private int salary;
#Column(name = "date") //Primary Key
private LocalDate date;
}
EmployeeRepo.java (Repository)
public interface EmployeesRepository extends JpaRepository<Employees, Long> {
String salary = "SELECT employees.name, salaries.salary FROM employees INNER JOIN salaries ON employees.emp_no=salaries.emp_no LIMIT 0,10";
#Query(value=salary, nativeQuery = true)
List<Employees> getUserSalary();
}
EmpController.java (Controller)
#GetMapping("/salary")
public ResponseEntity<List<Employees>> getSalaries(#RequestParam(required = false) String args){
List<Employees> employees = new ArrayList<Employees>();
employeesRepository.getUserSalary().forEach(employees::add);
if(employees.isEmpty()){
return new ResponseEntity<>(employees, HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(employees, HttpStatus.OK);
}
application.properties
spring.datasource.url= jdbc:mysql://127.0.0.1:3306/employees?useSSL=false
spring.datasource.username= ""
spring.datasource.password= ""
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto= update
And the error message:
Column 'emp_no' not found.
I dont understand why I get this error when I have emp_no column both on my tables in db.
I guess your problem is
String salary = "SELECT employees.name, salaries.salary FROM employees INNER JOIN salaries ON employees.emp_no=salaries.emp_no LIMIT 0,10";
#Query(value=salary, nativeQuery = true)
List<Employees> getUserSalary();
you are trying to save name,salary in Employees which only has emp_no, name. So the error is sating you have mapped name but emp_no is not available from query.
Create POJO/model like below
public class EmpSalary {
private String name;
private double salary;
}
Then update your repository to return EmpSalary
String salary = "SELECT employees.name, salaries.salary FROM employees INNER JOIN salaries ON employees.emp_no=salaries.emp_no LIMIT 0,10";
#Query(value=salary, nativeQuery = true)
List<EmpSalary> getUserSalary();

Not expected N+1 queries with Hibernate Projection

Just face the N+1 query problem with such Spring Data repository
public interface ToDoRepository extends CrudRepository<ToDo, Long> {
#Query("select new com.package.repr.ToDoRepr(t) from ToDo t " +
"where t.user.id = :userId")
List<ToDoRepr> findToDosByUserId(#Param("userId") Long userId);
}
I see in the logs one such query
Hibernate:
select
todo0_.id as col_0_0_
from
todos todo0_
where
todo0_.user_id=?
]
And N such queries
Hibernate:
select
todo0_.id as id1_0_0_,
todo0_.description as descript2_0_0_,
todo0_.target_date as target_d3_0_0_,
todo0_.user_id as user_id4_0_0_,
user1_.id as id1_1_1_,
user1_.password as password2_1_1_,
user1_.username as username3_1_1_
from
todos todo0_
left outer join
users user1_
on todo0_.user_id=user1_.id
where
todo0_.id=?
ToDoRepr is a simple POJO. With constructor which accepts ToDo entity as a parameter.
Here are two JPA entities I use in this query
#Entity
#Table(name = "todos")
public class ToDo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String description;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#Column
private LocalDate targetDate;
// geters, setters, etc.
}
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true, nullable = false)
private String username;
#Column(nullable = false)
private String password;
#OneToMany(
mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<ToDo> todos;
// geters, setters, etc.
}
UPD. It possible to resolve the problem by that query but why it's not working with constructor which accepts entity as a parameter?
public interface ToDoRepository extends CrudRepository<ToDo, Long> {
#Query("select new com.package.repr.ToDoRepr(t.id, t.description, t.user.username, t.targetDate) " +
"from ToDo t " +
"where t.user.id = :userId")
List<ToDoRepr> findToDosByUserId(#Param("userId") Long userId);
}
This is a very common question so I created the article Eliminate Spring Hibernate N+1 queries detailing the solutions
The best practice with Hibernate is to define all associations as Lazy to avoid fetching it when you don't need it.
For more reasons, check Vlad Mihalcea's article https://vladmihalcea.com/eager-fetching-is-a-code-smell/
For fixing your issue, in your class ToDo, you should define the ManyToOne as Lazy:
#Entity
#Table(name = "todos")
public class ToDo {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
...
// geters, setters, etc.
}
If you need to access the user in your ToDoRepr, it will not be loaded by default so you need to add it to your query:
JPQL, use JOIN FETCH:
public interface ToDoRepository extends CrudRepository<ToDo, Long> {
#Query("select new com.package.repr.ToDoRepr(t) " +
"from ToDo t " +
"inner join fetch t.user " +
"where t.user.id = :userId")
List<ToDoRepr> findToDosByUserId(#Param("userId") Long userId);
}
JPA, use EntityGraph:
public interface ToDoRepository extends CrudRepository<ToDo, Long> {
#EntityGraph(attributePaths = {"user"})
List<ToDoRepr> findToDosByUser_Id(Long userId);
}
I want to collect here some workaround about this my own question. There is a simple solution without explicit JPQL queries. Spring Data JPA can treat any POJO with proper getters and setters as a projection.
Just that works perfectly for me
public interface ToDoRepository extends CrudRepository<ToDo, Long> {
List<ToDoRepr> findToDosByUser_Id(Long userId);
}

how to find the data by an attribute (name_techno) in jpql

I am developing an application that allows managing candidates in a company, for that I use spring-boot, in order to select the employees who master such a technology (Techno) I used a request JPQL.
So, How can I find a candidate by techno?
In my project I used this code:
1 - the class candidat.java
#Entity
public class Candidat {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "candidat_id")
private int id;
private String nom;
private String prenom;
private String ville;
private int numTel;
private String mail;
private String pseudo;
private String roleCible;
private String typeContrat;
private String villeRecherchee;
#OneToMany(mappedBy="candidat")
private List<Techno> techno;
#Temporal(TemporalType.DATE)
private Date date;
#OneToMany
private List<SecteurActivites> secteurActivites;
public Candidat() {
// TODO Auto-generated constructor stub
}
2- the class Techno.java
#Entity
public class Techno {
#Id
#GeneratedValue
#Column(name = "techno_id")
private int id ;
private String nomTechno;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "candidat_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private Candidat candidat;
public Techno() {
// TODO Auto-generated constructor stub
}
/**
* #param nomTechno
* #param candidat
*/
public Techno(String nomTechno, Candidat candidat) {
super();
this.nomTechno = nomTechno;
this.candidat = candidat;
}
3- My CandidatController
#GetMapping(value = "/GetAllCandidats/{nomTechno}")
public List<Candidat> afficherCandidat(#PathVariable ("nomTechno") String nomTechno){
return candidatdao.findByTechno(nomTechno);
}
4- the repository:
#Repository
public interface CandidatDao extends JpaRepository <Candidat, String>{
List<Candidat> findByDate(Date date);
#Query("SELECT DISTINCT e FROM Candidat e INNER JOIN e.Techno t")
List<Candidat> findByTechno(String nomTechno);
}
5- app.properties
server.port= 9090
spring.jpa.show-sql = true
spring.datasource.url= jdbc:mysql://localhost:3306/database
spring.datasource.username=??
spring.datasource.password=??
spring.jpa.hibernate.ddl-auto=update
The result in console is:
"Validation failed for query for method public abstract java.util.List com.avatar.dao.CandidatDao.findByTechno(java.lang.String)!"
You can declare the following method into your JpaRepository (also remove the #Query, it is not needed).
List<Candidat> findDistinctByTechnoNomTechno(String nomTechno);
Also in Techno.java you should add the #Column annotation and map it with the DB schema.
I am not sure if you have pasted incomplete code of your entities on purpose. If not your entities are not correct. You should create setters/getters as the following
private String nomTechno;
#Column(name = "NOM_TECHNO")
public String getNomTechno() {
return nomTechno;
}
public void setNomTechno(String nomTechno){
this.nomTechno = nomTechno;
}
Do the above for all variables in your entities.
You do not need to add explicit #Query for this, Spring data can formulate a query if you have right method names
Instead of
#Query("SELECT DISTINCT e FROM Candidat e INNER JOIN e.Techno t")
List<Candidat> findByTechno(String nomTechno);
Try this
List<Candidat> findDistinctByTechno_NomTechno(String nomTechno);

Hibernate/JPA JPQL to wrong SQL when querying Map<String,String> field

This is my Entity configuration
#Entity
#NamedQuery(name = "Payment.findByEmail", query = "SELECT p FROM Payment p JOIN p.additionalAuthData a " +
"WHERE KEY(a) = 'email' AND VALUE(a) = ?1 AND (p.paymentType = 4 OR p.paymentType = 10)")
public class Payment {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Column(name = "payment_type")
private Integer paymentType;
/** other properties, getters and setters */
#ElementCollection
#CollectionTable(name = "additional_auth_data")
#MapKeyJoinColumn(name = "id", referencedColumnName = "id")
#MapKeyColumn(name = "field")
#Column(name = "data_value")
private Map<String, String> additionalAuthData;
}
The NamedQuery findByEmail("test#example.com") generates the following SQL
select -- all fields ...
from payment payment0_ inner join additional_auth_data additional1_ on payment0_.id=additional1_.id
where
additional1_.field='email' and (select additional1_.data_value from additional_auth_data additional1_ where payment0_.id=additional1_.id)='test#example.com' and (payment0_.payment_type=4 or payment0_.payment_type=10)
which is wrong: it may work if you have only one row but it blows up otherwise. H2 complains Scalar subquery contains more than one row and PostgreSQL more than one row returned by a subquery used as an expression. In fact, query's where condition compares a scalar value ('test#example.com') with a subquery.
The correct SQL should be:
select -- all fields
from payment payment0_ inner join additional_auth_data additional1_ on payment0_.id=additional1_.id
where additional1_.field='payerEmail' and additional1_.data_value='test#example.com' and (payment0_.payment_type=4 or payment0_.payment_type=10)
Is the HSQL correct? Is there a way to instruct Hibernate to generates a clever, better SQL? Is this a Hibernate bug?
Note: Hibernate shipped with Spring Boot Starter 1.3.7.RELEASE
Edit:
Using an #Embeddable class
#ElementCollection
#JoinTable(name = "additional_auth_data", joinColumns = #JoinColumn(name = "id"))
#MapKeyColumn(name = "field")
#Column(name = "data_value")
private Set<AdditionalData> additionalAuthData;
#Embeddable
public static class AdditionalData {
#Column(name = "field", nullable = false)
private String field;
#Column(name = "data_value")
private String dataValue;
protected AdditionalData() {
}
public AdditionalData(String field, String dataValue) {
this.field = field;
this.dataValue = dataValue;
}
/** Getters, setters; equals and hashCode on "field" */
}
#NamedQuery(name = "Payment.findByEmail", query = "SELECT p FROM Payment p JOIN p.additionalAuthData a " +
"WHERE a.field = 'email' AND a.dataValue = ?1 AND (p.paymentType = 4 OR p.paymentType = 10)")
solves the problem, and the SQL is correct, but it looks just plain wrong, like shooting a fly with a bazooka...
It generates correct SQL without value().
Use just a=?1
But I would expect is should generate it simple also with it.

How to define custom query using JPA #Query annotation

How do you use the JPA #Query notation? I wish to run the query:
SELECT region, winner_count
FROM awards_regions
WHERE award_id = ?
ORDER BY region
I have the repository:
public interface AwardsRegionsRepository extends JpaRepository<AwardRegion,AwardRegionPk>{
List<AwardRegion> findAllByOrderByAwardRegionPk_RegionAscSortKeyAsc();
List<AwardRegion> findByAwardRegionPk_RegionOrderBySortKeyAsc(String region);
#Query("select a.region, a.winner_count from awards_regions a "
+ "where a.award_id = :awardId order by a.region")
List<AwardRegion> findByAwardRegionPk_AwardId(#Param("awardId") Long awardId);
}
My entity Java beans are
#Entity
public class AwardRegion implements Serializable{
#EmbeddedId
private AwardRegionPk awardRegionPk;
#Column(name = "award")
private String award;
#Column(name = "sort_key")
private String sortKey;
#Column(name = "winner_count")
private String winnerCount;
}
...and embedded PK
#Embeddable
public class AwardRegionPk implements Serializable{
#Column(name = "region")
private String region;
#Column(name = "award_id")
private Long awardId;
}
It looks as though you are using the database table column names in your JPA query. Use the Java bean names.
public interface AwardsRegionsRepository extends JpaRepository<AwardRegion,AwardRegionPk>{
List<AwardRegion> findAllByOrderByAwardRegionPk_RegionAscSortKeyAsc();
List<AwardRegion> findByAwardRegionPk_RegionOrderBySortKeyAsc(String region);
#Query("select a.awardRegionPk.region, a.winnerCount from AwardRegion a "
+ "where a.awardRegionPk.awardId = :awardId order by a.awardRegionPk.region")
List<AwardRegion> findByAwardRegionPk_AwardId(#Param("awardId") Long awardId);
}

Categories

Resources