Why does subselect make 2 queries instead of one? - java

I have a simple Restaurant Entity that has a List of Votes ( some fields and methods are omitted for brevity ).
public class Restaurant extends AbstractNamedEntity {
#OneToMany(mappedBy = "restaurant", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonManagedReference
#Fetch(FetchMode.SUBSELECT)
private List<Vote> votes;
}
Here is another side of the relationship
public class Vote extends AbstractBaseEntity {
#ManyToOne
#JsonBackReference
private Restaurant restaurant;
}
When I try to fetch data using Spring data JPA findAll() method and then convert it to DTOs
public static RestaurantResponseDTO toRestaurantDto(Restaurant restaurant) {
return new RestaurantResponseDTO(restaurant.getId(), restaurant.getName(),
restaurant.getAddress(), getRestaurantVoteCount(restaurant));
}
public static long getRestaurantVoteCount(Restaurant restaurant) {
var votes = restaurant.getVotes();
if (votes == null) return 0;
return votes.stream().filter(vote -> vote.getVoteDate().equals(LocalDate.now())).count();
}
these are the SQLs I have:
Hibernate:
select
restaurant0_.id as id1_1_,
restaurant0_.name as name2_1_,
restaurant0_.address as address3_1_
from
restaurant restaurant0_
Hibernate:
select
votes0_.restaurant_id as restaura3_4_1_,
votes0_.id as id1_4_1_,
votes0_.id as id1_4_0_,
votes0_.restaurant_id as restaura3_4_0_,
votes0_.user_id as user_id4_4_0_,
votes0_.vote_date as vote_dat2_4_0_
from
vote votes0_
where
votes0_.restaurant_id in (
select
restaurant0_.id
from
restaurant restaurant0_
)
I thought that Subselect needs only 1, why do I have 2?

Actually, what you see is expected hibernate behavior. See this section of the hibernate documentation.
Hibernate is going to avoid the N+1 query issue by generating a single SQL statement to initialize all votes collections for all Restaurant entities that were previously fetched. Instead of using passing all entity identifiers, Hibernate simply reruns the previous query that fetched the Restaurant entities.
P.S. As for the FetchMode.JOIN option, please note that as it's mentioned here:
The reason why we are not using a JPQL query to fetch multiple Department entities is because the FetchMode.JOIN strategy would be overridden by the query fetching directive.
To fetch multiple relationships with a JPQL query, the JOIN FETCH directive must be used instead.
So, I guess the most flexible way for your case would be to write own JPQL query and use JOIN FETCH directive where it's needed.

Related

How to configure Hibernate Envers to avoid certain collections (join tables) in entity revision queries

