InMemoryJpaRepository.java
package com.reallifedeveloper.tools.test.database.inmemory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.DeleteSpecification;
import org.springframework.data.jpa.domain.PredicateSpecification;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.domain.UpdateSpecification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
/**
* An implementation of the Spring Data JPA {@code JpaRepository} interface that holds entities in a map. Useful for testing.
*
* @param <T> the type of the entities handled by this repository
* @param <ID> the type of the entities' primary keys
*
* @author RealLifeDeveloper
*/
@SuppressWarnings({ "PMD.CouplingBetweenObjects", "PMD.TooManyMethods" })
public class InMemoryJpaRepository<T, ID extends Comparable<? super ID>> extends AbstractInMemoryCrudRepository<T, ID>
implements JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
private final PredicateSpecificationEvaluator<T> predicateSpecificationEvaluator = new PredicateSpecificationEvaluator<>();
/**
* Creates a new {@code InMemoryJpaRepository} with no primary key generator. If an entity with a {@code null} primary key is saved, an
* exception is thrown.
*/
public InMemoryJpaRepository() {
super();
}
/**
* Creates a new {@code InMemoryJpaRepository} with the provided primary key generator. If an entity with a {@code null} primary key is
* saved, the generator is used to create a new primary key that is stored in the entity before saving.
*
* @param primaryKeyGenerator the primary key generator to use
*/
public InMemoryJpaRepository(PrimaryKeyGenerator<ID> primaryKeyGenerator) {
super(primaryKeyGenerator);
}
/**
* Gives the {@link PredicateSpecificationEvaluator} used by this repository. This is useful for registering functions used by
* {@code PredicateSpecifications}, and could occasionally be useful to directly check if a predicate specification matches an entity.
*
* @return the {@code PredicateSpecificationEvaluator} used by this repository.
*/
public PredicateSpecificationEvaluator<T> predicateSpecificationEvaluator() {
return predicateSpecificationEvaluator;
}
/**
* {@inheritDoc}
*/
@Override
public void deleteAllInBatch() {
deleteAll();
}
/**
* {@inheritDoc}
*/
@Override
public void deleteAllInBatch(Iterable<T> entities) {
deleteAll(entities);
}
/**
* {@inheritDoc}
*/
@Override
public void deleteAllByIdInBatch(Iterable<ID> ids) {
deleteAllById(ids);
}
/**
* {@inheritDoc}
*/
@Override
public void flush() {
// Do nothing.
}
/**
* {@inheritDoc}
*/
@Override
public <S extends T> S saveAndFlush(S entity) {
S savedEntity = save(entity);
flush();
return savedEntity;
}
/**
* {@inheritDoc}
*/
@Override
public <S extends T> List<S> saveAllAndFlush(Iterable<S> entities) {
List<S> savedEntities = saveAll(entities);
flush();
return savedEntities;
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("deprecation")
public T getById(ID id) {
Optional<T> optionalElement = findById(id);
return optionalElement.orElseThrow(() -> new EntityNotFoundException("Entity with ID " + id + " not found"));
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("deprecation")
public T getOne(ID id) {
return getById(id);
}
@Override
protected boolean isIdField(Field field) {
return field.getAnnotation(Id.class) != null || field.getAnnotation(EmbeddedId.class) != null;
}
@Override
protected boolean isIdMethod(Method method) {
return method.getAnnotation(Id.class) != null || method.getAnnotation(EmbeddedId.class) != null;
}
@Override
@SuppressWarnings("unchecked")
protected Optional<Class<ID>> getCompositeIdClass(Object entity) {
IdClass idClass = entity.getClass().getAnnotation(IdClass.class);
if (idClass == null) {
return Optional.empty();
} else {
return Optional.ofNullable((Class<ID>) idClass.value());
}
}
@Override
public <S extends T, R> R findBy(Example<S> example, Function<FetchableFluentQuery<S>, R> queryFunction) {
throw new UnsupportedOperationException("findBy(Example, Function<FetchableFluentQuery, R>)");
}
@Override
public T getReferenceById(ID id) {
throw new UnsupportedOperationException("getReferenceById(ID)");
}
//
// JpaSpecificationExecutor methods
//
@Override
public long count(PredicateSpecification<T> spec) {
return findAll(spec).size();
}
@Override
public long delete(PredicateSpecification<T> spec) {
List<T> toDelete = findAll(spec);
deleteAll(toDelete);
return toDelete.size();
}
@Override
public boolean exists(PredicateSpecification<T> spec) {
return !findAll(spec).isEmpty();
}
@Override
public List<T> findAll(PredicateSpecification<T> spec) {
return predicateSpecificationEvaluator.filter(spec, findAll());
}
@Override
public <S extends T, R> R findBy(PredicateSpecification<T> spec, Function<? super SpecificationFluentQuery<S>, R> queryFunction) {
throw new UnsupportedOperationException("findBy(PredicateSpecification, Function<SpecificationFluentQuery, R>)");
}
@Override
public Optional<T> findOne(PredicateSpecification<T> spec) {
List<T> found = findAll(spec);
if (found.isEmpty()) {
return Optional.empty();
} else if (found.size() == 1) {
return Optional.of(found.getFirst());
} else {
throw new IncorrectResultSizeDataAccessException(1, found.size());
}
}
@Override
public long delete(DeleteSpecification<T> spec) {
throw new UnsupportedOperationException("delete(DeleteSpecification)");
}
@Override
public long count(Specification<T> spec) {
throw new UnsupportedOperationException("count(Specification)");
}
@Override
public boolean exists(Specification<T> spec) {
throw new UnsupportedOperationException("exists(Specification)");
}
@Override
public List<T> findAll(Specification<T> spec) {
throw new UnsupportedOperationException("findAll(Specification)");
}
@Override
public Page<T> findAll(Specification<T> spec, Pageable pageable) {
throw new UnsupportedOperationException("findAll(Specification, Pageable)");
}
@Override
public List<T> findAll(Specification<T> spec, Sort sort) {
throw new UnsupportedOperationException("findAll(Specification, Sort)");
}
@Override
public Page<T> findAll(Specification<T> spec, Specification<T> countSpec, Pageable pageable) {
throw new UnsupportedOperationException("findAll(Specification, Specification, Pageable)");
}
@Override
public <S extends T, R> R findBy(Specification<T> spec, Function<? super SpecificationFluentQuery<S>, R> queryFunction) {
throw new UnsupportedOperationException("findBy(Specification, Function<SpecificationFluentQuery, R>)");
}
@Override
public Optional<T> findOne(Specification<T> spec) {
throw new UnsupportedOperationException("findOne(Specification)");
}
@Override
public long update(UpdateSpecification<T> spec) {
throw new UnsupportedOperationException("update(UpdateSpecification)");
}
}