mongotemplate example data conectar con java mongodb spring-boot spring-data

java - example - Spring Data MongoRepository guardar(T) no funciona... a veces



spring data mongodb (2)

Así que hay una pequeña aplicación Angular + Java + Spring Boot + MongoDB con la que estoy trabajando. Está recibiendo mucha acción últimamente (lea: modificaciones de código), pero las clases de acceso a datos han quedado prácticamente intactas en AFAIK.
Sin embargo, parece que MongoRepository decidió repentinamente dejar de persistir los cambios que estoy MongoRepository save() en la base de datos.

Al inspeccionar mongod.log esto es lo que veo cuando funciona save() :

2018-04-11T15:04:06.840+0200 I COMMAND [conn6] command pdfviewer.bookData command: find { find: "bookData", filter: { _id: "ID_1" }, limit: 1, singleBatch: true } planSummary: IDHACK keysExamined:1 docsExamined:1 idhack:1 cursorExhausted:1 keyUpdates:0 writeConflicts:0 numYields:1 nreturned:1 reslen:716 locks:{ Global: { acquireCount: { r: 4 } }, Database: { acquireCount: { r: 2 } }, Collection: { acquireCount: { r: 2 } } } protocol:op_query 102ms 2018-04-11T17:30:19.615+0200 I WRITE [conn7] update pdfviewer.bookData query: { _id: "ID_1" } update: { _class: "model.BookData", _id: "ID_1", config: { mode: "normal", offlineEnabled: true }, metadata: { title: "PDFdePrueba3pag copia 6 ", ...}, downloaded: false, currentPageNumber: 2, availablePages: 3, bookmarks: [], stats: { _id: "c919e517-3c68-462c-8396-d4ba391762e6", dateOpen: new Date(1523460575872), dateClose: new Date(1523460575951), timeZone: "+2", ... }, ... } keysExamined:1 docsExamined:1 nMatched:1 nModified:1 keyUpdates:0 writeConflicts:1 numYields:1 locks:{ Global: { acquireCount: { r: 2, w: 2 } }, Database: { acquireCount: { w: 2 } }, Collection: { acquireCount: { w: 2 } } } 315ms 2018-04-11T17:30:19.615+0200 I COMMAND [conn7] command pdfviewer.$cmd command: update { update: "bookData", ordered: false, updates: [ { q: { _id: "ID_1" }, u: { _class: "model.BookData", _id: "ID_1", config: { mode: "normal", offlineEnabled: true }, metadata: { title: "PDFdePrueba3pag copia 6 ", ...}, downloaded: false, currentPageNumber: 2, availablePages: 3, bookmarks: [], stats: { _id: "c919e517-3c68-462c-8396-d4ba391762e6", dateOpen: new Date(1523460575872), dateClose: new Date(1523460575951), timeZone: "+2", ... }, ... }, upsert: true } ] } keyUpdates:0 writeConflicts:0 numYields:0 reslen:55 locks:{ Global: { acquireCount: { r: 2, w: 2 } }, Database: { acquireCount: { w: 2 } }, Collection: { acquireCount: { w: 2 } } } protocol:op_query 316ms

Y esto es lo que veo cuando no lo hace:

2018-04-11T18:13:21.864+0200 I NETWORK [initandlisten] connection accepted from 127.0.0.1:64271 #1 (1 connection now open) 2018-04-11T18:18:51.425+0200 I NETWORK [initandlisten] connection accepted from 127.0.0.1:64329 #2 (2 connections now open) 2018-04-11T18:19:06.967+0200 I NETWORK [initandlisten] connection accepted from 127.0.0.1:64346 #3 (3 connections now open)

Al hacer una tail -f 1 en el archivo de registro durante la depuración, he visto que esas conexiones aparecen justo cuando mi código llama a findById() o save() , por lo que parece que la aplicación puede alcanzar la base de datos.

Este es (más o menos) el código Java relevante:

