JPA #Query gives NonUniqueResultException - java

i have entity like this
#Builder
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#Table(name = "fulfillment_purchase_orders")
public class FulfillmentPurchaseOrder extends Audit implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private Integer id;
#Column(name = "purchase_order_id")
private Integer purchaseOrderId;
#Column(name = "version")
private Integer version;
but i want to get the latest data from version. i have try use this code :
#Query("select f from FulfillmentPurchaseOrder f where f.purchaseOrderId =?1 order by f.version desc")
Optional<FulfillmentPurchaseOrder> findByPurchaseOrderId(Integer purchaseOrderId);
but i got error like this
query did not return a unique result: 2; nested exception is javax.persistence.NonUniqueResultException: query did not return a unique result: 2",
my question is how to make query for getting only the latest use order by Version ?

use subquery like :
#Query("select * from FulfillmentPurchaseOrder f where f.purchaseOrderId = (SELECT max(fp.purchaseOrderId) FROM FulfillmentPurchaseOrder fp)")
Optional<FulfillmentPurchaseOrder> findByPurchaseOrderId(Integer purchaseOrderId);

Your query is returning more then one result. Try making the query native and adding LIMIT 1 at the end like so:
#Query(nativeQuery = true, value = "select f from FulfillmentPurchaseOrder f where f.purchaseOrderId =?1 order by f.version desc LIMIT 1")
Optional<FulfillmentPurchaseOrder> findByPurchaseOrderId(Integer purchaseOrderId);
LIMIT 1 means return the first row that matches.

Related

Spring Data JPA Native Query N + 1 problem

I've run into some code which behaviour is not clear for me.
We have first entity:
#Data
#Entity
#Table(name = "Info")
public class Info {
#OneToOne
#JoinColumn(name = "data_id")
private Data data;
}
and second one:
#Data
#Entity
#Table(name = "Data")
public class Data {
#Id
private Long dataId
}
And we have next method for retrieving data:
#Query(value = "SELECT * FROM Info i inner join Data d on d.data_id=i.data_id",
nativeQuery = true)
List<Info> getInfo() {}
As nativeQuery = true is present I expect this method make just one SQL select and retrieve me data. But If I take a look at logs actually there are 2 selects:
SELECT * FROM Info i inner join Data d on d.data_id=i.data_id;
SELECT * FROM Data d where d.data_id = 123;
Why this is happening ? How to fix it to make only one select and retrieve all data ?
It's not possible to specify native query fetches without Hibernate specific APIs. I would suggest you to use a normal JPQL/HQL query:
#Query(value = "FROM Info i join fetch i.data")
List<Info> getInfo();
This will do the same as your native query but at the same time just run only a single query.

JPA criteria: order by child field gives error

