Springboot jpa, get rows with specific fields using two joins - spring-boot

I have entities :Student, Class, Room.
student M-M class, student M-M rooms, thus two junction tables. Now I want to get all students assigned to class X and room Y. Thus I've made:
#Query("SELECT s from Student s INNER JOIN s.classes c INNER JOIN s.rooms r WHERE c.id LIKE ?1 AND r.id LIKE ?2")
Page<Student> findAllInClassAndRoom(final Long classId, final Long roomId, final Pageable pageable);
But it gives me wrong results. Is there an error in my query ?

The only error in your query is the LIKE statement. Just change the equal sign "=". As below:
#Query("SELECT s from Student s INNER JOIN s.classes c INNER JOIN s.rooms r WHERE c.id = ?1 AND r.id = ?2")
Page<Student> findAllInClassAndRoom(final Long classId, final Long roomId, final Pageable pageable);
The LIKE statement allows a greater mass of data. Because it would allow any Room or Class that part of the code is the id entered as a parameter.
LIKE statement specification

Related

JpaRepository find by manyToMany attribute

Let's imagine classic example of manyToMany relation. I have entities Student and Course and manyToMany relation table between those two entities.
#Entity
data class Student(
#Id
val id: UUID,
val name: String,
#ManyToMany
val courses: Set<Course>
)
#Entity
data class Course(
#Id
val id: UUID,
val title: String,
#ManyToMany
val students: Set<Student>
)
repository can looks like this:
#Repository
interface StudentRepository : JpaRepository<Student, UUID> {
fun findAllByCoursesTitle(title: String): List<Student>
}
Function findAllByCoursesTitle should return all student signed up for math, for example.
My problem is:
How will function looks like when i need to return all students signed up for two specific courses. But i need it for dynamic number of courses, 1 or more.
for example input to function would looks like listOf("math", "geography") or in case of course_ids listOf("UUID", "UUID"). It does not matter if it will be ID or title. What is doalbe.
When i would want to write sql query for that it would looks like this:
SELECT student.id, sc1.course_id, sc2.course_id FROM student
JOIN student_course cs1
ON student.id = cs1.student_id
JOIN student_course cs2
ON student.id = cs2.student_id
WHERE cs1.course_id = ?
AND cs2.course_id = ?
But as i said, i need to have it done for various number of courses.
Please somebody advise.
If you want to resolve logic at SQL statement you can try something like this:
SELECT st.id, sc.course_id, sc.course_id FROM student as st
JOIN (
SELECT st.id as stId, sum(case when c.id IN (:courseIdList) then 1 else 0 end) as cc FROM student as std
JOIN student_course as cs ON std.id = cs.student_id
JOIN cource as c ON c.id = cs.cource_id
GROUP BY (st.id) having cc >= :minimumCourseNumbers
) A on A.stId = st.id
There're courseIdList: List course Ids. minimumCourseNumbers: Minimum course numbers that student must register.
However, I think the logic should move to application to filter.

Spring MVC, Select Special Columns in Native SELECT Query

this is my native SELECT Query in Repository
#Modifying
#Query(value = "SELECT * FROM tasks WHERE title LIKE '%Java%' ORDER BY id DESC ", nativeQuery = true)
List<Task> listAllTasks();
this works ok, but when I use custom column name instead of *, like this
#Modifying
#Query(value = "SELECT title FROM tasks WHERE title LIKE '%Java%' ORDER BY id DESC ", nativeQuery = true)
List<Task> listAllTasks();
I have this error :
org.postgresql.util.PSQLException: The column name id was not found in this ResultSet.
any Help?
The resultset doesn't have the "id" in it, you have to provide it.
You should change the way you are declaring your SQL:
SELECT t.title, t.id FROM tasks t WHERE t.title LIKE '%Java%' ORDER BY t.id DESC
Check out this sort example:Native Queries
Select * from Entity -> returns a List of Entity
Example:
#Query(select * from tasks)
List<Task> findAllTasks();
Select column from Entity -> returns a List of Types of the entity.
Example:
#Query(select t.title from tasks t)
List<String> findTitle_AllTasks();
title is of the type String
Select multiple columns from Entity -> returns an Object[] holding the data
Example:
#Query(select t.id, t.title from tasks t)
List<Object[]> findIdTitle_AllTasks();
So, you are retrieving String type data - title and asking to return a List of Task type. This is causing the problem. You can actually check the hibernate docs under HQL and JPQL to understand this.
Plus, you are doing a SELECT (DQL operation). #Modifying is rudimentary here as it is used for DML operations using Data JPA - UPDATE/DELETE.

