なぜ同じコンパレータは、Webアプリケーションとして実行したときに対ユニットテストでは異なる動作していますか?

AEvans:

TL; DRは:問題は多分設定されたjavaのバージョンではなく、Java言語そのものに関しては、Tomcatを関連しているかのように多くの試行錯誤の後、それが表示されます。詳細については、下記「編集3」を参照してください。

私はしばらく今のJava 8のストリームおよびコンパレータを使用していると私は誰もが私の流れが悪いの見つけることができるかどうかを確認するために好奇心から求めているので、前にこの種の動作を見たことがありません。

私はストリームに私たちの時代遅れの収集処理を置き換えることによって、「Javaの-8-ifying」レガシープロジェクトに取り組んでいる(私は、なぜ私はこれをやっている頼まれた、と短い答えは、我々は基本的にプロジェクトを再度書いているということです、そこ収集ロジックの周りの汚いコードの多くは、あるので、「Javaの-8-ifying」Javaのバージョンを更新し、多くをクリーンアップするために提供している- 。。しかし、唯一私が最初のステップをやっているインクリメンタルそれを行うための時間の予算を持っていますコードと)読み、維持するために、より簡単に物事を作ります今のところ、我々はまだ古いデータ型を使用しているので、任意の「日付の言及は、java.util.Dateインスタンスの代わりに、新しいJava 8種類を扱っています。

これは(POJOである)ServiceRequest.javaの私のコンパレータです。

public static final Comparator<ServiceRequest> BY_ACTIVITY_DATE_DESC = Comparator.comparing(
        ServiceRequest::getActivityDate, Comparator.nullsLast(Comparator.reverseOrder()));

ユニットがテストしたときに、このコンパレータは期待通りに動作します。後activityDateとのServiceRequestsが結果リストの最初にあり、以前activityDate有するものがリストのさらに下で、ヌルactivityDate有するものが一番下にあります。参考のため、ここではユニットテストの完全なコピーは、次のとおりです。

@Test
public void testComparator_BY_ACTIVITY_DATE_DESC() {
    ServiceRequest olderRequest = new ServiceRequest();
    olderRequest.setActivityDate(DateUtil.yesterday());

    ServiceRequest newerRequest = new ServiceRequest();
    newerRequest.setActivityDate(DateUtil.tomorrow());

    ServiceRequest noActivityDateRequest = new ServiceRequest();

    List<ServiceRequest> sortedRequests = Arrays.asList(olderRequest, noActivityDateRequest, newerRequest).stream()
            .sorted(ServiceRequest.BY_ACTIVITY_DATE_DESC)
            .collect(Collectors.toList());

    assertEquals(sortedRequests.get(0), newerRequest);
    assertEquals(sortedRequests.get(1), olderRequest);
    assertEquals(sortedRequests.get(2), noActivityDateRequest);
}

注:DateUtilは、私たちのテストの目的のためにjava.util.Dateインスタンスを作成し、レガシーユーティリティです。

私が期待するように、このテストでは、常に、見事に渡します。しかし、私はマップに依頼者の識別子と選択することによって、そのユーザーのための唯一の最も最近の要求を、それらを開いたサービス要求のリストをアセンブルし、グループのコントローラを持っています。私は、指定されたストリームにこのロジックを変換しよう:

private Map<Long, ServiceRequestViewBean> ServiceRequestsByUser(List<ServiceRequest> serviceRequests) {
    return serviceRequests.stream()
            .sorted(ServiceRequest.BY_ACTIVITY_DATE_DESC)
            .collect(Collectors.toMap(
                    serviceRequest -> serviceRequest.getRequester().getId(),
                    serviceRequest -> new ServiceRequestViewBean(serviceRequest),
                    (firstServiceRequest, secondServiceRequest) -> firstServiceRequest)
            );
}

私のロジックは要求が最初に最も最近の要求によって選別した後、同じユーザによって行われた複数の要求が処理された時はいつでも、唯一の最も最近のものは、マップに置かれるということでした。

