Deleting an element from one-to-many collection (Java + HIbernate + Struts) - java

I can't delete a child object from the database. From the org.apache.struts.action.Action.execute() method, I am removing the child from the parent's List, and also calling session.delete(child). I've simplified the code below and only included what I believe to be relavent.
Hibernate Mapping
<class
name="xxx.xxx.hibernate.Parent"
table="parent">
...
<list
name="children"
cascade="all,delete-orphan"
lazy="true"
inverse="true">
<key column="parent_id"/>
<index column="list_index"/>
<one-to-many class="xxx.xxx.hibernate.Child"/>
</list>
</class>
<class
name="xxx.xxx.hibernate.Child"
table="child">
...
<many-to-one
name="parent"
class="xxx.xxx.hibernate.Parent"
not-null="true"
column="parent_id" />
</class>
Excerpt from execute() method
Transaction tx = session.beginTransaction(); //session is of type org.hibernate.Session
try {
Parent parent = (Parent) session.get(Parent.class, getParentId());
Iterator i = form.getDeleteItems().iterator(); //form is of type org.apache.struts.action.ActionForm
while(i.hasNext()){
Child child = (Child) i.next();
session.delete(child);
parent.getChildren().remove(child); //getChildren() returns type java.util.List
}
session.saveOrUpdate(parent);
tx.commit();
} ...
I've tried with only session.delete(child); and I've tried with only parent.getChildren().remove(child); and with both lines, all without success. There are no errors or thrown exceptions or anything of the sort. I'm sure this code gets called (I've even used System.out.println(); to trace what's happening), but the database isn't updated. I can add children using similar code, edit non-collection properties of existing children, edit the parent's properties, all of that works, just not deleting!
According to the Hibernate FAQ I'm doing the mapping right, and according to this SO question I've got the right logic. I've looked all over the internet and can't seem to find anything else.
What am I doing wrong? Please help! Thanks.
Notes on versions
Everything is a few years old:
Java 1.4.2
SQL Server 2005
Hibernate 3.0.5
Struts 1.2.7
Apache Tomcat 5.0.28

