Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate Natural ID duplicate issue

I'm new to Hibernate and DBs in general, so pardon the elementary question.

I am working with the DIS protocol and specifically the Open-DIS implementation of DIS. In DIS, each EntityStatePdu (containing the state of an entity in the simulation) has an EntityId object, a tuple of 3 integers. I want to use this object as a Natural Id, and maintain a standard surrogate ID as well. My problem is I cannot figure out how to ensure that the DB determines that a given EntityId already exists and use that EntityId's primary key as the foreign key in the EntityStatePdu.

In other words, say I have two EntityStatePdus, with EntityID (1, 2, 3); i.e. we have two updates from the same entity. I would want something like the following:

tables:

entity_id 
pk   site   app    entity
0    1      2      3


entity_state_pdu
pk  entity_id_fk  timestamp
0   0             1
1   0             2

Here are the simplified classes I am testing with:

@Entity
public class TestEntity {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;

    @NaturalId
    @ManyToOne(cascade = CascadeType.ALL)
    private TestId naturalId;

    public Long getId() {
        return id;
    }

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

    public TestId getNaturalId() {
        return naturalId;
    }

    public void setNaturalId(TestId naturalId) {
        this.naturalId = naturalId;
    }
}

and

@Entity
public class TestId {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;

    @NaturalId
    private int site;
    @NaturalId
    private int app;
    @NaturalId
    private int entity;

    public TestId(int site, int app, int entity) {
        this.site = site;
        this.app = app;
        this.entity = entity;
    }

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public int getSite() {
        return site;
    }
    public void setSite(int site) {
        this.site = site;
    }
    public int getApp() {
        return app;
    }
    public void setApp(int app) {
        this.app = app;
    }
    public int getEntity() {
        return entity;
    }
    public void setEntity(int entity) {
        this.entity = entity;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + app;
        result = prime * result + entity;
        result = prime * result + site;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        TestId other = (TestId) obj;
        if (app != other.app)
            return false;
        if (entity != other.entity)
            return false;
        if (site != other.site)
            return false;
        return true;
    }
}

I attempt to store two TestEntity objects into the DB with two separate TestId objects (which are equal in terms of having the same site, app, and entity) in the following way:

public static void main(String[] args) {

    SessionFactory factory = createFactory();

    Session session = factory.openSession();
    Transaction tx = session.beginTransaction();
    TestId id1 = new TestId(1,2,3);
    TestEntity entity1 = new TestEntity();
    entity1.setNaturalId(id1);
    session.save(entity1);
    tx.commit();
    session.close();

    Session session2 = factory.openSession();
    Transaction tx2 = session2.beginTransaction();
    TestId id2 = new TestId(1,2,3);
    TestEntity entity2 = new TestEntity();
    entity2.setNaturalId(id2);
    session2.save(entity2);
    tx2.commit();
    session2.close();
}

I get a long stack trace on the session2.save(entity2) line, with the salient line being

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '2-3-1' for key 'app'

I hope this is clear enough. Thank you.

like image 432
I82Much Avatar asked May 19 '11 12:05

I82Much


2 Answers

As Beatles said, when you tell Hibernate you have a Natural Key, it tells the DB to enforce uniqueness. The uniqueness in this case is being enforced by raising an exception of the type you saw (MySQLIntegrityConstraintViolationException).

The only way around this that I'm aware of is to first try and fetch an object that matches your business identity (TestId's equal) first, and then work either with that instance if it's found or with a new instance if it's not. Hibernate doesn't automatically do that for you.

like image 134
Paul Avatar answered Nov 01 '22 12:11

Paul


When you mark some fields as Natural ID it means that in that context the combination of these fields will be unique therefore for example if you have a class called person that has FirstName and LastName as Natural ID there could be only one entity having John as its FirstName and Smith as its LastName.(it is similar to a unique index)

In your code :

TestId id1 = new TestId(1,2,3);

and

TestId id2 = new TestId(1,2,3);

are referring to two different entities with the same Natural ID thus they can't both be persisted to the database.

like image 26
Beatles1692 Avatar answered Nov 01 '22 11:11

Beatles1692