views:

677

answers:

2

I have a JPA project connected to a MySQL database where my entity object is mapped to a table with a constraint on 2 columns. That is:

@Entity
@Table(name = "my_entity")
class MyEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "id")
    private Integer id;
    @Basic(optional = false)
    @Column(name = "myField1")
    private String myField1;
    @Basic(optional = false)
    @Column(name = "myField2")
    private int myField2;
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "myEntity")
    private Set<OtherEntity> otherEntitySet;
}

In the database, the my_entity table has a unique constraint on (myField1, myField2). The issue is that if I remove an existing entity with EntityManager.remove(entity) and then add a new one with EntityManager.persist(entity), the database throws an error about a duplicate row.

For example:

entityManager.getTransaction().begin();

MyEntity entity1 = new MyEntity();
entity1.setMyField1("Foo");
entity1.setMyField2(500);
entityManager.persist(entity1);

entityManager.getTransaction().commit();
entityManager.getTransaction().begin();

entityManager.remove(entity1);

MyEntity entity2 = new MyEntity();
entity2.setMyField1("Foo");
entity2.setMyField2(500); 
entityManager.persist(entity2);

entityManager.getTransaction().commit();

This gives me a MySQLIntegrityConstraintViolationException complaining about this being a duplicate entry. I imagine it's because it's trying to add the new entry before removing the old one. Is there any way to maintain that order? Or, is there a way to use JPA to prevent this situation? It's not exactly a common use case, but I'm concerned about a user who tries to delete an entity to get rid of all the associated data and start over, and then recreates the easier fields, then finding that the data was never removed.

The hashCode and equals implementations are as follows:

public int hashCode() {
    int hash = 0;
    hash += (getMyField1().hashCode() + getMyField2());
    return hash;
}

public boolean equals(Object object) {
    if (!(object instanceof MyEntity)) {
        return false;
    }
    MyEntity other = (MyEntity) other;
    return (getMyField2() == other.getMyField2()) &&
        (getMyField1().equals(other.getMyField1()));
}
A: 

I don't think there is any standard way to specify operational ordering in JPA, so there is no guarantee which order the statements will be executed. Ideally, the JPA implementation would be clever enough to detect this situation and perform the delete before the insert, but that is an area they frequently fail in. Alternatively, if your database supported deferred constraint checking (e.g., Oracle does but MySQL doesn't), the database would handle this by waiting until commit time to give the unique constraint violation exception.

So one solution is to just perform an extra commit after your call to remove(entity1). Another possibility would be, before you create entity2, to first check to see if it exists in the database, and if so, just use that one. Both these options can be somewhat cumbersome and not be suitable for all workflows. You might want to dig into the documentation for your current JPA implementation to see if they offer any extensions that might help.

mprudhom
This is definitely pointing me in the right direction. I'm now seeing references to JPA (I'm using EclipseLink, specifically) doing inserts before deletes by default. The application allows the user to save or cancel at any time, so I can't do a commit after the remove, but I'm looking into either changing that default or building in the reuse functionality.
Jon
@mprudhom: I suspect you wanted to say: an explicit `flush()` after the remove(entity1). No need to commit, and it is a bad idea (better put the whole method in one TRA)
pihentagy
A: 

I had the same problem involving a UC and the insertions/deletions occurring in the wrong order. I had to create a composite key for my entity that matched the columns of the UC. After that, the deletions and insertions occurred in the correct order. See this post, and the comment about adding an @EmbeddedId:

https://forum.hibernate.org/viewtopic.php?p=2382504

cold-gin
I think I get the general idea, but I'm a little lost on the specifics. Could you give a simple example? Say, what if I have a simple table of people for contact info. I have a unique constraint in the database for the person's name (id int, name varchar), and other tables link to that id.I'm editing my address book and go, "Oh, I'm not friends with Eddie anymore," so I delete that entry, cascading to any other associated data. But then after updating some other people (and not saving), I enter "Eddie" as an entry for someone else entirely. Under the JPA I designed, that throws an error.
Jon