I'm inherited a hibernate mapping and am having trouble moving a child node from one parent node to another. Either I get a duplicate reference, or I get an error.
I have locations in a tree. I want to move one leaf node to another leaf position. In code I'm trying to do this:
GeographicLocation oldParent = location.getParent();
location.setParent(newParent);
newParent.getChildren().add(location);
oldParent.getChildren().remove(location);
Causes:
org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): [com.test.GeographicLocation#11]
If I remove the line oldParent.getChildren().remove(location), the newParent node correctly points to the child, but the oldParent still has a reference to the child as well(!).
Snippets from hibernate config file:
<class name="GeographicLocation" table="GeographicLocation">
<id column="GeographicLocationId" name="geographicLocationId" type="java.lang.Long">
<generator class="native">
<param name="sequence">GeographicLocationId</param>
</generator>
</id>
<many-to-one class="com.test.GeographicLocation"
foreign-key="ParentFK" name="parent">
<column name="parent"/>
</many-to-one>
<bag cascade="all,delete-orphan" inverse="true" lazy="false" name="children">
<key column="parent" not-null="true"/>
<one-to-many class="com.test.GeographicLocation"/>
</bag>
I haven't been using Hibernate very long. My understanding is that the location node, being a managed object, will save itself when modified. Since the hibernate config file specifies cascade=all changes to the collection will also save changes to the child. However, I can't seem to find a legal way to remove the old reference. Any help?
I would remove the delete-orphan from the mapping, since it says that as soon as you remove an element from the collection, it should be removed (which is clearly not what you want).
Related
I'm working with Hibernate 4.3.5, Java 1.6 and Spring 4.0.3.
I've mapped the entities through hbm, and I want my application works with logical deletion.
So, in each mapped entity, I've added a property named 'deleted', which indicates if an entity is deleted or not.
Because I don't want to load the deleted entities (the ones having true the deleted property), I've used the where clause in the mapped classes, so I only get the entities aren't logically deleted.
And also, I've added the same where clause to every one-to-many relationship.
In one particular case, I've got a Report entity that has a one-to-many relationship with the Document entity.
So, when I mark a Document as deleted, and I save the Report entity (with merge), I expect than the Report entity doesn't keep the Document marked as deleted. But this doesn't occur.
This is the hbm for the Report entity:
<hibernate-mapping>
<class
name="es.entities.Report"
table="reports"
dynamic-insert="false"
dynamic-update="false"
where="deleted = 0">
<id name="id">
<generator class="identity"/>
</id>
<property name="title"></property>
<property name="deleted"></property>
<set
name="documents"
table="documents"
cascade="all"
lazy="false"
where="deleted=0">
<key column="id_report"/>
<one-to-many class="es.entities.Document"/>
</set>
</class>
</hibernate-mapping>
Here it is the hbm for the Document entity:
<hibernate-mapping>
<class
name="es.entities.Document"
table="documents"
dynamic-insert="false"
dynamic-update="false"
where="deleted = 0">
<id name="id">
<generator class="identity"/>
</id>
<property name="name"></property>
<property name="type"></property>
<property name="size"></property>
<property name="deleted"></property>
</class>
</hibernate-mapping>
I use a Service (ReportService) to open a Spring transaction. The method is:
#Autowired
private ReportDao reportDao;
#Transactional
public Report save(Report report) {
this.reportDao.save(report);
}
And this is the DAO (ReportDao) method I use to save the Report entity:
public Report save(Report report) {
return (Report) this.currentSession().merge(report);
}
I put an example:
The parameter I send to the service contains a Report object, with two Document objects, one of them deleted and the other not.
The DAO method returns the same information, but I'd like this method returns only the documents are not deleted.
Note: if I use another method with another transaction, I obtain the report only with the document is not deleted, but I'd like to do this in the same transaction.
Can anybody help me or show me an alternate to this? It is possible to use other Session method than merge?
Thanks a lot.
Merge method create a copy from the passed entity object and return it. Try re-fetching the report entity post merge.
I'm working with Hibernate 3.6 version, with xml mapping files. In my case I have three mapped entities, which are Detector, Antenna and Location. Basically, having Detector->Set<Antenna> and Location->Set<Antenna> relations, I would like to have also Detector->Set<Location> available.
Each Detector has a Set of Antenna entities, mapped like that:
<set name="_Antennas" table="tantenna" inverse="true" cascade="all">
<key>
<column name="id_detector" not-null="true" />
</key>
<one-to-many class="Antenna" />
</set>
Also each Antenna belongs to a specific Location and to a specific Detector. That's the many-to-one mapping to refer that:
<many-to-one name="_Detector" class="com.tadic.model.detector.Detector"
column="id_detector" />
<many-to-one name="_Location" class="com.tadic.model.location.Location"
column="id_location" />
In the same way, Location has a Set of its Antennas:
<set name="_Antennas" table="tantenna">
<key>
<column name="id_location" />
</key>
<one-to-many class="com.tadic.model.detector.Antenna" />
</set>
So Detector knows about its Antennas, Antennas know about their Detector and Location. Location entity has a set of its Antennas, but tlocation table has no foreign-keys.
However, I'm interested in knowing all the Locations of a Detector in a specific point. I know I can do it writing an HQL, but I would like to know if this is possible when Detector loads, just mapping it as a Set of Location entities.
Remember tlocation table has no iddetector column to link it with, also I think there's no need for it.
If I got it right from a database point
tdetector [1]--[id_detector]-->[n] tantenna
tlocation [1]--[id_location]-->[m] tantenna
Meaning tantenna has a column tuple of (id_detector, id_location) and is essentially a link table between tdetector and tlocation. This could be used to facilitate a many-to-many mapping between Detectors and Locations.
And here is the mapping fragment for the Detector hibernate mapping.
<set name="locations" table="tantenna">
<key column="id_detector" />
<many-to-many class="com.tadic.model.location.Location" column="id_location" />
</set>
One more thing. In my experience, having such a complex relations scheme mapped on the ORM does not come without cost. Even if hibernate finds your mapping files to be fine during the session factory initialization, I urge you to test thoroughly and, if necessary, specify some relations to be read-only (i.e. only useful when reading data) with insert="false" update="false".
I have a many-to-many association defined like:
Parent.hbm.xml:
<set name="children" table="child_parent_map" lazy="true">
<cache usage="nonstrict-read-write" />
<key column="parent_id" />
<many-to-many class="Child">
<column name="child_id" not-null="true" index="child_parent_by_child"/>
</many-to-many>
</set>
Child.hbm.xml:
<set name="parents" table="child_parent_map" lazy="true">
<cache usage="nonstrict-read-write" />
<key column="child_id" />
<many-to-many column="parent_id" class="Parent" lazy="false"/>
</set>
I am quite sure I am initializing Parent.children by walking the collection. Something like:
for(Child child : parent.getChildren()) {
Hibernate.initialize(child.getAnotherProperty());
}
Parent has six children. However, in one session parent appears to have only five, and in another (2 seconds later, nothing changed in DB or in another session) - all six. Actually, I discovered it after detaching these entities from session with a custom cloner.
I thought that lazy collections are either completely initialized (i.e. all elements are), or not. Is it possible that somehow only a part of the collection was initialized? Can it be an issue with caching?
EDIT: This session handles a fairly large data set (a few thousands of entities). Is it possible that this is because some already-loaded entities got evicted from the session?
Start by checking your hashCode() and equals() methods, incorrect implementation of these methods often cause this kind of behavior.
I have seen posts all over the internet that talk about how to fix the TransientObjectExceptions during save/update/delete but I am having this problem when calling list on my Criteria.
I have two objects A and B. A has a field named b which is of type B. In my mapping b is mapped as a many-to-one. This all runs in a larger persistence framework (the framework is kind of like Core Data) and so I don't use any cascades in my hibernate mappings since cascades are handled at a higher level.
This is the interesting code surrounding my criteria:
A a = new A();
B b = new B();
a.setB(b);
session.save("B", b); // Actually handled by the higher level
session.save("A", a); // framework, this is just for clarity
// transaction committed and session closed
...
// new session opened
Criteria criteria = session.createCriteria(A.class);
criteria.add(Restrictions.eq("b", b));
List<?> objects = criteria.list();
Basically I am looking for all objects of type A such that A.b equals a particular instance of b (I actually tried restructuring a query so that I was passing in the id of b just to make sure that b wasn't causing me problems).
Here is the stack trace that occurs when I call criteria.list():
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: B
at org.hibernate.engine.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:244)
at org.hibernate.type.EntityType.getIdentifier(EntityType.java:449)
at org.hibernate.type.ManyToOneType.nullSafeSet(ManyToOneType.java:141)
at org.hibernate.loader.Loader.bindPositionalParameters(Loader.java:1769)
at org.hibernate.loader.Loader.bindParameterValues(Loader.java:1740)
at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1612)
at org.hibernate.loader.Loader.doQuery(Loader.java:717)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:270)
at org.hibernate.loader.Loader.doList(Loader.java:2294)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2172)
at org.hibernate.loader.Loader.list(Loader.java:2167)
at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:119)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1706)
at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:347)
Here is my mapping:
<class entity-name="A" lazy="false">
<tuplizer entity-mode="dynamic-map" class="MyTuplizer" />
<id type="long" column="id">
<generator class="native" />
</id>
<many-to-one name="b" entity-name="B" column="b_id" lazy="false" />
</class>
<class entity-name="B" lazy="false">
<tuplizer entity-mode="dynamic-map" class="MyTuplizer" />
<id type="long" column="id">
<generator class="native" />
</id>
</class>
Can anyone help me figure out why I would be getting a TransientObjectException during a fetch? Preferably I would like to find a solution that does not rely on cascades since they tend to mask problems that occur in the higher level framework.
The problem is that b was made persistent in another session, which is closed and the query is created in a new session. When a session is closed, all objects in its persistence context become detached. If you want to later reuse them in another session, you need to re-attach them to that session first:
session.update(b);
Quote from the Hibernate book:
The update() operation
on the Session reattaches the detached object to the persistence context and
schedules an SQL UDPATE. Hibernate must assume that the client modified the
object while it was detached. (Otherwise, if you’re certain that it hasn’t been modified,
a lock() would be sufficient.) The persistence context is flushed automatically
when the second transaction in the conversation commits, and any
modifications to the once detached and now persistent object are synchronized
with the database.
The saveOrUpdate() method is in practice more useful than update(),
save(), or lock(): In complex conversations, you don’t know if the item is in
detached state or if it’s new and transient and must be saved. The automatic
state-detection provided by saveOrUpdate() becomes even more useful when you
not only work with single instances, but also want to reattach or persist a network
of connected objects and apply cascading options.
Note that there is also a merge() method, for cases when the same entity has been loaded into the new persistence context before the older detached instance could be re-attached. In this case, you have two physically distinct instances representing the same entity, thus they should be merged to avoid a NonUniqueObjectException.
Another easy way accomplish the same is to use the attribute cascade=all on the collection mapping of child class from within the parent class mapping. Here's how the mapping looks
<class entity-name="A" lazy="false">
<tuplizer entity-mode="dynamic-map" class="MyTuplizer" />
<id type="long" column="id">
<generator class="native" />
</id>
<many-to-one name="b" entity-name="B" column="b_id" lazy="false" cascade="all" />
</class>
<class entity-name="B" lazy="false">
<tuplizer entity-mode="dynamic-map" class="MyTuplizer" />
<id type="long" column="id">
<generator class="native" />
</id>
</class>
Since you have insert data such as in persist(),save() in hibernate..But the above error is you have just do merge() and others method which can perform the update information.
I have an entity that I want to persist through Hibernate (3.2)
The EntityBean has a column that indicates how another value of the entity bean should be unmarshalled:
<class name="ServiceAttributeValue" table="service_attribute_value">
<cache usage="nonstrict-read-write"/>
<id name="id" column="id" type="int-long">
<generator class="native"/>
</id>
<property name="serviceAttribute" type="service-attribute" column="service_attribute" not-null="true" />
<!-- order is important here -->
<property name="value" type="attribute-value" not-null="true">
<column name="service_attribute" />
<column name="id_value"/>
<column name="enum_value"/>
<column name="string_value"/>
<column name="int_value"/>
<column name="boolean_value"/>
<column name="double_value"/>
</property>
</class>
The "service_attribute" column indicates which of the columns for the "value" property to look at when it unmarshalls the value and, more importantly, exactly what Type the value should be, for example the class of the Enum if the enum_value is to be read, or the type of Bean if the the id_value is to be read.
The value property uses a custom CompositeUserType to do the unmarshalling and within this I wish to reference the service_attribute column (although not write to it), however when I try to do this I get the following error:
org.hibernate.MappingException: Repeated column in mapping for entity: com.precurse.apps.rank.model.service.ServiceAttributeValue column: service_attribute (should be mapped with insert="false" update="false")
However within the definition of the composite property these xml attributes are not defined (only within a normal property).
Does anyone know of a way of overcoming this, or if there is a better solution to this propblem.
If you need any more information please let me know,
Cheers
Simon
I had a similar problem and changing the case of one column solved the problem. Could give a try!
e.g., one column could be service_attribute other Service_Attribute.
You can try this. Instead of mapping both values as property on the same table, map one of the property using join to itself and keep the other property as the way it is. This case you will be able to access the same property in both places. Just remember to name the property as different name.
<join table="service_attribute_value">
<key column = "id" />
<property name="serviceAttribute" type="service-attribute" column="service_attribute" not-null="true" />
</join>
<!-- order is important here -->
<property name="value" type="attribute-value" not-null="true">
<column name="service_attribute" />
<column name="id_value"/>
<column name="enum_value"/>
<column name="string_value"/>
<column name="int_value"/>
<column name="boolean_value"/>
<column name="double_value"/>
</property>
based on your description, it seems like what you want to do is creating different subclasses based on the service_attribute. Instead of trying to achieve repeated column mapping which is not allow in hibernate, you can take a look hibernate inheritance mapping.
I Think I found a solution albeit not a very elegant one.
in the
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner)
throws HibernateException, SQLException {
method of the CompositeUserType the "owner" argument passed to the method contains the id of the object who's service_attribute I want to access.
Annoyingly the actual serviceAttribute of the owner is not accessable or has not been set at this stage (I played around with the ordering of the elements in the hbm.xml config, in case this was an ordering thing, but unfortunatly still no joy), so I can't simply access it.
Anyway the id of the owner object is set, so I then used the session argument to run a HQL query based on the id to access the serviceAttribute which I then used to correctly unmarshall the value property.
The drawback of this solution is that it requires a HQL query as an overhead to the unmarshalling process, although its within the same session, its still not optimal.
If anyone has any ideas for a better solution I'd be very grateful.
Cheers