Hibernate Books

All times are UTC - 5 hours [ DST ]



Post new topic Reply to topic  [ 147 posts ]  Go to page Previous  1 ... 6, 7, 8, 9, 10
Author Message
 Post subject: Re: What Are You Trying to Accomplish?
PostPosted: Sun Jul 22, 2007 12:25 am 
Newbie

Joined: Sat Jul 21, 2007 11:11 pm
Posts: 1
gerrypower wrote:
I am definitely no expert here, but I stand aghast that no one has addressed this critical issue by summarizing the key real life issues, and the Hibernate issues and implications to consider for each approach. 109.html doesn't do it, nor does this thread, nor does the documentation.

So I ask the community at large, is there a Hibernate expert out there that can address this critical issue for us?


Gerry's right.

One looks for a fully general, safe solution to these things.

I'll describe how I see it. If that's wrong, hopefully it'll annoy someone smarter than me into correcting me.

The real problem, as others put it, is that the hibernate ID is the only real candidate for equals()/hashCode(), but most people generate them in the DB, so don't have it until they've saved, by which point it's usually too late. General code doesn't like distinguishing between created vs. loaded objects. Wanting to mix the two types in a collection, including one that can survive across Sessions, is quite an ordinary, even basic desire IMO.

It seems like the official advice is to fake out equals()/hashCode() with data that's highly unlikely to meet the contract of those methods. Some people are lucky and have "business key" fields that change less often than their collections live for. Most people aren't lucky. Or if they are today, who can guarantee what someone may change tomorrow? The classic example of a business key is "name" or a "username" or whatever, yet names and usernames change all the time (i.e. it's an editor and the name/username's not immutable), and that makes it a fundamentally wrong thing to build equals/hashcode on. Using more mutable properties at once makes this problem worse, not better. If you've got a property that really doesn't ever change, why did you bother making a separate ID column anyway?

Don't get me wrong. I can see why this "business key" approach would work for some people, sometimes. I would simply caution against it far more strongly than the hibernate documentation does. Let's call it what it is. It's usually going to be a risky shortcut. And when you lose at identifer roulette, you will not always get a pretty error message to tell you.

I'd have laid out the solutions differently:

1) Have your equals/hashCode methods work off of IDs, and use an ID generation strategy that allows you to know the ID when the object is created.

1a) UUID, or something more exotic. Everyone hates these. UUIDs are too big, and annoying if you share your tables with non-hibernate apps. They're also just plain uncommon, which makes them seem spooky to most people.

1b) Keep your sequences, and use an API, rather than new, to create your entities, have it hit the DB to get the identifier in advance (i.e. run the sequence). Nobody seems to like this either, because you pay a (by comparison to the normal case) staggering IO cost to instantiate an object.

See also things like: http://www.devx.com/Java/Article/30396

2) Try this dangerous fakery with "business keys." I hope you have some, and that you don't change them, nor does anyone else who comes along later doing things you didn't anticipate today.

3) Get scared away and try not to need equals()/hashCode() after all. Hibernate doesn't, according to Gavin. (Nice work, by the way.)

I'll say this, it seems clear this is not a Hibernate problem, except inasmuch as Hibernate makes your life so easy it can obscure certain immutable underlying facts about life with an RDBMS. Any system managing sometimes-new/sometimes-persistent objects will have to find an answer for this.


Top
 Profile  
 
 Post subject: Using annotations?
PostPosted: Sat Aug 02, 2008 12:28 pm 
Newbie

Joined: Sat Aug 02, 2008 12:09 pm
Posts: 12
As a user of JPA I'm used to define my domain class behavior with annotations and have been thinking about introducing a @BusinessKey annotation for the auto generation of eauals, hashCode and toString. This approach makes it easy to keep track of business keys and makes the addition of them a natural part of declaring JPA annotations.

A simple example implementation (can be improved).

Business key annotation:
Code:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BusinessKey {}


Base class:
Code:
import java.io.Serializable;
import java.lang.reflect.Method;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;

public abstract class Base implements Serializable {
   private static final long serialVersionUID = 2870479197588294145L;

   @Override
    public String toString() {
      ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);

      Method[] methods = this.getClass().getMethods();

      for (Method method : methods) {
         if (method.isAnnotationPresent(BusinessKey.class)) {
            try {
               builder.append(method.getName(), method.invoke(this, (Object[]) null));
            } catch (Exception e) {}
         }
      }

