¿Por qué la misma Comparador actuando de manera diferente en las pruebas de unidad frente a cuando se ejecuta como una aplicación web?

AEvans:

TL; DR: Después de mucho ensayo y error, que parece como si el problema está relacionado Tomcat, tal vez en lo que respecta a las versiones de Java configurados, en lugar del propio lenguaje Java. Ver 'Editar 3' abajo para más detalles.

He estado usando Java 8 arroyos y comparadores para un rato y nunca he visto este tipo de comportamiento antes, por lo que estoy preguntando por curiosidad para ver si alguien puede encontrar lo que está mal con mi corriente.

Estoy trabajando en "Java-8-ifying" un proyecto legado mediante la sustitución de nuestra colección de procesamiento anticuado con las corrientes (me pidieron por qué estoy haciendo esto, y la respuesta corta es que estamos esencialmente re-escribir el proyecto, pero sólo tienen el presupuesto de tiempo para hacerlo de forma incremental que estoy haciendo el primer paso -.. actualización de la versión de Java Hay una gran cantidad de código desordenado en torno a la lógica de colección, por lo que "Java-8-ifying" está sirviendo para limpiar un montón de código y hacer las cosas más fáciles de leer y mantener) . Por ahora, todavía estamos utilizando los viejos tipos de datos, por lo que cualquier fecha de caducidad es mencionado se trata de casos java.util.Date en lugar de los nuevos tipos de Java 8.

Este es mi Comparador de ServiceRequest.java (que es un POJO):

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

Cuando análisis unidad, este comparador funciona como se espera. ServiceRequests con un activityDate más tarde son primero en la lista resultante, que tienen un activityDate anterior son más abajo en la lista, y los que tienen un activityDate nula están en el fondo. Para referencia, aquí es una copia completa de la prueba unitaria:

@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);
}

Nota: DateUtil es una utilidad legado que crea instancias java.util.Date para nuestros propósitos de prueba.

Esta prueba pasa siempre con gran éxito, lo que cabe esperar. Sin embargo, tengo un controlador que ensambla una lista de peticiones de servicio abierto y los agrupa por identificador del solicitante y selecciona solo la petición más reciente para ese usuario en un mapa. Me trató de convertir esta lógica en la corriente dada:

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)
            );
}

Mi lógica era que después de que las solicitudes fueron ordenados por la mayoría de las solicitudes recientes en primer lugar, cada vez que se procesaron múltiples peticiones realizadas por el mismo usuario, sólo el más reciente se puso en el mapa.

Sin embargo, el comportamiento observado fue que la solicitud más antigua fue que se puso en el mapa del lugar. Nota: Me he puesto que verificó que cuando el código del controlador se invoca a través de una prueba unitaria, el comportamiento es el esperado; el comportamiento erróneo solamente exhibe cuando el punto final en el controlador se llama mientras se ejecuta en Tomcat. Ver 'Editar 3' para más detalles

Añadí algunos asoma para ver las IDs ServiceRequest (no los ID Solicitante, que en este caso sería el mismo cuando se encuentra la función de fusión) antes de la clasificación, después de la clasificación, y en la función de combinación para el mapa de recogida. Por simplicidad, Limité los datos a 4 solicitudes de un único solicitante.

El orden esperado de ID ServiceRequest:

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

Salida de mis asoma:

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

Pensé que la intercalación de la salida de mapa de combinación con la mirada después de la especie era interesante, pero supongo que ya no había más operaciones con estado-intermedio, simplemente decidió añadir cosas al mapa ya que se miran a escondidas en ellos.

Puesto que la salida peek era la misma antes y después de la clasificación, llegué a la conclusión que, o bien la clasificación no tuvo efecto en el encuentro-orden, o que el comparador estaba clasificando en orden ascendente por alguna razón (al contrario de diseño previsto), y, o bien la entrada a partir de la base de datos que acaba de pasar a ser en este orden, o la corriente resuelva la clasificación antes de que ninguno de los picos (aunque no estoy seguro de que es posible ...). Por curiosidad, puse una clase en la llamada base de datos a ver si sería cambiar el resultado de esta corriente. Le dije a la llamada base de datos para ordenar por fecha de la actividad descendente de manera que el orden de la entrada de entrar en la corriente estaría garantizada. Si el comparador de alguna manera se invierte, se debe dar la vuelta al orden de los artículos de nuevo a orden ascendente.