しかし、観察された行動は、OLDEST要求ではなく、マップに置かれていたということでした。注:私は以来、コントローラコードはJUnitテストを介して呼び出された場合、動作は以下のように期待されていることを確認しました。誤った動作は、Tomcat上で実行している間、コントローラ上のエンドポイントが呼び出されたときに示します。詳細については、「編集3」を参照してください。

私は見るためにいくつかののぞき見を追加したのServiceRequest IDをソートする前に、仕分け後、地図収集するためのマージ機能で(マージ機能が検出されたときに、この場合には同じになるではない要求者IDを、)。簡単にするために、私は、単一の要求者の4つの要求にデータを制限しました。

ServiceRequest IDの期待順:

ID      ACTIVITY DATE
365668  06-JUL-18 09:01:44
365649  05-JUL-18 15:41:40
365648  05-JUL-18 15:37:43
365647  05-JUL-18 15:31:47

私ののぞき見の出力:

Before Sorting: 365647
Before Sorting: 365648
Before Sorting: 365649
Before Sorting: 365668
After Sorting: 365647
After Sorting: 365648
First request: 365647, Second request: 365648
After Sorting: 365649
First request: 365647, Second request: 365649
After Sorting: 365668
First request: 365647, Second request: 365668

それはちょうどそれが彼らを覗いたようなマップに物事を追加することを決めた、私は後にソートPEEKでマップマージ出力の散在が面白いと思ったが、これ以上のステートフル・中間操作がなかったので、私は推測します。

PEEK出力は前とソート後に同じだったので、私は、ソートがコンパレータが何らかの理由(設計意図に反して)のための昇順にソートされたことを出会いオーダーに影響を及ぼさなかった、どちらかと結論し、どちらかの入力データベースからちょうど(私は確信して...可能だということではないんだが)、この順であることを起こっ、またはストリームがのぞき見のいずれかの前に並べ替え解決。好奇心から、私はそれがこのストリームの結果を変更するだろうかどうかを確認するために、データベースの呼び出しでソートをかけます。ストリームに入る入力の順序が保証されますように、私は活動日の降順でソートするには、データベース・コールに語りました。コンパレータは何とか反転した場合、それが戻って昇順に項目の順序を反転する必要があります。

しかし、DB-注文したストリームの出力はずっと私のコンパレータは、この流れに全く影響を持っていないことを信じてリードソートデータベースによって生成元の順序に真開催された第1回、順序だけ...のようでした。

これはなぜ私の質問はありますか?toMapコレクタは出会いの順番を無視していますか?もしそうなら、なぜこれは効果がないことがソートされたコール原因でしょうか?私は、ステートフル中間ステップとして、ソート(forEachOrderedがあるので、forEachのを除いて)出会いの順序を観察するために、後続のステップを余儀なくていると思いました。

私はtoMapのJavadocを見上げたとき、それは同時実行に関するメモを持っていました:

返されたコレクターは、同時ではありません。並列ストリームパイプラインのために、コンバイナ関数は、高価な操作であり得る他へのマップからキーをマージすることによって動作します。それは結果がtoConcurrentMapを使用して、出会いの順に地図にマージされていることを必要とされていない場合は(関数、関数、BinaryOperator)は、より良い並列パフォーマンスを提供することがあります。

これは、リード線、私は出会いの順序がtoMapコレクタによって保存されるべきであると信じます。私は非常に失われたと私は、この特定の行動を観察してる理由として混乱しています。私は私のマージ機能で日付の比較を行うことによって、この問題を解決することができます知っているが、私はなくtoMapコレクタと、ToListメソッドのコレクターで使用した場合、私のコンパレータが動作しているようです理由を理解しようとしています。

あなたの洞察力のために事前にありがとうございます!

編集1:私はそうのように、そのソリューションを実装するための多くは、問題を解決するためのLinkedHashMapを使用して提案しています:

