3

私は次のテストを持っています..

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/schedule-agents-config-context.xml"})
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional
public class H2TransactionNotWorkingTest extends SubmitAgentIntegratedTestBase {
    private static final int FIVE_SUBMISSIONS = 5;

    @Autowired
    private ApplicationSubmissionInfoDao submissionDao;

    private FakeApplicationSubmissionInfoRepository fakeRepo;

    @Before
    public void setUp() {
        fakeRepo = fakeRepoThatNeverFails(submissionDao, null);
        submitApplication(FIVE_SUBMISSIONS, fakeRepo);
    }

    @Test
    @Rollback(true)
    public void shouldSaveSubmissionInfoWhenFailureInDatabase() {
        assertThat(fakeRepo.retrieveAll(), hasSize(FIVE_SUBMISSIONS));

    }

    @Test
    @Rollback(true)
    public void shouldSaveSubmissionInfoWhenFailureInXmlService() {
        assertThat(fakeRepo.retrieveAll().size(), equalTo(FIVE_SUBMISSIONS));
    }
}

...そして次の構成...

   <jdbc:embedded-database id="dataSource" type="H2">
        <jdbc:script location="classpath:/db/h2-schema.sql" />
    </jdbc:embedded-database>

    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="transactionalSessionFactory"
          class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
                <prop key="hibernate.show_sql">false</prop>
                <prop key="hibernate.cache.use_second_level_cache">false</prop>
                <prop key="hibernate.cache.use_query_cache">false</prop>
            </props>
        </property>
        <property name="namingStrategy">
            <bean class="org.hibernate.cfg.ImprovedNamingStrategy"/>
        </property>
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration"/>
        <property name="packagesToScan" value="au.com.mycomp.life.snapp"/>
    </bean>

    <bean id="regionDependentProperties" class="org.springframework.core.io.ClassPathResource">
        <constructor-arg value="region-dependent-service-test.properties"/>
    </bean

>

また、SQLスクリプトで自動コミットをfalseに設定しました

SET AUTOCOMMIT FALSE;

コードに REQUIRES_NEW はありません。テストでロールバックが機能しないのはなぜですか?

乾杯プラビン

4

6 に答える 6

3

私は同じ問題に直面しましたが、Hibernateを使用していませんが、最終的に解決しました(実際には問題ではありません)。

それを機能させるための重要な項目は、適切なSpring単体テストクラス、つまりAbstractTransactionalJUnit4SpringContextTests. クラス名の「Transactional」に注意してください。したがって、動作するトランザクション ユニット テスト クラスのスケルトンは次のようになります。

@ContextConfiguration(locations = {"classpath:/com/.../testContext.xml"})
public class Test extends AbstractTransactionalJUnit4SpringContextTests {

    @Test
    @Transactional
    public void test() {
    }
}

関連する XML コンテキスト ファイルには、次の項目が含まれています。

<jdbc:embedded-database id="dataSource" type="H2" />

<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

このセットアップを使用すると、各テスト メソッドによる変更が適切にロールバックされます。

よろしく、 オラ

于 2014-05-20T11:46:59.417 に答える
2

同様の問題が発生しています。TestNG + Spring テスト サポートと Hibernate も使用しています。何が起こるかというと、Hibernate はトランザクションが開始される前に接続で自動コミットを無効にし、元の自動コミット設定を記憶します。

org.hibernate.engine.transaction.internal.jdbc#JdbcTransaction:

@Override
protected void doBegin() {
    try {
        if ( managedConnection != null ) {
            throw new TransactionException( "Already have an associated managed connection" );
        }
        managedConnection = transactionCoordinator().getJdbcCoordinator().getLogicalConnection().getConnection();
        wasInitiallyAutoCommit = managedConnection.getAutoCommit();
        LOG.debugv( "initial autocommit status: {0}", wasInitiallyAutoCommit );
        if ( wasInitiallyAutoCommit ) {
            LOG.debug( "disabling autocommit" );
            managedConnection.setAutoCommit( false );
        }
    }
    catch( SQLException e ) {
        throw new TransactionException( "JDBC begin transaction failed: ", e );
    }

    isDriver = transactionCoordinator().takeOwnership();
}

その後、トランザクションをロールバックした後、接続を解放します。そうすることで、休止状態は接続の元の自動コミット設定も復元します (同じ接続を渡される可能性のある他の人が元の設定で開始するように)。ただし、トランザクション中に自動コミットを設定すると、明示的なコミットがトリガーされます。JavaDocを参照してください。

以下のコードでは、これが起こっていることがわかります。ロールバックが発行され、最後に releaseManagedConnection で接続が解放されます。ここで自動コミットがリセットされ、コミットがトリガーされます。

