2017-06-06 83 views
0

我開始一個簡單的Spring啓動項目+ JPA + Hibernate + Mysql。在開始編碼之前,我創建了一些非常簡單的測試,但不幸的是,behaiour不是預期的。這是我的測試類:春季啓動1.5測試不回滾事務

@RunWith(SpringRunner.class) 
@SpringBootTest 
@TestPropertySource(locations = "classpath:test.properties") 
@Transactional 
public class TenantTest { 

    @Autowired 
    private TenantRepository tenantRepository; 

    @Test 
    public void createTenant() { 
     Tenant tenant = new Tenant(); 
     tenant.setAddress("Street n.1"); 
     tenant.setAlias("tenant"); 
     tenant.setCity("Milan"); 
     tenant.setCountry("Italy"); 
     tenant.setEmail("[email protected]"); 
     tenant.setLandlinePhone("3902123456"); 
     tenant.setMobilePhone("39347123456"); 
     tenant.setName("Tenant 1"); 
     tenant.setType(TenantType.RESELLER); 

     Tenant savedObj = tenantRepository.save(tenant); 

     assertNotNull("Tenant null after save!", savedObj); 

    } 

    @Test 
    public void createTenantWithInvalidEmail() { 
     Tenant tenant = new Tenant(); 
     tenant.setAddress("Street n.1"); 
     tenant.setAlias("tenant"); 
     tenant.setCity("Milan"); 
     tenant.setCountry("Italy"); 
     tenant.setEmail("[email protected]"); 
     tenant.setLandlinePhone("3902123456"); 
     tenant.setMobilePhone("39347123456"); 
     tenant.setName("Tenant 1"); 
     tenant.setType(TenantType.RESELLER); 

     Tenant savedObj = tenantRepository.save(tenant); 

     assertNotNull("Tenant null after save!", savedObj); 

    } 

根據Spring文檔(https://docs.spring.io/spring/docs/current/spring-framework-reference/html/integration-testing.html#testing-tx),每個測試應該回滾。相反在我的例子中,這不會發生。事實上,我有這個錯誤的第二個測試:

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [UK_6xsyaq3xonk8dkjed1lmu31ve]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement 
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:278) 
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244) 
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:488) 
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59) 
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213) 
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) 
    at com.sun.proxy.$Proxy130.save(Unknown Source) 
    at com.test.server.TenantTest.createTenantWithInvalidEmail(TenantTest.java:59) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:498) 
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) 
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) 
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) 
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) 
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) 
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) 
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) 
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252) 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) 
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) 
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) 
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) 
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) 
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) 
    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:363) 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) 
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) 
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192) 
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement 
    at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59) 
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42) 
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111) 
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:97) 
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:208) 
    at org.hibernate.dialect.identity.GetGeneratedKeysDelegate.executeAndExtract(GetGeneratedKeysDelegate.java:57) 
    at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:42) 
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2909) 
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3480) 
    at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:81) 
    at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:626) 
    at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:280) 
    at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:261) 
    at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:306) 
    at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:318) 
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:275) 
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182) 
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:113) 
    at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67) 
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189) 
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132) 
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:58) 
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:780) 
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:765) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:498) 
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298) 
    at com.sun.proxy.$Proxy124.persist(Unknown Source) 
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:508) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:498) 
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:504) 
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:489) 
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:461) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:61) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) 
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282) 
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) 
    ... 39 more 
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry 'tenant' for key 'UK_6xsyaq3xonk8dkjed1lmu31ve' 
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) 
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) 
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) 
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423) 
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:425) 
    at com.mysql.jdbc.Util.getInstance(Util.java:408) 
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:935) 
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3973) 
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3909) 
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2527) 
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2680) 
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2501) 
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1858) 
    at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2079) 
    at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2013) 
    at com.mysql.jdbc.PreparedStatement.executeLargeUpdate(PreparedStatement.java:5104) 
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1998) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:498) 
    at org.apache.tomcat.jdbc.pool.StatementFacade$StatementProxy.invoke(StatementFacade.java:114) 
    at com.sun.proxy.$Proxy82.executeUpdate(Unknown Source) 
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:205) 
    ... 80 more 

這很明顯,因爲事務沒有回滾。我春春的還印調試日誌說它是回滾事務:

