SqlResultSetMapping native SQL query and One2Many relations - mysql

Is it possible with help of SqlResultSetMapping and entityManager.createNativeQuery fetch object with One2Many relations from two different tables ?
For example
#Entity
#Table(name = "posts")
public class Post {
#OneToMany(mappedBy = "post")
private List<Comment> comments;
}
#Entity
#Table(name = "comments")
public class Comment {
#ManyToOne(optional = false)
#JoinColumn(name = "post_id", referencedColumnName = "post_id")
private Post post;
}
Query:
select p.*, c.* from posts p left join (
select * from comments where content like "%test%" order by last_edited limit 0, 3)
as c on p.post_id = c.post_id
based on native sql query I need to fetch posts objects with a comments.
I mean - as a result I need to receive List of Posts and each post of this list is already populated with an appropriate Comments.
Is it possible with JPA ? If so, could you please show an example ?

I know this question is old, but I still had trouble finding an answer, therefore adding one here.
Yes, it is not possible without additional mapping. The result will be a list of Object arrays. The problem is that the list of comments in your case won't be filled automatically. You need to do the mapping yourself.
Let's imagine your return is a resultset like this:
postid
postCommentId
postComment
1001
2001
comment content 1
1001
2002
comment content 2
1999
2999
comment content 1
1001
2003
comment content 3
1001
2004
comment content 4
In the list, you can see two posts. One with 4 comments and one with 1. The SqlResultsetMapping will only map each row as an object array, which means the post.comments list won't be filled. You have to do it manually.
Here is a sample of how you could do it.
List<Object[]> resultList = em.createQuery(query).getResultList();
// prepare a hashmap for easier mapping
final Map<Long, Post> mappedResult = new HashMap<>(resultList.size());
resultList.forEach(o -> {
Post p = (Post) o[0];
Comment c = (Comment) o[1];
var processedPost = mappedResult.get(p.getId());
if(processedPost != null) {
processedPost.addComment(c);
} else {
p.addComment(c);
mappedResult.put(p.getId(), p);
}
});
// return a sorted list from the created hashmap
return mappedResult.values().stream().sorted((p1, p2) -> p1.getId().compareTo(p2.getId())).toList();
I am pretty sure there are better and more performant possibilities, but I was not able to find any.

You can do something like this:
SELECT post from Post post
LEFT JOIN FETCH post.comments -- to fetch all comments in each post
LEFT JOIN FETCH post.comments comment -- to do the WHERE
WHERE comment.content like "%test%"
The problem is the order by last_edited. I think you cannot order the fetched list of comments in JPA, but you can put this annotation in the private List<Comment> comments; to set a default order in the collection:
#OneToMany(mappedBy = "post")
#OrderBy("lastEdited asc")
private List<Comment> comments;
And, finally, to the limit, use the methods firstResult and maxResults from JPA:
return em.createQuery(query)
.setFirstResult(0) // offset
.setMaxResults(3) // limit
.getResultList();

Related

spring jpa join and query repository

