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.
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.
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.
精彩评论