If you haven't overridden the equals() method, the entity is probably not found in the list, because it has been detached, and is now a different instance. That's why the remove isn't working. Then even if the delete works, the objects are re-cascacde because they still exist in the collection. Here's what to do:
either override the equals() (and hashCode()) method(s), using either the id (easy) or some sort of busines key (more appropriate) (search stackoverflow for tips for overrideing these two metods), and leave only getChildren().remove(child)
Iterate over the collection of children in the first loop, like this:
Iterator<Child> i = form.getDeleteItems().iterator();
while(i.hasNext()){
Child child = i.next();
for (Iterator<Child> it = parent.getChildren().iterator();) {
if (child.getId().equals(it.next().getId()) {
it.remove(); // this removes the child from the underlying collection
}
}
}

I'm not sure what causes this behavior in hibernate, you can get going by loading the Child first. Separately deleting the Child is not nessesary. Updated code should look like;
Transaction tx = session.beginTransaction(); //session is of type org.hibernate.Session
try {
Parent parent = (Parent) session.get(Parent.class, getParentId());
Iterator i = form.getDeleteItems().iterator(); //form is of type org.apache.struts.action.ActionForm
while(i.hasNext()){
Child child = (Child) session.get(Chile.class, ((Child) i.next()).getChildId());
parent.getChildren().remove(child); //getChildren() returns type java.util.List
}
session.saveOrUpdate(parent);
tx.commit();
} ...
show the SQL generated by Hibernate
<property name="show_sql">true</property>
<property name="format_sql">true</property>
Edit:
Check out this Chapter 10. Working with objects

In this case, the Child class is the owner of the inverse relation, Hibernate will look at the parent reference of the child to determine whether the relation is still there. Since you don't set the parent to null, the relation exists and the child may not be deleted. Try doing
parent.getChildren().remove(child);
child.parent = null;
session.delete(child);
Also remove the not-null="true" from the parent property mapping.
The best thing to do when working with inverse associations, is to update both sides in Java code, that way you can continue working with the objects in memory and you don't have to worry about which side owns the relation.
A similar situation is discussed here: http://simoes.org/docs/hibernate-2.1/155.html

Related

Hibernate Delete query

When I try to delete an entry from a db, using
session.delete(object)
then I can the following:
1) If the row is present in DB then two SQL queries are getting executed: A select and then a delete
2) If the row is not present in the DB then only the select query is getting executed
But again this is not the case for update. Irrespective of the presence of DB row, only the update query is getting executed.
Please let me know why this kind of behaviour for delete operation. Isn't it a performance issue since two queries are getting hit rather than one?
Edit:
I am using hibernate 3.2.5
Sample code:
SessionFactory sessionFactory = new Configuration().configure("student.cfg.xml").buildSessionFactory();
Session session = sessionFactory.openSession();
Student student = new Student();
student.setFirstName("AAA");
student.setLastName("BBB");
student.setCity("CCC");
student.setState("DDD");
student.setCountry("EEE");
student.setId("FFF");
session.delete(student);
session.flush();
session.close();
cfg.xml
<property name="hibernate.connection.username">system</property>
<property name="hibernate.connection.password">XXX</property>
<property name="hibernate.connection.driver_class">oracle.jdbc.OracleDriver</property>
<property name="hibernate.connection.url">jdbc:oracle:thin:#localhost:1521/orcl</property>
<property name="hibernate.jdbc.batch_size">30</property>
<property name="hibernate.dialect">org.hibernate.dialect.OracleDialect</property>
<property name="hibernate.cache.use_query_cache">false</property>
<property name="hibernate.cache.use_second_level_cache">false</property>
<property name="hibernate.connection.release_mode">after_transaction</property>
<property name="hibernate.connection.autocommit">true</property>
<property name="hibernate.connection.pool_size">0</property>
<property name="hibernate.current_session_context_class">thread</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.infy.model.Student" table="STUDENT">
<id name="id" column="ID">
<generator class="assigned"></generator>
</id>
<property name="firstName" type="string" column="FIRSTNAME"></property>
<property name="lastName" type="string" column="LASTNAME"></property>
<property name="city" type="string" column="CITY"></property>
<property name="state" type="string" column="STATE"></property>
<property name="country" type="string" column="COUNTRY"></property>
</class>
The reason is that for deleting an object, Hibernate requires that the object is in persistent state. Thus, Hibernate first fetches the object (SELECT) and then removes it (DELETE).
Why Hibernate needs to fetch the object first? The reason is that Hibernate interceptors might be enabled (http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/events.html), and the object must be passed through these interceptors to complete its lifecycle. If rows are delete directly in the database, the interceptor won't run.
On the other hand, it's possible to delete entities in one single SQL DELETE statement using bulk operations:
Query q = session.createQuery("delete Entity where id = X");
q.executeUpdate();
To understand this peculiar behavior of hibernate, it is important to understand a few hibernate concepts -
Hibernate Object States
Transient - An object is in transient status if it has been
instantiated and is still not associated with a Hibernate session.
Persistent - A persistent instance has a representation in the
database and an identifier value. It might just have been saved or
loaded, however, it is by definition in the scope of a Session.
Detached - A detached instance is an object that has been persistent,
but its Session has been closed.
http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/objectstate.html#objectstate-overview
Transaction Write-Behind
The next thing to understand is 'Transaction Write behind'. When objects attached to a hibernate session are modified they are not immediately propagated to the database. Hibernate does this for at least two different reasons.
To perform batch inserts and updates.
To propagate only the last change. If an object is updated more than once, it still fires only one update statement.
http://learningviacode.blogspot.com/2012/02/write-behind-technique-in-hibernate.html
First Level Cache
Hibernate has something called 'First Level Cache'. Whenever you pass an object to save(), update() or saveOrUpdate(), and whenever you retrieve an object using load(), get(), list(), iterate() or scroll(), that object is added to the internal cache of the Session. This is where it tracks changes to various objects.
Hibernate Intercepters and Object Lifecycle Listeners -
The Interceptor interface and listener callbacks from the session to the application, allow the application to inspect and/or manipulate properties of a persistent object before it is saved, updated, deleted or loaded.
http://docs.jboss.org/hibernate/orm/4.0/hem/en-US/html/listeners.html#d0e3069
This section Updated
Cascading
Hibernate allows applications to define cascade relationships between associations. For example, 'cascade-delete' from parent to child association will result in deletion of all children when a parent is deleted.
So, why are these important.
To be able to do transaction write-behind, to be able to track multiple changes to objects (object graphs) and to be able to execute lifecycle callbacks hibernate needs to know whether the object is transient/detached and it needs to have the object in it's first level cache before it makes any changes to the underlying object and associated relationships.
That's why hibernate (sometimes) issues a 'SELECT' statement to load the object (if it's not already loaded) in to it's first level cache before it makes changes to it.
Why does hibernate issue the 'SELECT' statement only sometimes?
Hibernate issues a 'SELECT' statement to determine what state the object is in. If the select statement returns an object, the object is in detached state and if it does not return an object, the object is in transient state.
Coming to your scenario -
Delete - The 'Delete' issued a SELECT statement because hibernate needs to know if the object exists in the database or not. If the object exists in the database, hibernate considers it as detached and then re-attches it to the session and processes delete lifecycle.
Update - Since you are explicitly calling 'Update' instead of 'SaveOrUpdate', hibernate blindly assumes that the object is in detached state, re-attaches the given object to the session first level cache and processes the update lifecycle. If it turns out that the object does not exist in the database contrary to hibernate's assumption, an exception is thrown when session flushes.
SaveOrUpdate - If you call 'SaveOrUpdate', hibernate has to determine the state of the object, so it uses a SELECT statement to determine if the object is in Transient/Detached state. If the object is in transient state, it processes the 'insert' lifecycle and if the object is in detached state, it processes the 'Update' lifecycle.
I'm not sure but:
If you call the delete method with a non transient object, this means first fetched the object from the DB. So it is normal to see a select statement. Perhaps in the end you see 2 select + 1 delete?
If you call the delete method with a transient object, then it is possible that you have a cascade="delete" or something similar which requires to retrieve first the object so that "nested actions" can be performed if it is required.
Edit:
Calling delete() with a transient instance means doing something like that:
MyEntity entity = new MyEntity();
entity.setId(1234);
session.delete(entity);
This will delete the row with id 1234, even if the object is a simple pojo not retrieved by Hibernate, not present in its session cache, not managed at all by Hibernate.
If you have an entity association Hibernate probably have to fetch the full entity so that it knows if the delete should be cascaded to associated entities.
instead of using
session.delete(object)
use
getHibernateTemplate().delete(object)
In both place for select query and also for delete use getHibernateTemplate()
In select query you have to use DetachedCriteria or Criteria
Example for select query
List<foo> fooList = new ArrayList<foo>();
DetachedCriteria queryCriteria = DetachedCriteria.forClass(foo.class);
queryCriteria.add(Restrictions.eq("Column_name",restriction));
fooList = getHibernateTemplate().findByCriteria(queryCriteria);
In hibernate avoid use of session,here I am not sure but problem occurs just because of session use