I have User class like this:
{
...
Long userID;
...
List<UserMovieRole> userMovieRoles=...
}
A Movie class like this:
{
...
Long movieID;
...
List<UserMovieRole> userMovieRoles=...
}
I have another class UserMovieRole like this:
{
Long userMovieRoleID;
Role role;
...
User user;
...
Movie movie;
}
Now I want to query on UserMovieRole and select where userID and movieID is given.
In sql I can simply write, I can simply write a join and where sql to select.
But in spring boot jpa query, it seems I can't do that, how can I do that?
Here is what I have tried:
#Query("select umr from UserMovieRole umr where umr.user.userID=?1 and umr.movie.movieID=?2")
#Query("select umrj from UserMovieRole.user full join UserMovieRole.movie umrj where umrj.userID=?1 and umrj.movieID=?2")
I dont't know if any of these are correct, what is the actual way of doing it ?
Write a query as you would in SQL in the #Query and then add another property of the annotation as nativeQuery = true and it will run the query as you would in sql.
Pass the parameters in query by adding a : in front of them. Also, don't forget to add #Param in your auguments.
Something like this:
#Query(value = "select umr from UserMovieRole umr join user u on u.id = umr.userId where u.userId (#got it by joining tables.) = :userId and umr.movieID=:movieId", nativeQuery = true)
returnType yourMethod(#Param("userId") userId, #Param("movieId") movieId);

Spring data JPA , result object has numbers instead of column names

i would like some help trying to do the following.I want to get the number of purchases of each user in the database grouped by his name and id.But it's very hard to do compared to simple sql.
I have the following code in my PurchaseRepository that extends CrudRepository
#Query("SELECT p.user.name as Name,p.user.id as Id,Count(p) as Purchases from Transaction p GROUP BY p.user.id")
List<Object> purchaseNumOfEachUser();
First of all im not sure if this is the right query because i wanted to do Count(*) but says its not valid.
Secondly , the object i get returned when converted to Json via a controller is like
0:{
0:"John",
1:2, //id
2:5 //purchases
}
What i would like to get is
0:{
"Name" : "John",
"Id" : 1 ,
"Purchases" : 2
},
1:{
....
}
Any ideas?
1) The query:
SELECT p.user.name as Name, p.user.id as Id, Count(p) as Purchases
from Transaction p GROUP BY p.user.id
should be
SELECT p.user.name as Name, p.user.id as Id, Count(p) as Purchases
from Transaction p GROUP BY p.user.name, p.user.id
You must group by all rows you are selecting.
2) the result
The result of the query is List if you want to have something meaningful you should consider the constructor expression that let's you create your own objects.
For example
package mypackage;
public class PurchaseInfo {
private final String name;
private final Integer id;
private final Long count;
public PurchaseInfo(String name, Integer id, Long count) {
this.name = name;
this.id = id;
this.cound = count;
}
// getters
}
This class can then be use in the query (please notice the fully qualified class name after NEW):
SELECT NEW mypackage.PurchaseInfo(p.user.name, p.user.id, Count(p))
from Transaction p GROUP BY p.user.name, p.user.id
The result will then be List and you will get it nicely serialized to JSON.
Read more about the Constructor Expression here:
https://vladmihalcea.com/the-best-way-to-map-a-projection-query-to-a-dto-with-jpa-and-hibernate/

(Laravel) Join 3 or more tables using "Eloquent Relationships"

I'm trying to join 3 tables using eloquent relationships, but it doesn't give the expected results.
Shipment model
class Shipment extends Model
{
protected $table = 'ccctadm.Shipment';
public function customergroupcode()
{
return $this->hasMany(DocumentRuleSet::class,'customergroupcode','customergroupcode');
}
public function shipmentcategory()
{
return $this->hasMany(DocumentRuleSet::class,'shipmentcategory','shipmentcategory');
}
public function status()
{
return $this->hasMany(DocumentRuleSet::class,'status','status');
}
}
to get the data i'm using this code
$shipment_data = Shipment::With(['customergroupcode' , 'shipmentcategory','status'])->
Where('shipment_cycle','!=','closed')->get();
I'm trying to make it equivalent to this query
select B.rulesetname,B.icon ,COUNT(*)As Total from
[ccct].[ccctadm]. [Shipment] A INNER JOIN
[ccct].[ccctadm].[documentruleset] B
ON
A.customergroupcode = B.customergroupcode AND A.shipmentcategory =
B.shipmentcategory AND A.status = B.status INNER
JOIN [ccctadm].[shipassign] S ON A.id = S.shipmentid AND
A.shipment_cycle != 'closed' GROUP BY rulesetname,B.icon
The first query returns all the data in 3 tables, but when the second one returns Release only and this what I want
I only want the data that has relation among these three tables not everything
What I'm doing wrong ?
Use has() method. It limits the records based on existence of the relationship.
$shipment_data = Shipment::has('customergroupcode' , 'shipmentcategory','status')->
Where('shipment_cycle','!=','closed')->get();

