2010-12-16 7 views
0

ich jetzt ein wenig verwirrt bin: -Sdoppelte ID mit JPA2 und Testen

Ich bin an einem Projekt arbeiten, das JPA2 verwendet, Frühling 3.0.5 Final 3.6.0 Ruhezustand. Wir haben den folgenden Code (nur relevante Klassen)

@Entity 
public class User extends AbstractEntity implements Serializable { 
    @Id 
    @Column(name = "ID", nullable = false, insertable = true, updatable = true, length = 36) 
    protected String id; 

    @NotNull 
    @Size(min = 1, max = 30) 
    @Column(name = "NAME", length = 30, nullable = false) 
    private String name; 

    protected User() { 
     id = java.util.UUID.randomUUID().toString(); 
    } 

    @Override 
    public boolean equals(Object object) { 
     if (!(object instanceof User)) { 
      return false; 
     } 
     User other = (User) object; 
     if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) { 
      return false; 
     } 
     return true; 
    } 


} 


@Repository("userDao") 
public class UserDaoImpl implements UserDao { 

    @PersistenceContext 
    private EntityManager em; 

    public void create(User user) throws PreexistingEntityException, Exception { 
     try { 
      em.persist(user); 

     } catch (EntityExistsException ex) { 
      logger.error("User " + user + " already exists.", ex); 
      throw new PreexistingEntityException("User " + user + " already exists.", ex); 
     } catch (Exception ex) { 
      logger.error("Exception occurred:", ex); 
      throw ex; 
     } 
    } 
} 



@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(locations = { "/testDaoContext.xml" }) 
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true) 
@Transactional 
public class UserDaoTest { 

    private UserDao userDao; 

    @Autowired 
    public void setUserDao(UserDao userDao) { 
     this.userDao = userDao; 
    } 

    @Test 
    public void testInsertUserExistingID() { 
     User user = User.valueOf("1"); 
     user.setFirstname("DUMMY"); 
     user.setName("CRASH"); 
     logger.debug(user); 
     try { 
      userDao.create(user); 
      sessionFactory.getCurrentSession().flush(); 
     } catch (PreexistingEntityException e) { 
      e.printStackTrace(); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
     logger.debug("id = " + user.getId()); 

     User retrieved = userDao.find(user.getId()); 
     Assert.assertEquals(user.getId(), retrieved.getId()); 
     Assert.assertEquals("DUMMY", retrieved.getFirstname()); 
     Assert.assertEquals("CRASH", retrieved.getName()); 
    } 

} 

Nun, wenn ich den Test ausführen (ich weiß, es ist nicht ein echter Unit-Test) mit Rollbacks auf false gesetzt, bekomme ich folgende Stacktrace:

org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [insert into PV_UMDB.USERS (CREATION_DT, CREATION_USR, MODIFICATION_USR, MODIFICATION_DT, VERSION, BIRTHDAY, EMAIL, FAX, FIRSTNAME, INTERNAL, MOBILE, NAME, PHONE, PICTURE, STAFF_NO, STAFF_NO_KBC, ID) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)]; constraint [PV_UMDB.USERS_PK]; nested exception is org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update 
    at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:637) 
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:102) 
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:471) 
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754) 
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723) 
    at org.springframework.test.context.transaction.TransactionalTestExecutionListener$TransactionContext.endTransaction(TransactionalTestExecutionListener.java:515) 
    at org.springframework.test.context.transaction.TransactionalTestExecutionListener.endTransaction(TransactionalTestExecutionListener.java:290) 
    at org.springframework.test.context.transaction.TransactionalTestExecutionListener.afterTestMethod(TransactionalTestExecutionListener.java:183) 
    at org.springframework.test.context.TestContextManager.afterTestMethod(TestContextManager.java:406) 
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:90) 
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72) 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240) 
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49) 
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193) 
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52) 
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191) 
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42) 
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184) 
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) 
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) 
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236) 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180) 
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:46) 
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) 
Caused by: org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update 
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96) 
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66) 
    at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275) 
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:268) 
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:184) 
    at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321) 
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51) 
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216) 
    at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383) 
    at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133) 
    at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:76) 
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:467) 
    ... 25 more 
Caused by: java.sql.BatchUpdateException: ORA-00001: unique constraint (PV_UMDB.USERS_PK) violated 

    at oracle.jdbc.driver.DatabaseError.throwBatchUpdateException(DatabaseError.java:343) 
    at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:10768) 
    at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70) 
    at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268) 
    ... 34 more 

Wenn ich Rollback verwende, dann wird der Test bestanden, was natürlich falsch ist.

Jetzt, gibt es eine gute Lösung?

Danke für Ihre Hilfe

BB Peter

+0

Was genau ist ein Problem, was ist ein erwartetes Verhalten? Was bedeutet 'User.valueOf()'? – axtavt

+0

@axtavt: Das Problem ist, dass die Ausnahme, von der ich denke, dass ich (PreexistingEntityException) bekommen sollte, von Spring wegen seines AOP gegessen wird und somit mein Test selbst nutzlos ist. Die Methode valueOf() ist eine Möglichkeit, ein Objekt mit einer bestimmten ID zurückzugeben, da die ID normalerweise eine UUID ist. –

+0

@Peter: Wie hast du 'SessionFactory' im Test bekommen? – axtavt

Antwort

1

Sie können nicht auf EntityExistsException verlassen von persist() geworfen.

Vom javadoc:

EntityExistsException - wenn das Unternehmen bereits vorhanden ist. (Wenn das Unternehmen bereits vorhanden ist, kann der EntityExistsException wenn sie geworfen werden, die bestehen bleiben Operation aufgerufen wird, oder die EntityExistsExceptionoder eine andere PersistenceException kann bei Flush oder begehen Zeit geworfen werden.)

In In Ihrem Fall erhalten Sie zum Zeitpunkt der Festschreibung eine weitere Ausnahme. Wenn Sie

ersetzen
sessionFactory.getCurrentSession().flush(); 

mit

em.flush(); 

können Sie eine PersistenceException am Spülzeit geworfen fangen (Ich bin nicht sicher, warum es nicht die gleiche Art und Weise mit SessionFactory funktioniert).

+0

+1 um die Ursache zu finden – Ralph

0

Der Test ist zu sehen, ob ein Benutzer mit einer bestehenden ID gespeichert ist, die PreexistingEntityException geworfen wird.

Das allgemeine Muster eine Ausnahme für die Überprüfung ist:

  • junit4: @Test (excpect = ExcpectedException.class) oder
  • JUnit3 oder wenn das einfache Muster funktioniert nicht:

psydocode für JUnit3

try { 
    invokeExceptionThrowingMethod(); 
    fail("ExceptionX expected"); 
} catch(ExcpectedException e) { 
    //expected - do nothing 
} 

Ich glaube fest, dass, wenn Sie Ihren Testfall klarer schreiben, als Sie den Fehler finden.

bearbeitet

In Ihrem Fall müssen Sie die zweite Variante, da der Test darf nicht eine Ausnahme von der ersten Benutzererstellung akzeptieren.

@Test 
public void testInsertUserExistingID()   
     //(See my comment to your question about throwing Exception) 
     throws Exception{ 
    User user = User.valueOf("1"); 
    user.setFirstname("DUMMY"); 
    user.setName("CRASH"); 

    try { 
     userDao.create(user); 
     sessionFactory.getCurrentSession().flush(); 
     fail("PreexistingEntityException expected"); 
    } catch (PreexistingEntityException e) { 
     //Thats what is execpected 
    } 
} 

Wie dem auch sei: axtavt ist richtig