Spring Boot #Formula displaying same likeCount on every result

I'm having a problem with my #Formula field. I have three tables: user_acc, song and user_likes_song. I'm trying to display likeCount on songs based on how many times the songs ID appears in the many-to-many table.
My #Formula field in the Song entity looks like this:
#Formula("(SELECT COUNT(s.id) FROM user_acc u INNER JOIN user_likes_song us on u.id = us.user_id " +
"INNER JOIN song s on us.song_id = s.id WHERE us.song_id = s.id )")
private Long likeCount;
Currently when I like a song for the first time it shows "likeCount: 1" on every result. After I like another song it returns "likeCount: 2" on every result and so on.
Seems to me like it's counting all the likes in total, not for the specific song. How can I make it so it so it shows how many times a specific song ID appears in the many-to-many table?
You do the match with every song rows :
INNER JOIN song s on us.song_id = s.id WHERE us.song_id = s.id
What you want is restricting it to the current song entity instance.
In fact, if the #Formula annotation is specified on a field of the Song entity, you don't want to specify Song in the query, instead you want to directly refer the field of the current entity (here id), that is :
WHERE us.song_id = id
So this should do the job :
#Formula("(SELECT COUNT(*) FROM user_acc u INNER JOIN user_likes_song us on u.id = us.user_id " + "WHERE us.song_id = id )")
And I think that you could also simplify more in this way :
#Formula(" (SELECT COUNT(*) FROM user_likes_song us WHERE us.song_id = id) ")
because the join with user account doesn't matter.

HQL Select New doesn't return row when a foreign key is null

I have the following named query
#NamedQuery(name = "UserFlight.getUserFlightDetails",
query = "SELECT new com.foobar.UserFlightDetails(uf.flight.divertedAirport, uf.flight.number) " +
"FROM UserFlight uf WHERE uf.user.id=?1 AND uf.flight.id=?2")
The UserFlightDetails constructor is as follows
public UserFlightDetails(Airport airport, String flightNumber) {
this.setDivertedAirport(airport);
this.setFlightNumber(flightNumber);
}
divertedAirport is a foreign key in the flight table, path=(uf.flight.divertedAirport)
My problem is when divertedAirport is null (it's a nullable foreign key), my HQL query returns null as the result (The code doesn't even trigger the constructor above), so I don't get the flightNumber which is never null.
If the divertedAirport isn't null, I get both the airport and the flight number fine (and the above constructor gets executed just fine).
What could be causing this and how could I resolve it? I tried some null functions like nullif and coalesce but nothing helped.
I'm using spring boot 1.2.7, hibernate-core 4.3.11.Final
Probably, the problem is the uf.flight.divertedAirport. This expression do a JOIN between flight and divertedAirport but, as you say, divertedAirport is a fk and can be null.
So, you need to use the LEFT JOIN.
I would rewrite your query like this:
#NamedQuery(name = "UserFlight.getUserFlightDetails",
query =
"SELECT new com.foobar.UserFlightDetails(divertedAirport, flight.number)
FROM UserFlight uf
JOIN uf.flight flight
LEFT JOIN flight.divertedAirport divertedAirport
JOIN uf.user user
WHERE user.id = ?1 AND flight.id = ?2 ")
I remove the references like uf.user.id for a explicit JOIN (JOIN uf.user user plus user.id), because is more legible and this kind of problem that generated your question is more easy to find using this way to write JPQL queries.

The greatest row per group - further combining the result set with left join with other related table in Hibernate - HQL. Is it possible?