org.hibernate.engine.transaction.internal.jdbc#JdbcTransaction:

    @Override
protected void doRollback() throws TransactionException {
    try {
        managedConnection.rollback();
        LOG.debug( "rolled JDBC Connection" );
    }
    catch( SQLException e ) {
        throw new TransactionException( "unable to rollback against JDBC connection", e );
    }
    finally {
        releaseManagedConnection();
    }
}


private void releaseManagedConnection() {
    try {
        if ( wasInitiallyAutoCommit ) {
            LOG.debug( "re-enabling autocommit" );
            managedConnection.setAutoCommit( true );
        }
        managedConnection = null;
    }
    catch ( Exception e ) {
        LOG.debug( "Could not toggle autocommit", e );
    }
}

ロールバック後にトランザクションが終了しているはずなので、これは通常は問題になりません。さらに、ロールバック後にコミットを発行した場合、ロールバックとコミットの間に変更がなければ、変更をコミットするべきではありません。コミットのjavadocから:

前回のコミット/ロールバック以降に行われたすべての変更を永続化し、この Connection オブジェクトによって現在保持されているデータベース ロックを解放します。このメソッドは、自動コミット モードが無効になっている場合にのみ使用してください。

この場合、コミット (自動コミットの再設定によって間接的にトリガーされる) は数ステートメント後に発生するため、ロールバックとコミットの間に変更はありませんでした。

回避策は、自動コミットを無効にすることです。これにより、自動コミットの復元が回避され(そもそも有効になっていないため)、コミットが発生しなくなります。これは、埋め込まれたデータソース Bean の ID を操作することで実行できます。ID は、データソースの識別だけでなく、データベース名にも使用されます。

<jdbc:embedded-database id="dataSource;AUTOCOMMIT=OFF" type="H2"/>

これにより、「dataSource」という名前のデータベースが作成されます。追加のパラメーターは H2 によって解釈されます。Spring は、"dataSource;AUTOCOMMIT=OFF"" という名前の Bean も作成します。注入のために Bean 名に依存している場合は、エイリアスを作成してクリーンにすることができます。

<alias name="dataSource;AUTOCOMMIT=OFF" alias="dataSource"/>

(組み込みデータベースの名前空間構成を操作するためのよりクリーンな方法はありません。Spring チームがこれをもう少し構成可能にしてくれればよかったのにと思います)

注:スクリプト (<jdbc:script location="...") を介して自動コミットを無効にすると、同じ接続がテストに再利用されるという保証がないため、機能しない可能性があります。

注:これは実際の修正ではなく、単なる回避策です。ロールバックが発生した後にデータがコミットされる原因となる問題がまだあります。

- - 編集 - -

検索したところ、本当の問題がわかりました。HibernateTransactionManager を (私が行っていたように) 使用していて、SessionFactory (Hibernate) 経由でデータベースを使用し、DataSource (プレーン JDBC) 経由で直接使用する場合は、SessionFactory と DataSource の両方を HibernateTransactionManager に渡す必要があります。Javadoc から:

注: プレーン JDBC コードの DataSource の接続を登録できるようにするには、このインスタンスが DataSource (setDataSource(javax.sql.DataSource)) を認識している必要があります。指定された >DataSource は、指定された SessionFactory によって使用されるものと明らかに一致する必要があります。

だから最終的に私はこれをしました:

<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
    <property name="dataSource" ref="dataSource" />
</bean>

そして、すべてがうまくいきました。

注:同じことが JpaTransactionManager にも当てはまります。EntityManager を使用し、DataSource を使用して未加工の JDBC アクセスを実行する場合は、EMF の隣に DataSource を個別に指定する必要があります。また、DataSourecUtils を使用して接続を取得することを忘れないでください (または、内部で DataSourceUtils を使用して接続を取得する JDBCTemplate を使用します)。

- - 編集 - -

上記は私の問題を解決しましたが、結局それは本当の原因ではありません:) 通常、Spring の LocalSessionFactoryBean を使用する場合、データソースを設定しても効果はありません

SessionFactory が LocalDataSourceConnectionProvider で構成された場合、つまり指定された「dataSource」を使用して Spring の LocalSessionFactoryBean によって構成された場合、DataSource は自動検出されます。DataSource を明示的に指定することはできますが、この場合は必要ありません。

私の場合、問題は、LocalSessionFactoryBean を拡張するキャッシング ファクトリ Bean を作成したことでした。これは、SessionFactory を複数回起動することを避けるために、テスト中にのみ使用します。前に述べたように、リソース キーが異なる場合、Spring テスト サポートは複数のアプリケーション コンテキストを起動します。このキャッシング メカニズムにより、オーバーヘッドが完全に軽減され、1 つの SF だけがロードされるようになります。

