Hibernate - set of compound values - java

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.

Related

Map complex value objects in Hibernate

I've already asked this question on the Hibernate's forum, but I thought I'd ask it here too.
I'm trying to map the following model while preserving the value semantics of the TranslatedText and Translation value objects:
Both values as dependent objects
Ideally I'd map TranslatedText as a <component> within Question and Translation as a <bag> of <composite-element> within TranslatedText.
It would have been simple to map if Question was only referencing one TranslatedText, but since it references two I need some kind of discriminator based on the name of the property holding the value (title or description) in order to map the Translation with a foreing key composed of (question_id,property_name,language_code).
One problem with that is that the propertyName isin't part of the model and shouldn't, but I haven't found a way to force Hibernate to insert a value that doesn't come from the model.
Therefore, I tried to change the model and introduce specialized Title and Description classes so that I'd have a type in there that I could use as a discriminator.
At the end that did not really help much:
<component name="title" class="TranslatedText">
<bag name="translations" table="Translation">
<key>
<!-- PROBLEM: Could not find a way to create a custom join expression on question.id and question.title.type in here. -->
</key>
<composite-element class="Translation">
<!-- PROBLEM: Could not found a way to make Hibernate insert title.type from here, without having this value on the Translation object. -->
<property name="languageCode" type="string" column="language_code"/>
<property name="text" type="string"/>
</composite-element>
</bag>
</component>
TranslatedText with <many-to-one>
I managed to get something close to what I need by mapping TranslatedText as an entity within Question using a <many-to-one> and then map Translation as a collection of values within TranslatedText, but the main problem with that approach is that there is no easy way to get rid of the orphaned TranslatedText and Translation. I'd either have to do this with a DB trigger or a scheduled process.
Conclusion
At this point I'm under the impression that Hibernate is not flexible enough to map the initial model with the proper semantics, but hopefully I'm wrong and there is a way to do it.
I have not found a way to map them as values. However the next solution works and it might be helpful for you. I removed TranslatedText and linked Question directly with collection of Translation.
#Entity
public class Question {
#Id
private String id;
#JoinTable
#OrderColumn
#OneToMany(fetch = EAGER, cascade = ALL)
private List<Translation> titleTranslations;
#JoinTable
#OrderColumn
#OneToMany(fetch = EAGER, cascade = ALL)
private List<Translation> descriptionTranslations;
}
The drawback here is that Translation has to be Entity class.
#Entity
public class Translation {
#Id
private String id;
private String languageCode;
private String text;
}

Hibernate use same mapping for multiple tables

I have 2 identical DB instances containing FOO_TABLE with the same schema. So, currently I have one class definition per DB instance:
<class name="FooTable" table="FOO_TABLE" entity-name="FooTableInstance1">
<property name="..." column="..." />
<property name="..." column="..." />
....
</class>
<class name="FooTable" table="FOO_TABLE" entity-name="FooTableInstance2">
<property name="..." column="..." />
<property name="..." column="..." />
....
</class>
The problem is that I don't want to copy-paste the properties, as the tables have the same schema. Is it possible to inherit the 2 classes from a base class which contains all the mappings and in the 2 children classes specify different entity-name?
An alternative (and perhaps the correct one if I understand your question correctly) is to use a #MappedSuperclass to define the common mappings. Whether you use this or the suggestion posted previously depends on the data model: for example are these two entities related so that you would like to be able to query across both of them?
e.g. select f from Foo returns all Foo1 and Foo2.
This cannot be done when Foo is a MappedSuperclass.
See here for further details:
http://en.wikibooks.org/wiki/Java_Persistence/Inheritance#Mapped_Superclasses
JPA: Implementing Model Hierarchy - #MappedSuperclass vs. #Inheritance
Yes, it is possible. Take a look at the relevant documentation: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/inheritance.html
More specifically, check 9.1.5. Table per concrete class. Make the parent class abstract and things should work fine.
According to the documentation you need 3 Java classes:
Foo (abstract, containing all fields you want in both tables)
FooChild1 (concrete, subclass of Foo, containing no new fields)
FooChild2 (concrete, subclass of Foo, containing no new fields)
You will need two tables. One mapping to FooChild1, and another to FooChild2.

Store List in hibernate as Serializable object

Normally we store a List in database by the hibernate mapping:
<list name="userItems" cascade="all" lazy="false">
<key column="user_date_id"/>
<index column="idx"/>
<one-to-many class="UserItem"/>
</list>
Is there any other way? Can we store it as a Seializable object? Like:
<property name="list" column="list" type="serializable" />
I use Hibernate Annotiations, and the trick here is to define your field as a List implementation like ArrayList. Then (like any other class implementing Serializable - i guess) Hibernate stores it as a bytea in the database.
Of course the list should only contain elements that implement Serializable.
I'm not sure you can directly serialize a list but if not you might create a wrapper object for the list and declare it as large object (don't know the xml variant, but it's the #Lob annotation). This should automatically be serialized then, since its not a string and thus not a clob but rather a blob.

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

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

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

Categories

Resources