Hibernate many-to-many mapping and cascade=delete - java

I have a mapping (only important parts):
<class name="xyz.Role" table="ROLE" lazy="true">
<id name="id" type="java.lang.Integer">
<column name="ROLE_ID"/>
<generator class="increment"/>
</id>
<set name="assignments" lazy="true" table="PERSON_ROLE" cascade="delete"
inverse="true">
<key column="ROLE_ID" />
<many-to-many class="xyz.Person" column="PERSON_ID" />
</set>
</class>
and
<class name="xyz.Person" table="PERSON" lazy="true">
<id name="Id" type="java.lang.Integer">
<column name="TPP_ID"/>
<generator class="increment"/>
</id>
<set name="roles" lazy="true" table="PERSON_ROLE" cascade="save-update">
<key column="PERSON_ID" />
<many-to-many class="xyz.Role" column="ROLE_ID" />
</set>
</class>
With this mapping, when I delete a role, very person with this role is also deleted. What I would like to achieve, is delete the association (row from PERSON_ROLE table) when I delete Role. Is there any way to achieve this?

Cascade works on the level of entities. Since Person_Role is not mapped as entity, cascade can not help you AFAIK.
You might use a database-level "on cascade delete" on the foreign key from Person_Role to Role.
Or you can - as sfussenegger points out - remove the association programatically. Note that since you mapped the association on both entities, every row in Person_Role will appear twice in your object model. In such cases, it is recommended to remove the relevant entries from both collections in order not to corrupt your object model. Hibernate however will only look at the end of the association that is not mapped with inverse="true" when persisting changes. That is, to delete from the association with your current mapping, you must delete from Person.roles, not Role.assignments:
for (Person p : role.assignments) {
person.roles.remove(role)
}
Or you might wish to replace the many-to-many mapping with an association entity, in which case you could simply use cascade. This would allow you to add more information to assignments more easily. For instance, if you had to express "Joe works 30% on QA and 70% as requirements engineer", you could simply add that field to the association.

Why not simply call role.getAssignments().clear() before deleting the role?
for (Person p : role.getAssignments()) {
person.getRoles.remove(role)
}
role.getAssignments.clear();
session.delete(role);
I'm not a big fan of cascade="delete". I get this weird gut feeling any time I think about a snippet of XML being able to delete entire tables of valuable data :)

Don't put a mapping on the Role's side at all. If you eventually need this information, get it with an HQL query. I.e. get rid of this:
<set name="assignments" lazy="true" table="PERSON_ROLE" cascade="delete"
inverse="true">
<key column="ROLE_ID" />
<many-to-many class="xyz.Person" column="PERSON_ID" />
</set>
And whenever you need the persons for a role (which I think should be rare) get it by:
SELECT p FROM Person p JOIN p.roles WHERE role=:role

Related

How to make hibernate unidirectional many-to-many association updateable?

I have two entities - Category and Attribute. Category can have multiple related attributes, and Attribute can be related to any number of categories. Association should be available only on Category side - Attribute objects are not aware of categories they are related to.
So I model this association as unidirectional many-to-many:
Category.hbm.xml
<class name="Category" table="category" proxy="ICategory" entity-name="category">
<id name="id" column="id" unsaved-value="null"><generator class="identity" /></id>
...some properties...
<bag name="relatedAttributes" table="category_attribute" fetch="select">
<key column="id_category" />
<many-to-many column="id_attribute" entity-name="attribute" />
</bag>
</class>
and Attribute.hbm.xml
<class name="Attribute" table="attribute" proxy="IAttribute" entity-name="attribute">
<id name="id" column="id" unsaved-value="null" ><generator class="identity" /></id>
...some properties...
</class>
Mapping works perfectly with current data until it needs an update. I just want to do things as simple as these:
ICategory c = (ICategory) session.get("category", 1);
c.getRelatedAttributes().add((IAttribute) session.get("attribute", 2));
session.update("category", c);
How can i make this association updateable?
Finally done. Changes that affect behavior:
<bag name="relatedAttributes" table="category_attribute" fetch="select" inverse="false" cascade="save-update">
...
</bag>
and do not forget to call session.flush() after operations.

Hibernate unnecessarily update joined tables

