Join 2 tables using Spring JPA - spring

I have two entities(Invoice and InvoiceItems) without adding any relationship.
Invoice
public class Invoice {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long invoiceID;
#Column(name="code")
private String code;
//other columns
}
Invoice Items
public class InvoiceItems {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long invItemID;
#Column(name="invoice_id")
private Integer invoiceId;
//other columns
}
Can I join these entities and get data without adding relationship using JPA?
If it isn't possible how to join 2 entities using JPQL or Native query?

If your data is valid then using native query you can do that
#Query(nativeQuery = true, "select * from Invoice i join InvoiceItems im on i.id = im.invoice_id")
public List<Invoice> findData();
But that is not a good way join without relation using JPA.

Yes, you can join these entities and get data without adding relationship using JPA, but it's a little bit losing the purpose of using JPA.
You need to create a java class first, which will be the returning data object from the DB. After that you can use entityManager's createNamedQuery method to get the result.
createNamedQuery(String sqlString, ResultClass.Class)
sqlString may be something like:
SELECT INV.INVOICE_ID
INV.CODE
INV_ITEMS.INV_ITEM_ID
FROM INVOICE INV
JOIN INVOICE_ITEMS INV_ITEMS
ON INV.INVOICE_ID = INV_ITEMS.INVOICE_ID;
And the corresponding ResultClass:
public class ResultClass {
private Long invoiceID;
private String code;
private Long invItemID;
// other columns
}
Or you can even use RowMapper to map the object all by yourself for more flexibility by using JdbcTemplate with query() method.

Related

Fetch specific columns dynamically

I have the following User entity:
public class User extends PanacheEntityBase{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "DataIdGenerator")
#Column(name = "id")
public Long id;
public String name;
public String location;
public int age;
}
I also have the following endpoint: '/user', with a 'select' query parameter where you provide the column names you want to receive. It should be possible to select any combination of columns like: /user?select=id,name, /user?select=id,age, /user?select=name,age, /user?select=age,name
Based on the 'select' query I want to use a projection to get the selected columns only. Currently I'm using the query to create the following query fe: /user?select=id,name to SELECT d.id, d.name FROM User d, however I need the DTO to be dynamic based on the columns provided too.
Currently I have the following projection where UserDTO is a class with id and name attributes. This works fine, but if I change any parameter I need a different DTO.
// This variable is dynamically created based on query parameters
String query = 'SELECT d.id, d.name FROM User d'
return User.find(query).project(UserDTO.class).list();
Is it possible to make this projection DTO class more dynamic, so it supports all combinations?
I suspect the Panache API is not flexible enough at the moment to do what you are asking.
But you could use the Hibernate Reactive API without Panache:
#Inject
Mutiny.SessionFactory sf;
public Uni<List<Tuple>> find(String query) {
return sf.withSession(session ->
session.createQuery(query, Tuple.class).getResultList()
);
}
Once you have the Tuple, you can convert it to the type you prefer.

Spring Data + View with Union return duplicate rows

