25

この問題について調査するために数日が経過した後、何が起こっているのか明らかに意味がないため、この質問を送信することにしました。

ケース

私のコンピュータは、ローカルの Oracle Express データベースで構成されています。@Before メソッドで OJDBC 接続 (10 接続の静的 Hikari 接続プールを使用) を開き、ロールする親クラスを拡張するいくつかの JUnit テストを含む Java プロジェクトがあります (「ベスト プラクティス」ではないことはわかっています)。 @After に戻します。

public class BaseLocalRollbackableConnectorTest {
private static Logger logger = LoggerFactory.getLogger(BaseLocalRollbackableConnectorTest.class);
protected Connection connection;

@Before
public void setup() throws SQLException{
    logger.debug("Getting connection and setting autocommit to FALSE");
    connection = StaticConnectionPool.getPooledConnection();
}

@After
public void teardown() throws SQLException{ 
    logger.debug("Rollback connection");
    connection.rollback();
    logger.debug("Close connection");
    connection.close();
}

静的接続プール

public class StaticConnectionPool {

private static HikariDataSource ds;

private static final Logger log = LoggerFactory.getLogger(StaticConnectionPool.class);

public static Connection getPooledConnection() throws SQLException {

    if (ds == null) {
        log.debug("Initializing ConnectionPool");
        HikariConfig config = new HikariConfig();
        config.setMaximumPoolSize(10);
        config.setDataSourceClassName("oracle.jdbc.pool.OracleDataSource");
        config.addDataSourceProperty("url", "jdbc:oracle:thin:@localhost:1521:XE");
        config.addDataSourceProperty("user", "MyUser");
        config.addDataSourceProperty("password", "MyPsw");
        config.setAutoCommit(false);
        ds = new HikariDataSource(config);

    }
    return ds.getConnection();

}

}

このプロジェクトには、この接続 (ローカルホスト上) を使用して Sql2o を使用してクエリ (挿入/更新および選択) を実行する何百ものテスト (並列ではない) がありますが、トランザクションと接続のクローズは外部でのみ管理されます (上記のテストによって)。ACID テストを実行するために、データベースは完全に空です。

したがって、期待される結果は、DB に何かを挿入し、アサーションを作成してからロールバックすることです。このようにして、2 番目のテストは、分離レベルを維持するために前のテストで追加されたデータを検出しません。

問題 すべてのテストを一緒に (順番に) 実行すると、90% の確率で正しく動作します。10% の 1 つまたは 2 つのテストは、ランダムに失敗します。これは、以前のテストでデータベースにダーティ データがあるためです (たとえば、一意の重複)。ログを見ると、以前のテストのロールバックは適切に行われていました。実際、データベースを確認すると、空です) このテストを、パフォーマンスが高いが同じ JDK、同じ Oracle DB XE のサーバーで実行すると、この失敗率は 50% に増加します。

テスト間で接続が異なり、毎回ロールバックが呼び出されるため、これは非常に奇妙でわかりません。JDBC Isolation レベルは READ COMMITTED であるため、同じ接続を使用したとしても、同じ接続を使用しても問題は発生しません。だから私の質問は:なぜそれが起こるのですか?何か考えはありますか?私が知っているように、JDBC ロールバックは同期的ですか?それとも完全に完了していなくても、前に進むことができる場合がありますか?

これらは私の主な DB パラメータです: プロセス 100 セッション 172 トランザクション 189

4

7 に答える 7

4

私は2〜3年前に同じ問題に遭遇しました(これを解決するために多くの時間を費やしました). 問題は、@Before と @After が必ずしも連続しているとは限らないことです。[デバッグでプロセスを開始し、注釈付きのメソッドにいくつかのブレークポイントを配置することで、これを試すことができます。

編集:トニオが指摘したように、私は十分に明確ではありませんでした. @Before と @After の順序は、テストの前と後に実行されるという点で保証されています。私の場合、@Before と @After がめちゃくちゃになることがあるという問題がありました。

期待される:

@Before -> test1() -> @After -> @Before -> @test2() -> @After

しかし、時々私は次の順序を経験しました:

@Before -> test1() -> @Before -> @After -> @test2() -> @After

それがバグかどうかはわかりません。当時、私はそれを深く掘り下げて調べたところ、ある種の (プロセッサ?) スケジューリング関連の魔法のように思えました。その問題の解決策は、私たちの場合、単一のスレッドでテストを実行し、init プロセスと cleanup プロセスを手動で呼び出すことでした...次のようなものです:

public class BaseLocalRollbackableConnectorTest {
    private static Logger logger = LoggerFactory.getLogger(BaseLocalRollbackableConnectorTest.class);
    protected Connection connection;

    public void setup() throws SQLException{
        logger.debug("Getting connection and setting autocommit to FALSE");
        connection = StaticConnectionPool.getPooledConnection();
    }

    public void teardown() throws SQLException{ 
        logger.debug("Rollback connection");
        connection.rollback();
        logger.debug("Close connection");
        connection.close();
    }

    @Test
    public void test() throws Exception{
        try{
            setup();
            //test
        }catch(Exception e){ //making sure that the teardown will run even if the test is failing 
            teardown();
            throw e;
        }
        teardown();
    }
}

私はそれをテストしていませんが、はるかに洗練された解決策は、同じオブジェクトで @Before メソッドと @After メソッドを同期することです。試してみる機会があれば更新してください。:)

あなたの問題も解決することを願っています。

于 2016-07-12T12:08:44.523 に答える
1

他のすべての回答が指摘しているように、提供された情報で何が問題になるかを言うのは困難です。さらに、auditによって現在の問題を見つけることができたとしても、テストにデータ エラーがないというわけではありません。

しかし、別の方法があります。既に空のデータベース スキーマがあるため、それを SQL ファイルにエクスポートできます。次に、各テストの前に:

  1. スキーマをドロップする
  2. スキーマを再作成する
  3. サンプル データをフィードする (必要な場合)

テストを実行するたびに、データベースが元の状態であることを確認してください。これらはすべてスクリプトで実行できます。

注: Oracle Enterprise には、お客様の操作をサポートするためのフラッシュバック機能があります。また、 Hibernateなどを使用することができれば、他のインメモリ データベース ( HSQLDBなど) を使用して、テスト速度を上げ、データ セットの一貫性を維持することができます。

編集:信じがたいようですが、念のため: その前に () をconnection.rollback()呼び出さない場合にのみ有効です。commit

于 2016-07-11T02:59:11.010 に答える
1

これで問題が解決するかどうかはわかりませんが、次のことを試してみてください。

public class BaseLocalRollbackableConnectorTest {
  private static Logger logger = LoggerFactory.getLogger(BaseLocalRollbackableConnectorTest.class);
  protected Connection connection;
  private Savepoint savepoint;

  @Before
  public void setup() throws SQLException{
    logger.debug("Getting connection and setting autocommit to FALSE");
    connection = StaticConnectionPool.getPooledConnection();
    savepoint = connection.setSavepoint();
  }

  @After
  public void teardown() throws SQLException{ 
    logger.debug("Rollback connection");
    connection.rollback(savepoint);
    logger.debug("Close connection");
    connection.close();
    while (!connection.isClosed()) {
      try { Thread.sleep(500); } catch (InterruptedException ie) {}
    }
}

実際には 2 つの「修正」があります。クローズ後にループして、プールに戻る前に接続が確実にクローズされるようにします。次に、テストの前にセーブポイントを作成し、後で復元します。

于 2016-07-08T18:29:58.213 に答える