Hibernate ConstraintViolationException cuando la entrada está en la base de datos o no

Estoy recibiendo un

org.hibernate.exception.ConstraintViolationException: Duplicate entry X for key 'PRIMARY'

Causado por

Caused by: com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Duplicate entry X for key 'PRIMARY'

I'm doing threaded work (with 4 threads), the logic is as follows: check whether something is in database (by primary key), if it then get it and modify the object otherwise create a new object. I think the problem is that when two more more threads use the same object and it is not in the database then they both want to create it and saving causes the violation. What puzzles me is that I've overridden the hashCode and equals methods and when I catch exception and query the database then the object in JVM and object in database are equal. Doesn't it mean that the exception shouldn't have happened?

What puzzles me even more is that sometimes when I query the database I get null atrás.

I tried to create a lock and then synchronize on it so that if one thread is doing work on object with the same id then the other on will for the first one to finish, but it didn't make it better.

I also tried to recreate the scenary with the following code, but I run just fine.

    public static void main(String[] args) throws Exception {
            ThreadPoolExecutor executionPool = new ThreadPoolExecutor(4, 4, 9999, TimeUnit.HOURS, new LinkedBlockingQueue<Runnable>());
            final Dao dao = new Dao();
            TestIdentity t1 = new TestIdentity(1, "aaa", false);
            dao.saveObject(t1);
            TestIdentity t2 = new TestIdentity(1, "aaa", true);
            dao.saveObject(t2);
            dao.saveObject(t1);
            dao.saveObject(t2);
            final Wrapper w1 = new Wrapper(t1);
            final Wrapper w2 = new Wrapper(t2); 
            Runnable runnable1 = new Runnable() {
                public void run() {
                    while(true) {
                        w1.work(dao);
                    }
                }
            };

            Runnable runnable2 = new Runnable() {
                public void run() {
                    while(true) {
                        w2.work(dao);
                    }
                }
            };

            executionPool.submit(runnable1);
            executionPool.submit(runnable2);
        }
private static class Wrapper {
        TestIdentity t;

        public Wrapper(TestIdentity t) {
            this.t = t;
        }
        public void work(Dao dao) {

            TestIdentity t2 = new TestIdentity(t.getId(), t.getField(), t.isBit());

            dao.saveObject(t2);
            t.setBit(true);
            dao.saveObject(t2);
            t.setBit(false);
        }
    }

Which uses class

@Entity
@Table(name="cycling_scraped.test_identity")
public class TestIdentity {
    int id;
    String field;
    boolean bit;
    public TestIdentity() {
        super();
    }
    public TestIdentity(int id, String field, boolean bit) {
        super();
        this.id = id;
        this.field = field;
        this.bit = bit;
    }
    @Id
    @Column(name="pk_id")
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    @Column(name="field")
    public String getField() {
        return field;
    }
    public void setField(String field) {
        this.field = field;
    }
    @Column(name="bit")
    public boolean isBit() {
        return bit;
    }
    public void setBit(boolean bit) {
        this.bit = bit;
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (bit ? 1231 : 1237);
        result = prime * result + ((field == null) ? 0 : field.hashCode());
        result = prime * result + id;
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        TestIdentity other = (TestIdentity) obj;
        if (bit != other.bit)
            return false;
        if (field == null) {
            if (other.field != null)
                return false;
        } else if (!field.equals(other.field))
            return false;
        if (id != other.id)
            return false;
        return true;
    }
    @Override
    public String toString() {
        return "TestIdentity [id=" + id + ", field=" + field + ", bit=" + bit
                + "]";
    }

}

Y mesa

CREATE TABLE test_database.test_identity (
    pk_id INT NOT NULL PRIMARY KEY,
    field VARCHAR(100),
    bit BIT
);

preguntado el 24 de agosto de 12 a las 10:08

I don't get your argument. Why equals/hashCode helps in avoiding the DB constraint issue? For your testing program, change it to something like 2 separate threads, opening two sessions, and both saving object with same identifier. You will hit the problem -

I'm opending two different session - my saveObject method starts with Session session = sessionFactory.openSession();. Seems that Hibernate didn't like that I created new Sessions, if I share the session then it is OK. About the equals/hashCode - I just figured that Hibernate finds objects to be equal then it updates (not tries to save new one). -

That's not the db exception you have means. Anyway good luck with threads and their synchronization. Maybe your computer have not the same configuration than the final production server - so you will not be able to replicate something tedious. -

Honestly I still don't get what's your question. Forget about Hibernate. Just think you are accessing DB manually by SQL. Now you have two threads, each doing the "if exists then update else insert" logic. Because of the race condition, you will hit the situation that 2 thread is inserting "same" records. The problem is as simple as that. How you are going to perform synchronization etc depends on your design. If whole DB is accessed within 1 process, you can simply use synchronize or concurrent utils. In case more than 1 process, DB lock is the normal way to go. -

Ok I think I know understand the problem. The test case doesn't run into exceptions, because there is relatively low probability that the race condition has an affect when there is only one possible chance for it to happen (when the record has been inserted once then the "else" block is going to be executed always and everything is OK). In my real work I had many such cases and some of them went to the "then" block simultaneously. Thanks again. -

1 Respuestas

I can't see any synchronized blocks/methods in your code.

Nothing is locked in order to have synchronized access.

The db exception is raised because it seems you can't add a record without specifying the primary key or doing the PK an auto-incremented field. You can add @GeneratedValue on ID after you have done that.

Respondido 24 ago 12, 14:08

This code was supposed to replicate the situation, but it didnt' (it ran fine, which is odd) - synchronized methods were in the original code (I didn't post it). The Id was constant in all the cases, I forgot to mention it, but dao.saveObject calls saveOrUpdate. - user1335014

Follow me: You are defining a field as a Primary Key (PK) inside your database. It means that each db record will have a distinct value for the field pk_id -> mapped to id. @Id @Column(name="pk_id") public int getId() { return id; } public void setId(int id) { this.id = id; } But you simply can't have the same value mapped to these field. It is normal that your code is breaking with these exception. - BendaThierry.com

If I understand correctly what you are saying then this is the kind of behaviour I want - if there is a record for pk_id then modify that record (not create a new one). - user1335014

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.