Hibernate Criterai API. JOINs - java

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();

Related

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.

Invalid alias when joining two JPA entities via JPA 2.0 CriteriaBuilder

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");

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.

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

hibernate order by nested entity field with possible null values

I've stumbled upon a problem with Hibernate. I've 2 entities - let's say A and B like so (Entity/Table annotations ommited):
class A {
#ManyToOne
#JoinColumn(name = "b_id")
private B b;
}
class B {
#Column(name = "name")
private String name;
}
Now, I'm trying to query all A entities and ordering them by name field of B's entity like so:
SELECT q FROM A AS q ORDER BY q.b.name asc nulls last
The problem is, there are rows in A's table having null foreign-key (b is null) - in result the aforementioned query returns only rows that don't contain null in b field, and I'd like to have them all.
I guess hibernate joins the table without using LEFT JOIN (OUTER JOIN?) resulting in null values being skipped.
Is there any way to change this behaviour? It would be great, if I could solve it by using annotations in entity classes, because the query-generating mechanism is pretty locked up.
You can use CriteriaBuilder and set alias on entityRoot
Root<A> entityRoot = criteriaQuery.from(A);
entityRoot.join("b", JoinType.LEFT).alias("b");
criteriaQuery.select(entityRoot)
.orderBy(criteriaBuilder.asc(entityRoot.get("b").get("name"))
;
you can use criteria query for this but you will have to create session while using that, it is simpler to access database using criteria:
Criteria criteria = session.createCriteria(A.class)
//create alias of your other class to provide ordering according to foriegn key
criteria.createAlias("foreignkey","keyin table A(eg..b)");
criteria.addOrder(Order.asc(b.name));
List list = criteria.getlist();
hope this helps

Categories

Resources