I have 3 entities. Customer, Process and Document.
A Customer has many processes and a process has many documents.
I want to sort customers by document's updateDate.
My entities are like below;
Customer-
#Entity
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Process> processes = new ArrayList<>();
// getter, setter etc.
}
Process-
#Entity
public class Process {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String type;
#ManyToOne(fetch = FetchType.LAZY)
private Customer customer;
#OneToMany(mappedBy = "process", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Document> documents = new ArrayList<>();
//getter, setter etc.
}
Document-
#Entity
public class Document {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String note;
private LocalDateTime updateDate;
#ManyToOne(fetch = FetchType.LAZY)
private Process process;
}
I have tried the following specification-
public static Specification<Customer> orderByDocumentUploadDate() {
return (root, query, criteriaBuilder) -> {
ListJoin<Customer, Process> processJoin = root.join(Customer_.processes);
ListJoin<Process, Document> documentJoin = processJoin.join(Process_.documents);
query.orderBy(criteriaBuilder.desc(documentJoin.get(Document_.updateDate)));
query.distinct(true);
return null;
};
}
It gives following error-
ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select
list
Generated SQL-
select distinct customer0_.id as id1_0_,
customer0_.name as name2_0_
from customer customer0_
inner join
process processes1_ on customer0_.id = processes1_.customer_id
inner join
document documents2_ on processes1_.id = documents2_.process_id
order by documents2_.update_date desc
limit ?
I have also tried by grouping, like below-
public static Specification<Customer> orderByDocumentUploadDate() {
return (root, query, criteriaBuilder) -> {
ListJoin<Customer, Process> processJoin = root.join(Customer_.processes);
ListJoin<Process, Document> documentJoin = processJoin.join(Process_.documents);
query.orderBy(criteriaBuilder.desc(documentJoin.get(Document_.updateDate)));
query.groupBy(root.get(Customer_.id));
return null;
};
}
Then it gave a different error-
ERROR: column "documents2_.update_date" must appear in the GROUP BY
clause or be used in an aggregate function
Generated SQL-
select
customer0_.id as id1_0_,
customer0_.name as name2_0_
from
customer customer0_
inner join
process processes1_
on customer0_.id=processes1_.customer_id
inner join
document documents2_
on processes1_.id=documents2_.process_id
group by
customer0_.id
order by
documents2_.update_date desc limit ?
I could do it by the following sql; max() solved it in below sql-
select customer.* from customer
inner join process p on customer.id = p.customer_id
inner join document d on p.id = d.process_id
group by customer.id
order by max(d.update_date);
But I can't do the same, using the criteria API.
Do you have any suggestion?
This is a conceptual misunderstanding.
First, you have to understand how does inner join works. And this portion is okay in this case: [join process table with document table based on document.process_id = process.id]
Second, you need to sort customers based on the document's update date
Unfortunately, you used group by here. GROUP BY only returns column in which it is grouped by. In this case, it will return only customer_id.
You can use aggregate functions like count(), sum() etc. on grouped data.
When you tried to access update_date, it will throw below error:
ERROR: column "documents2_.update_date" must appear in the GROUP BY clause or be used in an aggregate function
Now, how can we get rid of this?
So first we need to do join to get customer id. After getting customer id, we should group the data by the customer id and then use max() to get max_date of each group(if necessary then minimum)
SELECT
customer_id,
max(date) AS max_date
FROM
document
JOIN process ON process.id = document.process_id
GROUP BY customer_id
It will return a temporary table, that looks something like below:
customer_id
max_date
1
2020-10-24
2
2021-03-15
3
2020-09-24
4
2020-03-15
Using the temporary table, you can now sort customer_id by date
SELECT
customer_id,
max_date
FROM
(SELECT
customer_id,
max(date) AS max_date
FROM
document
JOIN process ON process.id = document.process_id
GROUP BY customer_id) AS pd
ORDER BY max_date DESC
Hope this helps.

Spring Data and Oracle returns different result from query? Why?

My JPA query is very complex because of that I am using nativequery, But when I run it returns different results from JAVA query and Oracle query. However Oracle query returns correct result.What could be the reason for this ?
I changed SELECT statement, try to fetch all of them by a.* and also try to fetch only necessary column. I also remove DATE comparison statements. However interestingly JPA query returns 4 row and all of them were same exactly. I also try to fetch data without TRUNC keyword in DATE comparison part, and it is not worked. In addition I add schema names in front of tables in native query which is not worked again.
public Optional<LocalDateTime> getPaymentPeriod2(LocalDate reportDate, String loanId)
{
final String reportDateParemeter = reportDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
final String query = "SELECT st.t_no, st.islem_tarihi, st.s_kodu, st.odeme_suresi " +
"FROM k_tarihce st, k_tanim sta, kt_tanim tta " +
"WHERE st.s_kodu = sta.s_kodu " +
"AND tta.s_tipi = sta.s_tipi AND tta.t_kodu = st.t_kodu " +
"AND st.t_no = :loanId AND st.t_kodu in (37,55) " +
"AND st.s_kodu = 'K1' AND (TRUNC(st.odeme_suresi) >= TRUNC(TO_DATE(:reportDateParemeter,'yyyy-mm-dd')))";
final Query loanGetPaymentPeriodQuery = entityManager.createNativeQuery(query, LoanStatusHistory.class);
loanGetPaymentPeriodQuery.setParameter("reportDateParemeter", reportDateParemeter);
loanGetPaymentPeriodQuery.setParameter("loanId", loanId);
List<LoanStatusHistory> loanStatusHistoryList = loanGetPaymentPeriodQuery.getResultList();
return loanStatusHistoryList.isEmpty() ? Optional.empty() : Optional.of(loanStatusHistoryList.get(0).getPaymentPeriod());
}
And my entity is:
#Entity
#Table(name = "K_TARIHCE", schema = "LOS")
#Getter
#Setter
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Cacheable(false)
#IdClass(LoanStatusHistoryId.class)
public class LoanStatusHistory {
#Id
#Column(name = "T_NO")
private String loanId;
#Id
#Column(name = "ISLEM_TARIHI")
private LocalDate processDate;
#Id
#Column(name = "S_KODU")
private String statusCode;
#Column(name = "ODEME_SURESI")
private LocalDateTime paymentPeriod;
#Column(name = "T_KODU")
private String historyCode;
}
class LoanStatusHistoryId implements Serializable {
private String loanId;
private transient LocalDate processDate;
private String statusCode;
}
I want to same result with Oracle SQL developer. My main objective is fetch ODEME_SURESI field.
I found solution, again !interestingly! Here my new code below, I just not mapped my native query with entity, and return some LocalDate.
final Query loanGetPaymentPeriodQuery = entityManager.createNativeQuery(query);
loanGetPaymentPeriodQuery.setParameter("reportDateParemeter", reportDateParemeter);
loanGetPaymentPeriodQuery.setParameter("loanId", loanId);
final List<LocalDate> loanStatusHistoryList = loanGetPaymentPeriodQuery.getResultList();
return loanStatusHistoryList.isEmpty() ? Optional.empty() : Optional.of(loanStatusHistoryList.get(0));
This is the only change I made. I still not know why it worked?.

