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

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.

Related

Is there a way to have custom SQL query on top of JPA repository to have BULK UPSERTS?

I have a snowflake database and it doesn't support unique constraint enforcement (https://docs.snowflake.com/en/sql-reference/constraints-overview.html).
I'm planning to have a method on JPA repository with a custom SQL query to check for duplicates before inserting to the table.
Entity
#Entity
#Table(name = "STUDENTS")
public class Students {
#Id
#Column(name = "ID", columnDefinition = "serial")
#GenericGenerator(name = "id_generator", strategy = "increment")
#GeneratedValue(generator = "id_generator")
private Long id;
#Column(name = "NAME")
private String studentName;
}
Snowflake create table query
CREATE table STUDENTS(
id int identity(1,1) primary key,
name VARCHAR NOT NULL,
UNIQUE(name)
);
Repository
public interface StudentRepository extends JpaRepository<Students, Long> {
//
#Query(value = "???", nativeQuery = true)
List<Student> bulkUpsertStudents(List<Student> students);
}
You can use a SELECT query to check for duplicate values in the name column before inserting a new record into the table. For example:
#Query(value = "SELECT * FROM STUDENTS WHERE name = :name", nativeQuery = true)
List<Student> findByName(#Param("name") String name);
This method will return a list of Student records with the specified name value. If the list is empty, it means that there are no records with that name value, and you can safely insert a new record with that name value.
List<Student> studentList = new ArrayList<>();
for (Student student : students) {
List<Student> existingStudents = studentRepository.findByName(student.getName());
if (existingStudents.isEmpty()) {
studentsToInsert.add(student);
}
}
studentRepository.bulkUpsertStudents(studentList)
EDIT
If the above solution doesn't work. You can use the MERGE statement to update existing records in the table if the data has changed. For example, if you want to update the name of a Student if it has changed, you can use the following MERGE statement:
#Query(value = "MERGE INTO students t USING (SELECT :name AS name, :newName AS newName) s
ON t.name = s.name
WHEN MATCHED AND t.name <> s.newName THEN UPDATE SET t.name = s.newName
WHEN NOT MATCHED THEN INSERT (name) VALUES (s.name)", nativeQuery = true)
List<Student> bulkUpsertStudents(List<Student> students);
This query will update the name of each Student in the students list if it has changed, and if a conflict occurs, it will not insert a new record. This will ensure that only unique name values are inserted into the table, without having to perform a separate query for each record.
I was able to overcome this using the below approach but need to verify the performance of the queries.
Repository saveAll() method to save all the entities.
Using the custom nativeQuery as below
INSERT OVERWRITE INTO STUDENTS
WITH CTE AS(SELECT ROW_NUMBER() OVER (PARTITION BY NAME ORDER BY ID) AS RNO, ID, NAME FROM STUDENTS)
SELECT ID, NAME FROM CTE WHERE RNO = 1;
Example code :
import static io.vavr.collection.List.ofAll;
import static io.vavr.control.Option.of;
import static java.util.function.Predicate.not;
public Validation<ValidationError, List<Students>> saveAll(List<String> students) {
return of(students)
.filter(not(List::isEmpty))
.map(this::mapToEntities) // maps the list to list of database entities
.map(repository::saveAll) // save all
.toValidation(ERROR_SAVING_STUDENTS) // vavr validation in case of error
.peek(x -> repository.purgeStudents()) // purging to remove duplicates
.toValidation(ERROR_PURGING_STUDENTS);
}
This issue is only due to snowflake's incapability to check uniqueness.

Spring OneToMany - how to limit list of objects to list of one field from that object

I wonder if it's possible to fetch List of some specific field of objects instead of list of whole objects from relation #OneToMany:
#Entity
public class Template
...
private Driver driver;
prvate boolean isIpen;
#OneToMany(
mappedBy = "template"
)
private List<Warehouse> warehouses = new ArrayList<>();
I want to fetch list of Template objects with list of Warehouse.name (List<String>) instead of List<Warehouse>. Is it possible?
My repository:
#QueryHints(value = {
#QueryHint(name = org.hibernate.jpa.QueryHints.HINT_PASS_DISTINCT_THROUGH, value = "false")
})
#Query("SELECT at FROM Template at " +
"WHERE at.driver.id = :companyId " +
"AND at.isOpen = true")
#EntityGraph(attributePaths = {"warehouses"})
List<Template> findAllOpenByCompanyId(Long companyId, Pageable pageable);
I wanto to reduce the number of queries to database
I would try using an #ElementCollection with #CollectionTable instead of the #OneToMany.
So it would turn like this:
#Entity
public class Template
...
private Driver driver;
prvate boolean isIpen;
#ElementCollection
#CollectionTable(
name="the name of the warehouse table",
joinColumns=#JoinColumn(name="warehouse id column")
)
#Column(name="warehouse name column in warehouse table")
private List<String> warehouseNames = new ArrayList<>();
I'm unable to test this at the moment, but hopefully it helps.

