All 5 entries tagged Hibernate

View all 12 entries tagged Hibernate on Warwick Blogs | View entries tagged Hibernate at Technorati | There are no images tagged Hibernate on this blog

April 27, 2006

Some more hibernate thingies

Writing about web page http://hibernate.org

Some more hibernate observations, although I haven't got time to prove via unit tests:

– if you specify an interface="" then the laziness is ignored; it is always lazy (not documented AFAICS)

– if you have final methods/class then regardless of access (field|property) you will get null values. This is because CGLIB cannot subclass/override final classes/methods (well known), but why doesn't hibernate throw an exception?

– if you specify non-lazy for a class, then the class is fully formed, even if it is using CGLIB with final classes/methods irrespective of access (property|field). I suspect in this case CGLIB isn't involved and hibernate is simply instantiating your implementation class and updating the fields via reflection.

– if you specify an outer join in the mapping, laziness will be ignored; it won't be lazy. This of course is intuitive; join makes no sense if it is not lazy.

When I have more time I will unit test all these combinations, but this is a reminder to me do so.

Will I ever understand hibernate completely :)


April 25, 2006

Hibernates bizarre interpretation of inverse ;)

Writing about web page hibernate.org

So my colleague asked me about the exact semantics of hibernate's "inverse" keyword which is surely the worst choice of word for a simple concept.

Essentially "inverse" indicates which end of a relationship should be ignored, so when persisting a parent who has a collection of children, should you ask the parent for its list of children, or ask the children who the parents are?

Why does this matter? Well if you are stupid then you might do something like:


  Parent parentA = new Parent();
  Parent parentB = new Parent();
  Child child = new Child();
  parentA.getChildren().add(child);
  child.setParent(parentB);

how should hibernate persist this situation? For unidirectional one–to–many it is trivial; only one end of the relationship is modelled (there is only parent.addChild(), not child.getParent()), but when it is bidirectional (parent.getChild and child.getChildren) you need to indicate whether the one–to–many is inverse or not.

What does it mean to be inverse? It informs hibernate to ignore that end of the relationship. If the one–to–many was marked as inverse, hibernate would create a child–>parentB relationship (child.getParent). If the one–to–many was marked as non–inverse then a child–>parentA relationship would be created.

Or at least that is what you would think :) First moan is why use the word "inverse"? By setting it to true you are effectively telling hibernate to ignore it. Setting something to true to exclude something is reverse logic and is incredibly unintuitive. Maybe "owner" would be a better term?

The second moan is that hibernate isn't consistent. If the parent–>child is not inverse (i.e. parent.getChildren will be used to define the relationship) then you would expect calling child.setParent to have no effect. This is not the case. Calling child.setParent does create the relationship. If you end up with the idiotic case I proposed earlier, than hibernate will give precendece to the parent.getChildren (as it should), but to be consistent child.setParent(parentA) should not create the relationship.

It is such a simple concept, but so many people seem to struggle with it. I really don't think hibernates terminology is very helpful.

Simply put; if you set inverse=true on a one–to–many then child.getParent will be called, if inverse=false then parent.getChildren will be called.

Of course, good developers will not run into this problem because they will all ensure that parents have appropriate accessors (parent.addChild, parent.removeChild etc.) :)

The invertedness (?) of the relationship does have quite an impact on performance and schema design; if the one–to–many is not inverse (default) then everytime you create a child, hibernate will execute 2 statements, one to create the child, one to update the child with the foreign key of the parent. Foreign key constraints/not null constraints? hah, who needs those anyway :) Hibernate will also instantiate the collection everytime you add a child….not sure why. Setting inverse to true will fix both of these annoyances/downright ridiculous design decisions.

One last note; cascade and inverse are completely orthogonal. Cascade simply tells hibernate how to reach objects. If I set inverse=true on a one–to–many then hibernate will not use parent.getChildren to define the relationship. If I set cascade to save, or all then hibernate will call parent.getChildren to see if there are any new children. Setting inverse=true and calling child.setParent will have absolutely no effect because hibernate won't know about your new child :)

So summary:

– always provide parent.addChild which updates both ends of the relationship

