17
17
18
18
import io .r2dbc .spi .Row ;
19
19
import io .r2dbc .spi .RowMetadata ;
20
+ import org .springframework .dao .OptimisticLockingFailureException ;
20
21
import reactor .core .publisher .Flux ;
21
22
import reactor .core .publisher .Mono ;
22
23
30
31
import org .springframework .beans .BeansException ;
31
32
import org .springframework .beans .factory .BeanFactory ;
32
33
import org .springframework .beans .factory .BeanFactoryAware ;
34
+ import org .springframework .core .convert .ConversionService ;
33
35
import org .springframework .dao .DataAccessException ;
34
36
import org .springframework .dao .TransientDataAccessResourceException ;
35
37
import org .springframework .data .mapping .IdentifierAccessor ;
36
38
import org .springframework .data .mapping .MappingException ;
39
+ import org .springframework .data .mapping .PersistentPropertyAccessor ;
37
40
import org .springframework .data .mapping .context .MappingContext ;
38
41
import org .springframework .data .projection .ProjectionInformation ;
39
42
import org .springframework .data .projection .SpelAwareProxyProjectionFactory ;
59
62
* prepared in an application context and given to services as bean reference.
60
63
*
61
64
* @author Mark Paluch
65
+ * @author Bogdan Ilchyshyn
62
66
* @since 1.1
63
67
*/
64
68
public class R2dbcEntityTemplate implements R2dbcEntityOperations , BeanFactoryAware {
@@ -373,6 +377,8 @@ <T> Mono<T> doInsert(T entity, SqlIdentifier tableName) {
373
377
374
378
RelationalPersistentEntity <T > persistentEntity = getRequiredEntity (entity );
375
379
380
+ setVersionIfNecessary (persistentEntity , entity );
381
+
376
382
return this .databaseClient .insert () //
377
383
.into (persistentEntity .getType ()) //
378
384
.table (tableName ).using (entity ) //
@@ -381,6 +387,19 @@ <T> Mono<T> doInsert(T entity, SqlIdentifier tableName) {
381
387
.defaultIfEmpty (entity );
382
388
}
383
389
390
+ private <T > void setVersionIfNecessary (RelationalPersistentEntity <T > persistentEntity , T entity ) {
391
+ RelationalPersistentProperty versionProperty = persistentEntity .getVersionProperty ();
392
+ if (versionProperty == null ) {
393
+ return ;
394
+ }
395
+
396
+ Class <?> versionPropertyType = versionProperty .getType ();
397
+ Long version = versionPropertyType .isPrimitive () ? 1L : 0L ;
398
+ ConversionService conversionService = this .dataAccessStrategy .getConverter ().getConversionService ();
399
+ PersistentPropertyAccessor <?> propertyAccessor = persistentEntity .getPropertyAccessor (entity );
400
+ propertyAccessor .setProperty (versionProperty , conversionService .convert (version , versionPropertyType ));
401
+ }
402
+
384
403
/*
385
404
* (non-Javadoc)
386
405
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#update(java.lang.Object)
@@ -392,21 +411,78 @@ public <T> Mono<T> update(T entity) throws DataAccessException {
392
411
393
412
RelationalPersistentEntity <T > persistentEntity = getRequiredEntity (entity );
394
413
395
- return this .databaseClient .update () //
414
+ DatabaseClient . UpdateMatchingSpec updateMatchingSpec = this .databaseClient .update () //
396
415
.table (persistentEntity .getType ()) //
397
- .table (persistentEntity .getTableName ()).using (entity ) //
398
- .fetch ().rowsUpdated ().handle ((rowsUpdated , sink ) -> {
416
+ .table (persistentEntity .getTableName ()) //
417
+ .using (entity );
418
+
419
+ DatabaseClient .UpdateSpec updateSpec = updateMatchingSpec ;
420
+ if (persistentEntity .hasVersionProperty ()) {
421
+ updateSpec = updateMatchingSpec .matching (createMatchingVersionCriteria (entity , persistentEntity ));
422
+ incrementVersion (entity , persistentEntity );
423
+ }
424
+
425
+ return updateSpec .fetch () //
426
+ .rowsUpdated () //
427
+ .flatMap (rowsUpdated -> rowsUpdated == 0
428
+ ? handleMissingUpdate (entity , persistentEntity ) : Mono .just (entity ));
429
+ }
399
430
400
- if (rowsUpdated == 0 ) {
401
- sink .error (new TransientDataAccessResourceException (
402
- String .format ("Failed to update table [%s]. Row with Id [%s] does not exist." ,
403
- persistentEntity .getTableName (), persistentEntity .getIdentifierAccessor (entity ).getIdentifier ())));
431
+ private <T > Mono <? extends T > handleMissingUpdate (T entity , RelationalPersistentEntity <T > persistentEntity ) {
432
+ if (!persistentEntity .hasVersionProperty ()) {
433
+ return Mono .error (new TransientDataAccessResourceException (
434
+ formatTransientEntityExceptionMessage (entity , persistentEntity )));
435
+ }
436
+
437
+ return doCount (getByIdQuery (entity , persistentEntity ), entity .getClass (), persistentEntity .getTableName ())
438
+ .map (count -> {
439
+ if (count == 0 ) {
440
+ throw new TransientDataAccessResourceException (
441
+ formatTransientEntityExceptionMessage (entity , persistentEntity ));
404
442
} else {
405
- sink .next (entity );
443
+ throw new OptimisticLockingFailureException (
444
+ formatOptimisticLockingExceptionMessage (entity , persistentEntity ));
406
445
}
407
446
});
408
447
}
409
448
449
+ private <T > String formatOptimisticLockingExceptionMessage (T entity , RelationalPersistentEntity <T > persistentEntity ) {
450
+ return String .format ("Failed to update table [%s]. Version does not match for row with Id [%s]." ,
451
+ persistentEntity .getTableName (), persistentEntity .getIdentifierAccessor (entity ).getIdentifier ());
452
+ }
453
+
454
+ private <T > String formatTransientEntityExceptionMessage (T entity , RelationalPersistentEntity <T > persistentEntity ) {
455
+ return String .format ("Failed to update table [%s]. Row with Id [%s] does not exist." ,
456
+ persistentEntity .getTableName (), persistentEntity .getIdentifierAccessor (entity ).getIdentifier ());
457
+ }
458
+
459
+ private <T > void incrementVersion (T entity , RelationalPersistentEntity <T > persistentEntity ) {
460
+ PersistentPropertyAccessor <?> propertyAccessor = persistentEntity .getPropertyAccessor (entity );
461
+ RelationalPersistentProperty versionProperty = persistentEntity .getVersionProperty ();
462
+
463
+ ConversionService conversionService = this .dataAccessStrategy .getConverter ().getConversionService ();
464
+ Object currentVersionValue = propertyAccessor .getProperty (versionProperty );
465
+ long newVersionValue = 1L ;
466
+ if (currentVersionValue != null ) {
467
+ newVersionValue = conversionService .convert (currentVersionValue , Long .class ) + 1 ;
468
+ }
469
+ Class <?> versionPropertyType = versionProperty .getType ();
470
+ propertyAccessor .setProperty (versionProperty , conversionService .convert (newVersionValue , versionPropertyType ));
471
+ }
472
+
473
+ private <T > Criteria createMatchingVersionCriteria (T entity , RelationalPersistentEntity <T > persistentEntity ) {
474
+ PersistentPropertyAccessor <?> propertyAccessor = persistentEntity .getPropertyAccessor (entity );
475
+ RelationalPersistentProperty versionProperty = persistentEntity .getVersionProperty ();
476
+
477
+ Object version = propertyAccessor .getProperty (versionProperty );
478
+ Criteria .CriteriaStep versionColumn = Criteria .where (dataAccessStrategy .toSql (versionProperty .getColumnName ()));
479
+ if (version == null ) {
480
+ return versionColumn .isNull ();
481
+ } else {
482
+ return versionColumn .is (version );
483
+ }
484
+ }
485
+
410
486
/*
411
487
* (non-Javadoc)
412
488
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#delete(java.lang.Object)
0 commit comments