How can I conditionally load a collection mapped in an object?

Given the following mapping
<class name="com.domain.Season" table="cm.pub.jsn_mstr">
<id name="seasonCode" column="season_code" length="1"/>
<property name="name" type="string" column="name" length="20"/>
<set name="promotions" lazy="false">
<key column="season_code"/>
<one-to-many class="com.domain.Promotion" not-found="ignore"/>
</set>
</class>
How can I include or exclude the load of promotions? I could use lazy="true" though I'm using Jackson to serialize the result which is after the session is closed.
public Collection<Season> getSeasons(boolean withPromotions) {
final Session session = sessionFactory.getCurrentSession();
try {
session.beginTransaction();
return (List<Season>) session.createQuery("from Season s").list();
} finally {
session.getTransaction().commit();
}
}
UPDATE: Problem with using lazy loading.
The getSeasons method above is used in an MVC controller that will retrieve seasons, then using jackson serialize them to JSON (using Spring/MVC's view-resolver) so I don't actually access the objects myself, therefore any attempt to lazily load the collection results in an exception (as jackson will call an iterator on all collection properties).
Here's an example that shows an exception will get thrown:
public Collection<Season> getSeasons(boolean withPromotions) {
final Session session = sessionFactory.getCurrentSession();
final List<Season> r;
try {
session.beginTransaction();
r = (List<Season>) session.createQuery(
withPromotions
? "from Season s join fetch s.promotions"
: "from Season s"
).list();
} finally {
session.getTransaction().commit();
}
try {
for (Season s : r) {
for (Promotion p : s.getPromotions()) {
// Exception thrown here as we attempted to get an iterator.
LOG.debug("Promotion: " + p.getName());
}
}
} catch (Exception ex) {
LOG.error("Couldn't get promotions", ex);
}
return r;
}
And of course this time the mapping needs to have lazy="true" otherwise it will always eager read the collection.
<class name="com.domain.Season" table="cm.pub.jsn_mstr">
<id name="seasonCode" column="jsn_seas" length="1"/>
<set name="promotions" lazy="true">
<key column="jpr_seas"/>
<one-to-many class="com.domain.Promotion" not-found="ignore"/>
</set>
</class>
Data type for promotions field is Collection<Promotion>.
Try this:
session.createQuery("from Season s join fetch s.promotions").list();
From hibernate reference:
A "fetch" join allows associations or collections of values to be initialized along with their parent objects using a single select.
You can use Hibernate.initialize() to initialize a collection from the proxies. In your example, add the below code.
public Collection<Season> getSeasons(boolean withPromotions) {
final Session session = sessionFactory.getCurrentSession();
try {
session.beginTransaction();
List<Season> list = session.createQuery("from Season s").list();
for(Season s : list){
if(condition){
Hibernate.initialize(s.getPromotions());
}
}
return list;
} finally {
session.getTransaction().commit();
}
}
From the API docs this will,
The static methods Hibernate.initialize() and Hibernate.isInitialized(), provide the application with a convenient way of working with lazily initialized collections or proxies. Hibernate.initialize(cat) will force the initialization of a proxy, cat, as long as its Session is still open. Hibernate.initialize( cat.getKittens() ) has a similar effect for the collection of kittens.
Read the API here or an example here.
I've found the only way I could do this is to not map the collection and instead send off two queries, and adding to the collection myself.
This gives me greater control of the association and also improves performance as I'm only sending off two queries instead of 1+n (where n is the rows retrieved from the first query).
Essentially, your problem is Jackson -- reading everything in the bean. Not Hibernate.
Either:
1) Control Jackson properly, to transmit the data you actually need -- and avoid what you don't want it to 'pull on'.
2) Use Spring's 'OpenSessionInViewFilter' or similar that binds a Session to a thread-local, and leaves it open until the view has run -- enabling Jackson to still 'pull on' & load the collections at view-rendering stage.
3) or, Map two different classes (one full details, one light-weight) to the same table, and retrieve the one you actually need.
SeasonLite could perhaps be an ancestor of Season (full details), but note that you're not mapping inheritance with Hibernate -- you're mapping two separate views of the same table.
PS: methods like 'getSomeData(boolean retrieveThisData)' appear to be a common anti-pattern. I've seen it many times before.
Sorry, this is a very late answer, but I only just found your question when looking for something else.
As already stated in some of the answers, the problem is that Hibernate uses a 'placeholder' PersistentCollection for a lazy loading collection. To stop Hibernate trying to fill out the collection when it is accessed, you could null out the actual Promotions list in each of the Season entities that are returned by the query. In code, something like this:
if (!withPromotions) {
for (Season s : r) {
s.setPromotions(null);
}
}
Setting the Promotions to null will clear out the reference to the Hibernate collection object. You should be able to do this outside the session.
What we do is create methods on our entities for each of our collections similar to this:
public boolean isPromotionsLoaded() {
if (((promotions instanceof org.hibernate.collection.PersistentCollection)
&& !((org.hibernate.collection.PersistentCollection) promotions).wasInitialized())
|| (getPromotions() == null)) {
return false;
} else {
return true;
}
}
Then we can use these methods to find out if we can safely access the collection.
Anyway, hope this helps someone.