Sin embargo la salida del flujo ordenado-DB era muy parecida a la primera, sólo el orden mantenido fiel a la orden original producida por la base de datos de tipo ... lo que me lleva a creer que mi comparador está teniendo ningún efecto en esta corriente.

Mi pregunta es ¿Por qué? ¿El colector tomap ignoran una orden encuentro? Si es así, ¿por qué esta causa la llamada ordenada a ser ineficaz? Pensé que ordenadas, como un paso intermedio de estado, forzado pasos subsiguientes para observar fin encuentro (con la excepción de forEach, ya que hay una forEachOrdered).

Al levantar la vista del Javadoc para tomap, tenía una nota sobre la concurrencia:

El colector devuelto no es concurrente. Para tuberías de flujo paralelo, la función combinador opera mediante la fusión de las claves de un mapa a otro, que puede ser una operación costosa. Si no se requiere que los resultados se combinan en el mapa con el fin encuentro, usando toConcurrentMap (Función, Función, BinaryOperator) puede ofrecer un mejor rendimiento en paralelo.

Esto me lleva a creer que el orden encuentro debe ser preservado por el colector tomap. Estoy muy perdido y confundido en cuanto a por qué estoy observar este comportamiento en particular. Sé que puedo resolver este problema haciendo una comparación de fechas en mi función de fusión, sino que busco entender por qué mi comparador parece funcionar cuando se utiliza con un colector toList, pero no con un colector tomap.

Gracias de antemano por su comprensión!

Editar 1: Muchos han sugerido el uso de un LinkedHashMap para resolver el problema, por lo que he implementado la solución de este modo:

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));

Pero cuando se prueba, en realidad es la resolución a una más antigua en lugar de la más reciente es deseable que el comparador debe estar haciendo cumplir. Todavía estoy confundido. Nota: ya que he verificado que la conducta errónea sólo se muestra cuando se ejecuta como una aplicación Web en Tomcat. Cuando este código se llama a través de una prueba unitaria, funciona como sería de esperar que lo haga. Ver 'Editar 3' para más detalles

Edición 2: Es interesante que, cuando he implementado la solución que -thought- trabajaría (clasificación en la función de fusión) tampoco está funcionando:

 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();
            }));

Que produce el siguiente resultado:

- 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

Nota: He puesto verificó que esta salida errónea se produce solamente cuando se ejecuta como una aplicación web en Tomcat. Cuando el código es llamada a través de pruebas jUnit, funciona correctamente, como cabría esperar. Ver 'Editar 3' para más detalles

Esto parece indicar que mi comparador realmente no está haciendo nada, o es la clasificación de forma agresiva en el orden opuesto al que lo hace en la prueba de la unidad, o tal vez findFirst está haciendo lo mismo que tomap estaba haciendo. Sin embargo, el Javadoc para FindFirst sugeriría que aspectos findFirst encuentran orden cuando se utiliza un paso intermedio como ordenada.

Datos 3: se me pidió hacer un proyecto de ejemplo verificable mínimo, completa y, por lo que hice: https://github.com/zepuka/ecounter-order-map-collect

He intentado un par de diferentes tácticas para tratar de reproducir el problema (cada uno etiquetado en el repositorio), pero era incapaz de reproducir el comportamiento erróneo que he recibido en mi controlador. Mi primera solución, y todas las sugerencias que he puesto intentó, tienen todos ellos producidos al, comportamiento correcto deseada! Entonces ¿por qué entonces, cuando la aplicación se ejecuta puedo obtener un comportamiento distinto? Por diversión y risas, expuse al método en mi controlador como público, por lo que pude unidad de prueba y utilizado los mismos datos exacta que ha sido darme problemas durante la carrera en la prueba de unidad - que funciona normalmente en la prueba unitaria. Tiene que haber algo diferente que permite que este código se ejecute correctamente en las pruebas unitarias y en un método principal de Java normal, pero incorrectamente cuando se ejecuta en mi servidor Tomcat.

