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.
Related
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.
I have a BUSINESS table that looks like this:
BUSINESS_ID | BRN | CODE | PARENT_ID
A Business can have a parentBusiness, which is joined from child.parent_id->parent.brn and child.code->parent.code and can be represented by the query:
select * from business childbus left join business parentbus on childbus.parent_id=parentbus.brn and childbus.yppa_code=parentbus.yppa_code where childbus.business_id=?
How can I create that many-to-one relationship for business.parentBusiness? The following many-to-one mapping creates the link from PARENT_ID to BRN but how do I get the constraint from the CODE column in there?
<many-to-one name="parentBusiness" class="Business" column="PARENT_ID" property-ref="brn"/>
EDIT
It was suggested in an answer below that I try to use a formula. According to the docs the formula needs to return just the PK for the object and it will figure out how to populate itself. Here is where I'm at:
<many-to-one name="parentBusiness">
<formula>(select parentBusiness.business_id from business parentBusiness where parentBusiness.brn=parent_id and parentBusiness.code=code)</formula>
</many-to-one>
But this creates an sql error
...
from Business business0_
left outer join Business business1_ on
(select parentBusiness.business_id from business parentBusiness where parentBusiness.brn=business0_.parent_id and parentBusiness.code=business0_.code)=business1_.BUSINESS_ID
because, of course
ORA-01799: a column may not be outer-joined to a subquery
How should my formula be set up?
Many thanks in advance.
you could try to use the formaula attribute.
here is some documentation about how to use it...
I was never able to get this complex relationship working as stated in the original question. I was, however, able to convince management that this was a poor data model.
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.
Is there a way to tell Hibernate to wrap a column in a to_char when using it to join to another table or conversely convert a NUMBER to a VARCHAR? I have a situation where I have a table which contains a generic key column of type VARCHAR which stores the Id of another table which is a Number. I am getting a SQL exception when Hibernate executes the SQL it generates which uses '=' to compare the two columns.
Thanks...
P.S. I know this is not ideal but I am stuck with the schema so I have to deal with it.
This should be possible using a formula in your many-to-one. From section 5.1.22. Column and formula elements (solution also mentioned in this previous answer):
column and formula attributes can
even be combined within the same
property or association mapping to
express, for example, exotic join
conditions.
<many-to-one name="homeAddress" class="Address"
insert="false" update="false">
<column name="person_id" not-null="true" length="10"/>
<formula>'MAILING'</formula>
</many-to-one>
With annotations (if you are using Hibernate 3.5.0-Beta-2+, see HHH-4382):
#ManyToOne
#Formula(value="( select v_pipe_offerprice.offerprice_fk from v_pipe_offerprice where v_pipe_offerprice.id = id )")
public OfferPrice getOfferPrice() { return offerPrice; }
Or maybe check the #JoinColumnsOrFormula:
#ManyToOne
#JoinColumnsOrFormulas(
{ #JoinColumnOrFormula(formula=#JoinFormula(value="SUBSTR(product_idnf, 1, 3)", referencedColumnName="product_idnf")) })
#Fetch(FetchMode.JOIN)
private Product productFamily;
I have two objects:
public class ParentObject {
// some basic bean info
}
public class ChildObject extends ParentObject {
// more bean info
}
Each of these tables corresponds to a differnet table in a database. I am using Hibernate to query the ChildObject, which will in turn populate the parent objects values.
I have defined my mapping file as so:
<hibernate-mapping>
<class name="ParentObject"
table="PARENT_OBJECT">
<id name="id"
column="parent"id">
<generator class="assigned"/>
</id>
<property name="beaninfo"/>
<!-- more properties -->
<joined-subclass name="ChildObject" table="CHILD_OBJECT">
<key column="CHILD_ID"/>
<!--properties again-->
</joined-subclass>
</class>
</hibernate-mapping>
I can use hibernate to query the two tables without issue.
I use
session.createQuery("from ChildObject as child ");
This is all basic hibernate stuff. However, the part which I am having issues with is that I need to apply locks to the all the tables in the query.
I can set the lock type for the child object by using the query.setLockType("child", LockMode.?). However, I cannot seem to find a way to place a lock on the parent table.
I am new to Hibernate, and am still working around a few mental roadblocks. The question is: how can I place a lock on the parent table?
I was wondering if there was a way around having to do this without undoing the Polymorphic structure that I have set up.
Why do you have to lock both tables? I'm asking because depending on what you're trying to do there may be alternative solutions to achieve what you want.
The way things are, Hibernate normally only locks the root table unless you're using some exotic database / dialect. So, chances are you're already locking your ParentObject table rather than ChildObject.
Update (based on comment):
Since you are using an exotic database :-) which doesn't support FOR UPDATE syntax, Hibernate is locking the "primary" tables as they are specified in query ("primary" in this case being table mapped for the entity listed in FROM clause, not the root of the hierarchy - e.g. ChildObject, not ParentObject). Since you want to lock both tables, I'd suggest you try one of the following:
Call session.lock() on entities after you've obtained them from the query. This should lock the root table of the hierarchy, however I'm not 100% sure on whether it'll work because technically you're trying to "upgrade" the lock that's already being held on a given entity.
Try to cheat by explicitly naming ParentObject table in your query and requesting lock mode for it:
String hql = "select c from ChildObject c, ParentObject p where c.id = p.id";
session.createQuery(hql)
.setLockMode("c", LockMode.READ)
.setLockMode("p", LockMode.READ).list();