Hibernate - set of compound values

I need a Collection of compound values in Hibernate. Something like:
class Parent {
Set<Child> children;
}
class Child {
String property;
String anotherProperty;
MyOtherClass oneToOneClass;
}
The key requirement is that elements in this collection are value objects. When I saveOrUpdate the Parent it also saves its children.
More importantly when I create another Parent with children based on the same set, these children need to be persisted separately. That's why regular one-to-many does not work for me.
Is there a clean way I can do it with Hibernate? Something like collection of values described here: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/collections.html - but for a concrete, compound class.
I prefer solution in XML rather than annotations.
Normally you map this as composite-element
<set ... >
<key .../>
<composite-element class="Child" ...>
<property name="property"/>
<property name="anotherProperty"/>
<nested-composite-element name="oneToOneClass">
<property name="..."/>
</nested-composite-element>
</set>
See Component Mapping.

Update one value from a list of dependent objects

Given an entity with a list of components:
class Entity{
Long id;
String name;
List<Component> components = new ArrayList<Component>();
}
class Component{ Object value; }
Configuration:
<hibernate-mapping>
<class name="Entity" table="entity">
<id name="id" access="field" column="id"/>
<property name="name" access="field" unique="true"/>
<list name="components" access="field" table="COMPONENTS" lazy="true">
<key column="id"/>
<list-index column="idx"/>
<composite-element class="Component">
<property name="value" access="field"/>
</composite-element>
</list>
</class>
</hibernate-mapping>
Is it possible to update one component from the list with HQL statement like
update Entity e set e.components[:index].value = :value where e.name = :name
that does not work?
Alternatively, is it possible to configure lazy loading of the list of components in a way that the first access:
entity.components.get(0).value = "..";
does not load the whole list?
Edit:
The lazy="extra" configuration does work for select (loads only the component to update), but it will not update the changed component.
You can't update a single collection element via HQL.
From the 13.4. DML-style operations chapter:
There can only be a single entity named in the from-clause.
No joins, either implicit or explicit, can be specified in a bulk HQL query.
Since your collection element is not an entity, it's not addressable from within bulk update. Technically speaking, non-entity collection elements are not addressable in general; indexed collections or sets with elements having natural ids being the only exceptions.
While it is possible to lazy-load collection elements few at a time (though it doesn't really make sense in this case unless you know ahead of time that you'll only be looking at Nth element since batch size is not easily changeable at runtime), it's not going to help because entire collection will be loaded anyway when you try to update it.
Selecting a single collection element is possible for indexed collection (not part of your question, but I wanted to clarify on this based on KLE answer and your comments):
select c
from Entity e join e.components c
where index(c) = :index