Basically, I have 2 classes P & M, and their corresponding tables T_P & T_M, and they are joined in a one-to-many relation, one P has a set of Ms. Something like this:
<class name="P" table="T_P">
<set name="ms" cascade="all" lazy="false" inverse="true">
<key column="P_ID" not-null="true" foreign-key="FK_M_P"/>
<one-to-many class="M"/>
</set>
</class>
<class name="M" table="T_M">
<many-to-one name="p" column="P_ID" foreign-key="FK_M_P" class="P"
update="false" not-null="true" cascade="none"/>
</class>
Now when I make changes to P and ask Hibernate to update DB. It's very likely Hibernate will do a batch of updates, one to T_P, several to T_M, and I know the later part is not necessary as I didn't change those Ms. But I think because my object is detached, hibernate has to update everything.
So my question is, in my case, can I ask Hibernate to only update partially, and not to make excessive DB hits? Or other suggestion to optimize this is also appreciated. But this is a legacy program, and I may not be able to make drastic changes.
As far as inverser="true" mapping is concern, it will only work for bidirectional association.
in your configuration , you have not mentioned mapping for P in M.
<class name="P" table="T_P">
<set name="ms" cascade="all" lazy="false" inverse="true">
<key column="P_ID" not-null="true" foreign-key="FK_M_P"/>
<one-to-many class="M"/>
</set>
</class>
<class name="M" table="T_M">
/*mention mapping for P here, if you want to use inverse property properly*/
</class>
issue might be because of cascade="all" mapping.
try to set cascade="save-update" in mapping for collection.
you go through the following link , which contain simple/basic example of parent-child relationship.
sample mapping for parent-child relation is as follow.
/*parent contain set of child entity*/
<set name="children" inverse="true">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
/*child has reference of parent*/
<many-to-one name="parent" column="parent_id" not-null="true"/>
I hope this help you to resolve issue.

Override lazy loading setting for related entities in a hibernate mapping file

I have a mapping file which sets lazy=false for some related entities. This makes sense for a lot of use cases but there are some exceptions. The problem is that I dont want to fetch the related associations at query time for these cases, which are very expensive time-wise.
Example of the mapping for the entity.
<class name="Category" table="category">
<id name="id" type="string">
<column length="50" name="id"/>
<generator class="uuid"/>
</id>
<property name="name" type="string">
<column length="100" name="name" not-null="true"/>
</property>
<set inverse="true" lazy="false" name="categorySourcesList">
<key>
<column length="50" name="categoryid" not-null="true"/>
</key>
<one-to-many class="CategorySource"/>
</set>
</class>
My question is, is there a way to override the lazy value which is set in the mapping file, either in the sql-query I custom write or enabling lazy load as one of the parameters in the DAO? or through some annotations?
Yes, you can override the annotated or xml mapped association fetching strategy.
Hibernate Documentation
Criteria criteria = session().createCriteria(MyClass.class);
criteria.add(Restrictions.eq("id", "1234"));
criteria.setFetchMode("children",FetchMode.EAGER);
return (MyClass) criteria.uniqueResult();
This will return you an instance of MyClass with its children eagerly fetched.
AFAIK you can't override EAGER loading with LAZY but only the other way round.
Thus, you'd need to define the association the be LAZY and override that in the queries using joins. There might be other ways to do that but I'm afraid that's all I know right now.

Hibernate Query Problem

