Skip to content

Commit f9533b2

Browse files
committed
DATACOUCH-623 - Add replace() method to CouchbaseRepository for CAS usage.
1 parent e72cdba commit f9533b2

File tree

5 files changed

+93
-8
lines changed

5 files changed

+93
-8
lines changed

src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@
1717
package org.springframework.data.couchbase.repository.support;
1818

1919
import java.util.Collection;
20+
import java.util.LinkedList;
2021
import java.util.List;
2122
import java.util.Objects;
2223
import java.util.Optional;
2324
import java.util.stream.Collectors;
2425

2526
import org.springframework.data.couchbase.core.CouchbaseOperations;
27+
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
28+
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
2629
import org.springframework.data.couchbase.core.query.Query;
2730
import org.springframework.data.couchbase.repository.CouchbaseRepository;
2831
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
@@ -75,15 +78,37 @@ public SimpleCouchbaseRepository(final CouchbaseEntityInformation<T, String> ent
7578
@SuppressWarnings("unchecked")
7679
public <S extends T> S save(final S entity) {
7780
Assert.notNull(entity, "Entity must not be null!");
78-
return (S) couchbaseOperations.upsertById(entityInformation.getJavaType()).one(entity);
81+
// if entity has non-null version property, then replace()
82+
if (hasVersionProperty(entity)) {
83+
return (S) couchbaseOperations.replaceById(entityInformation.getJavaType()).one(entity);
84+
} else {
85+
return (S) couchbaseOperations.upsertById(entityInformation.getJavaType()).one(entity);
86+
}
87+
}
88+
89+
private boolean hasVersionProperty(Object entity) {
90+
CouchbasePersistentEntity<?> mapperEntity = couchbaseOperations.getConverter().getMappingContext()
91+
.getPersistentEntity(entity.getClass());
92+
final CouchbasePersistentProperty versionProperty = mapperEntity.getVersionProperty();
93+
boolean hasVersionProperty = false;
94+
try {
95+
if (versionProperty != null && versionProperty.getField() != null) {
96+
Object versionValue = versionProperty.getField().get(entity);
97+
hasVersionProperty = versionValue != null && !versionValue.equals(Long.valueOf(0));
98+
}
99+
} catch (IllegalAccessException iae) {}
100+
return hasVersionProperty;
79101
}
80102

81103
@Override
82104
@SuppressWarnings("unchecked")
83105
public <S extends T> Iterable<S> saveAll(final Iterable<S> entities) {
84106
Assert.notNull(entities, "The given Iterable of entities must not be null!");
85-
return (Iterable<S>) couchbaseOperations.upsertById(entityInformation.getJavaType())
86-
.all(Streamable.of(entities).toList());
107+
List<S> result = new LinkedList<S>();
108+
for (S entity : entities) {
109+
result.add(save(entity));
110+
}
111+
return result;
87112
}
88113

89114
@Override

src/main/java/org/springframework/data/couchbase/repository/support/SimpleReactiveCouchbaseRepository.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.data.couchbase.repository.support;
1818

19+
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
20+
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
1921
import reactor.core.publisher.Flux;
2022
import reactor.core.publisher.Mono;
2123

@@ -77,7 +79,27 @@ public SimpleReactiveCouchbaseRepository(final CouchbaseEntityInformation<T, Str
7779
@SuppressWarnings("unchecked")
7880
public <S extends T> Mono<S> save(final S entity) {
7981
Assert.notNull(entity, "Entity must not be null!");
80-
return (Mono<S>) operations.upsertById(entityInformation.getJavaType()).one(entity);
82+
// if entity has non-null version property, then replace()
83+
if (hasVersionProperty(entity)) {
84+
return (Mono<S>) operations.replaceById(entityInformation.getJavaType()).one(entity);
85+
} else {
86+
return (Mono<S>) operations.upsertById(entityInformation.getJavaType()).one(entity);
87+
88+
}
89+
}
90+
91+
private boolean hasVersionProperty(Object entity) {
92+
CouchbasePersistentEntity<?> mapperEntity = operations.getConverter().getMappingContext()
93+
.getPersistentEntity(entity.getClass());
94+
final CouchbasePersistentProperty versionProperty = mapperEntity.getVersionProperty();
95+
boolean hasVersionProperty = false;
96+
try {
97+
if (versionProperty != null && versionProperty.getField() != null) {
98+
Object versionValue = versionProperty.getField().get(entity);
99+
hasVersionProperty = versionValue != null && !versionValue.equals(Long.valueOf(0));
100+
}
101+
} catch (IllegalAccessException iae) {}
102+
return hasVersionProperty;
81103
}
82104

83105
@Override
@@ -89,7 +111,7 @@ public Flux<T> findAll(final Sort sort) {
89111
@Override
90112
public <S extends T> Flux<S> saveAll(final Iterable<S> entities) {
91113
Assert.notNull(entities, "The given Iterable of entities must not be null!");
92-
return (Flux<S>) operations.upsertById(entityInformation.getJavaType()).all(Streamable.of(entities).toList());
114+
return Flux.fromIterable(entities).flatMap(this::save);
93115
}
94116

95117
@SuppressWarnings("unchecked")

src/test/java/org/springframework/data/couchbase/domain/UserRepository.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.util.List;
2020

21+
import com.couchbase.client.java.json.JsonArray;
22+
import org.springframework.data.couchbase.repository.CouchbaseRepository;
2123
import org.springframework.data.couchbase.repository.Query;
2224
import org.springframework.data.repository.PagingAndSortingRepository;
2325
import org.springframework.data.repository.query.Param;
@@ -30,10 +32,14 @@
3032
* @author Michael Reiche
3133
*/
3234
@Repository
33-
public interface UserRepository extends PagingAndSortingRepository<User, String> {
35+
public interface UserRepository extends CouchbaseRepository<User, String> {
3436

3537
List<User> findByFirstname(String firstname);
3638

39+
List<User> findByFirstnameIn(String... firstnames);
40+
41+
List<User> findByFirstnameIn(JsonArray firstnames);
42+
3743
List<User> findByFirstnameAndLastname(String firstname, String lastname);
3844

3945
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")

src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,14 @@
3131
import org.junit.jupiter.api.Test;
3232
import org.springframework.beans.factory.annotation.Autowired;
3333
import org.springframework.context.annotation.Configuration;
34+
import org.springframework.dao.DataIntegrityViolationException;
3435
import org.springframework.data.couchbase.CouchbaseClientFactory;
3536
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
3637
import org.springframework.data.couchbase.domain.Airport;
3738
import org.springframework.data.couchbase.domain.AirportRepository;
39+
import org.springframework.data.couchbase.domain.ReactiveUserRepository;
40+
import org.springframework.data.couchbase.domain.User;
41+
import org.springframework.data.couchbase.domain.UserRepository;
3842
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
3943
import org.springframework.data.couchbase.util.Capabilities;
4044
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
@@ -58,6 +62,8 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
5862

5963
@Autowired AirportRepository airportRepository;
6064

65+
@Autowired UserRepository userRepository;
66+
6167
@BeforeEach
6268
void beforeEach() {
6369
try {
@@ -118,6 +124,17 @@ void findBySimpleProperty() {
118124

119125
}
120126

127+
@Test
128+
public void testCas() {
129+
User user = new User("1", "Dave", "Wilson");
130+
userRepository.save(user);
131+
user.setVersion(user.getVersion() - 1);
132+
assertThrows(DataIntegrityViolationException.class, () -> userRepository.save(user));
133+
user.setVersion(0);
134+
userRepository.save(user);
135+
userRepository.delete(user);
136+
}
137+
121138
@Test
122139
void count() {
123140
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
@@ -132,8 +149,8 @@ void count() {
132149
airportRepository.save(airport);
133150
}
134151

135-
Long count = airportRepository.countFancyExpression( Arrays.asList("JFK"), Arrays.asList("jfk"), false);
136-
assertEquals( 1, count);
152+
Long count = airportRepository.countFancyExpression(Arrays.asList("JFK"), Arrays.asList("jfk"), false);
153+
assertEquals(1, count);
137154

138155
long airportCount = airportRepository.count();
139156
assertEquals(7, airportCount);

src/test/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepositoryQueryIntegrationTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,14 @@
2929
import org.junit.jupiter.api.Test;
3030
import org.springframework.beans.factory.annotation.Autowired;
3131
import org.springframework.context.annotation.Configuration;
32+
import org.springframework.dao.DataIntegrityViolationException;
3233
import org.springframework.dao.DataRetrievalFailureException;
3334
import org.springframework.data.couchbase.CouchbaseClientFactory;
3435
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
3536
import org.springframework.data.couchbase.domain.Airport;
3637
import org.springframework.data.couchbase.domain.ReactiveAirportRepository;
38+
import org.springframework.data.couchbase.domain.ReactiveUserRepository;
39+
import org.springframework.data.couchbase.domain.User;
3740
import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories;
3841
import org.springframework.data.couchbase.util.Capabilities;
3942
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
@@ -56,6 +59,7 @@ public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends ClusterAwa
5659
@Autowired CouchbaseClientFactory clientFactory;
5760

5861
@Autowired ReactiveAirportRepository airportRepository; // intellij flags "Could not Autowire", but it runs ok.
62+
@Autowired ReactiveUserRepository userRepository; // intellij flags "Could not Autowire", but it runs ok.
5963

6064
@BeforeEach
6165
void beforeEach() {
@@ -97,6 +101,17 @@ void findBySimpleProperty() {
97101
}
98102
}
99103

104+
@Test
105+
public void testCas() {
106+
User user = new User("1", "Dave", "Wilson");
107+
userRepository.save(user).block();
108+
user.setVersion(user.getVersion() - 1);
109+
assertThrows(DataIntegrityViolationException.class, () -> userRepository.save(user).block());
110+
user.setVersion(0);
111+
userRepository.save(user).block();
112+
userRepository.delete(user).block();
113+
}
114+
100115
@Test
101116
void count() {
102117
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };

0 commit comments

Comments
 (0)