Hibernate Delete Cascade

I Have one entity [Project] that contains a collection of other entities [Questions].
I have mapped the relation with a cascade attribute of "all-delete-orphan".
In my DB the relation is mapped with a project_id (FK) field on the questions table. this field cannot be null since I don't want a Question without a Project.
When I do session.delete(project) it throws an exception saying that project_id cant be null, but if I remove the not-null constraint to that field, the deletion works nice.
Anyone knows how to solve this?
Straight from the documentation. This explains your problem exactly i believe:
However, this code
Parent p = (Parent) session.Load(typeof(Parent), pid);
// Get one child out of the set
IEnumerator childEnumerator = p.Children.GetEnumerator();
childEnumerator.MoveNext();
Child c = (Child) childEnumerator.Current;
p.Children.Remove(c);
c.Parent = null;
session.Flush();
will not remove c from the database; it will only remove the link to p (and cause a NOT NULL constraint violation, in this case). You need to explicitly Delete() the Child.
Parent p = (Parent) session.Load(typeof(Parent), pid);
// Get one child out of the set
IEnumerator childEnumerator = p.Children.GetEnumerator();
childEnumerator.MoveNext();
Child c = (Child) childEnumerator.Current;
p.Children.Remove(c);
session.Delete(c);
session.Flush();
Now, in our case, a Child can't really exist without its parent. So if we remove a Child from the collection, we really do want it to be deleted. For this, we must use cascade="all-delete-orphan".
<set name="Children" inverse="true" cascade="all-delete-orphan">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
Edit:
With regards to the inverse stuff, i believe this only determines how the sql is generated, see this doc for more info.
One thing to note is, have you got
not-null="true"
on the many-to-one relationship in your hibernate config?
One strategy is to mark the foreign-key in the database with on-delete-cascade, so as soon as NHibernate tells the database to delete a project, the database itself will cascade the deletes. Then you have to tell NHibernate that the database itself does a cascade delete.
If anyone have situation like this then make sure you actually have set proper CascadeType (one of: ALL, REMOVE, DELETE). It needs to be in the entity that you try to delete:
public class Project {
#Id
private long id;
#OneToMany(mappedBy = "project", cascade = {CascadeType.REMOVE})
public List<Question> questions;
}
Deleting should work whether there is NOT NULL constraint on foreign key or not just with:
session.delete(project);
The delete is occurring on the Project first and cascading to the Question, but the Project delete includes a nulling of the project_id in the Questions (for referential integrity. You're not getting an exception on the deletion of the Question object, but because the cascade is trying to null the FK in the Question(s).
Looking at "Java Persistence with Hibernate", I think that what you really want a cascade type of delete or remove, not delete-orphans.

Categories

Resources