      return builder.toString();
   }

   @Override
    public int hashCode() {
      HashCodeBuilder builder = new HashCodeBuilder();

      Method[] methods = this.getClass().getMethods();

      for (Method method : methods) {
         if (method.isAnnotationPresent(BusinessKey.class)) {
            try {
               builder.append(method.invoke(this, (Object[]) null));
            } catch (Exception e) {}
         }
      }

      return builder.toHashCode();
   }

   @Override
    public boolean equals(Object obj) {
      if (this == obj) {
         return true;
      }
      
      if (obj == null || obj.getClass() != this.getClass()) {
         return false;
      }
      
      EqualsBuilder builder = new EqualsBuilder();

      Method[] methods = this.getClass().getMethods();

      for (Method method : methods) {
         if (method.isAnnotationPresent(BusinessKey.class)) {
            try {
               builder.append(method.invoke(this, (Object[]) null), method.invoke(obj, (Object[]) null));
            } catch (Exception e) {}
         }
      }

      return builder.isEquals();
   }
}


Test case:
Code:
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.builder.HashCodeBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class BaseTest {
   private Parent mother;
   private Parent father;
   private Child son;
   private Child daughter;
   private Pet pet;
   private String[] petAttributes = { "Tabby", "Male" };
   private List<Toy> toys;
   
   @Before
   public void setUp() throws Exception {
      pet = new Pet();
      pet.setId(1L);
      pet.setName("Moggie");
      pet.setHungry(true);
      pet.setAttributes(petAttributes);

      toys = new ArrayList<Toy>();
      toys.add(new Toy("Doll"));
      toys.add(new Toy("Lego"));
      
      son = new Child();
      son.setId(1L);
      son.setName("Kurt");
      son.setToys(toys);

      daughter = new Child();
      daughter.setId(2L);
      daughter.setPet(pet);
      daughter.setName("Lisa");

      mother = new Parent();
      mother.setId(1L);
      mother.setName("Anna");
      mother.getChildren().add(son);
      mother.getChildren().add(daughter);
      father = new Parent();
      father.setId(2L);
      father.setName("Sven");
      father.getChildren().add(son);
      father.getChildren().add(daughter);
   }

   @After
   public void tearDown() throws Exception {}

   private class Parent extends Base {
      private static final long serialVersionUID = -3099043614244781545L;

      private Long id;
      private String name;
      private List<Child> children;

      public Long getId() {
         return id;
      }

      public void setId(Long id) {
         this.id = id;
      }

      @BusinessKey
      public String getName() {
         return name;
      }

      public void setName(String name) {
         this.name = name;
      }

      public List<Child> getChildren() {
         if (children == null) {
            children = new ArrayList<Child>();
         }

         return children;
      }

      public void setChildren(List<Child> children) {
         this.children = children;
      }
   }

   private class Child extends Base {
      private static final long serialVersionUID = 5718858404340171451L;

      private Long id;
      private String name;
      private Pet pet;
      private List<Toy> toys;

      public Long getId() {
         return id;
      }

      public void setId(Long id) {
         this.id = id;
      }

      @BusinessKey
      public String getName() {
         return name;
      }

      public void setName(String name) {
         this.name = name;
      }

      @BusinessKey
      public Pet getPet() {
         return pet;
      }

      public void setPet(Pet pet) {
         this.pet = pet;
      }

      @BusinessKey
      public List<Toy> getToys() {
         return toys;
      }

      public void setToys(List<Toy> toys) {
         this.toys = toys;
      }
   }

   private class Pet extends Base {
      private static final long serialVersionUID = 3884487079340445906L;
      private Long id;
      private String name;
      private boolean hungry;
      private String[] attributes;

      public Long getId() {
         return id;
      }

      public void setId(Long id) {
         this.id = id;
      }

      @BusinessKey
      public String getName() {
         return name;
      }

      public void setName(String name) {
         this.name = name;
      }

      @BusinessKey
      public boolean isHungry() {
         return hungry;
      }

      public void setHungry(boolean hungry) {
         this.hungry = hungry;
      }

      @BusinessKey
      public String[] getAttributes() {
         return attributes;
      }

      public void setAttributes(String[] attributes) {
         this.attributes = attributes;
      }
   }

   private class Toy extends Base {
      private static final long serialVersionUID = 8567704246409333187L;
      String name;

      public Toy(String name) {
         this.name = name;
      }

      @BusinessKey
      public String getName() {
         return name;
      }

      public void setName(String name) {
         this.name = name;
      }
   }

   @Test
   public void testEquals() {
      assertFalse(mother.equals(null));
      assertFalse(father.equals(mother));
      assertFalse(mother.equals(father));
      assertTrue(mother.equals(mother));
      assertTrue(daughter.equals(daughter));
      assertTrue(pet.equals(pet));

      father.setName("Anna");
      assertTrue(mother.equals(father));

      father.getChildren().get(0).setName("Hans");
      assertTrue(mother.equals(father));
   }

   @Test
   public void testHashCode() {
      int motherHashCode = new HashCodeBuilder().append("Anna").toHashCode();
      int fatherHashCode = new HashCodeBuilder().append("Sven").toHashCode();
      int sonHashCode = new HashCodeBuilder().append("Kurt").append((Pet) null).append(toys).toHashCode();
      int daughterHashCode = new HashCodeBuilder().append("Lisa").append(pet).append((List<Toy>)null).toHashCode();
      int petHashCode = new HashCodeBuilder().append("Moggie").append(petAttributes).append(Boolean.TRUE).toHashCode();

      assertEquals(motherHashCode, mother.hashCode());
      assertEquals(fatherHashCode, father.hashCode());
      assertEquals(sonHashCode, son.hashCode());
      assertEquals(daughterHashCode, daughter.hashCode());
      assertEquals(petHashCode, pet.hashCode());
   }

   @Test
   public void testToString() {
      assertEquals("BaseTest.Parent[getName=Anna]", mother.toString());
      assertEquals("BaseTest.Parent[getName=Sven]", father.toString());
      assertEquals("BaseTest.Child[getName=Kurt,getPet=<null>,getToys=[BaseTest.Toy[getName=Doll], BaseTest.Toy[getName=Lego]]]", son.toString());
      assertEquals("BaseTest.Child[getName=Lisa,getPet=BaseTest.Pet[getName=Moggie,getAttributes={Tabby,Male},isHungry=true],getToys=<null>]", daughter.toString());
      assertEquals("BaseTest.Pet[getName=Moggie,getAttributes={Tabby,Male},isHungry=true]", pet.toString());
   }
}