There two tables in my Oracle database product and product_image. They have one-to-many relationship from product to product_image. Therefore, the relationship can be mapped in Hibernate something like the following.
The product entity:
#Entity
#Table(name = "PRODUCT", catalog = "", schema = "WAGAFASHIONDB")
public class Product implements java.io.Serializable
{
#OneToMany(mappedBy = "prodId", fetch = FetchType.LAZY)
private Set<ProductImage> productImageSet;
}
#Entity
#Table(name = "PRODUCT_IMAGE", catalog = "", schema = "WAGAFASHIONDB")
public class ProductImage implements java.io.Serializable
{
#ManyToOne(fetch = FetchType.LAZY)
private Product prodId;
}
I need to query that can fetch a list of rows with the maximum prod_image_id (primary key of the prduct_image table) from each group of products in the product_image table.
This was my previous question. This can be done with the following SQL.
SELECT
pi.prod_image_id,
pi.prod_id, pi.prod_image
FROM
product_image pi
INNER JOIN (
SELECT
MAX(pi.prod_image_id) AS prod_image_id
FROM
product_image pi
GROUP BY
pi.prod_id
) prod_image
ON pi.prod_image_id=prod_image.prod_image_id
The answer to that question corresponds to the following correct HQL.
SELECT
pi.prodImageId,
pi.prodId
FROM
ProductImage pi
WHERE
pi.prodImageId in (
SELECT
MAX(pis.prodImageId)
FROM
Product p
INNER JOIN
p.productImageSet pis
GROUP BY
p.prodId
)
This brings the following result exactly as intended.
PROD_IMAGE_ID PROD_ID PROD_IMAGE
662 284 3562298873030291049_Winter.jpg
644 283 7551758088174802741_9392401244_SS_2505.jpg
595 124 298082252715152799_SS_5012.jpg
566 62 7826143854352037374_SS_5004-A.jpg
But what I actually need is that the result set retrieved by the above SQL/HQL needs to be combined with the product table with LEFT OUTER JOIN so that it can retrieve each product from the product table regardless of their images in the product_image table something like the following.
PROD_IMAGE_ID PROD_ID PROD_IMAGE
662 284 3562298873030291049_Winter.jpg
644 283 7551758088174802741_9392401244_SS_2505.jpg
595 124 298082252715152799_SS_5012.jpg
- 101 -
- 81 -
566 62 7826143854352037374_SS_5004-A.jpg
This could be done by the following native SQL but doesn't seem possible with HQL which allows a subquery only in the SELECT and the WHERE clauses, a subquery in the FROM clause is disallowed in HQL.
SELECT
t.prod_image_id,
p.prod_id,
t.prod_image
FROM
product p
LEFT OUTER JOIN(
SELECT
pi.prod_image_id,
pi.prod_id,
pi.prod_image
FROM
product_image pi
INNER JOIN (
SELECT
MAX(pi.prod_image_id) AS prod_image_id
FROM
product_image pi
GROUP BY
pi.prod_id
) prod_image
ON pi.prod_image_id=prod_image.prod_image_id
)t ON p.prod_id=t.prod_id ORDER BY p.prod_id DESC;
My Google search says this is not feasible with a single HQL statement. Is this possible somehow with HQL? Please confirm me.
You're right, you can't use subqueries in from clause.
http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html_single/#queryhql-subqueries
But you can use a separate query like:
select p
from Product p
where p.productImageSet is empty
to find the products that has no product image.
I would create an Oracle view that implements your query that gives you what you want with a simple select in HQL.
I'm currently having the same table relationship in MySQL. I'm currently trying to learn JPA. Accordingly, I'm using JPA 2.0 provided by Hibernate - version 4.2.7 final. The following JPQL,
select
p.prodId,
pi.productImageId,
p.prodName,
pi.prodImage
from
Product p
left join
p.productImageSet pi
where
pi.productImageId in (
select
max(productImageId) as productImageId
from
ProductImage
group by
prodId
)
or pi.productImageId is null
order by p.prodId
and the corresponding criteria query,
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple>criteriaQuery=criteriaBuilder.createTupleQuery();
Metamodel metamodel=entityManager.getMetamodel();
EntityType<Product>entityType=metamodel.entity(Product.class);
Root<Product>root=criteriaQuery.from(entityType);
SetJoin<Product, ProductImage> join = root.join(Product_.productImageSet, JoinType.LEFT);
List<Selection<?>>selections=new ArrayList<Selection<?>>();
selections.add(root.get(Product_.prodId));
selections.add(root.get(Product_.prodName));
selections.add(join.get(ProductImage_.prodImage));
selections.add(join.get(ProductImage_.productImageId));
criteriaQuery.multiselect(selections);
Subquery<Long>subquery=criteriaQuery.subquery(Long.class);
Root<ProductImage> subRoot = subquery.from(ProductImage.class);
subquery.select(criteriaBuilder.max(subRoot.get(ProductImage_.productImageId)));
subquery.groupBy(subRoot.get(ProductImage_.prodId).get(Product_.prodId));
Predicate []predicates=new Predicate[2];
predicates[0]=criteriaBuilder.in(join.get(ProductImage_.productImageId)).value(subquery);
predicates[1]=join.get(ProductImage_.productImageId).isNull();
criteriaQuery.where(criteriaBuilder.or(predicates));
criteriaQuery.orderBy(criteriaBuilder.desc(root.get(Product_.prodId)));
TypedQuery<Tuple> typedQuery = entityManager.createQuery(criteriaQuery);
List<Tuple> tuples = typedQuery.getResultList();
Both of them, JPQL and criteria query produce the following SQL query.
select
product0_.prod_id as col_0_0_,
productima1_.product_image_id as col_1_0_,
product0_.prod_name as col_2_0_,
productima1_.prod_image as col_3_0_
from
social_networking.product product0_
left outer join
social_networking.product_image productima1_
on product0_.prod_id=productima1_.prod_id
where
productima1_.product_image_id in (
select
max(productima2_.product_image_id)
from
social_networking.product_image productima2_
group by
productima2_.prod_id
)
or productima1_.product_image_id is null
order by
product0_.prod_id desc
resulting in fetching the desired result set as mentioned in the question - not in the way we can see in the usual RDBMS systems but it works.
This can be done, if the query statement is slightly modified. Accordingly the following criteria query does what exactly is needed.
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<ProductUtils>criteriaQuery=criteriaBuilder.createQuery(ProductUtils.class);
Metamodel metamodel = entityManager.getMetamodel();
Root<Product> root = criteriaQuery.from(metamodel.entity(Product.class));
ListJoin<Product, ProductImage> join = root.join(Product_.productImageList, JoinType.LEFT);
List<Selection<?>>selections=new ArrayList<Selection<?>>();
selections.add(root.get(Product_.prodId));
selections.add(root.get(Product_.prodName));
selections.add(join.get(ProductImage_.prodImage));
selections.add(join.get(ProductImage_.productImageId));
criteriaQuery.select(criteriaBuilder.construct(ProductUtils.class, selections.toArray(new Selection[0])));
Subquery<Long> subquery = criteriaQuery.subquery(Long.class);
Root<ProductImage> prodImageRoot = subquery.from(metamodel.entity(ProductImage.class));
subquery.select(prodImageRoot.get(ProductImage_.productImageId));
subquery.where(criteriaBuilder.equal(root, prodImageRoot.get(ProductImage_.prodId)), criteriaBuilder.lessThan(join.get(ProductImage_.productImageId), prodImageRoot.get(ProductImage_.productImageId)));
criteriaQuery.where(criteriaBuilder.exists(subquery).not());
criteriaQuery.orderBy(criteriaBuilder.desc(root.get(Product_.prodId)));
List<ProductUtils> list = entityManager.createQuery(criteriaQuery).getResultList();
This criteria query produces the following desired SQL query that does equally the same function which is needed as specified in the question.
select
product0_.prod_id as col_0_0_,
product0_.prod_name as col_1_0_,
productima1_.prod_image as col_2_0_ ,
productima1_.product_image_id as col_3_0_
from
social_networking.product product0_
left outer join
social_networking.product_image productima1_
on product0_.prod_id=productima1_.prod_id
where
not (exists (select
productima2_.product_image_id
from
social_networking.product_image productima2_
where
product0_.prod_id=productima2_.prod_id
and productima1_.product_image_id<productima2_.product_image_id))
order by
product0_.prod_id desc

Resources