Hibernate DetachedCriteria in FROM clause - java

I have 2 tables:
orders: id
items: id, orderId, total, flag
I would like to make following query using Hibernate Criteria (DetachedCriteria):
SELECT
o.id,
SUM(i1.total),
SUM(i2.total)
FROM
orders o
LEFT JOIN
(
SELECT
i.orderId as orderId,
SUM(i.total) as total
FROM
items i
WHERE
i.flag = 0
GROUP BY
orderId
) AS i1
ON i1.orderId = o.id
LEFT JOIN
(
SELECT
i.orderId as orderId,
SUM(i.total) as total
FROM
items i
WHERE
i.flag = 1
GROUP BY
orderId
) AS i2
ON i2.orderId = o.id
GROUP BY
o.id
I know how to use DetachedCriteria to create subquery in WHERE clause, but as you can see, I need to do a subquery in FROM clause. If it is not possible, maybe there is a way to write it in SELECT clause (inside SUM()), because this query could be rewritten to such form.
I really need to use Criteria API even if I have to pass native SQL to the query.
I didn't show you classes or mapping, but as you can see, this is a very simple example.

I found solution for my problem. I had to make a POJO and mapping:
<class name="OrderTotal"
entity-name="OrderTotalForFlag0">
<subselect>
SELECT
i.orderId AS id,
SUM(i.total) AS total
FROM
items i
WHERE
i.flag = 0
GROUP BY
id
</subselect>
<id name="id" type="int" />
<property name="total" type="int" />
</class>
And of course for flag 1 it will be similar. This could be also done by declaring views in database and creating a mappings to that views. In this example I was using the same POJO class, but different entity-name.
Then I made a property in class Order and mapping:
<one-to-one name="orderTotalForFlag0" entity-name="OrderTotalForFlag0" />
<one-to-one name="orderTotalForFlag1" entity-name="OrderTotalForFlag1" />
Of course laziness could be set.
I'm pretty sure now, that this could be also done in different way - by making subquery in SELECT clause - using the formula attribute/element in mapping for properties, but this would work slower than subquery in FROM clause.
And of course all of that was in documentation to Hibernate ;)

Related

Hibernate: Getting data for One to Many Mapping

A table Product represents two entities Parent and Child.There relationship is defined in a separate table, lest's say Relation_Table.
hbm for Product is very straightforward. It does not have any reference to Reference_Table. hbm for Relation_Table looks like this:
<class name="RelationMember" table="RELATION_TABLE" lazy="true">
<id column="relation_id" type="int"/>
<many-to-one name="parent" class="Product" column="pId"/>
<many-to-one name="child" class="Product" column="child_id"/>
</class>
How would I fetch all the children for a given pId of a parent?
Is it like first I will fetch list of child_id and then use that list to read all entities from Product table?
Wondering if Hibernate provide some ways of mapping child_id directly to fetch entities from Product table.
You did not inlclude your classes but in typical cases, JPQL query is like:
select child from Product child where child.parent.id in :pids;
but Hibernate will still translate it to 2 joins, Product x Relation_Table x Product again.
You can use native query to have only 1 join (Product x Relation_Table).
But since the joins will be index based joins, you can trust DB to run it fast and there is no need for this extra optimization.

Hibernate retrieves many-to-one without asking