Top
 Profile  
 
 Post subject: Improved version of @BusinessKey added to Wiki
PostPosted: Mon Aug 18, 2008 10:56 pm 
Newbie

Joined: Sat Aug 02, 2008 12:09 pm
Posts: 12
http://www.hibernate.org/109.html


Top
 Profile  
 
 Post subject: Re: Improved version of @BusinessKey added to Wiki
PostPosted: Wed Nov 04, 2009 5:20 pm 
Newbie

Joined: Mon Nov 02, 2009 1:42 pm
Posts: 1
goran wrote:
http://www.hibernate.org/109.html


As noted in comments from melquiades and crbaker on that page, the main text of the page does not include discussion of equals/hashCode implementation in light of Hibernate Proxies. I found the discussion at the following link to be useful:

http://blog.xebia.com/2008/03/08/advanc ... -pitfalls/


Top
 Profile  
 
 Post subject: Re: equals() / hashCode(): Is there *any* non-broken approach?
PostPosted: Thu Nov 26, 2009 5:19 am 
Newbie

Joined: Thu Nov 26, 2009 4:34 am
Posts: 1
I am new to Hibernate, and what I will propose is not quite a solution, but it may work. How about simply NOT allowing equal() and hashCode() be called on unsaved objects? How about, in equal() and hashCode(), simply throw out an runtime exception saying the object has not been saved if it's the case, and otherwise compare / hashCode the ID? this way we don't need business IDs - they are relative stable but not really unchangable, and they also has the problem that sometimes they are just not available - yet. We can just use database ids and avoid adding unsaved objects into any set (if we can make it). If we accidentally add unsaved object into set, we will know at the first instance getting an exception.

I got this idea but haven't tested it and not knowing if it's a good idea. Any comments?


Top
 Profile  
 
 Post subject: Re: equals() / hashCode(): Is there *any* non-broken approach?
PostPosted: Thu Nov 26, 2009 8:39 am 
Regular
Regular

Joined: Mon Aug 07, 2006 5:07 am
Posts: 56
But bingtimren,

Then you would get RuntimeExceptions each time you add an unsaved instance to let's say a Set or Map.
So that would indeed not be a solution either.

