|
26 | 26 | import java.util.Map;
|
27 | 27 | import java.util.function.Predicate;
|
28 | 28 |
|
| 29 | +import org.springframework.core.convert.ConversionService; |
29 | 30 | import org.springframework.dao.DataRetrievalFailureException;
|
30 | 31 | import org.springframework.dao.EmptyResultDataAccessException;
|
31 | 32 | import org.springframework.dao.InvalidDataAccessApiUsageException;
|
| 33 | +import org.springframework.dao.OptimisticLockingFailureException; |
32 | 34 | import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
33 | 35 | import org.springframework.data.jdbc.core.convert.JdbcValue;
|
34 | 36 | import org.springframework.data.jdbc.support.JdbcUtil;
|
35 | 37 | import org.springframework.data.mapping.PersistentProperty;
|
36 | 38 | import org.springframework.data.mapping.PersistentPropertyAccessor;
|
37 | 39 | import org.springframework.data.mapping.PersistentPropertyPath;
|
38 | 40 | import org.springframework.data.mapping.PropertyHandler;
|
| 41 | +import org.springframework.data.mapping.model.ConvertingPropertyAccessor; |
| 42 | +import org.springframework.data.relational.core.conversion.RelationalConverter; |
39 | 43 | import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
40 | 44 | import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
41 | 45 | import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
|
48 | 52 | import org.springframework.jdbc.support.KeyHolder;
|
49 | 53 | import org.springframework.lang.Nullable;
|
50 | 54 | import org.springframework.util.Assert;
|
| 55 | +import static org.springframework.data.jdbc.core.SqlGenerator.*; |
51 | 56 |
|
52 | 57 | /**
|
53 | 58 | * The default {@link DataAccessStrategy} is to generate SQL statements based on meta data from the entity.
|
|
56 | 61 | * @author Mark Paluch
|
57 | 62 | * @author Thomas Lang
|
58 | 63 | * @author Bastian Wilhelm
|
| 64 | + * @author Tom Hombergs |
59 | 65 | */
|
60 | 66 | public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
61 | 67 |
|
@@ -124,6 +130,12 @@ public <T> Object insert(T instance, Class<T> domainType, Identifier identifier)
|
124 | 130 | KeyHolder holder = new GeneratedKeyHolder();
|
125 | 131 | RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
|
126 | 132 |
|
| 133 | + if (persistentEntity.hasVersionProperty()) { |
| 134 | + |
| 135 | + Number newVersion = getNextVersion(instance, persistentEntity, converter.getConversionService()); |
| 136 | + setVersion(instance, persistentEntity, newVersion); |
| 137 | + } |
| 138 | + |
127 | 139 | MapSqlParameterSource parameterSource = getParameterSource(instance, persistentEntity, "",
|
128 | 140 | PersistentProperty::isIdProperty);
|
129 | 141 |
|
@@ -151,13 +163,46 @@ public <T> Object insert(T instance, Class<T> domainType, Identifier identifier)
|
151 | 163 | */
|
152 | 164 | @Override
|
153 | 165 | public <S> boolean update(S instance, Class<S> domainType) {
|
| 166 | + RelationalPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType); |
| 167 | + |
| 168 | + if (persistentEntity.hasVersionProperty()) { |
| 169 | + return updateWithVersion(instance, domainType); |
| 170 | + } else { |
| 171 | + return updateWithoutVersion(instance, domainType); |
| 172 | + } |
| 173 | + } |
| 174 | + |
| 175 | + private <S> boolean updateWithoutVersion(S instance, Class<S> domainType) { |
154 | 176 |
|
155 | 177 | RelationalPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType);
|
156 | 178 |
|
157 | 179 | return operations.update(sql(domainType).getUpdate(),
|
158 | 180 | getParameterSource(instance, persistentEntity, "", Predicates.includeAll())) != 0;
|
159 | 181 | }
|
160 | 182 |
|
| 183 | + private <S> boolean updateWithVersion(S instance, Class<S> domainType) { |
| 184 | + |
| 185 | + RelationalPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType); |
| 186 | + |
| 187 | + Number oldVersion = getVersion(instance, persistentEntity, converter.getConversionService()); |
| 188 | + Number newVersion = getNextVersion(instance, persistentEntity, converter.getConversionService()); |
| 189 | + setVersion(instance, persistentEntity, newVersion); |
| 190 | + |
| 191 | + MapSqlParameterSource parameterSource = getParameterSource(instance, persistentEntity, "", Predicates.includeAll()); |
| 192 | + parameterSource.addValue(VERSION_PARAMETER, oldVersion); |
| 193 | + int affectedRows = operations.update(sql(domainType).getUpdateWithVersion(), |
| 194 | + parameterSource); |
| 195 | + |
| 196 | + if (affectedRows == 0) { |
| 197 | + // reverting version update on entity |
| 198 | + setVersion(instance, persistentEntity, oldVersion); |
| 199 | + throw new OptimisticLockingFailureException( |
| 200 | + String.format("Optimistic lock exception on saving entity of type %s.", persistentEntity.getName())); |
| 201 | + } |
| 202 | + |
| 203 | + return true; |
| 204 | + } |
| 205 | + |
161 | 206 | /*
|
162 | 207 | * (non-Javadoc)
|
163 | 208 | * @see org.springframework.data.jdbc.core.DataAccessStrategy#delete(java.lang.Object, java.lang.Class)
|
@@ -354,7 +399,7 @@ private <S, ID> ID getIdValueOrNull(S instance, RelationalPersistentEntity<S> pe
|
354 | 399 | }
|
355 | 400 |
|
356 | 401 | private static <S, ID> boolean isIdPropertyNullOrScalarZero(@Nullable ID idValue,
|
357 |
| - RelationalPersistentEntity<S> persistentEntity) { |
| 402 | + RelationalPersistentEntity<S> persistentEntity) { |
358 | 403 |
|
359 | 404 | RelationalPersistentProperty idProperty = persistentEntity.getIdProperty();
|
360 | 405 | return idValue == null //
|
@@ -481,4 +526,31 @@ static Predicate<RelationalPersistentProperty> includeAll() {
|
481 | 526 | return it -> false;
|
482 | 527 | }
|
483 | 528 | }
|
| 529 | + @Nullable |
| 530 | + private <T> Number getVersion(T instance, RelationalPersistentEntity<T> entity, ConversionService conversionService) { |
| 531 | + RelationalPersistentProperty versionProperty = entity.getRequiredVersionProperty(); |
| 532 | + PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(instance); |
| 533 | + ConvertingPropertyAccessor<T> convertingPropertyAccessor = new ConvertingPropertyAccessor<>(propertyAccessor, conversionService); |
| 534 | + return convertingPropertyAccessor.getProperty(versionProperty, Number.class); |
| 535 | + } |
| 536 | + |
| 537 | + private <T> Number getNextVersion(T instance, RelationalPersistentEntity<T> entity, ConversionService conversionService) { |
| 538 | + Number version = getVersion(instance, entity, conversionService); |
| 539 | + Class<?> versionType = entity.getRequiredVersionProperty().getType(); |
| 540 | + if (versionType == Integer.class || versionType == int.class) { |
| 541 | + return version == null ? 1 : version.intValue() + 1; |
| 542 | + } else if (versionType == Long.class || versionType == long.class) { |
| 543 | + return version == null ? 1L : version.longValue() + 1; |
| 544 | + } else if (versionType == Short.class || versionType == short.class) { |
| 545 | + return version == null ? (short) 1 : (short) (version.shortValue() + 1); |
| 546 | + } |
| 547 | + throw new IllegalStateException(String.format("Entity '%s' has version property of invalid type '%s'.", entity.getType().getName(), entity.getVersionProperty().getType().getName())); |
| 548 | + } |
| 549 | + |
| 550 | + private <T> void setVersion(T instance, RelationalPersistentEntity<T> entity, Number newVersion) { |
| 551 | + RelationalPersistentProperty versionProperty = entity.getRequiredVersionProperty(); |
| 552 | + PersistentPropertyAccessor<T> accessor = versionProperty.getOwner().getPropertyAccessor(instance); |
| 553 | + accessor.setProperty(versionProperty, newVersion); |
| 554 | + } |
| 555 | + |
484 | 556 | }
|
0 commit comments