– always set inverse to true on bidirectional one–to–many

– make sure you understand the difference between cascades and inverse ;)

P.S. Version 3.1.2 was used.

Code used for inverse relationship


<?xml version="1.0"?>
<!DOCTYPE hibernate–mapping PUBLIC
    "–//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate–mapping–3.0.dtd">

<hibernate–mapping>
  <class name="uk.ac.warwick.Child" table="child">
    <id name="id"
        column="id"
        type="java.lang.String"
        unsaved–value="null">
        <generator class="org.hibernate.id.UUIDHexGenerator"/>
    </id>
    <many–to–one name="parent"
                 class="uk.ac.warwick.Parent"
                 column="parent_id"/>
  </class>
</hibernate–mapping>

<?xml version="1.0"?>
<!DOCTYPE hibernate–mapping PUBLIC
    "–//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate–mapping–3.0.dtd">

<hibernate–mapping>
  <class name="uk.ac.warwick.Parent" table="parent">
    <id name="id"
        column="id"
        type="java.lang.String"
        unsaved–value="null">
        <generator class="org.hibernate.id.UUIDHexGenerator"/>
    </id>
    
    <bag name="children" inverse="true" cascade="save–update">
      <key column="parent_id"/>
      <one–to–many class="uk.ac.warwick.Child"/>
    </bag>
  </class>
</hibernate–mapping>

package uk.ac.warwick.inverse;

import junit.framework.TestCase;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import uk.ac.warwick.Child;
import uk.ac.warwick.Parent;

public final class TestRelationships extends TestCase {
    public void testChildrenOfParentAreIgnored() {
        Configuration config = new Configuration();
        config.addResource("uk/ac/warwick/inverse/Child.hbm.xml");
        config.addResource("uk/ac/warwick/inverse/Parent.hbm.xml");
        
        config.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
        config.setProperty("hibernate.connection.driver_class", "org.hsqldb.jdbcDriver");
        config.setProperty("hibernate.hbm2ddl.auto", "create–drop");
        config.setProperty("hibernate.show_sql", "true");
        config.setProperty("hibernate.connection.url", "jdbc:hsqldb:.");
        config.setProperty("hibernate.connection.username", "sa");
        
        SessionFactory factory = config.buildSessionFactory();
        Session session = factory.openSession();
        
        Parent parentA = new Parent();
        session.save(parentA);
        session.flush();
        
        /**
         * Because parent–>child is inverse, adding the child to the parent
         * will not associate it with the parent.
         * 
         * Cascade will save it though.
         */
        Child childA = new Child();
        parentA.getChildren().add(childA);
        session.save(parentA);
        session.flush();

        session.evict(childA);
        assertEquals("cascade works so child should be saved",
                     1,
                     session.createCriteria(Child.class).list().size());

        childA = (Child) session.load(Child.class, childA.getId());
        assertNull("childAs parent should not be set!",
                   childA.getParent());

        session.evict(parentA);
        parentA = (Parent) session.load(Parent.class, parentA.getId());
        assertTrue("but it shouldnt update the children of parentA",
                   parentA.getChildren().isEmpty());
        
        childA.setParent(parentA);
        session.save(childA);
        session.flush();
        
        /**
         * Because child–>parent is not inverse, setting the parent
         * will create the association 
         */
        childA.setParent(parentA);
        session.save(childA);
        session.flush();

        session.evict(childA);
        childA = (Child) session.load(Child.class, childA.getId());
        assertEquals("childAs parent should now be set!",
                   parentA,
                   childA.getParent());

        session.evict(parentA);
        parentA = (Parent) session.load(Parent.class, parentA.getId());
        assertFalse("and parentA should have children",
                   parentA.getChildren().isEmpty());
     }
}

Code used for non inverse relationship


<?xml version="1.0"?>
<!DOCTYPE hibernate–mapping PUBLIC
    "–//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate–mapping–3.0.dtd">

<hibernate–mapping>
  <class name="uk.ac.warwick.Child" table="child">
    <id name="id"
        column="id"
        type="java.lang.String"
        unsaved–value="null">
        <generator class="org.hibernate.id.UUIDHexGenerator"/>
    </id>
    <many–to–one name="parent"
                 class="uk.ac.warwick.Parent"
                 column="parent_id"/>
  </class>