we have a big problem in our development team.
We are using Hibernate and we have some entities which are related in two transitive one-to-many relations. The main object is a Group which has a list of Property instances, and each Property containing a list of Values.
(The mappings are down ahead)
We have two main problems:
A) When making a HQL Query, Criteria Query or SQLQuery it doesn't matter the conditions applied in JOINs or WHERE clauses, Hibernate always retrieves for us all the underlying objects. For example, if I make a Criteria or SQL getting only the Group objects, Hibernate comes and (lazy or not) gets all the Property and Value instances too. We want to control this. We want to do left joins and get only the properties with no values inside (Hibernate removes these properties with no value)
B) When making the Query, for example, a SQL, it shows in the log the SQL code we want. Everything seems perfect. But after that it brings every instance in the list without applying conditions, getting them only by id, and we can assure this because with lazy="true" we see the "load many-to-one" queries in the log.
Is there something we can do in hibernate config, fetching mode/strategy, the mappings configuration or anywhere? I'm thinking on going on Result transformers now.
I would be grateful if someone coud give me a hint or tell me where to find a solution to this problem. We are confused about how to get this, but it must be a way.
Thanks in advance
Query:
Criteria lstCriterios = this.getSession().createCriteria(CardGroup.class, CARD_GROUP)
.add(Restrictions.eq(ID_CATEGORY, idCategory));
lstCriterios.createAlias("listProperty", "listProperty", CriteriaSpecification.LEFT_JOIN);
if (clusterId != null) {
lstCriterios.add(Restrictions.or(
Restrictions.isNull("listPropertyValue" + ".value"),
Restrictions.and(Restrictions.eq("listPropertyValue" + ".clusterId", clusterId),
Restrictions.eq("listPropertValue" + ".companyWarehouseId", idCompanyWarehouse))));
lstCriterios
.createAlias("listProperty" + "." + "listPropertyValue", "listPropertyValue",
CriteriaSpecification.LEFT_JOIN,
Restrictions.eq("listPropertyValue" + ".clusterId", clusterId));
} else {
lstCriterios.createAlias("listProperty" + ".listPropertyValue", "listPropertyValue",
CriteriaSpecification.LEFT_JOIN);
}
lstCriterios.add(Restrictions.eq(ID_CATEGORY, idCategory));
lstCriterios.add(Restrictions.eq("listProperty" + ".groupId", idGroup));
lstCriterios.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
/*
* Sorting
*/
lstCriterios.addOrder(Order.asc("order"));
lstCriterios.addOrder(Order.asc("listProperty" + ".order"));
lstCriterios.addOrder(Order.asc("listPropertyValue"+ ".clusterId")); // Agrupacion, podrĂ­a ser nulo
lstCriterios.addOrder(Order.asc("listPropertyValue"+ ".propertyId")); // Propiedad
lstCriterios.addOrder(Order.asc("listPropertyValue"+ ".id"));
return lstCriterios.list();
Group mapping:
<list name="listProperty"
table="FICHA_PROPIEDAD" schema="${db2.siglo.schema}"
inverse="false" cascade="all" >
<key column="ID_FICHA_GRUPO" not-null="false" />
<list-index column="ORDEN" base="1"/>
<one-to-many
class="com.company.aslo.appwebsiglo.model.card.property.property.CardProperty" />
</list>
Property mapping:
<bag name="listPropertyValue"
table="FICHA_PROPIEDAD_VALOR" schema="${db2.siglo.schema}"
inverse="false" cascade="all">
<key column="ID_FICHA_PROPIEDAD" not-null="false" />
<one-to-many
class="com.company.aslo.appwebsiglo.model.card.propertyvalue.propertyvalue.CardPropertyValue" />
</bag>
It seems like our model design was bad and we didn't realize that if the DB table FICHA_PROPIEDAD_VALOR has Composite Key we can't map only one of the attributes in the composite key, because it brings us unexpected results.
Because of this and the nested objects, we had also bad implementations of the hashCode() and equals() methods which Hibernate uses.
I had solved this previously with a ResultTransformer getting the rows from a SQLQuery, but we got the Hibernate solution after that refactoring and changing the design of our model.

Deadlock in Hibernate Many-to-Many with join table

I am working on an enterprise application where we use Hibernate and a many-to-many relationship with a join table. We are seeing very sporadic database deadlocks in production (with high volume) that we cannot recreate.
Category.java
public class Category {
....
private Set<Product> products = new HashSet<Product>();
...
}
Category.hbm.xml
<class
name="Category"
table="CATEGORY"
>
...
<!-- uni-directional many-to-many association to Product -->
<set
name="products"
table="CATEGORY_PRODUCT_ASSC"
lazy="false"
cascade="none"
>
<key column="CATEGORY_ID" />
<many-to-many class="Product" column="PRODUCT_ID" />
</set>
</class>
Product.java, Product.hbm.xml do not have a set of Categories, as this is uni-directional many-to-many
The CATEGORY_PRODUCT_ASSC table is a simple join table that only has 2 columns: CATEGORY_ID and PRODUCT_ID.
Right now, we are calling Session.saveOrUpdate on the Category instance object for the sole purpose of getting the inserts in the CATEGORY_PRODUCT_ASSC join table (nothing changed on the Category)
I turn on Hibernate show_sql and see the following:
update CATEGORY set NAME=?, DESCRIPTION=?, where category_id=?
insert into CATEGORY_PRODUCT_ASSC (CATEGORY_ID, PRODUCT_ID) values (?, ?)
The problem is that we have many products being created at the exact same second on multiple servers, all for the same Category.
When we see deadlocks, the update CATEGORY call is inevitably involved. We need to prevent these update CATEGORY SQL statements from being executed.
Option 1: Is there any way that I can call Session.saveOrUpdate(category) and have it not update Category (since that has not changed), but still do the insert into the join table CATEGORY_PRODUCT_ASSC ?
Option 2: If not, we have thought about just doing a straight INSERT of the CATEGORY_PRODUCT_ASSC rows via JDBC. However, one concern is stale Hibernate objects (Category objects) in the cache. Any ideas/recommendations on this possible approach?
Thank you very much in advance for your help. :-)
We resolved this issue. It did turn out to be the update category statement. Instead of using the CATEGORY_PRODUCT_ASSC table as a join-through for the many-to-many relationship, we created a Hibernate-managed entity that represents this join table ... CategoryProductAssc.
This way, we could directly persist the relationship without having to call Session.saveOrUpdate on the Category instance object for the sole purpose of getting the inserts in the CATEGORY_PRODUCT_ASSC join table when nothing changed on the Category object.
I created Cactus tests that spun up 20 simultaneous executions, tested old vs new code and our DBAs monitored and saw concurrency with the old code and no concurrency with the new code.

