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.
Related
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.
I have two JPA entities
class A{
#OneToMany
Lis<B> entitiesB;
#Column("STATUS")
String status;// will sort based on this column
}
and
class B{
#ManyToOne
A entityA;
#Column("PROPERTY_ONE")
String propertyOne;
....
#Column("PROPERTY_M")
String propertyM;
....
}
I need to left join A with B and then perform filtering on columns from B. I have the following criteria:
Join<A, B>root=criteriaBuilder
.createQuery(A.class)
.from(A.class)
.join("entitiesB");
CriteriaQuery<A> query = criteriaBuilder.createQuery(A.class);
query.select(query.from(A.class).join("entitiesB"))
.distinct(true)
.where(formWhereClause(filters))
.orderBy(formOrderByClause());
How do I form the filter by the status property from A entity
criteriaBuilder.notEqual(root.get("A").get("status"), "SOME_STATUS_VALUE");
It has generated me the following SQL:
select distinct generatedAlias0 from A as generatedAlias1
inner join generatedAlias1.entitiesB as generatedAlias0
where ( generatedAlias2.A.status<>:param0 ) and ( generatedAlias2.propertyOne like :param1 )
order by generatedAlias2.propertyM desc
I got the following exception:
'org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.hql.internal.ast.QuerySyntaxException:
Invalid path generatedAlias2.A.status '
How can I fix it? I am using Hibernate 4.3.5 as the persistence provider.
CriteriaQuery query = criteriaBuilder.createQuery(A.class);
means that you want to return instances of type A. Therefore, your select clause must specify query root instead of an instance of Join as you did:
Define the root of the query because the join method can only be applied either to an instance of Root or Join types:
Root<A> root = query.from(A.class);
Define Join (I need to left join A with B):
Join<A, B> b = root.join("entitiesB", JoinType.LEFT);
Define the SELECTclause:
query.select(root)
.distinct(true)
.where(formWhereClause(filters))
.orderBy(formOrderByClause());
How do I form the filter by the status property from A entity
Form it as follows:
criteriaBuilder.notEqual(root.get("status"), "SOME_STATUS_VALUE");
and if you want to use attributes of B as a filter define it, for example, as:
criteriaBuilder.equal(b.get("propertyOne"), "SOME_VALUE");
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.
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.
It is possible to construct Criteria query for this SQL:
SELECT P.This FROM Position P INNER JOIN PersonOnPosition PP ON PP.Tail = P.This WHERE PP.Tail IS NOT NULL
I want to port Hibernate Criteria to custom SQL dialect (custom ECM framework), but I have troubles with JOIN'.
Thanks.
As #Julien Langlois pointed out, this depends on your Entity definitions.
Assuming you defined a relation like
#Entity
class PersonOnPosition {
#ManyToOne
#JoinColumn(name="Tail")
Position position;
}
you can go with
session.createCriteria(PersonOnPosition.class, "PP")
.createAlias("position", "P")
.setProjection(Property.forName("P.This"))
.add(Property.forName("PP.position").isNotNull())
.list();
If you didn't define the relation, you can achieve the same result using a subquery:
DetachedCriteria personOnPositionWithTail = DetachedCriteria.forClass(PersonOnPosition.class, "PP")
.setProjection(Property.forName("PP.Tail"))
.add(Property.forName("PP.Tail").isNotNull());
session.createCriteria(Position.class, "P")
.setProjection(Property.forName("P.This"))
.add(Property.forName("P.This").in(personOnPositionWithTail))
.list();