</hibernate–mapping>

<?xml version="1.0"?>
<!DOCTYPE hibernate–mapping PUBLIC
    "–//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate–mapping–3.0.dtd">

<hibernate–mapping>
  <class name="uk.ac.warwick.Parent" table="parent">
    <id name="id"
        column="id"
        type="java.lang.String"
        unsaved–value="null">
        <generator class="org.hibernate.id.UUIDHexGenerator"/>
    </id>
    
    <bag name="children" inverse="false" cascade="save–update">
      <key column="parent_id"/>
      <one–to–many class="uk.ac.warwick.Child"/>
    </bag>
  </class>
</hibernate–mapping>

package uk.ac.warwick.non_inverse;

import junit.framework.TestCase;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import uk.ac.warwick.Child;
import uk.ac.warwick.Parent;

public final class TestRelationships extends TestCase {
    public void testParentOfChildrenAreIgnored() {
        Configuration config = new Configuration();
        config.addResource("uk/ac/warwick/non_inverse/Child.hbm.xml");
        config.addResource("uk/ac/warwick/non_inverse/Parent.hbm.xml");
        
        config.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
        config.setProperty("hibernate.connection.driver_class", "org.hsqldb.jdbcDriver");
        config.setProperty("hibernate.hbm2ddl.auto", "create–drop");
        config.setProperty("hibernate.show_sql", "true");
        config.setProperty("hibernate.connection.url", "jdbc:hsqldb:.");
        config.setProperty("hibernate.connection.username", "sa");
        
        SessionFactory factory = config.buildSessionFactory();
        Session session = factory.openSession();
        
        Parent parentA = new Parent();
        session.save(parentA);
        session.flush();

        /**
         * Even though child–>parent is inverse, setting the parent
         * will associate it with the parent.
         */
        Child childA = new Child();
        childA.setParent(parentA);        
        session.save(childA);
        session.flush();

        session.evict(childA);
        assertEquals("cascade works so child should be saved",
                     1,
                     session.createCriteria(Child.class).list().size());

        childA = (Child) session.load(Child.class, childA.getId());
        assertNotNull("childAs parent will be set!",
                     childA.getParent());

        session.evict(parentA);
        parentA = (Parent) session.load(Parent.class, parentA.getId());
        assertFalse("and it will update the children of parentA",
                   parentA.getChildren().isEmpty());
     }
}

April 12, 2006

Yeah! Hibernate doesn't require cglib

Writing about web page http://hibernate.org/250.html#A37

There has been a long running problem with the combination of hibernate, CGLIB and an application server resulting in memory leaks. There seems to be some debate about whether it is purely a fault of CGLIB or not, and I don't know enough (or care) to make a guess. I do know that CGLIB is a fantastic and extremely powerful library :)

The problem manifests itself in only being able to deploy a handful of times on before running out of heap space.

Anyways, it appears Hibernate 3.2 is no longer dependant upon CGLIB. Yeah :)

Given that we do not use CGLIB in spring (we use JDK proxies) this might just mean we can deploy more than 5 times :) Very useful when developing ;)


February 21, 2006

hibernate executeUpdate + stale data

background

Whenever I develop applications I tend to do the simplest thing first and only "optimise" if needs be. So I had some logic which had to apply a Lock to a page and (optionally) all it's descendants.

So, the "lock application" logic should be seperated from the list of pages it is working on, so I introduced a LockAction which took a strategy for retrieving the pages that needed to be locked.

Fine, it all worked, but in terms of SQL it was extremely inefficient, executing one update for every page.

optimisation

Given that all the different strategies for selecting the pages to be locked could be expressed in SQL, an obvious optimisation is to execute a single SQL statement to update a number of pages, and then another SQL select to retrieve all the pages I had locked.