I am using Hibernate Envers 4.2, I want to fetch the revisions of entity with only details from some of the collections in the Entity.
But I could see hibernate queries fired for all collections in the Entity causing performance problems.
Also could not use #NotAudited annotation for the collections to avoid in this particular query as the auditing is desired in other scenarios for those collections.
For example, if my Audited Entity has these two joins as collections.
But I want the query getting revisions of my entity to fetch only address information and ignore Order information, is it possible? I don't want to annotate Order information with #NotAudited since the history information may be desired in other scenarios.
#ManyToOne
#JoinColumn(name="ADDR_ID")
public Address getAddress() { return address; }
#OneToMany
#JoinColumn(name="CUST_ID")
public Set<Order> getOrders() {return orders;}
As in this Hibernate documentation example, If I have a Customer with an Address association:
#Audited( withModifiedFlag = true )
#Entity(name = "Customer")
public class Customer {
...
#ManyToOne(fetch = FetchType.LAZY)
private Address address;
}
and I query the revisions using this query:
List<Customer> customers = AuditReaderFactory
.get( entityManager )
.createQuery()
.forRevisionsOfEntity( Customer.class, false, true )
.add( AuditEntity.id().eq( 1L ) )
.add( AuditEntity.property( "lastName" ).hasChanged() )
.getResultList();
Envers only queries for Customers, not Address:
select
c.id as id1_3_0_,
c.REV as REV2_3_0_,
defaultrev1_.REV as REV1_4_1_,
c.REVTYPE as REVTYPE3_3_0_,
c.REVEND as REVEND4_3_0_,
c.created_on as created_5_3_0_,
c.createdOn_MOD as createdO6_3_0_,
c.firstName as firstNam7_3_0_,
c.firstName_MOD as firstNam8_3_0_,
c.lastName as lastName9_3_0_,
c.lastName_MOD as lastNam10_3_0_,
c.address_id as address11_3_0_,
c.address_MOD as address12_3_0_,
defaultrev1_.REVTSTMP as REVTSTMP2_4_1_
from
Customer_AUD c cross
join
REVINFO defaultrev1_
where
c.id = ?
and c.lastName_MOD = ?
and c.REV=defaultrev1_.REV
order by
c.REV asc
-- binding parameter [1] as [BIGINT] - [1]
-- binding parameter [2] as [BOOLEAN] - [true]
Unless you navigate associations like this:
AuditQuery innerJoinAuditQuery = AuditReaderFactory
.get( entityManager )
.createQuery()
.forEntitiesAtRevision( Customer.class, 1 )
.traverseRelation( "address", JoinType.INNER );
Hibernate Envers should not include them in the SQL revision query.

Hibernate n select issue with bidirectional same entity relationship