これは、起動された異なるアプリケーション コンテキストに対して同じ SessionFactory が返されることを意味します。また、SF に渡されるデータソースは、SF を起動した最初のコンテキストからのデータソースになります。これで問題ありませんが、DataSource 自体は、新しいアプリケーション コンテキストごとに新しい「オブジェクト」になります。これにより矛盾が生じます。

トランザクションは HibernateTransactionManager によって開始されます。トランザクションの同期に使用されるデータソースは、SessionFactory から取得されます (つまり、SessionFactory が最初にロードされたアプリケーションコンテキストからの DataSource インスタンスを持つキャッシュされた SessionFactory です)。テスト (または運用コード) で DataSource を直接使用する場合、その時点でアクティブなアプリ コンテキストに属するインスタンスを使用します。このインスタンスは、トランザクションの同期に使用される (SF から抽出された) インスタンスと一致しません。これにより、取得された接続がトランザクションに適切に参加しないため、問題が発生します。

transactionmanager でデータソースを明示的に設定することで、初期化後のデータソースが SF から取得されず、代わりに挿入されたデータソースが使用されるため、これは解決されたようです。私にとって適切な方法は、キャッシュメカニズムを調整し、SF がキャッシュから返されるたびに、キャッシュされた SF のデータソースを現在の appcontext のデータソースに置き換えることでした。

結論:私の投稿は無視して構いません :) HibernateTransactionManager または JtaTransactionManager を SF または EM 用の何らかの Spring サポート ファクトリ Bean と組み合わせて使用​​している限り、バニラ JDBC と Hibernate を混在させても問題ありません。後者の場合、DataSourceUtils (または JDBCTemplate を使用) を介して接続を取得することを忘れないでください。

于 2013-09-19T11:10:15.923 に答える
1

これを試して:

org.springframework.jdbc.datasource.DataSourceTransactionManager を削除します

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

それを org.springframework.orm.jpa.JpaTransactionManager に置き換えます

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

または、代わりに EntityManagerFactory を注入します...

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

次に、次のように EntityManagerFactory が必要です

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
        <bean id="jpaAdapter"
            class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="showSql" value="true" />
            <property name="generateDdl" value="true" />
        </bean>
    </property>
</bean>
于 2013-06-25T14:05:53.213 に答える
0

ありがとうライアン

テストコードはこのようなものです。

@Test
@Rollback(true)
public void shouldHave5ApplicationSubmissionInfo() {
    for (int i = 0; i < 5; i++) {
        hibernateTemplate.saveOrUpdate(new ApplicationSubmissionInfoBuilder()
                .with(NOT_PROCESSED)
                .build());
    }
    assertThat(repo.retrieveAll(), hasSize(5));
}

@Test
@Rollback(true)
public void shouldHave5ApplicationSubmissionInfoAgainButHas10() {
    for (int i = 0; i < 5; i++) {
        hibernateTemplate.saveOrUpdate(new ApplicationSubmissionInfoBuilder()
                .with(NOT_PROCESSED)
                .build());
    }
    assertThat(repo.retrieveAll(), hasSize(5));
}

jdbc:embedded-database を使用した組み込み DB 定義にはトランザクション サポートがないことがわかりました。Commons DBCP を使用してデータソースを定義し、デフォルトの自動コミットを false に設定すると、機能しました。

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" scope="singleton">
    <property name="driverClassName" value="org.h2.Driver" />
    <property name="url" value="jdbc:h2:~/snappDb"/>
    <property name="username" value="sa"/>
    <property name="password" value=""/>
    <property name="defaultAutoCommit" value="false" />
    <property name="connectionInitSqls" value=""/>
</bean>
于 2013-01-30T01:42:37.110 に答える
0

上記のどれも私にとってはうまくいきませんでした!

ただし、使用しているスタックは [ spring-test 4.2.6.RELEASEspring-core 4.2.6.RELEASEspring-data 1.10.1.RELEASE ]です。

問題は、[SpringJUnit4ClassRunner.class] で注釈が付けられた単体テスト クラスを使用すると、Spring ライブラリ デザイン チェックによる自動ロールバック機能が発生することです。***org.springframework.test.context.transaction.TransactionalTestExecutionListener*** >> ***isDefaultRollback***

この動作を克服するには、単体テスト クラスに注釈を付けます。 @Rollback(value = false)

于 2016-06-01T14:18:19.860 に答える
0

パズルのすべてのピースを表示していません。この時点で、ApplicationSubmissionInfoDao はトランザクション対応であり、それ自体でコミットしていると思いますが、すべてが適切に構成されていれば、テスト トランザクションと競合すると思います。より多くの回答を得るには、より完全な質問をしてください。最善の方法は、 SSCCEを投稿することです。

于 2013-01-29T03:58:53.347 に答える