I am using Hibernate. My database is as follows
A Category has many attributes
class category
contains
private Set <Attribute> AllAttributes= new HashSet <Attribute>();
class attribute
How do I retrieve all categories together with their attributes because I am trying 'from category' but it is not working
Category Mapping File:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated Dec 16, 2010 8:37:02 AM by Hibernate Tools 3.4.0.Beta1 -->
<hibernate-mapping>
<class name="com.BiddingSystem.Models.Category" table="CATEGORY">
<id name="CategoryId" type="long">
<column name="CATEGORYID" />
<generator class="native" />
</id>
<property name="CategoryName" type="java.lang.String">
<column name="CATEGORYNAME" />
</property>
<many-to-one name="ParentCategory" class="com.BiddingSystem.Models.Category">
<column name="PARENT_CATEGORY_ID" />
</many-to-one>
<set name="SubCategory" lazy="false" cascade="all-delete-orphan" inverse="true">
<key>
<column name="PARENT_CATEGORY_ID" />
</key>
<one-to-many class="com.BiddingSystem.Models.Category" />
</set>
<set name="AllAttributes" table="ATTRIBUTE" inverse="false" lazy="true" cascade="all">
<key>
<column name="CATEGORYID" />
</key>
<one-to-many class="com.BiddingSystem.Models.Attribute" />
</set>
</class>
</hibernate-mapping>
Attribute Mapping File:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated Dec 16, 2010 5:25:09 AM by Hibernate Tools 3.4.0.Beta1 -->
<hibernate-mapping>
<class name="com.BiddingSystem.Models.Attribute" table="ATTRIBUTE">
<id name="AttributeId" type="long">
<column name="ATTRIBUTEID" />
<generator class="native" />
</id>
<property name="AttributeName" type="java.lang.String">
<column name="ATTRIBUTENAME" />
</property>
<set name="Options" table="ATTRIBUTEOPTION" inverse="false" cascade="all">
<key>
<column name="ATTRIBUTEID" />
</key>
<one-to-many class="com.BiddingSystem.Models.AttributeOption" />
</set>
</class>
</hibernate-mapping>
You have mapped the association with lazy="true". This tells hibernate that by default, a category's attributes should only be loaded from the database when they are actually accessed, in contrast to lazy="false" which would instruct hibernate to load the attributes whenever it loads a category. However, the directive in the mapping file affects all queries.
In case you want it only for a particular query, check out
http://docs.jboss.org/hibernate/core/3.5/reference/en/html/queryhql.html#queryhql-joins :
A "fetch" join allows associations or
collections of values to be
initialized along with their parent
objects using a single select. This is
particularly useful in the case of a
collection. It effectively overrides
the outer join and lazy declarations
of the mapping file for associations
and collections. See Section 20.1,
“Fetching strategies” for more
information.
from Cat as cat
inner join fetch cat.mate
left join fetch cat.kittens
A fetch join does not usually need to
assign an alias, because the
associated objects should not be used
in the where clause (or any other
clause). The associated objects are
also not returned directly in the
query results. Instead, they may be
accessed via the parent object. The
only reason you might need an alias is
if you are recursively join fetching a
further collection:
from Cat as cat
inner join fetch cat.mate
left join fetch cat.kittens child
left join fetch child.kittens
The fetch construct cannot be used in
queries called using iterate() (though
scroll() can be used). Fetch should be
used together with setMaxResults() or
setFirstResult(), as these operations
are based on the result rows which
usually contain duplicates for eager
collection fetching, hence, the number
of rows is not what you would expect.
Fetch should also not be used together
with impromptu with condition. It is
possible to create a cartesian product
by join fetching more than one
collection in a query, so take care in
this case. Join fetching multiple
collection roles can produce
unexpected results for bag mappings,
so user discretion is advised when
formulating queries in this case.
Finally, note that full join fetch and
right join fetch are not meaningful.

How to order by list Index in HQL query?

This is situation I have:
I have two entities with one-to-many relationship mapped like that (only relevant parts are given):
<class name="xyz.Survey">
<list name="answers" inverse="true" lazy="true" fetch="select"
cascade="all-delete-orphan">
<key column="OPA_OP_ID" not-null="true" />
<list-index column="OPA_SORT_ORDER" />
<one-to-many class="xyz.Answer" />
</list>
</class>
<class name="xyz.Answer">
<many-to-one name="survey" class="xyz.Survey" fetch="select">
<column name="OPA_OP_ID" not-null="true" />
</many-to-one>
there is no mapping for column OPA_SORT_ORDER, I let Hibernate take care of that
</class>
My question is: I want to perform HQL query on the answers for single survey and I want those answers sorted by OPA_SORT_ORDER. This query doesn't work like that:
select new AnswerDTO(a.id, a.answerText, 0)
from Survey p
join p.answers a
where p.id = ?
In log I can see that there is no ORDER BY in SQL query generated by this HQL. How can I do this? Do I need to add sortOrder property to Answer entity?
No, you don't need to add a property, HQL has a built-in function index for this:
select new AnswerDTO(a.id, a.answerText, 0)
from Survey p
join p.answers a
where p.id = ?
order by index(a)

Categories

Resources