In the following a "run down" of the Testcase, which reproduces a problem we ran into with EntityManager.merge after an Database Exception.
We have a Entity A has a Jpa OneToMany Relationship to Entity B The OneToMany Relationship has CasadeType.ALL
In the database addtionally to the mapped Tables and Foreign Key, a Insert Trigger on Entity B, which always raises an Exception.
Here the Testcase szenario, see also the code below:
1. Create new Entity Manager
2. Begin Transaction
3. Read Entity A from Database
4. Create a B and Set a Text
5. Add it to A
6. Persist A
7. Catch exptected Exception because of Trigger on Insert of B, which always raises a exception
8 In the Catch Block: Close Entity Manager (just in Case)
9. Create a New Entity Manager
10. Merge A with Casade All Option
11. Remove B from A,
12. Assert Size of Bs of A to be 0
13. Persist A
14. Unexpected Exception : The Exception is the same as in 7. See attached Exception Stack:
Hibernate generates a Insert of a B
A Bug? Potentially this has tremendous impact on our application, since we have many "Business Rules" in the Database: the Application has to be able to Recover from these Exceptions.
Or - also very possible - i am missing something completely .... the szenario, which i would welcome most ;-))
We are using Spring: 2.5.4 with Hibernate 3.2.6.ga with Jpa and Oracle 10g
My persistent classes:
Code:
@Entity
@Table(name = "A")
public class A {
@Id
@javax.persistence.GeneratedValue(strategy = javax.persistence.GenerationType.SEQUENCE, generator = "aIdSeq")
@javax.persistence.SequenceGenerator(name = "aIdSeq", sequenceName = "A_SEQ", allocationSize = 1)
@javax.persistence.Column(name = "AID", nullable = false)
private Long id;
@javax.persistence.OneToMany(mappedBy = "a", cascade=CascadeType.ALL)
private List<B> bs = new ArrayList<B>();
public List<B> getBs() {
return bs;
}
public void setBs(List<B> bs) {
this.bs = bs;
}
public B addB(final B b) {
getBs().add(b);
b.setA(this);
return b;
}
public B removeB(final B b) {
getBs().remove(b);
return b;
}
public Long getId() {
return id;
}
}
Code:
@Entity
@Table(name = "B")
public class B {
@SuppressWarnings("unused")
@Id
@javax.persistence.GeneratedValue(strategy = javax.persistence.GenerationType.SEQUENCE, generator = "bIdSeq")
@javax.persistence.SequenceGenerator(name = "bIdSeq", sequenceName = "B_SEQ", allocationSize = 1)
@javax.persistence.Column(name = "BID", nullable = false)
private Long id;
@javax.persistence.Column(name = "TEXT", nullable = false)
private String text;
@ManyToOne(fetch = javax.persistence.FetchType.LAZY, optional = false)
@JoinColumn(name = "AID",nullable = false)
private A a;
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Note : On Table B there is a Insert Trigger which always raises an Exception
And here my Testcase:
Code:
@Test
public void testInsertWithFlushAndExceptionAndRecovery() throws Exception {
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
A a = entityManager.find(A.class, 1L);
B b = new B();
b.setText("Some Test");
a.addB(b);
entityManager.persist(a);
try {
entityManager.flush();
}
catch (Exception e) {
// Exception is "ok" , because of Database Trigger on Insert of B
entityManager.close(); // Just in case
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
A mergedA = entityManager.merge(a);
mergedA.removeB(b);
Assert.assertTrue(mergedA.getBs().size() == 0);
entityManager.persist(mergedA);
try {
entityManager.flush();
// No Exception Expected
}
catch (Exception ex) {
Assert.fail("We get the same Exception with insert of b, although we remove b from a");
}
transaction.rollback();
entityManager.close();
}
}
Here the Exception, which is the same in both cases:
Code:
Hibernate: select a0_.AID as AID0_1_, bs1_.AID as AID3_, bs1_.BID as BID3_, bs1_.BID as BID1_0_, bs1_.AID as AID1_0_, bs1_.TEXT as TEXT1_0_ from A a0_ left outer join B bs1_ on a0_.AID=bs1_.AID where a0_.AID=?
Hibernate: select b0_.BID as BID1_0_, b0_.AID as AID1_0_, b0_.TEXT as TEXT1_0_ from B b0_ where b0_.BID=?
Hibernate: select B_SEQ.nextval from dual
Hibernate: insert into B (AID, TEXT, BID) values (?, ?, ?)
2009-11-09 13:27:57,425 WARN org.hibernate.util.JDBCExceptionReporter - SQL Error: 20499, SQLState: 72000
2009-11-09 13:27:57,425 ERROR org.hibernate.util.JDBCExceptionReporter - ORA-20499: GP-10074: Kontonummer ist nicht korrekt (Prüfziffer).
ORA-06512: at "AENV.EXCEPTION_PA", line 16
ORA-06512: at "VK.TRG_EXC", line 4
ORA-04088: error during execution of trigger 'VK.TRG_EXC'
2009-11-09 13:27:57,425 WARN org.hibernate.util.JDBCExceptionReporter - SQL Error: 20499, SQLState: 72000
2009-11-09 13:27:57,425 ERROR org.hibernate.util.JDBCExceptionReporter - ORA-20499: GP-10074: Kontonummer ist nicht korrekt (Prüfziffer).
ORA-06512: at "AENV.EXCEPTION_PA", line 16
ORA-06512: at "VK.TRG_EXC", line 4
ORA-04088: error during execution of trigger 'VK.TRG_EXC'
2009-11-09 13:27:57,425 ERROR org.hibernate.event.def.AbstractFlushingEventListener - Could not synchronize database state with session
I would'nt expect to get the second Exception upon the persist of A, since the first transaction has been rolled back and the instance of B is removed from A after the merge.
Any hints, input greatly appreciated
Christoph