I have Item.java entity with below properties
#OneToMany(cascade=CascadeType.ALL, mappedBy="kitItem", fetch=FetchType.EAGER)
private Set<KitItemDetails> kitItemDetails = new HashSet<KitItemDetails>(0);
.... Other properties with getter and setter
I have KitItemDetails.java entity with below properties
#ManyToOne(cascade=CascadeType.ALL)
private Item kitItem;
In my DAO access class we are using below query to fetch all items with other related attributes as EAGER
List<Item> items = getCurrentSession().createCriteria(Item.class).addOrder(Order.asc("itemOrder"))
.list();
But for this its firing n select queries to fecth item details as below
select kititemdet0_.kitItem_id as kitItem_2_9_0_, kititemdet0_.id as id1_12_0_, kititemdet0_.id as id1_12_1_, kititemdet0_.kitItem_id as kitItem_2_12_1_, kititemdet0_.ITEM_ID as ITEM_ID3_12_1_, item1_.id as id1_9_2_, item1_.BAR_CODE as BAR_CODE2_9_2_, item1_.COLOR_CODE as COLOR_CO3_9_2_, item1_.IS_ACTIVE as IS_ACTIV4_9_2_, item1_.ITEM_CATEGORY_ID as ITEM_CA14_9_2_, item1_.ITEM_CODE as ITEM_COD5_9_2_, item1_.ITEM_DISP_NAME as ITEM_DIS6_9_2_, item1_.ITEM_IMAGE as ITEM_IMA7_9_2_, item1_.ITEM_NAME as ITEM_NAM8_9_2_, item1_.ITEM_ORDER as ITEM_ORD9_9_2_, item1_.ITEM_PRICE as ITEM_PR10_9_2_, item1_.ITEM_PRICE_WITH_TAX as ITEM_PR11_9_2_, item1_.ITEM_SUB_CATEGORY_ID as ITEM_SU15_9_2_, item1_.ITEM_TYPE as ITEM_TY12_9_2_, item1_.TAX_ID as TAX_ID16_9_2_, item1_.TAX_CODE as TAX_COD13_9_2_, itemcatego2_.id as id1_10_3_, itemcatego2_.COLOR_CODE as COLOR_CO2_10_3_, itemcatego2_.IS_ACTIVE as IS_ACTIV3_10_3_, itemcatego2_.ITEM_CATEGORY_NAME as ITEM_CAT4_10_3_, itemcatego2_.LOCATION_ID as LOCATION6_10_3_, itemcatego2_.PRINTER_NAME as PRINTER_5_10_3_, itemsubcat3_.id as id1_11_4_, itemsubcat3_.COLOR_CODE as COLOR_CO2_11_4_, itemsubcat3_.IS_ACTIVE as IS_ACTIV3_11_4_, itemsubcat3_.ITEM_CATEGORY_ID as ITEM_CAT5_11_4_, itemsubcat3_.ITEM_SUB_CATEGORY_NAME as ITEM_SUB4_11_4_, tax4_.id as id1_21_5_, tax4_.IS_ACTIVE as IS_ACTIV2_21_5_, tax4_.LOCATION_ID as LOCATION6_21_5_, tax4_.TAX_CODE as TAX_CODE3_21_5_, tax4_.TAX_NAME as TAX_NAME4_21_5_, tax4_.TAX_PER as TAX_PER5_21_5_ from KIT_ITEM_DETAILS kititemdet0_ inner join ITEM item1_ on kititemdet0_.ITEM_ID=item1_.id left outer join ITEM_CATEGORY itemcatego2_ on item1_.ITEM_CATEGORY_ID=itemcatego2_.id left outer join ITEM_SUB_CATEGORY itemsubcat3_ on item1_.ITEM_SUB_CATEGORY_ID=itemsubcat3_.id left outer join TAX tax4_ on item1_.TAX_ID=tax4_.id where kititemdet0_.kitItem_id=?
I tried with mappedBy, JoinColumn, JoinType, FetchMode, etc but did not worked out, what could be the issue ?
In general, having a relation set as EAGER does result in the "N select queries" problem when using criteria queries. You can overcome this by explicitly instructing Hibernate to do a fetch join. See the Hibernate documentation for more details.
The documentation contains an example very similar to your scenario (tweaked to use your class names):
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Item> query = builder.createQuery( Item.class );
Root<Item> root = query.from( Item.class );
root.fetch( "kitItemDetails", JoinType.LEFT);
query.select(root).where(
builder.and(
builder.equal(root.get("foo"), foo),
builder.equal(root.get("bar"), bar)
)
);
Item item = entityManager.createQuery( query ).getSingleResult();
// Alternatively, .getResultList() to return List<Item>
#Fetch(FetchMode.JOIN) will not work in your case because, as the documentation goes on to state in its example for FetchMode.JOIN:
The reason why we are not using a JPQL query to fetch multiple Department entities is because the FetchMode.JOIN strategy would be overridden by the query fetching directive.

Hibernate Issuing Individual queries instead of joining