return serviceRequests.stream()
            .sorted(ServiceRequest.BY_ACTIVITY_DATE_DESC)
            .collect(Collectors.toMap(
                    serviceRequest -> serviceRequest.getRequester().getId(),
                    serviceRequest -> new ServiceRequestViewBean(serviceRequest),
                    (serviceRequestA, serviceRequestB) -> serviceRequestA,
                    LinkedHashMap::new));

試験された場合でも、それは実際には1つ年上の代わりにコンパレータを強制することが望ま最新のものに解決しています。私はまだ混乱しています。注:私は、以来、TomcatでWebアプリケーションとして実行したときに誤った動作にのみ示すことを確認しました。このコードはJUnitテストを経由して呼び出されると、1つとしての機能が、それには期待します。詳細については、「編集3」を参照してください。

編集2:私は、ソリューションを実装する際興味深いことに、私は(マージ機能でソート)に働くだろう-thought-も機能していません。

 return serviceRequests.stream()
            .sorted(ServiceRequest.BY_ACTIVITY_DATE_DESC)
            .collect(Collectors.toMap(
                    serviceRequest -> serviceRequest.getRequester().getId(),
                    serviceRequest -> new ServiceRequestViewBean(serviceRequest),
                    (firstServiceRequest, secondServiceRequest) -> {
                        return Stream.of(firstServiceRequest, secondServiceRequest)
                                .peek(request -> System.out.println("- Before Sort -\n\tRequester ID: "
                                + request.getRequester().getId() + "\n\tRequest ID: " + request.getId()))
                                .sorted(ServiceRequest.BY_ACTIVITY_DATE_DESC)
                                .peek(request -> System.out.println("- After sort -\n\tRequester ID: "
                                + request.getRequester().getId() + "\n\tRequest ID: " + request.getId()))
                                .findFirst().get();
            }));

これは次の出力を生成します。

- Before Sort -
    Requester ID: 67200307
    Request ID: 365647
- Before Sort -
    Requester ID: 67200307
    Request ID: 365648
- After sort -
    Requester ID: 67200307
    Request ID: 365647
- Before Sort -
    Requester ID: 67200307
    Request ID: 365647
- Before Sort -
    Requester ID: 67200307
    Request ID: 365649
- After sort -
    Requester ID: 67200307
    Request ID: 365647
- Before Sort -
    Requester ID: 67200307
    Request ID: 365647
- Before Sort -
    Requester ID: 67200307
    Request ID: 365668
- After sort -
    Requester ID: 67200307
    Request ID: 365647

注:私は以来、TomcatでWebアプリケーションとして動作しているときに、この誤った出力のみが生成されていることを確認しました。コードはJUnitテストを経由して呼び出されると予想されるように、それが正しく機能します。詳細については、「編集3」を参照してください。

これは私のコンパレータは、本当に何もしていない、または積極的にそれはユニットテストでないことを逆の順序でソートされた、または多分FindFirstのはtoMapがやったのと同じことをやっていることを示していると思われます。しかし、この関数は、FindFirstのJavadocは、ソートされたような中間ステップを使用した場合関数は、FindFirst点が順に遭遇することを示唆しています。

編集3は:私がしたので、私は、最低限、完全、かつ検証可能なサンプルプロジェクトを作るように頼まれた。 https://github.com/zepuka/ecounter-order-map-collect

私は、(それぞれがレポでタグ付けされた)問題を試してみて、再現するカップル異なる戦術を試してみましたが、私は私のコントローラになってきた誤った動作を再現することができませんでした。私の最初の解決策、そして試してみましたので、私がしたすべての提案は、すべての希望、正しい動作を生産しています!では、なぜそのアプリケーションが実行されたとき、私は異なる動作を得るのですか?キックと笑いのために、私はユニットテストができるよう公共としての私のコントローラ上の方法を露出し、ユニットテストで実行中に私に迷惑を与えています正確に同じデータを使用する - それは、JUnitテストで正常に機能します。私のTomcatサーバ上で実行したときに、誤ってこのコードは、ユニットテストでは、通常のJava mainメソッドで正しく実行することができますが、何か別の存在でなければなりません。