017-06-06 18:29:12.683 INFO 7556 --- [   main] o.s.t.c.transaction.TransactionContext : Began transaction (1) for test context [[email protected] testClass = TenantTest, testInstance = [email protected], testMethod = [email protected], testException = [null], mergedContextConfiguration = [[email protected] testClass = TenantTest, locations = '{}', classes = '{class com.test.server.TestApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{classpath:test.properties}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.sp[email protected]5025a98f, org.springfr[email protected]192b07fd, org.[email protected]0, org.springframework.boot[email protected]0, org.springframework.boot.test.autocon[email protected]520a3426], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]]]; transaction manager [[email protected]]; rollback [true] 
2017-06-06 18:29:12.753 INFO 7556 --- [   main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test context [[email protected] testClass = TenantTest, testInstance = [email protected], testMethod = [email protected], testException = [null], mergedContextConfiguration = [[email protected] testClass = TenantTest, locations = '{}', classes = '{class com.test.server.TestApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{classpath:test.properties}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.sp[email protected]5025a98f, org.springfr[email protected]192b07fd, org.[email protected]0, org.springframework.boot[email protected]0, org.springframework.boot.test.autocon[email protected]520a3426], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]]]. 
2017-06-06 18:29:12.756 INFO 7556 --- [   main] o.s.t.c.transaction.TransactionContext : Began transaction (1) for test context [[email protected] testClass = TenantTest, testInstance = [email protected], testMethod = [email protected], testException = [null], mergedContextConfiguration = [[email protected] testClass = TenantTest, locations = '{}', classes = '{class com.test.server.TestApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{classpath:test.properties}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.sp[email protected]5025a98f, org.springfr[email protected]192b07fd, org.[email protected]0, org.springframework.boot[email protected]0, org.springframework.boot.test.autocon[email protected]520a3426], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]]]; transaction manager [[email protected]]; rollback [true] 
2017-06-06 18:29:12.760 WARN 7556 --- [   main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1062, SQLState: 23000 
2017-06-06 18:29:12.760 ERROR 7556 --- [   main] o.h.engine.jdbc.spi.SqlExceptionHelper : Duplicate entry 'tenant' for key 'UK_6xsyaq3xonk8dkjed1lmu31ve' 
2017-06-06 18:29:12.764 INFO 7556 --- [   main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test context [[email protected] testClass = TenantTest, testInstance = [email protected], testMethod = [email protected], testException = [null], mergedContextConfiguration = [[email protected] testClass = TenantTest, locations = '{}', classes = '{class com.test.server.TestApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{classpath:test.properties}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.sp[email protected]5025a98f, org.springfr[email protected]192b07fd, org.[email protected]0, org.springframework.boot[email protected]0, org.springframework.boot.test.autocon[email protected]520a3426], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]]]. 
我的屬性配置的

相關部分:

spring.datasource.url=jdbc:mysql://localhost:3306/test 
spring.datasource.username=root 
spring.datasource.password=root 
spring.datasource.driver-class-name=com.mysql.jdbc.Driver 
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect 
spring.jpa.hibernate.ddl-auto: create-drop 
spring.jpa.hibernate.naming_strategy: org.hibernate.cfg.ImprovedNamingStrategy 
spring.jpa.database: mysql 
spring.jpa.show-sql: false 
spring.jpa.properties.hibernate.max_fetch_depth=3 
spring.jpa.properties.hibernate.jdbc.fetch_size=150 
spring.jpa.properties.hibernate.jdbc.batch_size=100 
spring.jpa.properties.hibernate.cache.use_second_level_cache=true 

我使用的是在一級類註釋@Transaction 。我沒有看到這個配置有什麼問題。你有什麼建議嗎?

+1

嘗試使用'MySQL5InnoDBDialect'。你也可以指定數據庫平臺或數據庫,但不能同時指定。 –

+0

@ M.Deinum我刪除了數據庫屬性,但不是那個問題。使用MySQL5InnoDBDialect它的作品!在我的情況下,我換成spring.jpa.database-platform = org.hibernate.dialect.MySQL5Dialect spring.jpa.hibernate.dialect.storage_engine = innodb,因爲我使用的是Hibernate 5.2,並且您建議的值已被棄用。請張貼您的答案,以便我可以確認。另外,你有一個想法,爲什麼用InnoDB方言工作,而沒有?謝謝 – drenda

+0

在你的測試課上添加'@ TransactionConfiguration'。看看:http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/test/context/transaction/TransactionConfiguration.html。 還提到它已被棄用,從Spring Framework 4.2開始,在類級別使用@Rollback或@Commit,在@Transactional中使用transactionManager限定符。 –

回答

0

my.cfg設置爲MyISAM)。

確實支持事務的存儲引擎是InnoDB

現在你基本上有2種解決方法。

  1. 升級MySQL到最低5.5.5(或更近)
  2. 使用正確的Hibernate方言

要指定您需要的spring.jpa.database-platform設置爲正確的類正確的方言。

spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 

設置此方言將強制執行存儲引擎InnoDB。

注意:您應該設置spring.jpa.database-platformspring.jpa.databse,但不能同時設置(因爲後者將覆蓋方言!)。

如果您使用的是Hibernate 5.2。8或以上,你可以省略spring.jpa.database-platform,只設置spring.jpa.database(或讓它自動檢測)並將hibernate.dialect.storage_engine屬性設置爲innodb。 (另請參閱this blog)。

spring.jpa.database=MYSQL 
spring.jpa.properties.hibernate.dialect.storage_engine=innodb 
0

提交事務和刷新/同步JPA持久性上下文是有區別的。刷新生存環境將生成的SQL寫入數據庫。這可以在由持久性提供者主要在事務提交時確定的任何時候發生。但是,當您使用在數據庫端a.k.a IDENTITY中生成的自動生成的標識符或執行查詢時,也可能發生這種情況。

因此,即使持久性上下文已被刷新,它也可能回滾交換。

即使是Spring日誌也表明測試事務正在回滾。與不產生異常的記錄嘗試並當在使用MySQL版本5.5.5 <則默認存儲引擎是MyISAM它不支持事務處理(或default-storage-engine財產本應回滾以及

+0

我明白了,這很清楚。不幸的是,在2個不會產生異常的測試中,我找到了數據庫中的條目(我更改了ddl-auto來創建)。所以似乎還有其他錯誤。 – drenda

+0

哪個PlatformTransactionManager爲您的應用程序上下文定義? –

+0

我正在使用Spring Boot和註釋。我沒有定義任何PratformTransactionManager。所以,我的猜測是使用默認的。 – drenda