I did not follow the entire thread about equals and hashCode, but normally using some kind of business key (if applicable indeed) and building your methods with an commons-lang EqualsBuilder, HashCodeBuilder and ToStringBuilder would suffice for most cases.
I am trying out his approach now (but changing the code slightly because it doesn't pass my Checkstyle config):
https://www.hibernate.org/451.html

If you then work with a superclass with a general equals, hashCode and toString method which will be inherited in every subclass, you only have to apply the annotations.


Top
 Profile  
 
 Post subject: Re: equals() / hashCode(): Is there *any* non-broken approach?
PostPosted: Wed Jul 07, 2010 9:37 am 
Newbie

Joined: Wed Jul 07, 2010 9:23 am
Posts: 1
I was going for the Gerry Power technique for the equals method in my JPA entities. For comparing the entire object graph of data inside my entities (useful for dirty checks in a GUI) I made an isEqualData() method which uses a small open source library (jogs) which compares the object graph but can ignore fields such as property change support.

So far so good. My objects can be safely used inside maps and lists and I can also compare them for changes.

But wait until you try databinding (e.g. JFace) to a GUI!!!!!! Java's PropertyChangeSupport does not fire changes when the old and new property are equal. This is a real problem with master/detail databinding when you replace the master entity in the model by a newer version.

See also my post at the jface forum here http://www.eclipse.org/forums/index.php?t=msg&th=171456&start=0&.

You can solve it by including a version field in your entities and taking this into account in the equals method. But it MUST be updated every time you update the entity and must also be done with owned entities in a list in your entity. (Something which didn't work with our JPA implementation out of the box).
Furthermore this solution is still broken with respect to maps and lists.

I think I will simply go to the default equals behavior which is to compare by object reference (/VM identity). Data binding is too important for us.


Top
 Profile  
 
 Post subject: Re: equals() / hashCode(): Is there *any* non-broken approach?
PostPosted: Mon Sep 20, 2010 1:11 pm 
Newbie

Joined: Thu Jun 14, 2007 6:14 pm
Posts: 15
I thought I would mention an approach that I was considering using, and would appreciate any feedback. It seems that a UUID generated at the time of object creation, and also used as the hashcode/equals solves the main problem, but brings about all the problems associated with UUIDs, particularly join speed and ugly urls vs using a simple long id. What about using both?

For example, keep your long id as your primary key. Add a UUID to all your domain objects (base class preferrably) and specify it as a unique column. Generate it at the time of object creation, but let hibernate change it when loading from the db. And then let equals and hashcode rely entirely on the UUID, again in the base class.

I believe this solves both issues at the expense of adding another column to your tables. Whether that is an acceptable cost is a matter of judgement. In my opinion, I have to add an id anyway, so I'm not particularly bothered about adding a string as well. Of course, if you have objects that don't need this functionality or are too numerous to handle the memory cost, you could always ignore it and override hashcode/equals for them.

What do you think?


Top
 Profile  
 
 Post subject: Re: equals() / hashCode(): Is there *any* non-broken approach?
PostPosted: Wed Oct 06, 2010 8:05 pm 
Newbie

Joined: Wed Feb 10, 2010 6:58 pm
Posts: 1
Wow, this is the exact solution we have been discussing for our project to solve the equality/hashcode challenges.

Quote:
I thought I would mention an approach that I was considering using, and would appreciate any feedback. It seems that a UUID generated at the time of object creation, and also used as the hashcode/equals solves the main problem, but brings about all the problems associated with UUIDs, particularly join speed and ugly urls vs using a simple long id. What about using both?

For example, keep your long id as your primary key. Add a UUID to all your domain objects (base class preferrably) and specify it as a unique column. Generate it at the time of object creation, but let hibernate change it when loading from the db. And then let equals and hashcode rely entirely on the UUID, again in the base class.

I believe this solves both issues at the expense of adding another column to your tables. Whether that is an acceptable cost is a matter of judgement. In my opinion, I have to add an id anyway, so I'm not particularly bothered about adding a string as well. Of course, if you have objects that don't need this functionality or are too numerous to handle the memory cost, you could always ignore it and override hashcode/equals for them.


I'm curious to hear what the Hibernate team (or anyone else) would say as a critique.

Thanks for the feedback.


Top
 Profile  
 
 Post subject: Re: equals() / hashCode(): Is there *any* non-broken approach?
PostPosted: Wed Mar 06, 2013 5:13 pm 
Newbie

Joined: Wed Mar 06, 2013 4:58 pm
Posts: 1
I personally consider all those hacks broken. All is save if you are in an open EntityManager/Hibernate session (because Hibernate _and_ the JPA spec guarantees that there is only 1 single instance of an entity in the EntityManager), and all bets are off if you close the EntityManager/Hibernate Session. That's it, nothing to add.

If you store detached entities from different H-Sessions into a Set, who guarantees that no changes have been done to the entity in between the first and the second loading? What happens if you just put the second version over the first or the other way around?
Doing this would either loose data or break optimistic locking. In other words it could seriously harm your data consistency. Of course this only will happen under high load, but those bugs are the ugliest...

I also hardly had the need to store entities in a Collection (exception being inside other Entities, but then we are attached again or can compare the IDs (Map<id, entity>).


Top
 Profile  
 
 Post subject: Re: equals() / hashCode(): Is there *any* non-broken approach?
PostPosted: Tue Mar 26, 2013 8:27 am 
Newbie

Joined: Tue Mar 26, 2013 8:18 am
Posts: 1
intersting


Top
 Profile  
 
 Post subject: Re: equals() / hashCode(): Is there *any* non-broken approach?
PostPosted: Sat Dec 21, 2013 7:01 am 
Newbie

Joined: Sun Aug 30, 2009 1:15 pm
Posts: 1
I was getting similar kind of problem, not sure whether http://muhammadkhojaye.blogspot.com/2010/02/java-hashing.html helpful?


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 147 posts ]  Go to page Previous  1 ... 6, 7, 8, 9, 10

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.