Hibernate One To Many Unidirectional Mapping List - java

I have one-to-many relationship between parent and child Java objects. The parent object uses java.util.List that stores multiple child objects. The problem I am experiencing is when updating the parent object after I have added one or more child object(s) to the List in the parent. I am using the saveOrUpdate method to save or update the parent. It works fine if I am saving a new instance of the parent, but after saving it, I try to add child object(s) into the parent List and then attempt to call saveOrUpdate on the parent object, but no entries of child object(s) get persisted into the database. I just would like some pointers. Note: I am not using annotations. A snippet of the Parent.hbm.xml, that defines the one-to-many unidirectional relationship:
<list name="children" cascade="all">
<key column="parent_id"/>
<index column="idx"/>
<one-to-many class="Child"/>
</list>

I just tried to reproduce this example and it worked OK for me.
Here are my mappings:
<hibernate-mapping package="com.example.domain">
<class name="com.example.domain.Parent" table="PARENT">
<id name="id" column="parent_id" access="field">
<generator class="increment" />
</id>
<property name="name" column="parent_name" access="field" />
<list name="children" access="field" cascade="all">
<key column="parent_id" not-null="true" />
<index column="idx" />
<one-to-many class="Child" />
</list>
</class>
</hibernate-mapping>
<hibernate-mapping package="com.example.domain">
<class name="com.example.domain.Child" table="CHILD">
<id name="id" column="child_id" access="field">
<generator class="increment" />
</id>
<property name="name" column="child_name" access="field" />
</class>
</hibernate-mapping>
I added not-null="true" to the parent mapping.
Did you try to set show_sql in your hibernate config to see generated SQL?

Related

A different object with the same identifier value was already associated with the session, even when evicting the objects

