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.
Related
I am migrating a legacy hibernate project from version 4.3 (with Java 11) to 5.6 (with Java 16). The HBM files below map an object graph of Jurisdiction -> Unit -> UnitAux. Units are lazy-loaded, and UnitAux is one-to-one with Unit. Under version 4.3, when initiazing Units, it would take about 100ms to load. Under version 5.6, it now takes 600-800ms.
These are the abbreviated HBM files for the 3 entities:
Jurisdiction.hbm.xml
<hibernate-mapping>
<class name="com.edc.c2c.core.model.impl.Jurisdiction" table="Jurisdiction" schema="domain" dynamic-update="true">
<set name="units"
inverse="true" cascade="all" lazy="true" fetch="select"
optimistic-lock="false" batch-size="1000" where="recordStatus = 'A'">
<key>
<column name="jurisdictionId"/>
</key>
<one-to-many class="com.edc.c2c.core.model.impl.Unit"/>
</set>
</class>
</hibernate-mapping>
Unit.hbm.xml
<hibernate-mapping>
<class name="com.edc.c2c.core.model.impl.Unit" table="Unit" schema="domain" dynamic-update="false">
<composite-id>
<key-property name="id" column="id" type="long"/>
<key-property name="owningJurisdictionId" column="jurisdictionId" type="long"/>
</composite-id>
<one-to-one name="unitAux" class="com.edc.c2c.core.model.impl.UnitAux" cascade="all" fetch="join" property-ref="unit"/>
</class>
</hibernate-mapping>
UnitAux.hbm.xml
<hibernate-mapping>
<class name="com.edc.c2c.core.model.impl.UnitAux" table="UnitAux" schema="domain" dynamic-update="true">
<composite-id>
<key-property name="id" column="id" type="long"/>
<key-property name="jurisdictionId" column="jurisdictionId" type="long"/>
</composite-id>
<many-to-one name="unit" class="com.edc.c2c.core.model.impl.Unit" unique="true" not-null="true"
cascade="all" insert="false" update="false">
<column name="id"/>
<column name="jurisdictionId"/>
</many-to-one>
</class>
</hibernate-mapping>
If I comment out the one-to-one in Unit.hbm.xml, the unit(s) Set loads fast, as expected.
In the UnitAux.hbm.xml, I replaced the many-to-one with a bag containing a one-to-many, something like this:
<bag name="unitGroup" inverse="true" cascade="all" lazy="true" fetch="select">
<key>
<column name="id"/>
<column name="jurisdictionId"/>
</key>
<one-to-many class="com.edc.c2c.core.model.impl.unit"/>
</bag>
With this, the UnitAux class had a List property called unitGroup. With the bag, the unit(s) load time dropped to 300ms.
I'm at a loss as to how to get the hibernate 5.6 to perform at the same load times as 4.3.
Any ideas or suggestions would be greatly appreciated.
Update: I forgot to mention, both versions effectively produce the same SQL. Something about how the objects themselves are initialized must be causing the slow down.
Update 2: The session statistics between 4.3 and 5.6 were very similar; not enough to explain the performance difference. My investigation has shown that the delays appear to be centered around initializing the entities. Specifically, the call to
Loader.initializeEntitiesAndCollections( final List hydratedObjects, final Object resultSetId, final SharedSessionContractImplementor session, final boolean readOnly, List<AfterLoadAction> afterLoadActions)
The time spent here is where the latency lies. Every property in each entity is tested for bytecode enhancement. In my test, I'm loading 600+ Units, along with the 600+ UnitAux entities. Is than an alternate loader that doesn't do this?
Update 3: Changing the association for Unit -> UnitAux to unidirectional reduced the latency by roughly half. Now it's only 3x slower.
Update 4: This is very strange. After experimenting with a variety of things, I made the following discovery. If I enable logging at the INFO (or ERROR) level for hibernate (see below config), everything runs fast, at the expected timing:
<logger name="org.hibernate" additivity="false">
<level value="info"/>
<appender-ref ref="STDOUT"/>
</logger>
If logging isn't declared, it runs slow (meaning nothing is specifically configured for hibernate). Is this something peculiar with jboss logging? I'm using jboss-logging-3.4.2.Final.jar. Does it run slower if nothing is explicitly declared in the log4j.xml? It's like the classic problem of having debug statements that never get used, but Java has to build all of the string values, which leads to extreme latency.
Update 5: I just did a spot check of the source code for Hibernate Core 5.6.0-Final. 141 classes use log.trace, and there are 249 class that use log.debug. Most of the log.trace calls don't pre-check to see if TRACE is enabled. The log.debug calls are checked more frequently, but there are stil a ton that don't pre-check to see if DEBUG is enabled.
In the end, I found my own answer to this problem.
First, there was no problem at all with Hibernate 5.x and one-to-one bidirectional associations. The problem had everything to do with debug/trace logging. With Hibernate 5.x and bytecode enhancement, a lot of new logging was added. Not only is logging done for each row-wise entity, but also for each column/property (testing for column lazy loading?).
Second, my entity classes have special formatting for toString(). Invocations of these toString()s can take a lot of time to execute, and then multiply if over hundreds of entities.
Third, there must be some bug between jboss-logging and log4j1-12.x. Internally, jboss-logging#doLogf() checks for whether or not the logging level is enabled, however, it is still reaching the String formatter. Here is a snapshot from VisualVM with the application running in Tomcat; you can see that 700ms was added for debug formatting, even though it would never be printed out to the log file:
If the Hibernate code had debug/trace level checks before the logging calls, this would not happen.
In the end, with Tomcat, I removed all log4j and slf4j-log4j jar files. That solved the problem.
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'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).
I have to create complex hibernate mapping. The following simplified example explains my problem.
I have two entities:
public class Work {
private WorkType type;
private Set<Workers>;
...
}
public class Worker {
private Map<WorkType,Work>;
...
}
I have 3 tables:
t_works columns: id, type,...
t_workers columns: id,...
t_work_worker columns: worker_id, work_id.
I want to map the map with hibernate, without copying the type values to t_work_worker.
The problem here is that the map key (WorkType) is a part of the map value (Work).
My hibernate hbm:
<typedef class="org.hibernate.type.EnumType" name="workType">
<param name="enumClass">myPackage.WorkType</param>
<param name="type">12</param>
</typedef>
<class name="work" table="T_WORKS">
<property name="type" type="workType" column="type"/>
<set name="workers" table="T_WORK_WORKER" inverse="true" lazy="false" cascade="none">
<key column="WORK_ID" />
<many-to-many column="WORKER_ID"class="myPackage.Worker"/>
</set>
</class>
<class name="Worker" table="T_WORKERS">
<map name="channels" table="T_WORK_WORKER" lazy="false" cascade="all">
<key column="WORKER_ID" />
<map-key formula="(select w.TYPE from t_works w where w.ID=WORK_ID)"type="workType"/>
<many-to-many column="WORK_ID" class="myPackage.Work"/>
</map>
</class>
This mapping works but requires additional select statement (see formula atribute).
I wonder whether there's a way to map the work type as a key, without using "formula" and without adding the type column to the relations table.
As you said, part of your problem is that you have a circular reference. This is usually not a very good idea, so you might want to think of an alternative structure. Do you really need the circular reference? Why does the worker need to have a map of worktype and work? I would probably make the connection between worker and work in another class, so you won't need the circular reference.
I'm doing a search on one of my tables (legacy database) and recieving a horrible time here. The query is build by criteria api of hibernate, e.g.:
Criteria crit = getSessionFactory().getCurrentSession().createCriteria(P1.class);
crit.add(Restrictions.sqlRestriction("{alias}.compno like ?", "%" + s + "%", new StringType()));
crit.setMaxResults(25);
crit.setFirstResult(0);
crit.addOrder(Order.asc("compno"));
crit.list();
As you can see I'm already doing a paging here to improve the perfomance. This criteria needs ~6 seconds on average.
Well the native query which looks like this
select * from SCHEM.P1 where compno like '%100%' order by compno fetch first 25 rows only
takes only 10 ms which is a huge difference imo. Why does the criteria runs so slow? Need I switch back to the native sql query?
Good point on the comments:
Yes there are some relationships which I didn't had on the scope:
<set name="pI" table="P12" lazy="false">
<key column="awcompno" update="false" />
<one-to-many class="org.gee.hibernate.P12" not-found="ignore"/>
</set>
<one-to-one name="info" class="org.gee.hibernate.P13" />
<set name="ma" table="P03" lazy="true" schema="SCHEMP" mutable="false" >
<key column="macountry" property-ref="land" update="false" />
<one-to-many class="org.gee.hibernate.P03" not-found="ignore" />
</set>
<set name="users" table="P15" lazy="true">
<key column="apcompno" update="false" />
<one-to-many class="org.gee.hibernate.P15" not-found="ignore"/>
</set>
My tip is:
<set name="pI" table="P12" lazy="false">
<key column="awcompno" update="false" />
<one-to-many class="org.gee.hibernate.P12" not-found="ignore"/>
</set>
This collection is not lazy. That may be your bottleneck.
Do you need all informations? You may read fields of your entity with hibernate, if you only want to read the IDs.
IBM pureQuery has some really nice facilities for accelerating Hibernate applications that work with DB2. The other benefit ... it makes it much easier to debug as it allows you to correlate your SQL and your Java code.
Take a look at this article http://www.ibm.com/developerworks/data/library/techarticle/dm-1008hibernateibatispurequery1/index.html
I'd say have a look at the DB logs to check what are the exact SQL instructions that get executed. Hibernate maybe loading more than just the native query you would expect, as it may load eager collections etc.
So - enable Hibernate query logging, or better yet, check the DB logs to see what gets executed.