Javaのバージョンは、私が使用してコンパイルと同じにかかわらず、私のサーバーを実行している:1.8.0_171-B11(オラクル)。最初のIで構築し、実行しているNetbeansの中から、私は念の干渉設定するいくつかの奇妙なNetBeansはありませんでしたように、コマンドラインビルドとTomcatの起動をしました。私はかかわらず、NetBeansで実行プロパティを見ると、それは、サーバーの設定と一緒のJava EEのバージョンなどのJava EE 7のWeb '(Apache Tomcatの8.5.29、実行中のJava 8である)、およびI'LLを使っていると言ってん私は「Java EEのバージョンは」すべてに約あるか見当がつかない認めます。

私の問題ではなく、Java言語に関連するよりも、Tomcatの関係のようだとして、この投稿にTomcatのタグを追加しました私はそう。この時点で、私の問題を解決する唯一の方法は、マップの構築に非ストリームのアプローチを使用しているようだが、私はまだ人々の考えは、私は問題を解決するために見ることができる構成のものにしているかを知るのが大好きです。

編集4:私は物事の古い方法を使用して問題を周りを取得しようとした、と私はストリームにコンパレータを使用しないようにする場合、物事は罰金ですが、私はどこかのプロセスでストリームにコンパレータを紹介するや否や、 Webアプリケーションが正しく動作するように失敗しました。私は、ストリームせずにリストを処理しようとした、とだけマップにマージする際、比較器を使用するために2つの要求にストリームを使用して、それは仕事をしません。私はちょうど代わりComparator.comparingの、昔ながらのJavaを使用して、インラインクラス定義との比較は、昔ながらの作りが、ストリームが失敗したことに使ってみました。私はストリームとコンパレータを避けていた場合にのみ、完全にそれが動作するようには思えません。

AEvans:

最後に、それの一番下になりました!

私が最初にどこにでもそれを必要と新しいコンパレータを適用することによって、問題を特定することができました。私は、これらのほとんどは私が期待するように振る舞っていることを観察することができた、とだけ特定のページに問題が浮上しました。

私の前にデバッグ出力はIDのみが含まれていますが、今回は私が便宜上活動日を含めて、私はその1つのJSPを打ったとき、彼らがnullでした!

、それは私が前にチェックしていた行マッパーを使用していませんでした...これは-問題の根は(ええ、それはだ混乱異なる内部メソッドを呼び出すために、いくつかの正規表現の構文解析を行いました)単一DAOメソッドでONE場合ということです特定のヘルパーmehodが含まれる不正なインライン「行マッパー」をことは、原始的クエリの結果を取得し、オブジェクトにそれらを置くためにループとインデックスを使用し、それが活動の日付の列がありませんでした。彼ら「改善されたパフォーマンスのパフォーマンスの問題に悩まされ、この特定のページ(私が通じ掘っ歴史をコミットすることにより、文書化)、の開発の歴史はそう、彼らはの最も重要な部分を含めるだけにそのインライン行マッパーを作ったときと思われます一度に必要なデータ。そして、それはそれは私が以前に気づいていなかった正規表現のロジック、その特定の枝に落ちたページのみだったことが判明します。

そしてそれは、このプロジェクトが賞「スパゲッティコードの最悪のケース」を獲得している理由だけで、別の理由があります。この1は「作業」の結果が本当に働いていた場合、またはすべての日付がnullだったときの順序をデータベースから保証、もいなかったので、それだけで仕事その時に何が起こった場合、それは確認することは不可能であったとして追跡することは特に困難でした。

TL; DR:未Tomcatの障害が、唯一の特定のJSPによってトリガDAOロジックのコーナーブランチにおける不正インライン行マッパー

おすすめ

転載: http://43.154.161.224:23101/article/api/json?id=176396&siteId=1