I have an application where i am using Hibernate and JPA. I have 2 objects (order and product). They join together and i can get the information i want but its actually issuing 1 query to get all my orders, i get a bunch of queries in my logs to find the product information. I have tried all sorts of annotations but cannot get it to work properly. Any help with this would be great.
Here is my code:
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import javax.persistence.*;
#Entity
public class ProductFeedback implements ProductFeedbackInterface {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(columnDefinition="INT")
private Long id;
private String decision;
private String reportId;
...
#ManyToOne
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumn(name = "sku", referencedColumnName = "sku", insertable = false, updatable = false)
public Product product;
When i try to query for this table i get somethign like the following queries:
Hibernate: select top 50 productfee0_.id as id1_1_... from product_feedback productfee0_ where 1=1 and productfee0_.date_reported_date>=? and productfee0_.date_reported_date<=? and (productfee0_.decision=? or productfee0_.decision is null) and (productfee0_.dtype=? or productfee0_.dtype=?) and (productfee0_.severity in (0) or productfee0_.dtype<>?) order by productfee0_.date_reported_date desc
Hibernate: select ... from product product0_ where product0_.sku=?
Hibernate: select ... from product product0_ where product0_.sku=?
EDIT::: I have tried using #Fetch(FetchMode.JOIN) also with no luck.
Any ideas?
EDIT:
We are using specifications to build the query:
public List<ProductFeedback> findAll(ProductFeedbackFilters productFeedbackFilters) {
Page recordsPage = jpaRecordRepository.findAll(buildSpecifications(productFeedbackFilters), sortPageable(productFeedbackFilters.getLimit()));
return recordsPage.getContent();
}
Here is a sample...
private Specifications<ProductFeedback> defaultSpecifications() {
return where((queryRoot, query, criteriaBuilder) -> {
return criteriaBuilder.and(new Predicate[]{}); // Always true
});
}
private Specification<ProductFeedback> buildSpecifications(ProductFeedbackFilters filters) {
return new RecordSpecificationsBuilder(filters)
.onOrAfterStartDate()
.onOrBeforeEndDate()
.status()
.type()
.userQuery()
.severity()
.keyword()
.build();
}
public ProductFeedback findByReturnIdentifier(Integer catalogNumber, String commentType) {
return jpaRecordRepository.findOne((queryRoot, query, criteriaBuilder) ->
criteriaBuilder.and(criteriaBuilder.equal(queryRoot.get("catalogNumber"), catalogNumber), criteriaBuilder.equal(queryRoot.get("commentType"), commentType)));
}
You can tell the persistence provider to fetch the ProductFeedback.product relation with your queried Product entites, so the simple query for ProductFeedback objects
SELECT pf FROM ProductFeedback pf
would become
SELECT pf FROM ProductFeedback pf LEFT JOIN FETCH pf.product
With criteria query, assuming you already have a Root<ProductFeedback> object, you can tell to eagerly fetch the associated ProductFeedback.product object with the following:
productFeedbackRoot.fetch("product", JoinType.LEFT);
or if you have your JPA static metamodel classes generated:
productFeedbackRoot.fetch(ProductFeedback_.product, JoinType.LEFT);
The fetching strategy is determined by the default behavior of hibernate which is explained in its documentation like this ..
*The fetch strategy defined in the mapping document affects:
retrieval via get() or load()
retrieval that happens implicitly when an association is navigated
Criteria queries
HQL queries if subselect fetching is used
Irrespective of the fetching strategy you use, the defined non-lazy graph is guaranteed to be loaded into memory.
This might, however, result in several immediate selects being used to execute a particular HQL query.
Usually, the mapping document is not used to customize fetching. Instead, we keep the default behavior, and override it for a particular transaction, using left join fetch in HQL. This tells Hibernate to fetch the association eagerly in the first select, using an outer join. In the Criteria query API, you would use setFetchMode(FetchMode.JOIN).
If you want to change the fetching strategy used by get() or load(), you can use a Criteria query. For example:
User user = (User) session.createCriteria(User.class)
.setFetchMode("permissions", FetchMode.JOIN)
.add( Restrictions.idEq(userId) )
.uniqueResult();
This is Hibernate's equivalent of what some ORM solutions call a "fetch plan".*
The key is that hibernate expects the fetch mode to be set at transaction level.

Hibernate JPA recursive query

I have the following query, I'm using hibernate a JPA provider:
entityManager().createQuery(
"SELECT page FROM ProjectPage page"
+" left join fetch page.categorySet as category"
+ " where page.id = :id "
+ " and category.parentCategory is null "
+ " and (category.status is null or category.status != :status_val) "
,ProjectPage.class).setParameter("id", id).setParameter("status_val", Status.DELETED).getSingleResult();
and below are the entities of ProjectPage and Category respectively:
#Entity
#Table(name="project_page")
#Configurable
public class ProjectPage {
#OneToMany( mappedBy = "parentPage")
private Set<Category> categorySet = new HashSet<Category>();
}
#Configurable
#Table(name="category")
#Entity
public class Category{
#OneToMany(cascade = CascadeType.ALL, mappedBy = "parentCategory",fetch=FetchType.EAGER)
private Set<com.se.dataadminbl.model.Category> categorySet = new HashSet<com.se.dataadminbl.model.Category>();
}
in the above query, i'm trying to fetch a ProjectPage along with its categorySet and as shown above class Category contains a set of its type, so every ProjectPage object will contain a set of Category and each object inside this set will contains a set of Category, now the problem is that when i retrieve the ProjectPage object the conditions in the where clause applied only on the first level set of Category not on each set inside each Category, i want to make a recursive query so that i can apply the where condition to the nth level instead of doing that with code, i tried to use interceptor but doesn't work, any idea of how to do that?
The WHERE condition will always filter out nulls when you reference a LEFT JOIN column in your WHERE clause. So the end result is an INNER JOIN.
Neither JPA nor Hibernate support recursive queries, because there's no one and only one standard implementation amongst all databases Hibernate supports.
In case you use PostgreSQL, you can use a Common Table Expression.

Join tables in Hibernate

I have two tables in my PostgreSQL database:
CREATE TABLE tableOne (id int, name varchar(10), address varchar(20))
CREATE TABLE tableTwo (id int, info text, addresses varchar(20)[])
now I want to create a join as follows:
SELECT * FROM tableOne JOIN tableTwo ON address = ANY(addresses)
I tried to achieve this using Hibernate - class TableOne:
#Entity
#Table(name = "tableOne")
class TableOne {
private int id;
private TableTwo tableTwo;
private String address;
#Id
#Column(name = "id")
public getId() { return id; }
#ManyToOne
#JoinFormula(value = "address = any(tableTwo.addresses)",
referencedColumnName = "addresses")
public TableTwo getTableTwo(){
return tableTwo;
}
// Setters follow here
}
But Hibernate keeps generating queries with non-sense JOIN clauses, like:
... JOIN tableTwo ON _this.address = any(tableTwo.addresses) = tableTwo.addresses
How do I tell Hibernate using annotations to format my join query correctly? Unfortunately, our project must be restricted only to the Criteria API.
EDIT:
After suggestion from ashokhein in the comments below, I annotated the method getTableTwo() with just #ManyToOne - and now I would like to do the join using Criteria API, presumably with createAlias(associationPath,alias,joinType,withClause) method where withClause would be my ON clause in the join.
But Im not sure what to put as associationPath and alias parameters.
Any hints?
To support PostgreSQL array you need a custom Hibernate Type. Having a dedicated user type will allow you to run native SQL queries to make use of the type:
String[] values = ...
Type arrayType = new CustomType(new ArrayUserType());
query.setParameter("value", values, arrayType);
HQL supports ANY/SOME syntax but only for sub-queries. In your case you'll need a native query to use the PostgreSQL specific ANY clause against array values.
You can try Named Query.
#NamedQuery(name="JOINQUERY", query="SELECT one FROM tableOne one JOIN tableTwo two ON one.address = :address" )
#Entity
class TableOne{......
Retrieving part is:
TypedQuery<TableOne> q = em.createNamedQuery("query", TableOne.class);
q.setParameter("address", "Mumbai");
for (TableOne t : q.getResultList())
System.out.println(t.address);
You might need to do some permutations on the query
So after a lot of time searching for the right answer, the only real solution that works for us is creating a view:
CREATE VIEW TableA_view AS SELECT TableOne.*,TableTwo.id FROM TableA JOIN TableTwo ON TableOne.address = ANY(TableTwo.addresses)
and mapping it to an entity TableOne instead of the original table.
This was the only solution for us besides, of course, using a named query, which was a no-go as we needed to stick to the Criteria API.
As #ericbn has mentioned in the comments this is really an example where ORM gets really annoying. I would never expect that custom join clause like this is not possible to do in Hibernate.
#JoinFormula should contain SQL instead of HQL.
https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/annotations/JoinFormula.html

Categories

Resources