Using the same column for multiple mappings in hibernate - java

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

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.

hibernate how to select the many-side from an one-to-many relation

everyone. I'm brand new using Hibernate.
So here I face a question, I have an entity like below:
<class name="cn.edu.scau.librarica.dao.MessageSession" table="msg_session">
<id name="msid" type="long" unsaved-value="null">
<generator class="identity"/>
</id>
<list name="msgs" cascade="all">
<key column="msid"
update="false" unique="true" not-null="true"/>
<list-index column="list_index"/>
<one-to-many class="Message" />
</list>
</class>
And now what I want to achieve is:
select Message m where msid=# and m.t<## and m.t>###
How can I represent it with Criteria?
Thank you for your attentions and advices, in advance.
UPDATE
As one of the answer guide, I have got the point that composite-element is not queryable, so I made a both-sid one-to-many map(changed are above)
And now I can query but now I found another problem:
How can I mapped the composite-id with foreign-key
Message are map like this:
<class name="Message">
<composite-id>
<generator class="foreign">
<!-- What here??? -->
</generator>
</composite-id>
</class>
As instructed, one-to-many may use set rather than list, so I can hardly find sample meets my need(due to Message should be ordered).
Any advice? I am still searching for that.
Thanks for help.
You can't directly select the Message as it is a component and not an entity. Components don't have an independent life cycle. They cannot be queried, created or deleted on their own, they always have to be accessed via the entity in which they are embedded (MessageSession in your case).
To make it into an Entity the table should have it's own primary key. In your case that doesn't seem to be so. You will need to change the schema so that the table corresponding to Message has a primary key and change the mapping to use one-to-many instead of composite-element.
If you can't do that you will have to query the MessageSession and get message out of it.
P.S: Hibernate is quite complex, I haven't see too many people being able to just pick it up along the way just trying things out. You are more likely to succeed if you spend some time studying the underlying concepts (which are more important than just mapping & querying).
here is the author of question.
After several days of research, I found the way to satisfied my require somehow.
Maping Message
<class name="cn.edu.scau.librarica.dao.Message" table="message">
<id name="id" type="long">
<generator class="identity" />
</id>
<many-to-one name="ms" class="cn.edu.scau.librarica.dao.MessageSession"
column="msid" not-null="true" insert="false" update="false" />
<property name="s" type="long" />
<property name="t" type="timestamp" />
<property name="m" type="string" />
</class>
Mapping MessageSession
<class name="cn.edu.scau.librarica.dao.MessageSession" table="msg_session">
<id name="msid" type="long" unsaved-value="null">
<generator class="identity"/>
</id>
<property name="latest" type="timestamp" />
<list name="msgs" table="msg_session_msgs" cascade="all">
<key column="msid"
update="false" not-null="true"/>
<list-index column="list_index"/>
<one-to-many class="cn.edu.scau.librarica.dao.Message" />
</list>
</class>
Among them, the unimportant are neglected.
Things go quite well while I can find the "one" with restrictions of "many" or inversely. just like this:
DetachedCriteria dc = DetachedCriteria.forClass(Message.class)
.createCriteria("ms")
.add(Restrictions.eq("msid", msid));
if (after != null)
dc.add(Restrictions.gt("t", after));
if (before != null)
dc.add(Restrictions.lt("t", before));
...can search out Message during the specified period from specified MessageSession.
So the trick is objects can't be returned by Criteria unless match them as entity.
On the underlaying database, it will be a little redundant, since Message can be uniquely identified by (msid,list_index), so I am finding a way to match <-this as the primary key for Message.

Hibernate: optional many-to-one with not-null columns

I've got an optional many-to-one relationship between two classes. Hibernate translates the property to be optional by setting the foreign keys to null.
My db-schema does not allow the columns to be null. The property to be optional is represented by the default-value of these columns.
<class name="sth.Alpha" ...>
....
<many-to-one name="beta" not-found="ignore" class="sth.Beta" insert="true" update="true">
<column name="a1/>
<column name="a2/>
</many-to-one>
</class>
<class name="sth.Alpha" ...>
<composite-id>
<key-property name="b1" type="int">
<column name="b1" precision="8" scale="0"/>
</key-property>
<key-property name="b2" type="int">
<column name="b2" precision="8" scale="0"/>
</key-property>
</composite-id>
</class>
selecting data is no problem because of not-found="ignore" in the may-to-one-tag it will result in a null-beta-object. But if I want to insert an Alpha? with beta set to null. I get an Exception, that it is not possible to insert null to a1 and a2.
I get rid of that problem if I set insert and update to false. But this results in not saving the relationship if it is set.
Database-Schema cannot be changed and Hibernate-version is fixed to 3.5
I would also be happy if you tell me, that it is not possible
how to use 0 instead of null in conjunction with <id unsavedvalue="whatever"> might help
or
other solution

Hibernate One To Many Unidirectional Mapping List

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?

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