Skip to content

Replace entities with same #102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class JpaImportTest
@Test
void testBasicSaveAndFindSingleRecords()
{
final PersonToTestInEclipseStore customer = new PersonToTestInEclipseStore("", "");
final PersonToTestInEclipseStore customer = new PersonToTestInEclipseStore("1", "", "");
this.personToTestInEclipseStoreRepository.save(customer);

final List<PersonToTestInEclipseStore> customers = this.personToTestInEclipseStoreRepository.findAll();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
public class PersonToTestInEclipseStore
{
@Id
private String id;
private final String id;

private final String firstName;
private final String lastName;

public PersonToTestInEclipseStore(final String firstName, final String lastName)
public PersonToTestInEclipseStore(final String id, final String firstName, final String lastName)
{
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
Expand Down
3 changes: 1 addition & 2 deletions spring-data-eclipse-store/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -406,8 +406,7 @@
<excludes>
<exclude>**/benchmark/**/jmh_generated/**</exclude>
<!-- Dynamic types for tests -->
<exclude>**/shared/**/Customer*</exclude>
<exclude>**/shared/**/Child*</exclude>
<exclude>**/store/integration/**</exclude>
</excludes>
</configuration>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import software.xdev.spring.data.eclipse.store.repository.SupportedChecker;
import software.xdev.spring.data.eclipse.store.repository.config.EclipseStoreClientConfiguration;
import software.xdev.spring.data.eclipse.store.repository.support.SimpleEclipseStoreRepository;
import software.xdev.spring.data.eclipse.store.repository.support.copier.id.IdManager;
import software.xdev.spring.data.eclipse.store.repository.support.copier.working.RecursiveWorkingCopier;
import software.xdev.spring.data.eclipse.store.transactions.EclipseStoreTransactionManager;

Expand Down Expand Up @@ -254,6 +255,7 @@ private <T> void createRepositoryForType(
private <T> SimpleEclipseStoreRepository<T, ?> createEclipseStoreRepo(final Class<T> domainClass)
{
final EclipseStoreStorage storageInstance = this.configuration.getStorageInstance();
final IdManager<T, Object> idManager = storageInstance.ensureIdManager(domainClass);
return new SimpleEclipseStoreRepository<>(
storageInstance,
new RecursiveWorkingCopier<>(
Expand All @@ -265,7 +267,8 @@ private <T> void createRepositoryForType(
storageInstance
),
domainClass,
new EclipseStoreTransactionManager()
new EclipseStoreTransactionManager(),
idManager
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,18 @@
import software.xdev.spring.data.eclipse.store.repository.support.SimpleEclipseStoreRepository;
import software.xdev.spring.data.eclipse.store.repository.support.concurrency.ReadWriteLock;
import software.xdev.spring.data.eclipse.store.repository.support.concurrency.ReentrantJavaReadWriteLock;
import software.xdev.spring.data.eclipse.store.repository.support.copier.id.IdManager;
import software.xdev.spring.data.eclipse.store.repository.support.copier.id.IdSetter;
import software.xdev.spring.data.eclipse.store.repository.support.reposyncer.RepositorySynchronizer;
import software.xdev.spring.data.eclipse.store.repository.support.reposyncer.SimpleRepositorySynchronizer;


public class EclipseStoreStorage
implements EntityListProvider, IdSetterProvider, PersistableChecker, ObjectSwizzling
implements EntityListProvider, IdManagerProvider, PersistableChecker, ObjectSwizzling
{
private static final Logger LOG = LoggerFactory.getLogger(EclipseStoreStorage.class);
private final Map<Class<?>, SimpleEclipseStoreRepository<?, ?>> entityClassToRepository = new HashMap<>();
private final Map<Class<?>, IdSetter<?>> idSetters = new ConcurrentHashMap<>();
private final Map<Class<?>, IdManager<?, ?>> idManagers = new ConcurrentHashMap<>();
private final EclipseStoreStorageFoundationProvider foundationProvider;
private EntitySetCollector entitySetCollector;
private PersistableChecker persistenceChecker;
Expand Down Expand Up @@ -317,7 +318,7 @@ public synchronized void stop()
this.storageManager = null;
this.root = null;
this.registry.reset();
this.idSetters.clear();
this.idManagers.clear();
LOG.info("Stopped storage.");
}
else
Expand All @@ -330,16 +331,20 @@ public synchronized void stop()

@Override
@SuppressWarnings("unchecked")
public <T> IdSetter<T> ensureIdSetter(final Class<T> domainClass)
public <T, ID> IdManager<T, ID> ensureIdManager(final Class<T> domainClass)
{
this.ensureEntitiesInRoot();
return (IdSetter<T>)this.idSetters.computeIfAbsent(
return (IdManager<T, ID>)this.idManagers.computeIfAbsent(
domainClass,
clazz ->
IdSetter.createIdSetter(
clazz,
id -> this.setLastId(clazz, id),
() -> this.getLastId(clazz)
new IdManager<>(
domainClass,
(IdSetter<T>)IdSetter.createIdSetter(
clazz,
id -> this.setLastId(clazz, id),
() -> this.getLastId(clazz)
),
this
)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
*/
package software.xdev.spring.data.eclipse.store.repository;

import software.xdev.spring.data.eclipse.store.repository.support.copier.id.IdSetter;
import software.xdev.spring.data.eclipse.store.repository.support.copier.id.IdManager;


public interface IdSetterProvider
public interface IdManagerProvider
{
<T> IdSetter<T> ensureIdSetter(final Class<T> domainClass);
<T, ID> IdManager<T, ID> ensureIdManager(final Class<T> domainClass);
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ protected Object getTargetRepository(@Nonnull final RepositoryInformation metada
this.storage,
this.createWorkingCopier(metadata.getDomainType(), this.storage),
metadata.getDomainType(),
this.transactionManager
this.transactionManager,
this.storage.ensureIdManager(metadata.getDomainType())
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
*/
package software.xdev.spring.data.eclipse.store.repository.support;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import jakarta.annotation.Nonnull;

Expand All @@ -34,10 +34,7 @@
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.query.FluentQuery;

import software.xdev.spring.data.eclipse.store.exceptions.FieldAccessReflectionException;
import software.xdev.spring.data.eclipse.store.exceptions.NoIdFieldFoundException;
import software.xdev.spring.data.eclipse.store.repository.EclipseStoreStorage;
import software.xdev.spring.data.eclipse.store.repository.access.modifier.FieldAccessModifier;
import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreCrudRepository;
import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreListCrudRepository;
import software.xdev.spring.data.eclipse.store.repository.interfaces.EclipseStoreListPagingAndSortingRepositoryRepository;
Expand All @@ -52,6 +49,7 @@
import software.xdev.spring.data.eclipse.store.repository.query.executors.ListQueryExecutor;
import software.xdev.spring.data.eclipse.store.repository.query.executors.PageableQueryExecutor;
import software.xdev.spring.data.eclipse.store.repository.query.executors.SingleOptionalQueryExecutor;
import software.xdev.spring.data.eclipse.store.repository.support.copier.id.IdManager;
import software.xdev.spring.data.eclipse.store.repository.support.copier.working.WorkingCopier;
import software.xdev.spring.data.eclipse.store.repository.support.copier.working.WorkingCopierResult;
import software.xdev.spring.data.eclipse.store.transactions.EclipseStoreTransaction;
Expand All @@ -72,37 +70,24 @@ public class SimpleEclipseStoreRepository<T, ID>
private final Class<T> domainClass;
private final WorkingCopier<T> copier;
private final EclipseStoreTransactionManager transactionManager;
private Field idField;
private final IdManager<T, ID> idManager;

public SimpleEclipseStoreRepository(
final EclipseStoreStorage storage,
final WorkingCopier<T> copier,
final Class<T> domainClass,
final EclipseStoreTransactionManager transactionManager)
final EclipseStoreTransactionManager transactionManager,
final IdManager<T, ID> idManager
)
{
this.storage = storage;
this.domainClass = domainClass;
this.idManager = idManager;
this.storage.registerEntity(domainClass, this);
this.copier = copier;
this.transactionManager = transactionManager;
}

public Field getIdField()
{
if(this.idField == null)
{
final Optional<Field> foundIdField = IdFieldFinder.findIdField(this.domainClass);
if(foundIdField.isEmpty())
{
throw new NoIdFieldFoundException(String.format(
"Could not find id field in class %s",
this.domainClass.getSimpleName()));
}
this.idField = foundIdField.get();
}
return this.idField;
}

@SuppressWarnings("unchecked")
public <S extends T> List<S> saveBulk(final Collection<S> entities)
{
Expand All @@ -119,9 +104,18 @@ private <S extends T> void uncachedStore(final Collection<S> entities)
{
LOG.debug("Saving {} entities...", entities.size());
}

this.checkEntityForNull(entities);
this.idManager.checkIds(entities);

Stream<S> entitiesStream = entities.stream();
if(this.isMergeParallelizable())
{
entitiesStream = entities.parallelStream();
}

final List<WorkingCopierResult<T>> results =
this.checkEntityForNull(entities)
.parallelStream()
entitiesStream
.map(this.copier::mergeBack)
.toList();
final Set<Object> nonEntitiesToStore =
Expand All @@ -145,6 +139,17 @@ private <S extends T> void uncachedStore(final Collection<S> entities)
);
}

/**
* If the entities class to merge has an id, it is not possible to parallelize the merge. To search for existing
* ids, we need a read lock. Since we are having a write lock to store the entities, we can not release the lock
* and
* would be stuck in a deadlock.
*/
private <S extends T> boolean isMergeParallelizable()
{
return !this.idManager.hasIdField();
}

@Override
@Nonnull
public <S extends T> S save(@Nonnull final S entity)
Expand Down Expand Up @@ -180,39 +185,17 @@ public <S extends T> List<S> saveAll(@Nonnull final Iterable<S> entities)
public Optional<T> findById(@Nonnull final ID id)
{
return this.storage.getReadWriteLock().read(
() -> this.storage
.getEntityList(this.domainClass)
.parallelStream()
.filter(
entity ->
{
try(final FieldAccessModifier<T> fam = FieldAccessModifier.prepareForField(
this.getIdField(),
entity))
{
if(id.equals(fam.getValueOfField(entity)))
{
return true;
}
}
catch(final Exception e)
{
throw new FieldAccessReflectionException(String.format(
FieldAccessReflectionException.COULD_NOT_READ_FIELD,
this.getIdField().getName()), e);
}
return false;
}
)
.findAny()
() -> this.idManager.findById(id)
.map(foundEntity -> this.copier.copy(foundEntity))
);
}

@Override
public boolean existsById(@Nonnull final ID id)
{
return this.findById(id).isPresent();
return this.storage.getReadWriteLock().read(
() -> this.idManager.findById(id).isPresent()
);
}

@Override
Expand All @@ -233,37 +216,7 @@ public List<T> findAllById(@Nonnull final Iterable<ID> idsToFind)
// Must get copied as one list to keep same references objects the same.
// (Example: If o1 and o2 (both part of the entity list) are referencing o3,
// o3 should be the same no matter from where it is referenced.
() -> this.copier.copy(
this.storage
.getEntityList(this.domainClass)
.parallelStream()
.filter(
entity ->
{
try(final FieldAccessModifier<T> fam = FieldAccessModifier.prepareForField(
this.getIdField(),
entity))
{
final Object idOfEntity = fam.getValueOfField(entity);
for(final ID idToFind : idsToFind)
{
if(idToFind.equals(idOfEntity))
{
return true;
}
}
}
catch(final Exception e)
{
throw new FieldAccessReflectionException(String.format(
FieldAccessReflectionException.COULD_NOT_READ_FIELD,
this.getIdField().getName()), e);
}
return false;
}
)
.toList()
)
() -> this.copier.copy(this.idManager.findAllById(idsToFind))
);
}

Expand All @@ -276,11 +229,18 @@ public long count()
@Override
public void deleteById(@Nonnull final ID id)
{
this.storage.getReadWriteLock().write(
() -> {
final Optional<T> byId = this.findById(id);
byId.ifPresent(this::delete);
}
final EclipseStoreTransaction transaction = this.transactionManager.getTransaction();
transaction.addAction(() ->
this.storage.getReadWriteLock().write(
() -> {
this
.idManager
.findById(id)
.ifPresent(
foundEntity -> this.storage.delete(this.domainClass, foundEntity)
);
}
)
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright © 2024 XDEV Software (https://xdev.software)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package software.xdev.spring.data.eclipse.store.repository.support.copier.id;

import java.util.Optional;


public interface EntityGetterById<T, ID>
{
Optional<T> findById(ID id);
}
Loading