Spring Data findTop10- Ten queries instead of one - java

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)

Related

JPA #Query gives NonUniqueResultException

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.

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.

NamedNativeQuery in Hibernate generates many select statements? How get referenced entities in a batch-way?

I thought I understood hibernate's fetching strategies, but it seems I was wrong.
So, I have an namedNativeQuery:
#NamedNativeQueries({
#NamedNativeQuery(
name = "getTest",
resultClass = ArticleOnDate.class,
query = "SELECT `a`.`id` AS `article_id`, `a`.`name` AS `name`, `b`.`price` AS `price` FROM article a LEFT JOIN price b ON (a.id = b.article_id) WHERE a.date <= :date"
)
})
#Entity()
#Immutable
public class ArtikelOnDate implements Serializable {
#Id
#OneToOne
#JoinColumn(name = "article_id")
private Article article;
...
}
Then I call it:
Query query = session.getNamedQuery("getTest").setDate("date", date);
List<ArticleOnDate> list = (List<ArticleOnDate>) query.list();
The query returns thousand of entities... Well, ok, but after that query hibernate queries thousand other queries:
Hibernate:
select
article0_.id as id1_0_0_,
article0_.bereich as name2_0_0_,
price1_.price as price1_14_1_
from
article artikel0_
where
artikel0_.id=?
Ok, that's logic, because the #OneToOne relation is fetched eagerly. I don't want to fetch it lazy, so I want a batch fetching strategy.
I tried to annotate the Article property but it didn't work:
#Id
#OneToOne
#JoinColumn(name = "article_id")
#BatchSize(size=100)
private Article article;
So what can I do to fetch the relation in a batch?

Hibernate does change the result set structure (puts Objects[]) into the result set when changing DESC to ASC on a many to many sorted column

I have the following two different HQL statements.
My Data Structure looks like this:
User
#Entity (name = "User")
public class User
{
#Id
#GeneratedValue
#Column (name = "id")
private int id;
#Column (name = "user_name")
private String username;
#Column (name = "password")
private String password;
#Column (name = "enabled")
private boolean enabled;
#ManyToMany (targetEntity = Role.class, cascade =
{
CascadeType.ALL
})
#JoinTable (name = "user_role", joinColumns =
{
#JoinColumn (name = "user_id")
}, inverseJoinColumns =
{
#JoinColumn (name = "role_id")
})
private Set<Role> roles;
/* getters and setters)
}
To cut it short the only difference between the two queries is that one is ASC the other is DESC
#NamedQuery (name = "user.getUsersOrderByRoleAsc",
query = "FROM User as u left outer join u.roles roles WHERE u.username like :username ORDER BY roles.name ASC"),
#NamedQuery (name = "user.getUsersOrderByRoleDesc",
query = "FROM User as u left outer join u.roles roles WHERE u.username like :username ORDER BY roles.name DESC"),
The query for ASC returns: A list of Users -> As I would expect.
The query of DESC returns: An List of Object[], and in each object the [0] is the User, while the [1] is just another null object.
That does not make any sense to me. How can simply changing ASC to DESC change the structure of the result set ?
I am using Hibernate 4.3.6.Final.
The fastest way to determin, what went wrong is to set the show_sql flag to true in you hibernate configuration file. This will log every rendered query.
See Hibernate show real SQL
Probably there is some Hibernate bug you bumped into, but because you are join fetching a one to many children collections, it's safer to use distinct as well:
#NamedQuery (name = "user.getUsersOrderByRoleAsc",
query = "select distinct u FROM User as u left outer join u.roles roles WHERE u.username like :username ORDER BY roles.name ASC"),
#NamedQuery (name = "user.getUsersOrderByRoleDesc",
query = "select distinct u FROM User as u left outer join u.roles roles WHERE u.username like :username ORDER BY roles.name DESC")

Categories

Resources