Skip to content

Commit 5769df9

Browse files
Entities with same IDs are now replaced
1 parent 4564958 commit 5769df9

17 files changed

Lines changed: 279 additions & 126 deletions

File tree

spring-data-eclipse-store-jpa/src/test/java/software/xdev/spring/data/eclipse/store/jpa/integration/JpaImportTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class JpaImportTest
4949
@Test
5050
void testBasicSaveAndFindSingleRecords()
5151
{
52-
final PersonToTestInEclipseStore customer = new PersonToTestInEclipseStore("", "");
52+
final PersonToTestInEclipseStore customer = new PersonToTestInEclipseStore("1", "", "");
5353
this.personToTestInEclipseStoreRepository.save(customer);
5454

5555
final List<PersonToTestInEclipseStore> customers = this.personToTestInEclipseStoreRepository.findAll();

spring-data-eclipse-store-jpa/src/test/java/software/xdev/spring/data/eclipse/store/jpa/integration/repository/PersonToTestInEclipseStore.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
public class PersonToTestInEclipseStore
99
{
1010
@Id
11-
private String id;
11+
private final String id;
1212

1313
private final String firstName;
1414
private final String lastName;
1515

16-
public PersonToTestInEclipseStore(final String firstName, final String lastName)
16+
public PersonToTestInEclipseStore(final String id, final String firstName, final String lastName)
1717
{
18+
this.id = id;
1819
this.firstName = firstName;
1920
this.lastName = lastName;
2021
}

spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/importer/EclipseStoreDataImporter.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,7 @@ private <T> void createRepositoryForType(
264264
storageInstance,
265265
storageInstance,
266266
new SupportedChecker.Implementation(),
267-
storageInstance,
268-
idManager
267+
storageInstance
269268
),
270269
domainClass,
271270
new EclipseStoreTransactionManager(),

spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/EclipseStoreStorage.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545

4646

4747
public class EclipseStoreStorage
48-
implements EntityListProvider, IdSetterProvider, PersistableChecker, ObjectSwizzling
48+
implements EntityListProvider, IdManagerProvider, PersistableChecker, ObjectSwizzling
4949
{
5050
private static final Logger LOG = LoggerFactory.getLogger(EclipseStoreStorage.class);
5151
private final Map<Class<?>, SimpleEclipseStoreRepository<?, ?>> entityClassToRepository = new HashMap<>();
@@ -329,6 +329,7 @@ public synchronized void stop()
329329
);
330330
}
331331

332+
@Override
332333
@SuppressWarnings("unchecked")
333334
public <T, ID> IdManager<T, ID> ensureIdManager(final Class<T> domainClass)
334335
{
@@ -338,7 +339,7 @@ public <T, ID> IdManager<T, ID> ensureIdManager(final Class<T> domainClass)
338339
clazz ->
339340
new IdManager<>(
340341
domainClass,
341-
(IdSetter<T, ID>)IdSetter.createIdSetter(
342+
(IdSetter<T>)IdSetter.createIdSetter(
342343
clazz,
343344
id -> this.setLastId(clazz, id),
344345
() -> this.getLastId(clazz)
@@ -348,13 +349,6 @@ public <T, ID> IdManager<T, ID> ensureIdManager(final Class<T> domainClass)
348349
);
349350
}
350351

351-
@Override
352-
@SuppressWarnings("unchecked")
353-
public <T, ID> IdSetter<T, ID> ensureIdSetter(final Class<T> domainClass)
354-
{
355-
return (IdSetter<T, ID>)this.ensureIdManager(domainClass).getIdSetter();
356-
}
357-
358352
public Object getLastId(final Class<?> entityClass)
359353
{
360354
return this.readWriteLock.read(() -> this.root.getLastId(entityClass));

spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/IdSetterProvider.java renamed to spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/IdManagerProvider.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
*/
1616
package software.xdev.spring.data.eclipse.store.repository;
1717

18-
import software.xdev.spring.data.eclipse.store.repository.support.copier.id.IdSetter;
18+
import software.xdev.spring.data.eclipse.store.repository.support.copier.id.IdManager;
1919

2020

21-
public interface IdSetterProvider
21+
public interface IdManagerProvider
2222
{
23-
<T, ID> IdSetter<T, ID> ensureIdSetter(final Class<T> domainClass);
23+
<T, ID> IdManager<T, ID> ensureIdManager(final Class<T> domainClass);
2424
}

spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/EclipseStoreRepositoryFactory.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,7 @@ private <T> WorkingCopier<T> createWorkingCopier(
8080
storage,
8181
storage,
8282
new SupportedChecker.Implementation(),
83-
storage,
84-
storage.ensureIdManager(domainType)
83+
storage
8584
);
8685
}
8786

spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/SimpleEclipseStoreRepository.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.Set;
2323
import java.util.function.Function;
2424
import java.util.stream.Collectors;
25+
import java.util.stream.Stream;
2526

2627
import jakarta.annotation.Nonnull;
2728

@@ -103,9 +104,18 @@ private <S extends T> void uncachedStore(final Collection<S> entities)
103104
{
104105
LOG.debug("Saving {} entities...", entities.size());
105106
}
107+
108+
this.checkEntityForNull(entities);
109+
this.idManager.checkIds(entities);
110+
111+
Stream<S> entitiesStream = entities.stream();
112+
if(this.isMergeParallelizable())
113+
{
114+
entitiesStream = entities.parallelStream();
115+
}
116+
106117
final List<WorkingCopierResult<T>> results =
107-
this.checkEntityForNull(entities)
108-
.parallelStream()
118+
entitiesStream
109119
.map(this.copier::mergeBack)
110120
.toList();
111121
final Set<Object> nonEntitiesToStore =
@@ -129,6 +139,17 @@ private <S extends T> void uncachedStore(final Collection<S> entities)
129139
);
130140
}
131141

142+
/**
143+
* If the entities class to merge has an id, it is not possible to parallelize the merge. To search for existing
144+
* ids, we need a read lock. Since we are having a write lock to store the entities, we can not release the lock
145+
* and
146+
* would be stuck in a deadlock.
147+
*/
148+
private <S extends T> boolean isMergeParallelizable()
149+
{
150+
return !this.idManager.hasIdField();
151+
}
152+
132153
@Override
133154
@Nonnull
134155
public <S extends T> S save(@Nonnull final S entity)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright © 2024 XDEV Software (https://xdev.software)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package software.xdev.spring.data.eclipse.store.repository.support.copier.id;
17+
18+
public interface IdGetter<T, ID>
19+
{
20+
ID getId(T objectToSetIdIn) throws Exception;
21+
}

spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/copier/id/IdManager.java

Lines changed: 83 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616
package software.xdev.spring.data.eclipse.store.repository.support.copier.id;
1717

1818
import java.lang.reflect.Field;
19+
import java.util.Collection;
20+
import java.util.Collections;
1921
import java.util.List;
2022
import java.util.Optional;
23+
import java.util.stream.StreamSupport;
2124

2225
import jakarta.annotation.Nonnull;
2326

@@ -28,16 +31,16 @@
2831
import software.xdev.spring.data.eclipse.store.repository.support.IdFieldFinder;
2932

3033

31-
public class IdManager<T, ID> implements EntityGetterById<T, ID>
34+
public class IdManager<T, ID> implements EntityGetterById<T, ID>, IdGetter<T, ID>
3235
{
3336
private final Class<T> domainClass;
34-
private final IdSetter<T, ID> idSetter;
37+
private final IdSetter<T> idSetter;
3538
private final Optional<Field> idField;
3639
private final EclipseStoreStorage storage;
3740

3841
public IdManager(
3942
final Class<T> domainClass,
40-
final IdSetter<T, ID> idSetter,
43+
final IdSetter<T> idSetter,
4144
final EclipseStoreStorage storage
4245
)
4346
{
@@ -65,28 +68,8 @@ public Optional<T> findById(@Nonnull final ID id)
6568
return this.storage.getReadWriteLock().read(
6669
() -> this.storage
6770
.getEntityList(this.domainClass)
68-
.parallelStream()
69-
.filter(
70-
entity ->
71-
{
72-
try(final FieldAccessModifier<T> fam = FieldAccessModifier.prepareForField(
73-
this.ensureIdField(),
74-
entity))
75-
{
76-
if(id.equals(fam.getValueOfField(entity)))
77-
{
78-
return true;
79-
}
80-
}
81-
catch(final Exception e)
82-
{
83-
throw new FieldAccessReflectionException(String.format(
84-
FieldAccessReflectionException.COULD_NOT_READ_FIELD,
85-
this.ensureIdField().getName()), e);
86-
}
87-
return false;
88-
}
89-
)
71+
.stream()
72+
.filter(entity -> id.equals(this.getId(entity)))
9073
.findAny()
9174
);
9275
}
@@ -97,38 +80,91 @@ public List<T> findAllById(@Nonnull final Iterable<ID> idsToFind)
9780
return this.storage.getReadWriteLock().read(
9881
() -> this.storage
9982
.getEntityList(this.domainClass)
100-
.parallelStream()
83+
.stream()
10184
.filter(
10285
entity ->
10386
{
104-
try(final FieldAccessModifier<T> fam = FieldAccessModifier.prepareForField(
105-
this.ensureIdField(),
106-
entity))
107-
{
108-
final Object idOfEntity = fam.getValueOfField(entity);
109-
for(final ID idToFind : idsToFind)
110-
{
111-
if(idToFind.equals(idOfEntity))
112-
{
113-
return true;
114-
}
115-
}
116-
}
117-
catch(final Exception e)
118-
{
119-
throw new FieldAccessReflectionException(String.format(
120-
FieldAccessReflectionException.COULD_NOT_READ_FIELD,
121-
this.ensureIdField().getName()), e);
122-
}
123-
return false;
87+
final Object idOfEntity = this.getId(entity);
88+
return StreamSupport
89+
.stream(idsToFind.spliterator(), false)
90+
.anyMatch(idToFind -> idToFind.equals(idOfEntity));
12491
}
12592
)
12693
.toList()
12794
);
12895
}
12996

130-
public IdSetter<T, ID> getIdSetter()
97+
public IdSetter<T> getIdSetter()
13198
{
13299
return this.idSetter;
133100
}
101+
102+
@Override
103+
public ID getId(final T entity)
104+
{
105+
if(this.hasIdField())
106+
{
107+
try(final FieldAccessModifier<T> fam = FieldAccessModifier.prepareForField(
108+
this.ensureIdField(),
109+
entity))
110+
{
111+
return (ID)fam.getValueOfField(entity);
112+
}
113+
catch(final Exception e)
114+
{
115+
throw new FieldAccessReflectionException(String.format(
116+
FieldAccessReflectionException.COULD_NOT_READ_FIELD,
117+
this.ensureIdField().getName()), e);
118+
}
119+
}
120+
return null;
121+
}
122+
123+
public boolean hasIdField()
124+
{
125+
return this.idField.isPresent();
126+
}
127+
128+
/**
129+
* This method makes sure, that an id is set for the given object. If it is already set (not null), then nothing is
130+
* done. If it is not set, a new one will be generated and set.
131+
*/
132+
public void ensureId(final T objectToSetIdIn)
133+
{
134+
this.getIdSetter().ensureId(objectToSetIdIn);
135+
}
136+
137+
public <S extends T> void checkIds(final Collection<S> entities)
138+
{
139+
if(!this.hasIdField())
140+
{
141+
return;
142+
}
143+
final List<ID> ids = entities
144+
.stream()
145+
.map(entity -> this.getId(entity))
146+
.toList();
147+
148+
if(!this.getIdSetter().isAutomaticSetter() && ids.contains(null))
149+
{
150+
final Optional<S> entityWithNullId =
151+
entities.stream().filter(entity -> this.getId(entity) == null).findAny();
152+
if(entityWithNullId.isPresent())
153+
{
154+
throw new IllegalArgumentException(
155+
"Invalid ID (null) for entity " + entityWithNullId
156+
);
157+
}
158+
}
159+
160+
final List<ID> multipleEqualIds = ids.stream()
161+
.filter(id -> Collections.frequency(ids, id) > 1)
162+
.toList();
163+
if(!multipleEqualIds.isEmpty())
164+
{
165+
throw new IllegalArgumentException(
166+
"Same ID %s is set multiple times in one save call ".formatted(multipleEqualIds.get(0))
167+
);
168+
}
169+
}
134170
}

spring-data-eclipse-store/src/main/java/software/xdev/spring/data/eclipse/store/repository/support/copier/id/IdSetter.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@
3131
* A IdSetter <b>must be unique</b> in one storage for one entity-class. It creates Ids and therefore must know all
3232
* existing entities of one class.
3333
*/
34-
public interface IdSetter<T, ID>
34+
public interface IdSetter<T>
3535
{
36-
static <T, ID> IdSetter<T, ID> createIdSetter(
36+
static <T> IdSetter<T> createIdSetter(
3737
final Class<T> classWithId,
3838
final Consumer<Object> lastIdPersister,
3939
final Supplier<Object> lastIdGetter)
@@ -44,14 +44,14 @@ static <T, ID> IdSetter<T, ID> createIdSetter(
4444
final Optional<Field> idField = IdFieldFinder.findIdField(classWithId);
4545
if(idField.isEmpty())
4646
{
47-
return (IdSetter<T, ID>)new NotSettingIdSetter<T>();
47+
return new NotSettingIdSetter<>();
4848
}
4949
final GeneratedValue generatedValueAnnotation = idField.get().getAnnotation(GeneratedValue.class);
5050
if(generatedValueAnnotation == null)
5151
{
52-
return (IdSetter<T, ID>)new NotSettingIdSetter<T>();
52+
return new NotSettingIdSetter<>();
5353
}
54-
return new SimpleIdSetter<T, ID>(
54+
return new SimpleIdSetter<>(
5555
idField.get(),
5656
IdFinder.createIdFinder(idField.get(), generatedValueAnnotation, lastIdGetter),
5757
lastIdPersister);
@@ -60,7 +60,8 @@ static <T, ID> IdSetter<T, ID> createIdSetter(
6060
/**
6161
* This method makes sure, that an id is set for the given object. If it is already set (not null), then nothing is
6262
* done. If it is not set, a new one will be generated and set.
63-
* @return the existing or newly created id or empty optional if no id is used
6463
*/
65-
ID ensureId(T objectToSetIdIn);
64+
void ensureId(T objectToSetIdIn);
65+
66+
boolean isAutomaticSetter();
6667
}

0 commit comments

Comments
 (0)