Join 2 tables using Spring JPA

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.

Select latest record for each id spring jpa

I have an entity like this:
#Entity
class Point{
#EmbeddedId
private PointIdentity pointIdentity;
private float latitude;
private float longitude;
#Embeddable
public static class PointIdentity implements Serializable {
private Long id;
private ZonedDateTime timestamp;
}
}
There is EmbeddedId, so in "id" column can be multiple records with the same ids.
And I need to get latest record for each id, using CriteriaQuery and JPA specifications I think, but don't know how.
In SQL, this would be something like this:
SELECT id, MAX(timestamp)
FROM geodata
GROUP BY id
Is there any way to do it?
Any help, thanks.
You can easily write a JPQL query:
TypedQuery<Object[]> query = entityManager.createQuery(
"select p.pointIdentity.id, max(p.pointIdentity.timestamp) from Point p group by p.pointIdentity.id",
Object[].class);
List<Object[]> results = query.getResultList();
which translates to:
select
point0_.id as col_0_0_,
max(point0_.timestamp) as col_1_0_
from
point point0_
group by
point0_.id
Alternatively, you can use criteria query:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> query = criteriaBuilder.createQuery(Object[].class);
Root<Point> point = query.from(Point.class);
query.groupBy(point.get("pointIdentity").get("id"));
query.multiselect(
point.get("pointIdentity").get("id"),
criteriaBuilder.max(point.get("pointIdentity").get("timestamp"))
);
TypedQuery<Object[]> typedQuery = entityManager.createQuery(query);
List<Object[]> results = typedQuery.getResultList();
which produces identical SQL.

JPA Criteria API query with Join and inheritance

I am trying to write Query using JPA Criteria API. I have the following classes:
Class Booking {
#ForeignKey(name = "BVisit_ID_FK")
private List<BVisit> BVisits = new LinkedList<>();
//other properties
...
}
Class Visit{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private Long id;
}
Class VisitSpecial extends Visit{
#Column(name = "ARRIVAL_TIME", nullable = false)
private Date arrivalTime;
#Column(name = "DEPARTURE_TIME", nullable = false)
private Date departureTime;
//other properties...
}`
How can I write query using JPA Criteria Api (and metamodels) that will find all bookings that have
visits with date value(parameter) that is between min arrival time, an max departure time for its booking visits.
I use org.springframework.data.jpa.domain.Specification
The query should look like something like this:
SELECT Booking
from Booking B, Visit V, VisitSpecial VS
where Visit.bookingId = Booking.id and Visit.id = VisitSpecial.id
and VisitSpecial.arrivalTime = (SELECT MIN(VisitSpecial.arrivalTime) from VisitSpecial VS1 WHERE V.id = VS1.id)
and VisitSpecial.arrivalTime <= :date
and VisitSpecial.departureTime = (SELECT MAX(VisitSpecial.departureTime) from VisitSpecial VS1 Where V.id = VS1.id)
and VisitSpecial.departureTime >= :date
See my answer here for dealing with InheritanceType.JOINED and other inheritance strategies through JPA Criteria Subquery.
Downcasting with treat may work, but I do not recommend it for complex queries as it may produce inefficient queries (redundant joins) or not work at all based on your JPA provider (Hibernate, Eclipselink, etc.).

Resources