java.sql.SQLIntegrityConstraintViolationException when updating an entity
I am new to JPA and I am experiencing the following problem, probably due to a not sufficient understanding of cascade and associations? I have two entities which are in a bidirectional OneToMany - ManyToOne association:
@Entity
public class TestRun implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
public Long getId() {
return id;
}
public TestRun(){
}
public void setId(Long id) {
this.id = id;
}
@ManyToOne(optional=false, cascade={CascadeType.PERSIST, CascadeType.MERGE})
private Test performedTest;
public Test getPerformedTest() {
return performedTest;
}
public void setPerformedTest(Test performedTest) {
this.performedTest = performedTest;
}
.....
@Override
public boolean equals(Object object) {
if (!(object instanceof TestRun)) {
return false;
}
TestRun other = (TestRun) object开发者_JS百科;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
@Override
public String toString() {
return "TestRun[ id=" + id + " ]";
}
}
And:
@Entity
public class Test implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Basic(optional=false)
@Column(nullable=false)
private String name;
@OneToMany(mappedBy="performedTest", cascade={CascadeType.MERGE, CascadeType.PERSIST})
private Collection<TestRun> appearsOnTestRuns;
public Collection<TestRun> getAppearsOnTestRuns() {
return appearsOnTestRuns;
}
public void setAppearsOnTestRuns(Collection<TestRun> appearsOnTestRuns) {
this.appearsOnTestRuns = appearsOnTestRuns;
}
//id based equals and hashcode
...
}
The relationship means that I have a set of Test-s which are perfomed several times, each time being associated with a TestRun. To create a TestRun, I use the following method:
public void createTestRun(String name){
Test testPerformed = ejb.findByName(name); //Returns null iff forTest is not in the DB--> INSERT new Test
if (testPerformed == null){
testPerformed = EntityFactory.newTest(name);//I set the name for test
}
TestRun run = EntityFactory.newTestRun(testPerformed);
ejb.persist(run);
}
public class EntityFactory{
public static TestRun newTestRun(Test test){
TestRun run = new TestRun();
run.setPerformedTest(test);
return run;
}
public static Test newTest(String name){
Test test = new Test();
test.setName(name);
return test;
}
}
when createTestRun deals with a new Test (insert), the Test object is correctly persisted as well as the TestRun. But when the test already exists, JPA (EclipseLink in my case) tries anyway to execute an INSERT:
....
Caused by: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.2.0.v20110202-r8913): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: The statement was aborted because it would have caused a duplicate key value in a unique or primary key constraint or unique index identified by 'SQL110705102925930' defined on 'TEST'.
Error Code: -1
Call: INSERT INTO TEST (ID, NAME) VALUES (?, ?)
What does it happen behind the scenes? Does the problem have to do with the appearsOnTestRuns property of Test? Why doesn't JPA understand that Test already exists in the DB (after all, the id property is set during the find operation and equals() uses this value to decide whether 2 Tests are equal..) Thanks a lot for your help! :)
PS: Do you know a good tutorial / book where these concepts are extensively explained?
First of all, you should check that the whole createTestRun
method (which finds the test, creates it if necessary, and then creates the TestRun
instance and persists it) runs inside a single transaction.
And your newTestRun method should initialize both sides of the relationship, which is bidirectional. It's the responsibility of the developer to maintain the coherence of the entity graph. It should thus do:
public static TestRun newTestRun(Test test){
TestRun run = new TestRun();
run.setPerformedTest(test);
test.getAppearsOnTestRuns().add(run);
return run;
}
精彩评论