1

(This is a simplification of the real problem)

Let's start with the following little class:

@Entity
class Test {
        Test(int id, String name) {
            this.id = id;
            this.name = name;
        }

        @Id
        private int id;

        @Column
        private String name;

        @Override
        public int hashCode() {
            return id;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Test) {
                return id == ((Test) obj).id;
            }
            return false;
        }
    }

If we execute the following, no exception occurs:

EntityManagerFactory factory = Persistence.createEntityManagerFactory("local_h2_persistence");
EntityManager theManager = factory.createEntityManager();
EntityTransaction t = theManager.getTransaction();

Test obj1  = new Test(1, "uno");

tA.begin();
AtheManager.persist(obj1);
AtheManager.persist(obj1); // <-- No exception
tA.commit();

I guess the second call is ignored, or maybe the object is saved to the DB again. The thing is there is no problem in saving the same entity twice. Now let's try the following:

EntityManagerFactory factory = Persistence.createEntityManagerFactory("local_h2_persistence");
EntityManager theManager = factory.createEntityManager();
EntityTransaction t = theManager.getTransaction();

Test obj1  = new Test(1, "uno");
Test obj1_ = new Test(1, "uno");

tA.begin();
AtheManager.persist(obj1);
AtheManager.persist(obj1_); // <-- javax.persistence.EntityExistsException: a different object with the same identifier value was already associated with the session
tA.commit();

What? How could it possibly be relevant that the object is in a different memory location? Somehow it is and the code throws an exception.

How can I make the second example work just like the first?

Cœur
  • 37,241
  • 25
  • 195
  • 267
José D.
  • 4,175
  • 7
  • 28
  • 47

3 Answers3

1

I am just rewriting what @jb-nizet wrote in the comments, which feels like the answer to me:

Hibernate doesn't use ==. It simply does what you're telling it to do. persist's contract is: associate this object with the session. If it's already associated to the session, it's a noop. If it isn't, it is associated to the session to be inserted in the database later. If what yo want to do is make sure the state of this object is copied to a persistent entity, and give me back that persistent entity, then you're looking for merge().

So the solution was to just use

AtheManager.merge(obj1);

instead of

AtheManager.persist(obj1);
José D.
  • 4,175
  • 7
  • 28
  • 47
0

In first case, you save the same object twice, which is allowed. But in second case, you save two different object to database, but both has the same primary key. It is database constraint violation.

jsosnowski
  • 1,560
  • 3
  • 26
  • 56
  • How are they 'different'? They represent the same entity, so it should not be a database constraint violation. – José D. Aug 11 '15 at 21:38
  • Only one instance of a given entity can be in the persistence context at a time. What you're trying to do makes no sense. Let's say it was allowed. What should Hibernate do if you set the name of the first object to 'foo' and the name of the second one to 'bar'? Should it write foo or bar to the database? – JB Nizet Aug 11 '15 at 21:41
  • The `obj1` and `obj1_` are different Java object - so Hibernate thinks that they should create two different row in database table - but it is impossible, because there can't be two row with equal primary key. – jsosnowski Aug 11 '15 at 21:41
  • @JBNizet obj1.equals(obj_1) == true, so there is no problem. If the names were different then obj1.equals(obj1_) == false. Why does Hibernate use reference equality instead of value equality? – José D. Aug 11 '15 at 21:48
  • @jsosnowski they are stored in different memory regions, but they are equal (obj1.equals(obj1) == true) and represents the same entity. What is the problem? – José D. Aug 11 '15 at 21:49
  • Because the contract of the persist() method clearly specifies that it associates the passed entity with the persistence context. The fact that they're equal is irrelevant: you still try having 2 objects representing the same database row. If you use merge(), it will copy the detached entity to the persistent one and return it. – JB Nizet Aug 11 '15 at 21:49
  • @José D. You have right: `ob1` and `obj1_` are stored in different memory regions (`obj1 == obj1_` is false - different reference) - and this is why the Hibernate try save both object in database - but both has @Id value set to 1. Please take any database manager tool and look at Test table. Then try manually put there two rows with values 1 in 'id' column. Then you should see database error. – jsosnowski Aug 11 '15 at 21:57
  • @jsosnowski So my question is: Is there anyway to tell Hibernate to use .equals criteria instead of == to decide when to try to save objects? – José D. Aug 11 '15 at 21:58
  • I'm not sure how to do that, but this might help you: http://stackoverflow.com/questions/1638723/equals-and-hashcode-in-hibernate/1638886#1638886 – jsosnowski Aug 11 '15 at 22:03
  • 1
    Hibernate doesn't use ==. It simply does what you're telling it to do. persist's contract is: associate this object with the session. If it's already associated to the session, it's a noop. If it isn't, it is associated to the session to be inserted in the database later. If what yo want to do is make sure the state of this object is copied to a persistent entity, and give me back that persistent entity, then you're looking for merge(). – JB Nizet Aug 11 '15 at 22:04
  • @JBNizet Thank you, that is what I was looking for, and the correct answer for my question. If you want, you can write a little answer stating that and I will mark it as accepted (If you dont care about the rep, I can response myself giving you credit, saving you the work) – José D. Aug 11 '15 at 22:09
0

In the first example you pass a reference to an object to save it and in the second call you pass exactly the same reference; they both point to the same object in memory.

However, in the second example you allocated two objects with two new calls which creates the objects at two different memory addresses; they are two different objects. The first reference points to some other memory address then the second object's reference. If you tried this in the second example it would return false: obj1 == obj1_

univise
  • 509
  • 2
  • 11
  • I already noted that in the question. The thing is, why does Hibernate use reference equality instead of value equiality, and how to make it use value equality? – José D. Aug 11 '15 at 21:50
  • My guess it uses a reference check because then you can guarantee that the values are equal. Remember that equals can be overwritten freely by client code; its implementation could be simply to always return true. Doing a memory location check is safer. – univise Aug 11 '15 at 21:53
  • That would be a problem in the equals implementation, so undefined behaviour would be fine in that context. Anyway, isn't there a way to accomplish what I want? – José D. Aug 11 '15 at 21:54
  • 1
    @JoséD. Such an 'error' would compromise the state of the entity manager, which is why its designers decided to make a memory address check. Try the merge method? I have not familiar with specifically this interface but from what I read from the documentation you should be using the merge method. – univise Aug 11 '15 at 22:02