Spring Data findTop10- Ten queries instead of one

I have Spring Data method like this:
List<RegionBasics> findTop10ByRegionMappingsActiveTrue();
What I expect is that it will find top 10 records from db in one query but What I see in logs is (I didn't paste whole logs in order to keep this readable but this select query is invoke 10 times):
select regionmapp0_.id as id1_2_1_, regionmapp0_.is_active as is_activ2_2_1_, regionmapp0_.region_basic_id as region_b3_2_1_, regionbasi1_.id as id1_1_0_, regionbasi1_.hotel_count as hotel_co2_1_0_, regionbasi1_.name_long as name_lon3_1_0_, regionbasi1_.name as name4_1_0_, regionbasi1_.type as type5_1_0_ from region_mappings regionmapp0_ left outer join region_basics regionbasi1_ on regionmapp0_.region_basic_id=regionbasi1_.id where regionmapp0_.region_basic_id=?
How can I ensure that this method will hit db only once (instead of 10)?
My Model:
#Data
#NoArgsConstructor
#Entity
#Table(name = "region_basics")
public class RegionBasics {
#Id
Integer id;
#Column
String type;
#Column
String name;
#Column(name = "name_long")
String longName;
#Column(name = "hotel_count")
Integer hotelCount;
#OneToOne(mappedBy="regionBasics")
RegionMappings regionMappings;
}
I think you should join fetching the RegionMappings:
Like this: #Query("SELECT rb FROM RegionBasics r JOIN FETCH r.regionMappings rm WHERE rm.active=true")
With pageable parameter new PageRequest(0,10)

JPQL querying a collection of non-entites

I want to make a JPQL query with a collection of non entities. This is my Table entity :
#Entity
#Table(name = "ct_table")
public class Table {
...
#CollectionOfElements(fetch = FetchType.EAGER)
#JoinTable(name = "ct_table_result", joinColumns = #JoinColumn(name = "tableId"))
#MapKey(columns = { #Column(name = "label") })
#Column(name = "value")
private Map<String, String> tableResults;
...
then I try to make a query like this
select count(*) from table where table.tableResults['somekey'].value='somevalue'
but I get the following exception:
Cannot create element join for a collection of non-entities!
Any suggestion??
thanks for your time
EDIT:
I use JPA 1 and hibernate 3.3. Default libraries in JBoss 5
The JPA 2 spec (page 139) defines the KEY() and VALUE() functions to access the key and the value of a map-valued element collection:
select count(t.id) from Table t
where KEY(t.tableResults) = 'somekey' and VALUE(t.tableResults) = 'somevalue'

Categories

Resources