i'm using Spring Boot 2.4.2 and Data module for JPA implementation.
Now, i'm using an Oracle View, mapped by this JPA Entity:
#Entity
#Immutable
#Table(name = "ORDER_EXPORT_V")
#ToString
#Data
#NoArgsConstructor
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class OrderExportView implements Serializable {
private static final long serialVersionUID = -4417678438840201704L;
#Id
#Column(name = "ID", nullable = false)
#EqualsAndHashCode.Include
private Long id;
....
The view uses an UNION which allows me to obtain two different attributes of the same parent entity, so for one same parent entity (A) with this UNION I get the attribute B in row 1 and attribute C in row 2: this means that the rows will be different from each other.
If I run the query with an Oracle client, I get the result set I expect: same parent entity with 2 different rows containing the different attributes.
Now the issue: when I run the query with Spring Data (JPA), I get the wrong result set: two lines but duplicate.
In debug, I check the query that perform Spring Data and it's correct; if I run the same query, the result set is correct, but from Java/Spring Data not. Why??
Thanks for your support!
I got it! I was wrong in the ID field.
The two rows have the same parent id, which is not good for JPA, which instead expects a unique value for each line.
So, now I introduced a UUID field into the view:
sys_guid() AS uuid
and in JPA Entity:
#Id
#Column(name = "UUID", nullable = false)
#EqualsAndHashCode.Include
private UUID uuid;
#Column(name = "ID")
private Long id;
and now everything works fine, as the new field has a unique value for each row.

Spring Data JPA query to select all value from one join table

I have problem to select all value from one table and few other columns using Spring Data JPA. I am using PostgreSql database and when I send query through PgAdmin I get values I want, but if I use it in Spring Boot Rest returns only one table values (subquery not working). What I am doing wrong?
#Query(value = "SELECT item.*, MIN(myBid.bid) AS myBid, (SELECT MIN(lowestBid.bid) AS lowestbid FROM bids lowestBid WHERE lowestBid.item_id = item.item_id GROUP BY lowestBid.item_id) FROM item JOIN bids myBid ON item.item_id = myBid.item_id WHERE myBid.user_id = :user_id GROUP BY item.item_id", nativeQuery = true)
public List<Item> findAllWithDescriptionQuery(#Param("user_id") UUID userId);
Added Item class
#Data
#Entity(name = "item")
public class Item {
#Id
#GeneratedValue
private UUID itemId;
#NotNull
#Column(name = "title")
#Size(max = 255)
private String title;
#NotNull
#Column(name = "description")
private String description;
#NotNull
#Column(name = "created_user_id")
private UUID createdUserId;
}
The result from your native query cannot simply be mapped to entities due to the in-database aggregation performed to calculate the MIN of own bids, and the MIN of other bids. In particular, your Item entity doesn't carry any attributes to hold myBid or lowestbid.
What you want to return from the query method is therefore a Projection. A projection is a mere interface with getter methods matching exactly the fields returned by your query:
public interface BidSummary {
UUID getItem_id();
String getTitle();
String getDescription();
double getMyBid();
double getLowestbid();
}
Notice how the query method returns the BidSummary projection:
#Query(value = "SELECT item.*, MIN(myBid.bid) AS myBid, (SELECT MIN(lowestBid.bid) AS lowestbid FROM bids lowestBid WHERE lowestBid.item_id = item.item_id GROUP BY lowestBid.item_id) FROM item JOIN bids myBid ON item.item_id = myBid.item_id WHERE myBid.user_id = :user_id GROUP BY item.item_id", nativeQuery = true)
public List<BidSummary> findOwnBids(#Param("user_id") UUID userId);
Return type is List of Item objects and the query specified is having columns which are not part of return object. I recommend using appropriate Entity which full-fills your response type.

Selecting from Multiple Tables in Spring JPA with Pageable and Sorting

I saw the Selecting from Multiple Tables in Spring Data already had the solution for multiple tables.
I would like to know if it is possible to write custom query that has tables with pageable and sorting feature at the same time in Spring JPA/DATA.
SELECT s.service_id, s.name, us.rating_id
FROM services s,
ratings r,
user_services us
where
us.service_id = s.service_id and
us.rating_id = r.rating_id and
us.user_id= ?
;
Thanks for you help in advance.
Sorting feature is under question, but pagination is possible to use.
Assume that we have:
#Entity
public class Service {
#Id
private Long id;
private String name;
//...
}
#Entity
public class UserService {
#Id
private Long id;
#ManyToOne
User user;
#ManyToOne
Service service;
#ManyToOne
Rating rating;
//...
}
Then we create a projection:
public interface ServiceRating {
Long getServiceId();
String getServiceName();
Long getRatingId();
}
And then create a query method supported pagination:
public interface UserServiceRepo extends CrudRepository<UserService, Long> {
#Query("select s.id as serviceId, s.name as serviceName, us.rating.id as ratingId from UserService us join us.service s where us.user.id = ?1")
Page<ServiceRating> getServiceRating(Long userId, Pageable pageable);
}
(Since this query does not contain grouping it's not necessary to use an additional countQuery (see the parameter of #Query)).
Test:
Page<ServiceRating> pages = userServiceRepo.getServiceRating(1L, new PageRequest(0, 10));
assertThat(pages.getContent()).hasSize(10));
UPDATE
Sorting also working perfectly.
Just create a Sort object, specify direction and filed name (from the projection):
Sort sort = new Sort(Sort.Direction.ASC, "serviceName");
userServiceRepo.getServiceRating(1L, new PageRequest(0, 10, sort));

Spring Data find by inner relation

My question is about the way Spring data is generating the query .
I have two entities : Message , Sender
#Entity
public class Message extends BaseEntity {
#ManyToOne
protected Account sender;
}
I have a call to
messageDao.findBySenderId(Long id)
The result is query all columns from the two two table with a left outer join between the two tables , but my expectation was simply to just select from message table where sender_id = the passed value.
So is there a way to force selecting only the first message entity and not to join with the other one? I want simple condition in the where clause
by using findBy not custom #Query
You will need a repository like (untested) :
#Repository
public interface MessageRepository extends JpaRepository<Message, Long> {
Message findFirstBySenderId(Long id);
}
See repositories.query-methods
I think Hibernate is doing a LEFT JOIN because your #ManyToOne is optional = true (default).
Try:
#ManyTone(optional = false)
And you will see Hibernate doing a query without a JOIN, as you expected.
You can use fetch type LAZY for the associations types:
#ManyToOne(optional = false, fetch = FetchType.LAZY)

Resources