/* BookData.java */ @Document public class BookData { @Id private String id; // Some more non-Id Strings... private Config config; private Metadata metadata; private Boolean downloaded; private Integer currentPageNumber; private int availablePages; private List<Bookmark> bookmarks; private StatsModel stats; @Transient private byte[] contents; public BookData() {} // getters and setters } /* BookDataRepository.java */ // MongoRepository comes from spring-boot-starter-parent-1.4.5.RELEASE public interface BookDataRepository extends MongoRepository<BookData, String> { BookData findById(String id); } /* BookDataServiceImpl.java */ public BookData updateBookData(String id, BookData newData) { final BookData original = bookDataRepository.findById(id); if (original == null) { return null; } original.setCurrentPageNumber(Optional.ofNullable(newData.getCurrentPageNumber()).orElseGet(original::getCurrentPageNumber)); // similar code for a couple other fields return bookDataRepository.save(original); }

He recorrido esa parte cientos de veces mientras depuraba y todo parece estar bien:

  • findById(id) devuelve correctamente el objeto BookData original esperado de BookData original : marque ✓
  • newData contiene los valores esperados que se utilizarán para la actualización: verificar ✓
  • justo antes de llamar a save(original) , el original se ha modificado correctamente usando los newData valores de newData : verificar ✓
  • save() ejecuta sin errores: cheque ✓
  • save() devuelve un nuevo BookData con valores correctamente actualizados: para mi propia sorpresa, marque ✓
  • después de save() devuelve, una consulta db.bookData.find() en Mongo Shell muestra que los valores se han actualizado: falla .
  • después de que retorna save() , el objeto BookData recuperado por las nuevas llamadas a findById() contiene los valores actualizados: falla (a veces lo hace, a veces no).

Parece que MongoDB está esperando algún tipo de flush() , pero este no es un repositorio JPA al que se puede llamar saveAndFlush() lugar.

Alguna idea de por qué esto esta pasando?

EDITAR: versiones (según lo solicitado):

  • Java 8
  • Bota de primavera 1.4.5
  • MongoDB 3.2.6
  • Windows 10

También BookData arriba.


MongoDB es inherentemente una tienda de caché, por lo que quiero decir, no se garantiza que los contenidos sean los últimos o necesariamente correctos. No he podido encontrar las opciones de configuración para el tiempo de descarga (pero se configurarían en la propia base de datos), pero MongoDB ha agregado funciones para que pueda elegir rápido + sucio, o lento + limpio. Este factor de "frescura" es probablemente su problema si está viendo este tipo de problema. (Incluso si no está ejecutando la distribución, hay una diferencia de tiempo entre el reconocimiento de la solicitud y la solicitud confirmada)

Aquí hay un enlace para publicar sobre "lectura limpia" (punto clave en la siguiente cita)

http://www.dagolden.com/index.php/2633/no-more-dirty-reads-with-mongodb/

Recomiendo a los usuarios de MongoDB que se coloquen (o al menos, sus actividades de aplicación) en uno de los siguientes grupos:

"Quiero baja latencia": las lecturas sucias están bien siempre que las cosas sean rápidas. Use w = 1 y lea la preocupación ''local''. (Estas son las configuraciones predeterminadas). "Quiero consistencia": las lecturas sucias no están bien, incluso a costa de la latencia o datos ligeramente desactualizados. Use w = "mayoría" y lea la mayoría de "preocupación". utilizar MongoDB v1.2.0;

my $mc = MongoDB->connect( $uri, { read_concern_level => ''majority'', w => ''majority'', } );

Lecturas adicionales que pueden o no ser útiles.

Actualizar

Si se ejecuta en un entorno de subprocesos múltiples, asegúrese de que sus subprocesos no estén pisoteando las actualizaciones de otro. Puede verificar si esto ocurre configurando el sistema o el nivel de registro de consultas en 5. https://docs.mongodb.com/manual/reference/log-messages/#log-messages-configure-verbosity


Problema resuelto.
Una llamada asíncrona diferente del cliente JS, a un punto final diferente en el backend de Java, estaba sobrescribiendo mi documento actualizado en un hilo diferente con los valores originales.

Ambas operaciones de actualización llamaban a findById antes de guardar. El problema fue que lo hicieron al mismo tiempo, por lo que obtuvieron los mismos valores originales.
Cada uno continuó con la actualización de sus campos relevantes y con la opción de save al final, lo que provocó que el otro subproceso anulara mis cambios.
Cada llamada se registró solo con los campos modificados relevantes, por lo que no me di cuenta de que uno de ellos estaba sobrescribiendo los cambios del otro.

Una vez que agregué systemLog.verbosity: 3 al config.cfg de MongoDB para que registrara todas las operaciones, quedó claro que 2 operaciones WRITE diferentes se estaban realizando al mismo tiempo (con una diferencia de ~ 500 ms) pero con valores diferentes.
Luego solo fue cuestión de mover el findById más cerca del save y garantizar que las llamadas de JS se hicieran en orden (haciendo que una de las promesas dependiera de la otra).

En retrospectiva, esto probablemente no habría ocurrido si usé MongoOperations o MongoTemplate , que ofrecen métodos de update única y findAndModify que también permiten operaciones de campo único, en lugar de MongoRepository donde me veo obligado a hacerlo en 3 pasos ( find , modificar entidad devuelta, save ) y para trabajar con el documento completo.

EDITAR: Realmente no me gustó mi primer findById "mudar findById más cerca de save ", así que al final hice lo que sentí que era correcto e implementé métodos de guardado personalizados que usaban la API de update más detallada de MongoTemplate . Código final:

/* MongoRepository provides entity-based default Spring Data methods */ /* BookDataRepositoryCustom provides field-level update methods */ public interface BookDataRepository extends MongoRepository<BookData, String>, BookDataRepositoryCustom { BookData findById(String id); } /* Interface for the custom methods */ public interface BookDataRepositoryCustom { int saveCurrentPage(String id, Integer currentPage); } /* Custom implementation using MongoTemplate. */ @SuppressWarnings("unused") public class BookDataRepositoryImpl implements BookDataRepositoryCustom { @Inject MongoTemplate mongoTemplate; @Override public int saveCurrentPage(String id, Integer currentPage) { Query query = new Query(Criteria.where("_id").is(id)); Update update = new Update(); update.set("currentPage", currentPage); WriteResult result = mongoTemplate.updateFirst(query, update, BookData.class); return result == null ? 0 : result.getN(); } } // Old code: get entity from DB, update, save. 3 steps with plenty of room for interferences. // BookData bookData = bookDataRepository.findById(bookDataId); // bookData.setCurrentPage(currentPage); // bookDataRepository.save(bookData); // New code: update single field. 1 step, 0 problems. bookDataRepository.saveCurrentPage(bookDataId, currentPage);

Al hacerlo, cada punto final puede update con la frecuencia que sea necesaria a través de MongoTemplate sin tener que preocuparse de sobrescribir campos no relacionados, y aún MongoRepository métodos MongoRepository basados ​​en MongoRepository para cosas como la creación de nuevas entidades, los métodos @Query , los @Query anotados, etc.