Fair enough, easy to do:


  Date dateLockExpires = new Date(System.currentTimeMillis() – lockThresholdInMs);
  String hql = "update " + AbstractPage.class.getName() + " p " +
                        " set p.lock.lockedBy=:user, p.lock.lockedSince=:lockDate" +
                        " where (" +
                        " p.lock.lockedBy is null" +
                        " or p.lock.lockedSince is null" +
                        " or p.lock.lockedBy = :user" +
                        " or p.lock.lockedSince <= :dateLockExpires" +
                        ") and ";
  hql += "p.url = :url";   // this is the strategy to select the set of pages
  Query query = session.createQuery(hql);
  query.setString("user", user.getUserId());
  query.setString("url", page.getUrl());
  query.setDate("dateLockExpires", dateLockExpires);
  query.setDate("lockDate", new Date());
  int numberUpdated = query.executeUpdate();

  String hqlForAllPages = "from " + AbstractPage.class.getName() + " p";
  hqlForAllPages += " where ";
  hqlForAllPages += "p.url ='" + page.getUrl() + "'";  // strategy to select the pages

  List lockedPages = new ArrayList();
  List unlockedPages = new ArrayList();
  List allPages = session.createQuery(hqlForAllPages).list();
  for (Page p: allPages) {
    Lock lock = p.getLock();
    if (lock == null || lock.getLockedBy() != user.getUserId()) {
      unlockedPages.add(p);
    } else {
      lockedPages.add(p);
    }
  }
  return new LockReportImpl(lockedPages, unlockedPages);

(ignore the horrible use of hql for the second select)
So basically we execute an update and then execute a select.

Simple? Yes. Works? No :( Unfortunately, even though the SQL statements are being sent to the database, and this all happens within the same transaction, the second select retrieves stale data, as if the previous executeUpdate() had never happened :(

One possible reason why is because we are using a version column, and the executeUpdate is not updating it. Modifying the update to increment the version column also does nothing :(

The only thing that works is to clear the entire session (session.clear()), but why should we have to do this? I am not asking Hibernate to execute raw SQL, it is executing hql, so why doesn't it do something sensible? I cannot expect it to know which rows have been updated, but I could (surely) expect it to invalidate all instances of that page from the first level cache?

Anyways, session.clear() works. Not sure what the impact will be on objects already loaded by that session…....


November 30, 2005

Some more hibernate fun :)

So I was debugging a problem with some code and I discovered this little gem:

Assume you have the following hierarchy:

– ClassInterface (use as proxy in mapping files)

– AbstractClass implements ClassInterface (used as the persistent class)

– ClassImpl extends AbstractClass (mapped as subclass of AbstractClass)

Load an instance of ClassImpl ala "ClassImpl ci = (ClassImpl)session.load(AbstractClass.class, someId)"

Now if you are going to compare two instances (using hashCode/equals) then guess what; if you implement that logic in ClassImpl it won't work. You must implement that logic in the abstract class that you have specified in the mapping file.

This is all to do with the joy that is cglib; even though the instance you are loading will eventually be a ClassImpl (based on the discriminator) CGLIB claims not to know that until it is fully initialised :(

OK, what happens if you try and load an instance of ClassImpl? Same thing. The equals method AbstractClass is used.

I have also run into situations with hibernate where code works if you step through, but not if you run it without debugging. I can only think that stepping through is maybe causing toString to initialise the object properly, hence subclass logic is utilised, but I cannot reproduce it.

Lame. Understandable, but lame.

To be fair; hibernate is great. It rocks, but it is not transparent* :)


March 2023

Mo Tu We Th Fr Sa Su
Feb |  Today  |
      1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31      

Search this blog

Tags

Galleries

Most recent comments

  • Interesting… While I'm not completely convinced in such microbenchmarks, I'm pretty sure that 1ms … by Alexander Snaps on this entry
  • Hello. I bought the book yesterday. I was trying to find the source code for chapter 11 and chapter … by Suleman on this entry
  • http://woosight.net/account/login?username=demo by live mashup demo on this entry
  • Thanks mate ….. This blog was really helpful. by Maaz Hurzuk on this entry
  • Ty. Not directly helpful for my problem, but pointed me in the right direction. You will also get th… by Mike E. on this entry

Blog archive

Loading…
RSS2.0 Atom
Not signed in
Sign in

Powered by BlogBuilder
© MMXXIII