I am using Hibernate 5.6 and have user Profile with Groups, which have Columns, which have ColumnProperties. I'd like to clone a column of a profile and attach it to the column group.
Profile -> Groups -> Columns -> ColumnProperties
I get this error:
10:58:39,793 ERROR
[com.myApp.core.rmgt.profile.service.RmgtProfileServiceImpl] (default
task-2) javax.persistence.EntityExistsException: A different object
with the same identifier value was already associated with the session
:
[com.myApp.core.rmgt.profile.business.object.RmgtColumnPropertyBVOImpl#124320]
Here's my mapping files:
<hibernate-mapping>
<class name="com.myapp.core.rmgt.profile.business.object.RmgtProfileBVOImpl" table="rmgt_t_profile" proxy="com.myapp.core.rmgt.profile.common.business.object.RmgtProfileBVO">
<id name="rowguid" column="rowguid" type="java.lang.Long">
<generator class="sequence">
<param name="sequence_name">rmgt_t_profile_rowguid_seq</param>
</generator>
</id>
...
<set name="groups" table="rmgt_t_group" lazy="false" cascade="all-delete-orphan" order-by="rmgtg_group_order asc">
<key column="rmgtg_fk_profile_id" not-null="true"/>
<one-to-many class="com.myapp.core.rmgt.profile.business.object.RmgtGroupBVOImpl"/>
</set>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="com.myapp.core.rmgt.profile.business.object.RmgtGroupBVOImpl" table="rmgt_t_group" proxy="com.myapp.core.rmgt.profile.common.business.object.RmgtGroupBVO">
<id name="rowguid" column="rowguid" type="java.lang.Long">
<generator class="sequence">
<param name="sequence_name">rmgt_t_group_rowguid_seq</param>
</generator>
</id>
...
<set name="columns" table="rmgt_t_column" lazy="false" cascade="all-delete-orphan" order-by="rmgtc_column_order asc">
<key column="rmgtc_fk_group_id" not-null="true"/>
<one-to-many class="com.myapp.core.rmgt.profile.business.object.RmgtColumnBVOImpl"/>
</set>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="com.myapp.core.rmgt.profile.business.object.RmgtColumnBVOImpl" table="rmgt_t_column" proxy="com.myapp.core.rmgt.profile.common.business.object.RmgtColumnBVO">
<id name="rowguid" column="rowguid" type="java.lang.Long">
<generator class="sequence">
<param name="sequence_name">rmgt_t_column_rowguid_seq</param>
</generator>
</id>
...
<set name="columnProperties" table="rmgt_t_column_property" lazy="false" cascade="all-delete-orphan" order-by="rmgtcp_order asc">
<key column="rmgtcp_fk_column_id" not-null="true"/>
<one-to-many class="com.myapp.core.rmgt.profile.business.object.RmgtColumnPropertyBVOImpl"/>
</set>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="com.myapp.core.rmgt.profile.business.object.RmgtColumnPropertyBVOImpl" table="rmgt_t_column_property" proxy="com.myapp.core.rmgt.profile.common.business.object.RmgtColumnPropertyBVO">
<id name="rowguid" column="rowguid" type="java.lang.Long">
<generator class="sequence">
<param name="sequence_name">rmgt_t_column_property_rowguid_seq</param>
</generator>
</id>
<property name="key" type="java.lang.String" column="rmgtcp_key" not-null="true" />
<property name="value" type="java.lang.String" column="rmgtcp_value" not-null="true" />
<property name="order" type="java.lang.Integer" column="rmgtcp_order" not-null="true" />
</class>
</hibernate-mapping>
What I do is i fetch a Profile from DB, then I use Spring's BeanUtils.copyProperties() to create a copy of the column that I want to clone. I made sure to evict all columnProperties before I proceed.
RmgtColumnBVO column = columnService.findLazyById(columnId);
// Evict all properties to force hibernate write new instances to DB
column.getColumnProperties().stream().forEach(x -> profileDAO.getSession().evict(x));
RmgtColumnBVO columnClone = new RmgtColumnBVOImpl();
BeanUtils.copyProperties(column, columnClone);
columnClone.setRowguid(null);
columnClone.setProfileId(profileId);
columnClone.setGroupId(groupId);
Then I null all rowguids of columnProperties objects of the columnClone:
// clone properties
Set<RmgtColumnPropertyBVO> propertys = column.getColumnProperties();
Set<RmgtColumnPropertyBVO> propertysClone = new HashSet<RmgtColumnPropertyBVO>();
for (RmgtColumnPropertyBVO property : propertys) {
RmgtColumnPropertyBVO propertyClone = new RmgtColumnPropertyBVOImpl();
BeanUtils.copyProperties(property, propertyClone);
propertyClone.setRowguid(null);
propertysClone.add(propertyClone);
}
columnClone.setColumnProperties(propertysClone);
After that, I fetch the profile and attach the cloned column to it and save the profile again:
RmgtProfileBVO profile = profileService.findById(column.getProfileId());
profile.getGroup(groupId).getColumns().add(columnClone);
profileService.saveOrUpdate(profile, object.getCreationUser());
What can I do to fix the problem? I don't understand why Hibernate is still complaining about the properties when I evicted every original one.
I found a solution for my problem. I replaced the profileService.saveOrUpdate() with profileService.merge() which did the job (although I do not fully understand why saveOrUpdate() does not).
In my opinion, saveOrUpdate should
Check if profile exists and check if there are changes (new elements in collections, new attribute values).
Check for every mapped collection (in my case: columns) if there are attribute changes or new items
Traverse the mapping tree and check for every mapped collection (columnProperties) of a mapped collection (columns) if value changes or new items (which was the case for me) and update/insert accordingly.

Why hibernate selects entities before persisting entities mapped by joined_subclass inheritance type?

I have superclass Person and two subclasses - Parent and Child. They are mapped with JOINED_TABLE inheritance type.
Parent and Child have bidirectional one-to-many relationship.
Person.hbm.xml contains all configuration:
<hibernate-mapping>
<class name="com.masterhibernate.SimpleHibernateDemo.Person"
table="Person">
<cache usage="read-write" />
<id name="id" column="id">
<generator class="assigned" />
</id>
<property name="name">
<column name="name" length="16" not-null="true" />
</property>
<property name="surname">
<column name="surname" length="36"></column>
</property>
<property name="address">
<column name="address" length="22"></column>
</property>
<joined-subclass name="com.masterhibernate.SimpleHibernateDemo.Parent"
table="Parent">
<key column="person_id" foreign-key="parent_person" />
<property name="job" column="WorkPlace" length="22" type="string" />
<set name="children" inverse="true" cascade="save-update" lazy="true">
<cache usage="read-write" />
<!-- specifies foreign key column of child table -->
<key column="ParentPK" />
<one-to-many class="com.masterhibernate.SimpleHibernateDemo.Child" />
</set>
</joined-subclass>
<joined-subclass name="com.masterhibernate.SimpleHibernateDemo.Child"
table="Child">
<key column="person_id" foreign-key="child_person" />
<property name="toy" column="toy" length="55" type="string" />
<many-to-one name="parent"
class="com.masterhibernate.SimpleHibernateDemo.Parent" column="ParentPK"
foreign-key="child_parent" />
</joined-subclass>
</class>
</hibernate-mapping>
When I persist Parent and associated Child entities hibernate issues select statements before inserting. These select statements query for data of Child entities from child and person tables.
Database is recreated on each run and there is nothing to select.
Here is extract from main method that persists Parent and associated Child entities:
Parent parent = new Parent("Volodia", "Levytskyi", "Mykolaiv");
parent.setId((long) 1);
Set<Child> children = new HashSet<>();
children.add(new Child((long) 2, "Ivan", "McGregor", "Odessa"));
children.add(new Child((long) 3, "Borys", "McRobin", "Donetsk"));
children.add(new Child((long) 4, "Nazar", "McCrespo", "Teernopil"));
parent.setChildren(children);
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
session.save(parent);
// Criteria criteria = session.createCriteria(Person.class);
// List<Person> list = criteria.list();
// System.out.println("list1=" + list);
transaction.commit();
session.close();
Why are those select statements before insert statements?
The problem was in id generator strategy of Parent and Child entities:
<generator class="assigned" />
Hibernate cannot determine if entity with assigned id exists in database or not. That's why it issues select statements to ensure that no duplicate ids are inserted.
After I moved to id generator strategy native it stopped selecting before inserting:
<generator class="native" />

Hibernate many-to-many with subclasses using the same pivot table

Having this model schema:
Person
|__ Student
|__ SchoolBoy
|__ CollegeStudent
I'm using Hibernate 3.6 and I use the tperson table for all the classes, using a discriminator column. My mapping is done like that:
<class name="Person" table="tperson" discriminator-value="PERSON">
<id name="Id" column="id" type="integer">
<generator class="increment" />
</id>
<discriminator column="person_type" />
<subclass name="Student" discriminator-value="STUDENT">
<key column="id_person" />
<subclass name="SchoolBoy" discriminator-value="SCHOOL_BOY">
<join table="tstudent">
<key column="id_person" />
</join>
</subclass>
<subclass name="CollegeStudent" discriminator-value="COLLEGE_STUDENT">
<join table="tstudent">
<key column="id_person" />
</join>
</subclass>
</subclass>
</class>
Now I want to introduce the Course entity, implementing a relation between courses and students. Of course, this is a many-to-many relation. Let's suppose I use a pivot table named tstudent_course, which contains students of both types SchoolBoy and CollegeStudent. This table contains a reference to the person itself and the course he's studying.
Now I want to differ between college and school students when I load the Course entity. I do it like that:
<set name="CollegeStudents" table="tstudent_course"
inverse="true">
<key>
<column name="id_course" not-null="true" />
</key>
<many-to-many entity-name="CollegeStudent">
<column name="id_person" not-null="true" />
</many-to-many>
</set>
<set name="SchoolStudents" table="tstudent_course"
inverse="true">
<key>
<column name="id_course" not-null="true" />
</key>
<many-to-many entity-name="SchoolBoy">
<column name="id_person" not-null="true" />
</many-to-many>
</set>
However, being the pivot table a table which contains references to every type of students, it tries to load every single student in my collections and I receive the next Exception:
Object with id: 2 was not of the specified subclass:
CollegeStudent (loaded object was of wrong class class SchoolBoy)
It seems Hibernate is doing the join whithout evaluating the concrete type of student I have and tries to inject an SchoolBoy in my collection of College Students.
What can I do to avoid that? Is it possible to stablish a kind of discrimination in the pivot table? Or do I have to create an specific pivot table for each kind of subclass?
In your set you can add a filter:
<set name="CollegeStudents" table="tstudent_course"
inverse="true">
<key>
<column name="id_course" not-null="true" />
</key>
<many-to-many entity-name="CollegeStudent" where="person_type='COLLEGE_STUDENT'">
<column name="id_person" not-null="true" />
</many-to-many>
</set>
IMHO the mapping would be better without that filter (just a set of all students).

Using the same column for multiple mappings in hibernate

I have run my own tests and don't believe this is possible but was hoping to get some confirmation.
I have two objects that are being persisted using hibernate, Child and Parent. A Child always has a reference to a single Parent and and Parent has a Set of Child Objects. On top of this a parent has a List of favorites which consists of Child objects.
This is mapped using the following HBM definitions:
<class name="Parent" table="parent">
<id name="id" column="id">
<generator class="increment"/>
</id>
<set cascade="all,delete-orphan" inverse="true" name="children">
<cache usage="nonstrict-read-write"/>
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>
<list cascade="none" name="favorites">
<cache usage="nonstrict-read-write"/>
<key>
<column name="parent_id" not-null="false"/>
</key>
<index>
<column name="fav_index"/>
</index>
<one-to-many class="Child"/>
</list>
</class>
<class name="Child" table="child">
<id name="id" column="id">
<generator class="increment"/>
</id>
<many-to-one column="parent_id" name="parent" not-null="false"/>
</class>
The trouble I am having is with the column parent_id in the child table. It works fine when I persist Child objects that have references to a parent and when I persist Parent objects that have a list of favorites. However, when I try and remove a child from a parents list of favorites, the column child.parent_id gets set to null, even though I have not removed the Parent Object from the Child before it is persisted.
From my tests, I don't think there is anyway around this other than using a different column name for the favorites key column in the Parent mapping. It just seems a bit of a shame to do that since these two columns will always have the same value.
Does anyone if this is possible or will I have to use two columns?
Thanks

Delete element from Set

I have 2 classes Tema(Homework) and Disciplina (course), where a Course has a Set of homeworks.
In Hibernate i have mapped this to a one-to-many associations like this:
<class name="model.Disciplina" table="devgar_scoala.discipline" >
<id name="id" >
<generator class="increment"/>
</id>
<set name="listaTeme" table="devgar_scoala.teme">
<key column="Discipline_id" not-null="true" ></key>
<one-to-many class="model.Tema" ></one-to-many>
</set>
</class>
<class name="model.Tema" table="devgar_scoala.teme" >
<id name="id">
<generator class="increment" />
</id>
<property name="titlu" type="string" />
<property name="cerinta" type="binary">
<column name="cerinta" sql-type="blob" />
</property>
</class>
The problem is that it will add (insert rows in the table 'Teme') but it won't delete any rows and i get no exceptions thrown.
Im using the merge() method.
Although your question is unclear (how do you save and delete?), I'd suggest you need to set cascade:
<set cascade="all-delete-orphan">
As a sidenote - avoid names in your native language.
According to your description, I understand that a Tema cannot exist without its Disciplina: if you remove a Tema from the collection, you want it to be deleted. To tell Hibernate to do this, you must use cascade="all-delete-orphan".
<set name="listaTeme" table="devgar_scoala.teme" cascade="all-delete-orphan">
<key column="Discipline_id" not-null="true" ></key>
<one-to-many class="model.Tema" ></one-to-many>
</set>
Refer to the online documentation.

Categories