hibernate optional relationship not showing data

I am using hibernate with MySQL Db. I have a table of business with some fields and relations. in relations, one relation is optional.
#ManyToOne(fetch = FetchType.LAZY)
#NotFound(action=NotFoundAction.IGNORE)
#JoinColumn(name = "modified_by", nullable = true)
public Users getModifiedBy() {
return this.modifiedBy;
}
public void setModifiedBy(Users modifiedBy) {
this.modifiedBy = modifiedBy;
}
now when I fetch data using the following hql it work fine
String hql = "from Business";
Query query = session.createQuery(hql);
list = query.list();
if i changed hql to the following then it shows 0 result.
String hql = "select new com.ba.Business(business.businessId,business.slUsersByCreatedBy.userId,business.modifiedBy.userId,business.bizType.bizTypeId) from com.ba.Business business order by business.businessName";
How to manage this as modifiedBy is null. There were different solution available which i tried like setting optional to true and setting #NotFound but nothing worked.
SQL Created by hql is following.
select business0_.business_id as col_0_0_, business0_.createdBy as col_1_0_, business0_.modified_by as col_5_0_, business0_.biz_type_id as col_9_0_ from _business business0_, _users users1_, _users users4_, _biz_type biztype7_ where business0_.createdBy= users1_.web_user_id and business0_.modified_by= users4_.web_user_id and business0_.biz_type_id= biztype7_.biz_type_id order by business0_.business_name
it is using "and" for joins. If i explicitly add joins by adding following with hql then the result remain same.
left join business.modifiedBy modifiedBy
Is there any solution available?
When you use business.modifiedBy in the query, it implicitly converts to inner join, and that's why you don't get any results. Change it to this and it should work
String hql = "select new com.ba.Business(business.businessId, business.slUsersByCreatedBy.userId, mb.userId, business.bizType.bizTypeId) from com.ba.Business business left join business.modifiedBy mb order by business.businessName";

Not returning all the required rows using MySQL

so here is my issue:
i have 3 tables:
ROLE : RID ,NAME
CLIENT : CID, NAME
USER : UID, RID, CID, USERNAME, PASSWORD
Below is the SQL statement that I have written:
SELECT USER.UID,USERNAME,PASSWORD,ROLE.NAME, ROLE.RID
FROM USER
INNER JOIN ROLE ON USER.RID=ROLE.RID
WHERE CID=1;
The above statement is returning only 1 row when there should actually be 2 rows.
I don't understand what is not working.
When i do the following, i get my 2 rows:
SELECT *
FROM USER
WHERE CID =1;
Note that i am using spring framework and also implementing a RowMapper. Below is my actual code with the field names as per the dbase.
public List<User> viewUserClient(int client_id) {
String sql =
"SELECT USER.ID,USERNAME,PASSWORD,ACTIVE,ROLE.NAME, ROLE.ID FROM USER INNER JOIN ROLE ON USER.ROLE_ID=ROLE.ID WHERE CLIENT_ID=?";
List<User> users = this.getJdbcTemplate().query(sql, new Object[] { client_id }, new UserClientRowMapper());
return users;
}
private static final class UserClientRowMapper implements RowMapper<User> {
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
Client client = new Client();
Role role = new Role();
user.setID(rs.getInt("ID"));
user.setUSERNAME(rs.getString("USERNAME"));
user.setPASSWORD(rs.getString("PASSWORD"));
user.setACTIVE(rs.getBoolean("ACTIVE"));
role.setNAME(rs.getString("NAME"));
role.setID(rs.getInt("ROLE.ID"));
client.setId(rs.getInt("id"));
client.setName(rs.getString("name"));
user.setRole(role);
user.setClient(client);
return user;
}
}
Thanks in advance for your help.
The INNER JOIN keyword returns rows when there is at least one match in both tables. If there are rows in "USER" that do not have matches in "ROLE", those rows will NOT be listed; of the two users returned by your plain select query, probably one has a null RID column value, or a value that is not in ROLE table.
Use a LEFT JOIN.