Query with the many-to-many set (joined table) in the WHERE clause

For example, I have the mapping file like this
<class name="my.test.model.Product" table="PRODUCT">
...
<set name="retailers" table="product_shop" lazy="false">
<key column="PRODUCT_ID" />
<many-to-many column="SHOP_ID" class="my.test.model.Shop" />
</set>
...
</class>
Now I want to query the Products of a particular Shop A. Something like this come to mind:
String searchHql = "select p from Product p inner join p.retailers retailing where p.retailers.shop_id = :shopId";
#SuppressWarnings("unchecked")
List<Product> productList = sessionFactory.getCurrentSession().createQuery(searchHql ).setInteger("shopId", shopId).list();
But it won't work. The error returned is:
could not resolve property: shop_id of: my.test.model.Shop. I have searched a lot, but still not find the right way to access the "many-to-many" subset in hql. Is this possible? Or I need to map the Product_Shop table to a model class?
UPDATE: as it seems there's no other way, I end up mapping Product_Shop into a class.
You're supposed to use the alias you assigned to the joined entity in the wgere clause:
select p from Product p inner join p.retailers retailing
where retailing.shop_id = :shopId
Side note: you should respect the Java naming conventions: shopId rather than shop_id.

Creating a new object instance in hibernate using a sql query

I am attempting to create an object in hibernate using a query, which will then be saved back to the table representing the class.
Excerpt from hbm.xml file:
<class name="MyClass" table="MY_TABLE">
<id column="ID" name="ID">
<generator class="sequence">
<param name="sequence">MY_SEQ</param>
</generator>
</id>
<property column="VAL" name="val" type="string"/>
</class>
<sql-query name="myQuery">
<return class="MyClass"/>
SELECT MY_SEQ.nextval ID, 'something' VAL from DUAL
</sql-query>
Code snippet from test case:
MyClass myClass = (MyClass) session.getNamedQuery("myQuery").list().get(0);
Transaction t = session.beginTransaction();
session.save(myClass);
t.commit();
My aim is that there should now be a new record in table MY_TABLE, but the insert does not occur, I assume that this is due the fact that Hibernate does not know that the instance has not been persisted in the db.
I have tried changing the query to read:
SELECT NULL ID, 'something' VAL from DUAL
But this results in Hibernate not instantiating an object.
So how can i create a new object instance from a query that is not associated with a persisted instance of the class and use this to create a persisted instance?
Update: I tested the approach suggested below and I couldn't get it working for this particular scenario, Hibernate expects you to select columns for all attributes while we definitely don't want the id. However, using a ResultTransformer did work:
16.1.5. Returning non-managed entities
It is possible to apply a
ResultTransformer to native SQL
queries, allowing it to return
non-managed entities.
sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS")
.setResultTransformer(Transformers.aliasToBean(CatDTO.class))
This query specified:
the SQL query string
a result transformer
The above query will return a list of
CatDTO which has been instantiated and
injected the values of NAME and
BIRTHNAME into its corresponding
properties or fields.
The documentation mentions returning non-managed entities but it also work with an entity (there is no reason it wouldn't work) and I could persist the transient entity successfully.
See also
Hibernate 3.2: Transformers for HQL and SQL
I'm leaving the initial answer for clarity sake.
Maybe the following will help:
16.1.2. Entity queries
The above queries were all about
returning scalar values, basically
returning the "raw" values from the
resultset. The following shows how to
get entity objects from a native sql
query via addEntity().
sess.createSQLQuery("SELECT * FROM CATS").addEntity(Cat.class);
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").addEntity(Cat.class);
This query specified:
the SQL query string
the entity returned by the query
Assuming that Cat is mapped as a class
with the columns ID, NAME and
BIRTHDATE the above queries will both
return a List where each element is a
Cat entity.
If the entity is mapped with a
many-to-one to another entity it is
required to also return this when
performing the native query, otherwise
a database specific "column not found"
error will occur. The additional
columns will automatically be returned
when using the * notation, but we
prefer to be explicit as in the
following example for a many-to-one to
a Dog:
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, DOG_ID FROM CATS").addEntity(Cat.class);
This will allow cat.getDog() to
function properly.
But I don't think you should set the ID if you want to save it and want Hibernate to perform an insert.

Categories