La versión de Java Estoy compilando y corriendo con mi servidor en el mismo son, sin embargo: 1.8.0_171-b11 (Oracle). Al principio yo estaba construyendo y corriendo desde el interior de NetBeans, pero lo hice una versión de línea de comandos y Tomcat arranque sólo para estar seguro de que no había algunos extraños Netbeans ajuste de interferencia. Cuando miro a las propiedades de ejecución en NetBeans sin embargo, no diciendo que es el uso de un 'Java EE 7 Web' como la versión de Java EE junto con la configuración del servidor (que es Apache Tomcat 8.5.29, ejecutar Java 8), y voy a admitir que no tengo ni idea de lo que la 'versión de Java EE' se trata.

Así que he añadido la etiqueta Tomcat para este post como mi un problema parece estar relacionado con Tomcat-en lugar de relacionados con el lenguaje Java. En este punto, la única manera de resolver mi problema parece estar usando un enfoque no-corriente a la construcción del mapa, pero aun encantaría saber qué pensamientos de las personas están en qué configuración que podía mirar en solucionar el problema.

Editar 4: Traté de evitar el problema mediante el uso de las viejas formas de hacer las cosas, y cuando evito el uso de un comparador en un arroyo, las cosas están bien, pero tan pronto como introduzco un comparador a una corriente en cualquier parte del proceso, la aplicación web no se comporta correctamente. He intentado procesar la lista sin una corriente, y sólo con una corriente en las dos solicitudes para utilizar el comparador cuando la fusión en el mapa, pero que no funciona. He intentado sólo hacer Comparador pasado de moda con una definición de clase en línea, el uso de Java convencionales de edad en lugar de Comparator.comparing, pero el uso que en una corriente falla. Sólo cuando estoy evitando las corrientes y el comparador del todo qué parece al trabajo.

AEvans:

Finalmente llegamos a la parte inferior de la misma!

Yo era capaz de aislar el problema mediante la aplicación por primera vez el nuevo comparador de todas partes que necesitaba. Pude observar que la mayoría de éstos se comportaban como me esperaba, y sólo en una determinada página estaba la superficie problema.

Mi salida de depuración previa sólo incluye los identificadores, pero esta vez incluye las fechas de las actividades por conveniencia, y cuando llegué a la que uno JSP, que eran nulas!

La raíz del problema es que en un caso en un solo método DAO (que hizo un poco de análisis de expresión regular para llamar a diferentes métodos internos - si, es un desastre), que no estaba usando el asignador fila me había registrado antes ... esto en particular mehod ayudante contenía una línea nefasto 'fila mapeador'que utilizó primitivamente un bucle y el índice para obtener los resultados de la consulta y ponerlos en el objeto, y le faltaba la columna de la fecha de la actividad. Parece que la historia del desarrollo de esta página en particular (como se documenta por el cometer la historia Cavé a través), sufría de problemas de rendimiento, por lo que cuando 'un mejor rendimiento', que hizo esa fila línea mapeador para incluir sólo las piezas más importantes de datos necesarios en el momento. Y es que resulta que era la única página que cayó en esa rama de la lógica expresión regular, que no me había fijado previamente.

Y eso es sólo otra razón por la cual este proyecto está ganando el 'peor de los casos de espaguetis código' premio. Éste fue particularmente difícil de rastrear, ya que era imposible confirmar si un resultado de 'trabajo' estaba trabajando realmente o si acaba de pasar al trabajo ese momento porque el orden no estaba garantizada a partir de la base de datos, ni cuando todas las fechas eran nulos.

TL; DR: culpa no es Tomcat, pero una fila asignador pícaro en línea en una rama de la lógica esquina DAO sólo se desencadenan por una específica JSP

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=176399&siteId=1
Recomendado
Clasificación