committed by
Jens Schauder
parent
ee6c2c89b5
commit
a6fb4df590
@@ -15,9 +15,13 @@
|
||||
*/
|
||||
package org.springframework.data.jdbc.core;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
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.relational.core.query.Query;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -27,6 +31,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Thomas Lang
|
||||
* @author Milan Milanov
|
||||
* @author Chirag Tailor
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
public interface JdbcAggregateOperations {
|
||||
|
||||
@@ -183,4 +188,54 @@ public interface JdbcAggregateOperations {
|
||||
* @since 2.0
|
||||
*/
|
||||
<T> Page<T> findAll(Class<T> domainType, Pageable pageable);
|
||||
|
||||
/**
|
||||
* Execute a {@code SELECT} query and convert the resulting item to an entity ensuring exactly one result.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityClass the entity type must not be {@literal null}.
|
||||
* @return exactly one result or {@link Optional#empty()} if no match found.
|
||||
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
|
||||
*/
|
||||
<T> Optional<T> selectOne(Query query, Class<T> entityClass);
|
||||
|
||||
/**
|
||||
* Execute a {@code SELECT} query and convert the resulting items to a {@link Iterable} that is sorted.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityClass the entity type must not be {@literal null}.
|
||||
* @param sort the sorting that should be used on the result.
|
||||
* @return a non-null sorted list with all the matching results.
|
||||
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
|
||||
*/
|
||||
<T> Iterable<T> select(Query query, Class<T> entityClass, Sort sort);
|
||||
|
||||
/**
|
||||
* Determine whether there are aggregates that match the {@link Query}
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityClass the entity type must not be {@literal null}.
|
||||
* @return {@literal true} if the object exists.
|
||||
*/
|
||||
<T> boolean exists(Query query, Class<T> entityClass);
|
||||
|
||||
/**
|
||||
* Counts the number of aggregates of a given type that match the given <code>query</code>.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityClass the entity type must not be {@literal null}.
|
||||
* @return the number of instances stored in the database. Guaranteed to be not {@code null}.
|
||||
*/
|
||||
<T> long count(Query query, Class<T> entityClass);
|
||||
|
||||
/**
|
||||
* Returns a {@link Page} of entities matching the given {@link Query}. In case no match could be found, an empty
|
||||
* {@link Page} is returned.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityClass the entity type must not be {@literal null}.
|
||||
* @param pageable can be null.
|
||||
* @return a {@link Page} of entities matching the given {@link Example}.
|
||||
*/
|
||||
<T> Page<T> select(Query query, Class<T> entityClass, Pageable pageable);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
@@ -46,6 +47,7 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.mapping.event.*;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.support.PageableExecutionUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -61,6 +63,7 @@ import org.springframework.util.ClassUtils;
|
||||
* @author Milan Milanov
|
||||
* @author Myeonghyeon Lee
|
||||
* @author Chirag Tailor
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
|
||||
@@ -71,6 +74,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
|
||||
private final DataAccessStrategy accessStrategy;
|
||||
private final AggregateChangeExecutor executor;
|
||||
|
||||
private final JdbcConverter converter;
|
||||
|
||||
private EntityCallbacks entityCallbacks = EntityCallbacks.create();
|
||||
@@ -238,6 +242,38 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
|
||||
return PageableExecutionUtils.getPage(content, pageable, () -> accessStrategy.count(domainType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> selectOne(Query query, Class<T> entityClass) {
|
||||
return accessStrategy.selectOne(query, entityClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Iterable<T> select(Query query, Class<T> entityClass, Sort sort) {
|
||||
return accessStrategy.select(query, entityClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean exists(Query query, Class<T> entityClass) {
|
||||
return accessStrategy.exists(query, entityClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> long count(Query query, Class<T> entityClass) {
|
||||
return accessStrategy.count(query, entityClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Page<T> select(Query query, Class<T> entityClass, Pageable pageable) {
|
||||
Iterable<T> items = triggerAfterConvert(accessStrategy.select(query, entityClass, pageable));
|
||||
List<T> content = StreamSupport.stream(items.spliterator(), false).collect(Collectors.toList());
|
||||
|
||||
return PageableExecutionUtils.getPage(content, pageable, () -> accessStrategy.count(query, entityClass));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.JdbcAggregateOperations#findAll(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> Iterable<T> findAll(Class<T> domainType) {
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
@@ -25,6 +26,7 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.conversion.IdValueSource;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.core.sql.LockMode;
|
||||
|
||||
import static java.lang.Boolean.*;
|
||||
@@ -39,6 +41,7 @@ import static java.lang.Boolean.*;
|
||||
* @author Milan Milanov
|
||||
* @author Myeonghyeon Lee
|
||||
* @author Chirag Tailor
|
||||
* @author Diego Krupitza
|
||||
* @since 1.1
|
||||
*/
|
||||
public class CascadingDataAccessStrategy implements DataAccessStrategy {
|
||||
@@ -160,6 +163,31 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy {
|
||||
return collect(das -> das.findAll(domainType, pageable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> selectOne(Query query, Class<T> probeType) {
|
||||
return collect(das -> das.selectOne(query, probeType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Iterable<T> select(Query query, Class<T> probeType) {
|
||||
return collect(das -> das.select(query, probeType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Iterable<T> select(Query query, Class<T> probeType, Pageable pageable) {
|
||||
return collect(das -> das.select(query, probeType, pageable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean exists(Query query, Class<T> probeType) {
|
||||
return collect(das -> das.exists(query, probeType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> long count(Query query, Class<T> probeType) {
|
||||
return collect(das -> das.count(query, probeType));
|
||||
}
|
||||
|
||||
private <T> T collect(Function<DataAccessStrategy, T> function) {
|
||||
|
||||
return strategies.stream().collect(new FunctionCollector<>(function));
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -25,6 +26,7 @@ import org.springframework.data.jdbc.core.JdbcAggregateOperations;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.conversion.IdValueSource;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.core.sql.LockMode;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -38,6 +40,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Milan Milanov
|
||||
* @author Myeonghyeon Lee
|
||||
* @author Chirag Tailor
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
public interface DataAccessStrategy extends RelationResolver {
|
||||
|
||||
@@ -281,4 +284,54 @@ public interface DataAccessStrategy extends RelationResolver {
|
||||
* @since 2.0
|
||||
*/
|
||||
<T> Iterable<T> findAll(Class<T> domainType, Pageable pageable);
|
||||
|
||||
/**
|
||||
* Execute a {@code SELECT} query and convert the resulting item to an entity ensuring exactly one result.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param probeType the type of entities. Must not be {@code null}.
|
||||
* @return exactly one result or {@link Optional#empty()} if no match found.
|
||||
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
|
||||
*/
|
||||
<T> Optional<T> selectOne(Query query, Class<T> probeType);
|
||||
|
||||
/**
|
||||
* Execute a {@code SELECT} query and convert the resulting items to a {@link Iterable}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param probeType the type of entities. Must not be {@code null}.
|
||||
* @return a non-null list with all the matching results.
|
||||
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
|
||||
*/
|
||||
<T> Iterable<T> select(Query query, Class<T> probeType);
|
||||
|
||||
/**
|
||||
* Execute a {@code SELECT} query and convert the resulting items to a {@link Iterable}. Applies the {@link Pageable}
|
||||
* to the result.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param probeType the type of entities. Must not be {@literal null}.
|
||||
* @param pageable the pagination that should be applied. Must not be {@literal null}.
|
||||
* @return a non-null list with all the matching results.
|
||||
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
|
||||
*/
|
||||
<T> Iterable<T> select(Query query, Class<T> probeType, Pageable pageable);
|
||||
|
||||
/**
|
||||
* Determine whether there is an aggregate of type <code>probeType</code> that matches the provided {@link Query}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param probeType the type of entities. Must not be {@code null}.
|
||||
* @return {@literal true} if the object exists.
|
||||
*/
|
||||
<T> boolean exists(Query query, Class<T> probeType);
|
||||
|
||||
/**
|
||||
* Counts the rows in the table representing the given probe type, that match the given <code>query</code>.
|
||||
*
|
||||
* @param probeType the probe type for which to count the elements. Must not be {@code null}.
|
||||
* @param query the query which elements have to match.
|
||||
* @return the count. Guaranteed to be not {@code null}.
|
||||
*/
|
||||
<T> long count(Query query, Class<T> probeType);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ import static org.springframework.data.jdbc.core.convert.SqlGenerator.*;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
@@ -32,10 +34,12 @@ import org.springframework.data.relational.core.mapping.PersistentPropertyPathEx
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.core.sql.IdentifierProcessing;
|
||||
import org.springframework.data.relational.core.sql.LockMode;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -56,6 +60,7 @@ import org.springframework.util.Assert;
|
||||
* @author Yunyoung LEE
|
||||
* @author Radim Tlusty
|
||||
* @author Chirag Tailor
|
||||
* @author Diego Krupitza
|
||||
* @since 1.1
|
||||
*/
|
||||
public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
@@ -336,6 +341,61 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
return operations.query(sql(domainType).getFindAll(pageable), (RowMapper<T>) getEntityRowMapper(domainType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> selectOne(Query query, Class<T> probeType) {
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
|
||||
String sqlQuery = sql(probeType).selectByQuery(query, parameterSource);
|
||||
|
||||
T foundObject;
|
||||
try {
|
||||
foundObject = operations.queryForObject(sqlQuery, parameterSource, (RowMapper<T>) getEntityRowMapper(probeType));
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
foundObject = null;
|
||||
}
|
||||
|
||||
return Optional.ofNullable(foundObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Iterable<T> select(Query query, Class<T> probeType) {
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
|
||||
String sqlQuery = sql(probeType).selectByQuery(query, parameterSource);
|
||||
|
||||
return operations.query(sqlQuery, parameterSource, (RowMapper<T>) getEntityRowMapper(probeType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Iterable<T> select(Query query, Class<T> probeType, Pageable pageable) {
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
|
||||
String sqlQuery = sql(probeType).selectByQuery(query, parameterSource, pageable);
|
||||
|
||||
return operations.query(sqlQuery, parameterSource, (RowMapper<T>) getEntityRowMapper(probeType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean exists(Query query, Class<T> probeType) {
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
|
||||
|
||||
String sqlQuery = sql(probeType).existsByQuery(query, parameterSource);
|
||||
|
||||
Boolean result = operations.queryForObject(sqlQuery, parameterSource, Boolean.class);
|
||||
Assert.notNull(result, "The result of an exists query must not be null");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> long count(Query query, Class<T> probeType) {
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
|
||||
String sqlQuery = sql(probeType).countByQuery(query, parameterSource);
|
||||
|
||||
Long result = operations.queryForObject(sqlQuery, parameterSource, Long.class);
|
||||
|
||||
Assert.notNull(result, "The result of a count query must not be null.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private EntityRowMapper<?> getEntityRowMapper(Class<?> domainType) {
|
||||
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), converter);
|
||||
}
|
||||
|
||||
@@ -22,9 +22,12 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.conversion.IdValueSource;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.core.sql.LockMode;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Delegates all method calls to an instance set after construction. This is useful for {@link DataAccessStrategy}s with
|
||||
* cyclic dependencies.
|
||||
@@ -34,6 +37,7 @@ import org.springframework.util.Assert;
|
||||
* @author Milan Milanov
|
||||
* @author Myeonghyeon Lee
|
||||
* @author Chirag Tailor
|
||||
* @author Diego Krupitza
|
||||
* @since 1.1
|
||||
*/
|
||||
public class DelegatingDataAccessStrategy implements DataAccessStrategy {
|
||||
@@ -155,6 +159,31 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy {
|
||||
return delegate.findAll(domainType, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> selectOne(Query query, Class<T> probeType) {
|
||||
return delegate.selectOne(query, probeType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Iterable<T> select(Query query, Class<T> probeType) {
|
||||
return delegate.select(query, probeType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Iterable<T> select(Query query, Class<T> probeType, Pageable pageable) {
|
||||
return delegate.select(query, probeType, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean exists(Query query, Class<T> probeType) {
|
||||
return delegate.exists(query, probeType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> long count(Query query, Class<T> probeType) {
|
||||
return delegate.count(query, probeType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called exactly once before calling any of the other methods.
|
||||
*
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jdbc.repository.query.QueryMapper;
|
||||
import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
@@ -31,10 +32,13 @@ import org.springframework.data.relational.core.mapping.PersistentPropertyPathEx
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.query.CriteriaDefinition;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.core.sql.*;
|
||||
import org.springframework.data.relational.core.sql.render.RenderContext;
|
||||
import org.springframework.data.relational.core.sql.render.SqlRenderer;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -52,6 +56,7 @@ import org.springframework.util.Assert;
|
||||
* @author Myeonghyeon Lee
|
||||
* @author Mikhail Polivakha
|
||||
* @author Chirag Tailor
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
class SqlGenerator {
|
||||
|
||||
@@ -83,6 +88,7 @@ class SqlGenerator {
|
||||
private final Lazy<String> deleteByIdInSql = Lazy.of(this::createDeleteByIdInSql);
|
||||
private final Lazy<String> deleteByIdAndVersionSql = Lazy.of(this::createDeleteByIdAndVersionSql);
|
||||
private final Lazy<String> deleteByListSql = Lazy.of(this::createDeleteByListSql);
|
||||
private final QueryMapper queryMapper;
|
||||
|
||||
/**
|
||||
* Create a new {@link SqlGenerator} given {@link RelationalMappingContext} and {@link RelationalPersistentEntity}.
|
||||
@@ -101,6 +107,7 @@ class SqlGenerator {
|
||||
this.renderContext = new RenderContextFactory(dialect).createRenderContext();
|
||||
this.sqlRenderer = SqlRenderer.create(renderContext);
|
||||
this.columns = new Columns(entity, mappingContext, converter);
|
||||
this.queryMapper = new QueryMapper(dialect, converter);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -766,6 +773,159 @@ class SqlGenerator {
|
||||
return OrderByField.from(column, order.getDirection()).withNullHandling(order.getNullHandling());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a single sql query that performs select based on the provided query. Additional the bindings for the
|
||||
* where clause are stored after execution into the <code>parameterSource</code>
|
||||
*
|
||||
* @param query the query to base the select on. Must not be null
|
||||
* @param parameterSource the source for holding the bindings
|
||||
* @return a non null query string.
|
||||
*/
|
||||
public String selectByQuery(Query query, MapSqlParameterSource parameterSource) {
|
||||
|
||||
Assert.notNull(parameterSource, "parameterSource must not be null");
|
||||
|
||||
SelectBuilder.SelectWhere selectBuilder = selectBuilder();
|
||||
|
||||
Select select = applyQueryOnSelect(query, parameterSource, selectBuilder) //
|
||||
.build();
|
||||
|
||||
return render(select);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a single sql query that performs select based on the provided query and pagination information.
|
||||
* Additional the bindings for the where clause are stored after execution into the <code>parameterSource</code>
|
||||
*
|
||||
* @param query the query to base the select on. Must not be null.
|
||||
* @param pageable the pageable to perform on the select.
|
||||
* @param parameterSource the source for holding the bindings.
|
||||
* @return a non null query string.
|
||||
*/
|
||||
public String selectByQuery(Query query, MapSqlParameterSource parameterSource, Pageable pageable) {
|
||||
|
||||
Assert.notNull(parameterSource, "parameterSource must not be null");
|
||||
|
||||
SelectBuilder.SelectWhere selectBuilder = selectBuilder();
|
||||
|
||||
// first apply query and then pagination. This means possible query sorting and limiting might be overwritten by the
|
||||
// pagination. This is desired.
|
||||
SelectBuilder.SelectOrdered selectOrdered = applyQueryOnSelect(query, parameterSource, selectBuilder);
|
||||
selectOrdered = applyPagination(pageable, selectOrdered);
|
||||
selectOrdered = selectOrdered.orderBy(extractOrderByFields(pageable.getSort()));
|
||||
|
||||
Select select = selectOrdered.build();
|
||||
return render(select);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a single sql query that performs select count based on the provided query for checking existence.
|
||||
* Additional the bindings for the where clause are stored after execution into the <code>parameterSource</code>
|
||||
*
|
||||
* @param query the query to base the select on. Must not be null
|
||||
* @param parameterSource the source for holding the bindings
|
||||
* @return a non null query string.
|
||||
*/
|
||||
public String existsByQuery(Query query, MapSqlParameterSource parameterSource) {
|
||||
|
||||
Expression idColumn = getIdColumn();
|
||||
SelectBuilder.SelectJoin baseSelect = getSelectCountWithExpression(idColumn);
|
||||
|
||||
Select select = applyQueryOnSelect(query, parameterSource, (SelectBuilder.SelectWhere) baseSelect) //
|
||||
.build();
|
||||
|
||||
return render(select);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a single sql query that performs select count based on the provided query. Additional the bindings for
|
||||
* the where clause are stored after execution into the <code>parameterSource</code>
|
||||
*
|
||||
* @param query the query to base the select on. Must not be null
|
||||
* @param parameterSource the source for holding the bindings
|
||||
* @return a non null query string.
|
||||
*/
|
||||
public String countByQuery(Query query, MapSqlParameterSource parameterSource) {
|
||||
|
||||
Expression countExpression = Expressions.just("1");
|
||||
SelectBuilder.SelectJoin baseSelect = getSelectCountWithExpression(countExpression);
|
||||
|
||||
Select select = applyQueryOnSelect(query, parameterSource, (SelectBuilder.SelectWhere) baseSelect) //
|
||||
.build();
|
||||
|
||||
return render(select);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a {@link org.springframework.data.relational.core.sql.SelectBuilder.SelectJoin} with a
|
||||
* <code>COUNT(...)</code> where the <code>countExpressions</code> are the parameters of the count.
|
||||
*
|
||||
* @param countExpressions the expression to use as count parameter.
|
||||
* @return a non-null {@link org.springframework.data.relational.core.sql.SelectBuilder.SelectJoin} that joins all the
|
||||
* columns and has only a count in the projection of the select.
|
||||
*/
|
||||
private SelectBuilder.SelectJoin getSelectCountWithExpression(Expression... countExpressions) {
|
||||
|
||||
Assert.notNull(countExpressions, "countExpressions must not be null");
|
||||
Assert.state(countExpressions.length >= 1, "countExpressions must contain at least one expression");
|
||||
|
||||
Table table = getTable();
|
||||
SelectBuilder.SelectFromAndJoin selectBuilder = StatementBuilder //
|
||||
.select(Functions.count(countExpressions)) //
|
||||
.from(table);//
|
||||
|
||||
SelectBuilder.SelectJoin baseSelect = selectBuilder;
|
||||
|
||||
// add possible joins
|
||||
for (PersistentPropertyPath<RelationalPersistentProperty> path : mappingContext
|
||||
.findPersistentPropertyPaths(entity.getType(), p -> true)) {
|
||||
|
||||
PersistentPropertyPathExtension extPath = new PersistentPropertyPathExtension(mappingContext, path);
|
||||
|
||||
// add a join if necessary
|
||||
Join join = getJoin(extPath);
|
||||
if (join != null) {
|
||||
baseSelect = baseSelect.leftOuterJoin(join.joinTable).on(join.joinColumn).equals(join.parentId);
|
||||
}
|
||||
}
|
||||
return baseSelect;
|
||||
}
|
||||
|
||||
private SelectBuilder.SelectOrdered applyQueryOnSelect(Query query, MapSqlParameterSource parameterSource,
|
||||
SelectBuilder.SelectWhere selectBuilder) {
|
||||
|
||||
Table table = Table.create(this.entity.getTableName());
|
||||
|
||||
SelectBuilder.SelectOrdered selectOrdered = query //
|
||||
.getCriteria() //
|
||||
.map(item -> this.applyCriteria(item, selectBuilder, parameterSource, table)) //
|
||||
.orElse(selectBuilder);
|
||||
|
||||
if (query.isSorted()) {
|
||||
List<OrderByField> sort = this.queryMapper.getMappedSort(table, query.getSort(), entity);
|
||||
selectOrdered = selectBuilder.orderBy(sort);
|
||||
}
|
||||
|
||||
SelectBuilder.SelectLimitOffset limitable = (SelectBuilder.SelectLimitOffset) selectOrdered;
|
||||
|
||||
if (query.getLimit() > 0) {
|
||||
limitable = limitable.limit(query.getLimit());
|
||||
}
|
||||
|
||||
if (query.getOffset() > 0) {
|
||||
limitable = limitable.offset(query.getOffset());
|
||||
}
|
||||
return (SelectBuilder.SelectOrdered) limitable;
|
||||
}
|
||||
|
||||
SelectBuilder.SelectOrdered applyCriteria(@Nullable CriteriaDefinition criteria,
|
||||
SelectBuilder.SelectWhere whereBuilder, MapSqlParameterSource parameterSource, Table table) {
|
||||
|
||||
return criteria != null //
|
||||
? whereBuilder.where(queryMapper.getMappedObject(parameterSource, criteria, table, entity)) //
|
||||
: whereBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object representing a {@code JOIN} association.
|
||||
*/
|
||||
|
||||
@@ -21,7 +21,11 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.ibatis.session.SqlSession;
|
||||
import org.mybatis.spring.SqlSessionTemplate;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
@@ -34,6 +38,7 @@ import org.springframework.data.relational.core.conversion.IdValueSource;
|
||||
import org.springframework.data.relational.core.dialect.Dialect;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.core.sql.IdentifierProcessing;
|
||||
import org.springframework.data.relational.core.sql.LockMode;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
@@ -322,6 +327,35 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
|
||||
new MyBatisContext(null, null, domainType, additionalContext));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> selectOne(Query query, Class<T> probeType) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Iterable<T> select(Query query, Class<T> probeType) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Iterable<T> select(Query query, Class<T> probeType, Pageable pageable) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean exists(Query query, Class<T> probeType) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> long count(Query query, Class<T> probeType) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.core.DataAccessStrategy#count(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public long count(Class<?> domainType) {
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ import org.springframework.util.ClassUtils;
|
||||
* @author Jens Schauder
|
||||
* @since 2.0
|
||||
*/
|
||||
class QueryMapper {
|
||||
public class QueryMapper {
|
||||
|
||||
private final JdbcConverter converter;
|
||||
private final Dialect dialect;
|
||||
@@ -71,7 +71,7 @@ class QueryMapper {
|
||||
* @param converter must not be {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
QueryMapper(Dialect dialect, JdbcConverter converter) {
|
||||
public QueryMapper(Dialect dialect, JdbcConverter converter) {
|
||||
|
||||
Assert.notNull(dialect, "Dialect must not be null");
|
||||
Assert.notNull(converter, "JdbcConverter must not be null");
|
||||
@@ -88,7 +88,7 @@ class QueryMapper {
|
||||
* @param entity related {@link RelationalPersistentEntity}, can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
List<OrderByField> getMappedSort(Table table, Sort sort, @Nullable RelationalPersistentEntity<?> entity) {
|
||||
public List<OrderByField> getMappedSort(Table table, Sort sort, @Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
List<OrderByField> mappedOrder = new ArrayList<>();
|
||||
|
||||
@@ -157,7 +157,7 @@ class QueryMapper {
|
||||
* @param entity related {@link RelationalPersistentEntity}, can be {@literal null}.
|
||||
* @return the mapped {@link Condition}.
|
||||
*/
|
||||
Condition getMappedObject(MapSqlParameterSource parameterSource, CriteriaDefinition criteria, Table table,
|
||||
public Condition getMappedObject(MapSqlParameterSource parameterSource, CriteriaDefinition criteria, Table table,
|
||||
@Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
Assert.notNull(parameterSource, "MapSqlParameterSource must not be null");
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.jdbc.repository.support;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
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.jdbc.core.JdbcAggregateOperations;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.repository.query.RelationalExampleMapper;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery} using {@link Example}.
|
||||
*
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<S, R> {
|
||||
|
||||
private final RelationalExampleMapper exampleMapper;
|
||||
private final JdbcAggregateOperations entityOperations;
|
||||
|
||||
FetchableFluentQueryByExample(Example<S> example, Class<R> resultType, RelationalExampleMapper exampleMapper,
|
||||
JdbcAggregateOperations entityOperations) {
|
||||
this(example, Sort.unsorted(), resultType, Collections.emptyList(), exampleMapper, entityOperations);
|
||||
}
|
||||
|
||||
FetchableFluentQueryByExample(Example<S> example, Sort sort, Class<R> resultType, List<String> fieldsToInclude,
|
||||
RelationalExampleMapper exampleMapper, JdbcAggregateOperations entityOperations) {
|
||||
super(example, sort, resultType, fieldsToInclude);
|
||||
this.exampleMapper = exampleMapper;
|
||||
this.entityOperations = entityOperations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R oneValue() {
|
||||
return this.entityOperations.selectOne(createQuery(), getExampleType())
|
||||
.map(item -> this.getConversionFunction().apply(item)).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public R firstValue() {
|
||||
return this.getConversionFunction()
|
||||
.apply(this.entityOperations.select(createQuery(), getExampleType(), getSort()).iterator().next());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<R> all() {
|
||||
return StreamSupport
|
||||
.stream(this.entityOperations.select(createQuery(), getExampleType(), getSort()).spliterator(), false)
|
||||
.map(item -> this.getConversionFunction().apply(item)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<R> page(Pageable pageable) {
|
||||
return this.entityOperations.select(createQuery(p -> p.with(pageable)), getExampleType(), pageable)
|
||||
.map(item -> this.getConversionFunction().apply(item));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<R> stream() {
|
||||
return StreamSupport
|
||||
.stream(this.entityOperations.select(createQuery(), getExampleType(), getSort()).spliterator(), false)
|
||||
.map(item -> this.getConversionFunction().apply(item));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count() {
|
||||
return this.entityOperations.count(createQuery(), getExampleType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return this.entityOperations.exists(createQuery(), getExampleType());
|
||||
}
|
||||
|
||||
private Query createQuery() {
|
||||
return createQuery(UnaryOperator.identity());
|
||||
}
|
||||
|
||||
private Query createQuery(UnaryOperator<Query> queryCustomizer) {
|
||||
|
||||
Query query = exampleMapper.getMappedExample(getExample());
|
||||
|
||||
if (getSort().isSorted()) {
|
||||
query = query.sort(getSort());
|
||||
}
|
||||
|
||||
if (!getFieldsToInclude().isEmpty()) {
|
||||
query = query.columns(getFieldsToInclude().toArray(new String[0]));
|
||||
}
|
||||
|
||||
query = queryCustomizer.apply(query);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <R> FluentQuerySupport<S, R> create(Example<S> example, Sort sort, Class<R> resultType,
|
||||
List<String> fieldsToInclude) {
|
||||
return new FetchableFluentQueryByExample<>(example, sort, resultType, fieldsToInclude, this.exampleMapper,
|
||||
this.entityOperations);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.data.jdbc.repository.support;
|
||||
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.query.FluentQuery;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Support class for {@link FluentQuery.FetchableFluentQuery} implementations.
|
||||
*
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
abstract class FluentQuerySupport<S, R> implements FluentQuery.FetchableFluentQuery<R> {
|
||||
|
||||
private final Example<S> example;
|
||||
private final Sort sort;
|
||||
private final Class<R> resultType;
|
||||
private final List<String> fieldsToInclude;
|
||||
|
||||
private final SpelAwareProxyProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory();
|
||||
|
||||
FluentQuerySupport(Example<S> example, Sort sort, Class<R> resultType, List<String> fieldsToInclude) {
|
||||
this.example = example;
|
||||
this.sort = sort;
|
||||
this.resultType = resultType;
|
||||
this.fieldsToInclude = fieldsToInclude;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#sortBy(org.springframework.data.domain.Sort)
|
||||
*/
|
||||
@Override
|
||||
public FetchableFluentQuery<R> sortBy(Sort sort) {
|
||||
|
||||
Assert.notNull(sort, "Sort must not be null!");
|
||||
|
||||
return create(example, sort, resultType, fieldsToInclude);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#as(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <R> FetchableFluentQuery<R> as(Class<R> projection) {
|
||||
|
||||
Assert.notNull(projection, "Projection target type must not be null!");
|
||||
|
||||
return create(example, sort, projection, fieldsToInclude);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery#project(java.util.Collection)
|
||||
*/
|
||||
@Override
|
||||
public FetchableFluentQuery<R> project(Collection<String> properties) {
|
||||
|
||||
Assert.notNull(properties, "Projection properties must not be null!");
|
||||
|
||||
return create(example, sort, resultType, new ArrayList<>(properties));
|
||||
}
|
||||
|
||||
protected abstract <R> FluentQuerySupport<S, R> create(Example<S> example, Sort sort, Class<R> resultType,
|
||||
List<String> fieldsToInclude);
|
||||
|
||||
Class<S> getExampleType() {
|
||||
return this.example.getProbeType();
|
||||
}
|
||||
|
||||
Example<S> getExample() {
|
||||
return this.example;
|
||||
}
|
||||
|
||||
Sort getSort() {
|
||||
return sort;
|
||||
}
|
||||
|
||||
Class<R> getResultType() {
|
||||
return resultType;
|
||||
}
|
||||
|
||||
List<String> getFieldsToInclude() {
|
||||
return fieldsToInclude;
|
||||
}
|
||||
|
||||
private Function<Object, R> getConversionFunction(Class<S> inputType, Class<R> targetType) {
|
||||
|
||||
if (targetType.isAssignableFrom(inputType)) {
|
||||
return (Function<Object, R>) Function.identity();
|
||||
}
|
||||
|
||||
if (targetType.isInterface()) {
|
||||
return o -> projectionFactory.createProjection(targetType, o);
|
||||
}
|
||||
|
||||
return o -> DefaultConversionService.getSharedInstance().convert(o, targetType);
|
||||
}
|
||||
|
||||
protected Function<Object, R> getConversionFunction() {
|
||||
return getConversionFunction(this.example.getProbeType(), getResultType());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -122,7 +122,8 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
|
||||
RelationalPersistentEntity<?> persistentEntity = context
|
||||
.getRequiredPersistentEntity(repositoryInformation.getDomainType());
|
||||
|
||||
return getTargetRepositoryViaReflection(repositoryInformation.getRepositoryBaseClass(), template, persistentEntity);
|
||||
return getTargetRepositoryViaReflection(repositoryInformation, template, persistentEntity,
|
||||
converter);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,14 +16,22 @@
|
||||
package org.springframework.data.jdbc.repository.support;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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.jdbc.core.JdbcAggregateOperations;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.relational.repository.query.RelationalExampleMapper;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.data.repository.query.FluentQuery;
|
||||
import org.springframework.data.repository.query.QueryByExampleExecutor;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -34,20 +42,25 @@ import org.springframework.util.Assert;
|
||||
* @author Oliver Gierke
|
||||
* @author Milan Milanov
|
||||
* @author Chirag Tailor
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public class SimpleJdbcRepository<T, ID> implements CrudRepository<T,ID>, PagingAndSortingRepository<T, ID> {
|
||||
public class SimpleJdbcRepository<T, ID>
|
||||
implements CrudRepository<T, ID>, PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
|
||||
|
||||
private final JdbcAggregateOperations entityOperations;
|
||||
private final PersistentEntity<T, ?> entity;
|
||||
private final RelationalExampleMapper exampleMapper;
|
||||
|
||||
public SimpleJdbcRepository(JdbcAggregateOperations entityOperations,PersistentEntity<T, ?> entity) {
|
||||
public SimpleJdbcRepository(JdbcAggregateOperations entityOperations, PersistentEntity<T, ?> entity,
|
||||
JdbcConverter converter) {
|
||||
|
||||
Assert.notNull(entityOperations, "EntityOperations must not be null");
|
||||
Assert.notNull(entity, "Entity must not be null");
|
||||
|
||||
this.entityOperations = entityOperations;
|
||||
this.entity = entity;
|
||||
this.exampleMapper = new RelationalExampleMapper(converter.getMappingContext());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -126,4 +139,56 @@ public class SimpleJdbcRepository<T, ID> implements CrudRepository<T,ID>, Paging
|
||||
return entityOperations.findAll(entity.getType(), pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Optional<S> findOne(Example<S> example) {
|
||||
Assert.notNull(example, "Example must not be null!");
|
||||
return this.entityOperations.selectOne(this.exampleMapper.getMappedExample(example), example.getProbeType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Iterable<S> findAll(Example<S> example) {
|
||||
Assert.notNull(example, "Example must not be null!");
|
||||
|
||||
return findAll(example, Sort.unsorted());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Iterable<S> findAll(Example<S> example, Sort sort) {
|
||||
Assert.notNull(example, "Example must not be null!");
|
||||
Assert.notNull(sort, "Sort must not be null!");
|
||||
|
||||
return this.entityOperations.select(this.exampleMapper.getMappedExample(example), example.getProbeType(), sort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {
|
||||
Assert.notNull(example, "Example must not be null!");
|
||||
|
||||
return this.entityOperations.select(this.exampleMapper.getMappedExample(example), example.getProbeType(), pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> long count(Example<S> example) {
|
||||
Assert.notNull(example, "Example must not be null!");
|
||||
|
||||
return this.entityOperations.count(this.exampleMapper.getMappedExample(example), example.getProbeType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> boolean exists(Example<S> example) {
|
||||
Assert.notNull(example, "Example must not be null!");
|
||||
|
||||
return this.entityOperations.exists(this.exampleMapper.getMappedExample(example), example.getProbeType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T, R> R findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
|
||||
Assert.notNull(example, "Sample must not be null!");
|
||||
Assert.notNull(queryFunction, "Query function must not be null!");
|
||||
|
||||
FluentQuery.FetchableFluentQuery<S> fluentQuery = new FetchableFluentQueryByExample<>(example,
|
||||
example.getProbeType(), this.exampleMapper, this.entityOperations);
|
||||
|
||||
return queryFunction.apply(fluentQuery);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,10 +46,13 @@ import org.springframework.data.relational.core.mapping.PersistentPropertyPathEx
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.query.Criteria;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.core.sql.Aliased;
|
||||
import org.springframework.data.relational.core.sql.LockMode;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.data.relational.core.sql.Table;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -65,6 +68,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Myeonghyeon Lee
|
||||
* @author Mikhail Polivakha
|
||||
* @author Chirag Tailor
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
@SuppressWarnings("Convert2MethodRef")
|
||||
class SqlGeneratorUnitTests {
|
||||
@@ -739,6 +743,92 @@ class SqlGeneratorUnitTests {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Test
|
||||
void selectByQueryValidTest() {
|
||||
final SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class);
|
||||
|
||||
DummyEntity probe = new DummyEntity();
|
||||
probe.name = "Diego";
|
||||
|
||||
Criteria criteria = Criteria.where("name").is(probe.name);
|
||||
Query query = Query.query(criteria);
|
||||
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
|
||||
|
||||
String generatedSQL = sqlGenerator.selectByQuery(query, parameterSource);
|
||||
assertThat(generatedSQL).isNotNull().contains(":x_name");
|
||||
|
||||
assertThat(parameterSource.getValues()) //
|
||||
.containsOnly(entry("x_name", probe.name));
|
||||
}
|
||||
|
||||
@Test
|
||||
void existsByQuerySimpleValidTest() {
|
||||
final SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class);
|
||||
|
||||
DummyEntity probe = new DummyEntity();
|
||||
probe.name = "Diego";
|
||||
|
||||
Criteria criteria = Criteria.where("name").is(probe.name);
|
||||
Query query = Query.query(criteria);
|
||||
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
|
||||
|
||||
String generatedSQL = sqlGenerator.existsByQuery(query, parameterSource);
|
||||
assertThat(generatedSQL).isNotNull().contains(":x_name");
|
||||
|
||||
assertThat(parameterSource.getValues()) //
|
||||
.containsOnly(entry("x_name", probe.name));
|
||||
}
|
||||
|
||||
@Test
|
||||
void countByQuerySimpleValidTest() {
|
||||
final SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class);
|
||||
|
||||
DummyEntity probe = new DummyEntity();
|
||||
probe.name = "Diego";
|
||||
|
||||
Criteria criteria = Criteria.where("name").is(probe.name);
|
||||
Query query = Query.query(criteria);
|
||||
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
|
||||
|
||||
String generatedSQL = sqlGenerator.countByQuery(query, parameterSource);
|
||||
assertThat(generatedSQL) //
|
||||
.isNotNull() //
|
||||
.containsIgnoringCase("COUNT(1)") //
|
||||
.contains(":x_name");
|
||||
|
||||
assertThat(parameterSource.getValues()) //
|
||||
.containsOnly(entry("x_name", probe.name));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectByQueryPaginationValidTest() {
|
||||
final SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class);
|
||||
|
||||
DummyEntity probe = new DummyEntity();
|
||||
probe.name = "Diego";
|
||||
|
||||
Criteria criteria = Criteria.where("name").is(probe.name);
|
||||
Query query = Query.query(criteria);
|
||||
|
||||
PageRequest pageRequest = PageRequest.of(2, 1, Sort.by(Sort.Order.asc("name")));
|
||||
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
|
||||
|
||||
String generatedSQL = sqlGenerator.selectByQuery(query, parameterSource, pageRequest);
|
||||
assertThat(generatedSQL) //
|
||||
.isNotNull() //
|
||||
.contains(":x_name") //
|
||||
.containsIgnoringCase("ORDER BY dummy_entity.x_name ASC") //
|
||||
.containsIgnoringCase("LIMIT 1") //
|
||||
.containsIgnoringCase("OFFSET 2 LIMIT 1");
|
||||
|
||||
assertThat(parameterSource.getValues()) //
|
||||
.containsOnly(entry("x_name", probe.name));
|
||||
}
|
||||
|
||||
private SqlIdentifier getAlias(Object maybeAliased) {
|
||||
|
||||
if (maybeAliased instanceof Aliased) {
|
||||
@@ -761,7 +851,8 @@ class SqlGeneratorUnitTests {
|
||||
@SuppressWarnings("unused")
|
||||
static class DummyEntity {
|
||||
|
||||
@Column("id1") @Id Long id;
|
||||
@Column("id1")
|
||||
@Id Long id;
|
||||
String name;
|
||||
ReferencedEntity ref;
|
||||
Set<Element> elements;
|
||||
@@ -838,7 +929,8 @@ class SqlGeneratorUnitTests {
|
||||
|
||||
// these column names behave like single double quote in the name since the get quoted and then doubling the double
|
||||
// quote escapes it.
|
||||
@Id @Column("test\"\"_@id") Long id;
|
||||
@Id
|
||||
@Column("test\"\"_@id") Long id;
|
||||
@Column("test\"\"_@123") String name;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,12 +33,18 @@ import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.PropertiesFactoryBean;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
@@ -46,11 +52,14 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jdbc.core.mapping.AggregateReference;
|
||||
import org.springframework.data.jdbc.repository.query.Modifying;
|
||||
import org.springframework.data.jdbc.repository.query.Query;
|
||||
@@ -68,7 +77,9 @@ import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.ListCrudRepository;
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
|
||||
import org.springframework.data.repository.query.FluentQuery;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.repository.query.QueryByExampleExecutor;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
@@ -84,6 +95,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
* @author Jens Schauder
|
||||
* @author Mark Paluch
|
||||
* @author Chirag Tailor
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
@Transactional
|
||||
@TestExecutionListeners(value = AssumeFeatureTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS)
|
||||
@@ -707,6 +719,557 @@ public class JdbcRepositoryIntegrationTests {
|
||||
.isEqualTo(root1.intermediates.get(0).leaves.get(0).name);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findOneByExampleShouldGetOne() {
|
||||
|
||||
DummyEntity dummyEntity1 = createDummyEntity();
|
||||
dummyEntity1.setFlag(true);
|
||||
|
||||
repository.save(dummyEntity1);
|
||||
|
||||
DummyEntity dummyEntity2 = createDummyEntity();
|
||||
dummyEntity2.setName("Diego");
|
||||
|
||||
repository.save(dummyEntity2);
|
||||
|
||||
Example<DummyEntity> diegoExample = Example.of(new DummyEntity("Diego"));
|
||||
|
||||
Optional<DummyEntity> foundExampleDiego = repository.findOne(diegoExample);
|
||||
|
||||
assertThat(foundExampleDiego).isPresent();
|
||||
assertThat(foundExampleDiego.get()).isNotNull();
|
||||
assertThat(foundExampleDiego.get().getName()).isEqualTo("Diego");
|
||||
}
|
||||
|
||||
@Test
|
||||
void findOneByExampleMultipleMatchShouldGetOne() {
|
||||
|
||||
DummyEntity dummyEntity1 = createDummyEntity();
|
||||
repository.save(dummyEntity1);
|
||||
|
||||
DummyEntity dummyEntity2 = createDummyEntity();
|
||||
repository.save(dummyEntity2);
|
||||
|
||||
Example<DummyEntity> example = Example.of(createDummyEntity());
|
||||
|
||||
assertThatThrownBy(() -> repository.findOne(example)).isInstanceOf(IncorrectResultSizeDataAccessException.class)
|
||||
.hasMessageContaining("expected 1, actual 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void findOneByExampleShouldGetNone() {
|
||||
|
||||
DummyEntity dummyEntity1 = createDummyEntity();
|
||||
dummyEntity1.setFlag(true);
|
||||
|
||||
repository.save(dummyEntity1);
|
||||
|
||||
Example<DummyEntity> diegoExample = Example.of(new DummyEntity("NotExisting"));
|
||||
|
||||
Optional<DummyEntity> foundExampleDiego = repository.findOne(diegoExample);
|
||||
|
||||
assertThat(foundExampleDiego).isNotPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAllByExampleShouldGetOne() {
|
||||
|
||||
DummyEntity dummyEntity1 = createDummyEntity();
|
||||
dummyEntity1.setFlag(true);
|
||||
|
||||
repository.save(dummyEntity1);
|
||||
|
||||
DummyEntity dummyEntity2 = createDummyEntity();
|
||||
dummyEntity2.setName("Diego");
|
||||
|
||||
repository.save(dummyEntity2);
|
||||
|
||||
Example<DummyEntity> example = Example.of(new DummyEntity("Diego"));
|
||||
|
||||
Iterable<DummyEntity> allFound = repository.findAll(example);
|
||||
|
||||
assertThat(allFound) //
|
||||
.isNotNull() //
|
||||
.hasSize(1) //
|
||||
.extracting(DummyEntity::getName) //
|
||||
.containsExactly(example.getProbe().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAllByExampleMultipleMatchShouldGetOne() {
|
||||
|
||||
DummyEntity dummyEntity1 = createDummyEntity();
|
||||
repository.save(dummyEntity1);
|
||||
|
||||
DummyEntity dummyEntity2 = createDummyEntity();
|
||||
repository.save(dummyEntity2);
|
||||
|
||||
Example<DummyEntity> example = Example.of(createDummyEntity());
|
||||
|
||||
Iterable<DummyEntity> allFound = repository.findAll(example);
|
||||
|
||||
assertThat(allFound) //
|
||||
.isNotNull() //
|
||||
.hasSize(2) //
|
||||
.extracting(DummyEntity::getName) //
|
||||
.containsOnly(example.getProbe().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAllByExampleShouldGetNone() {
|
||||
|
||||
DummyEntity dummyEntity1 = createDummyEntity();
|
||||
dummyEntity1.setFlag(true);
|
||||
|
||||
repository.save(dummyEntity1);
|
||||
|
||||
Example<DummyEntity> example = Example.of(new DummyEntity("NotExisting"));
|
||||
|
||||
Iterable<DummyEntity> allFound = repository.findAll(example);
|
||||
|
||||
assertThat(allFound) //
|
||||
.isNotNull() //
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAllByExamplePageableShouldGetOne() {
|
||||
|
||||
DummyEntity dummyEntity1 = createDummyEntity();
|
||||
dummyEntity1.setFlag(true);
|
||||
|
||||
repository.save(dummyEntity1);
|
||||
|
||||
DummyEntity dummyEntity2 = createDummyEntity();
|
||||
dummyEntity2.setName("Diego");
|
||||
|
||||
repository.save(dummyEntity2);
|
||||
|
||||
Example<DummyEntity> example = Example.of(new DummyEntity("Diego"));
|
||||
Pageable pageRequest = PageRequest.of(0, 10);
|
||||
|
||||
Iterable<DummyEntity> allFound = repository.findAll(example, pageRequest);
|
||||
|
||||
assertThat(allFound) //
|
||||
.isNotNull() //
|
||||
.hasSize(1) //
|
||||
.extracting(DummyEntity::getName) //
|
||||
.containsExactly(example.getProbe().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAllByExamplePageableMultipleMatchShouldGetOne() {
|
||||
|
||||
DummyEntity dummyEntity1 = createDummyEntity();
|
||||
repository.save(dummyEntity1);
|
||||
|
||||
DummyEntity dummyEntity2 = createDummyEntity();
|
||||
repository.save(dummyEntity2);
|
||||
|
||||
Example<DummyEntity> example = Example.of(createDummyEntity());
|
||||
Pageable pageRequest = PageRequest.of(0, 10);
|
||||
|
||||
Iterable<DummyEntity> allFound = repository.findAll(example, pageRequest);
|
||||
|
||||
assertThat(allFound) //
|
||||
.isNotNull() //
|
||||
.hasSize(2) //
|
||||
.extracting(DummyEntity::getName) //
|
||||
.containsOnly(example.getProbe().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAllByExamplePageableShouldGetNone() {
|
||||
|
||||
DummyEntity dummyEntity1 = createDummyEntity();
|
||||
dummyEntity1.setFlag(true);
|
||||
|
||||
repository.save(dummyEntity1);
|
||||
|
||||
Example<DummyEntity> example = Example.of(new DummyEntity("NotExisting"));
|
||||
Pageable pageRequest = PageRequest.of(0, 10);
|
||||
|
||||
Iterable<DummyEntity> allFound = repository.findAll(example, pageRequest);
|
||||
|
||||
assertThat(allFound) //
|
||||
.isNotNull() //
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAllByExamplePageableOutsidePageShouldGetNone() {
|
||||
|
||||
DummyEntity dummyEntity1 = createDummyEntity();
|
||||
repository.save(dummyEntity1);
|
||||
|
||||
DummyEntity dummyEntity2 = createDummyEntity();
|
||||
repository.save(dummyEntity2);
|
||||
|
||||
Example<DummyEntity> example = Example.of(createDummyEntity());
|
||||
Pageable pageRequest = PageRequest.of(10, 10);
|
||||
|
||||
Iterable<DummyEntity> allFound = repository.findAll(example, pageRequest);
|
||||
|
||||
assertThat(allFound) //
|
||||
.isNotNull() //
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("findAllByExamplePageableSource")
|
||||
void findAllByExamplePageable(Pageable pageRequest, int size, int totalPages, List<String> notContains) {
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
DummyEntity dummyEntity = createDummyEntity();
|
||||
dummyEntity.setFlag(true);
|
||||
dummyEntity.setName("" + i);
|
||||
|
||||
repository.save(dummyEntity);
|
||||
}
|
||||
|
||||
DummyEntity dummyEntityExample = createDummyEntity();
|
||||
dummyEntityExample.setName(null);
|
||||
dummyEntityExample.setFlag(true);
|
||||
|
||||
Example<DummyEntity> example = Example.of(dummyEntityExample);
|
||||
|
||||
Page<DummyEntity> allFound = repository.findAll(example, pageRequest);
|
||||
|
||||
// page has correct size
|
||||
assertThat(allFound) //
|
||||
.isNotNull() //
|
||||
.hasSize(size);
|
||||
|
||||
// correct number of total
|
||||
assertThat(allFound.getTotalElements()).isEqualTo(100);
|
||||
|
||||
assertThat(allFound.getTotalPages()).isEqualTo(totalPages);
|
||||
|
||||
if (!notContains.isEmpty()) {
|
||||
assertThat(allFound) //
|
||||
.extracting(DummyEntity::getName) //
|
||||
.doesNotContain(notContains.toArray(new String[0]));
|
||||
}
|
||||
}
|
||||
|
||||
public static Stream<Arguments> findAllByExamplePageableSource() {
|
||||
return Stream.of( //
|
||||
Arguments.of(PageRequest.of(0, 3), 3, 34, Arrays.asList("3", "4", "100")), //
|
||||
Arguments.of(PageRequest.of(1, 10), 10, 10, Arrays.asList("9", "20", "30")), //
|
||||
Arguments.of(PageRequest.of(2, 10), 10, 10, Arrays.asList("1", "2", "3")), //
|
||||
Arguments.of(PageRequest.of(33, 3), 1, 34, Collections.emptyList()), //
|
||||
Arguments.of(PageRequest.of(36, 3), 0, 34, Collections.emptyList()), //
|
||||
Arguments.of(PageRequest.of(0, 10000), 100, 1, Collections.emptyList()), //
|
||||
Arguments.of(PageRequest.of(100, 10000), 0, 1, Collections.emptyList()) //
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void existsByExampleShouldGetOne() {
|
||||
|
||||
DummyEntity dummyEntity1 = createDummyEntity();
|
||||
dummyEntity1.setFlag(true);
|
||||
|
||||
repository.save(dummyEntity1);
|
||||
|
||||
DummyEntity dummyEntity2 = createDummyEntity();
|
||||
dummyEntity2.setName("Diego");
|
||||
|
||||
repository.save(dummyEntity2);
|
||||
|
||||
Example<DummyEntity> example = Example.of(new DummyEntity("Diego"));
|
||||
|
||||
boolean exists = repository.exists(example);
|
||||
|
||||
assertThat(exists).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void existsByExampleMultipleMatchShouldGetOne() {
|
||||
|
||||
DummyEntity dummyEntity1 = createDummyEntity();
|
||||
repository.save(dummyEntity1);
|
||||
|
||||
DummyEntity dummyEntity2 = createDummyEntity();
|
||||
repository.save(dummyEntity2);
|
||||
|
||||
Example<DummyEntity> example = Example.of(createDummyEntity());
|
||||
|
||||
boolean exists = repository.exists(example);
|
||||
assertThat(exists).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void existsByExampleShouldGetNone() {
|
||||
|
||||
DummyEntity dummyEntity1 = createDummyEntity();
|
||||
dummyEntity1.setFlag(true);
|
||||
|
||||
repository.save(dummyEntity1);
|
||||
|
||||
Example<DummyEntity> example = Example.of(new DummyEntity("NotExisting"));
|
||||
|
||||
boolean exists = repository.exists(example);
|
||||
|
||||
assertThat(exists).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void existsByExampleComplex() {
|
||||
|
||||
final Instant pointInTime = Instant.now().minusSeconds(10000);
|
||||
|
||||
final DummyEntity one = repository.save(createDummyEntity());
|
||||
|
||||
DummyEntity two = createDummyEntity();
|
||||
two.setName("Diego");
|
||||
two.setPointInTime(pointInTime);
|
||||
two = repository.save(two);
|
||||
|
||||
DummyEntity exampleEntitiy = createDummyEntity();
|
||||
exampleEntitiy.setName("Diego");
|
||||
exampleEntitiy.setPointInTime(pointInTime);
|
||||
|
||||
Example<DummyEntity> example = Example.of(exampleEntitiy);
|
||||
|
||||
boolean exists = repository.exists(example);
|
||||
assertThat(exists).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void countByExampleShouldGetOne() {
|
||||
|
||||
DummyEntity dummyEntity1 = createDummyEntity();
|
||||
dummyEntity1.setFlag(true);
|
||||
|
||||
repository.save(dummyEntity1);
|
||||
|
||||
DummyEntity dummyEntity2 = createDummyEntity();
|
||||
dummyEntity2.setName("Diego");
|
||||
|
||||
repository.save(dummyEntity2);
|
||||
|
||||
Example<DummyEntity> example = Example.of(new DummyEntity("Diego"));
|
||||
|
||||
long count = repository.count(example);
|
||||
|
||||
assertThat(count).isOne();
|
||||
}
|
||||
|
||||
@Test
|
||||
void countByExampleMultipleMatchShouldGetOne() {
|
||||
|
||||
DummyEntity dummyEntity1 = createDummyEntity();
|
||||
repository.save(dummyEntity1);
|
||||
|
||||
DummyEntity dummyEntity2 = createDummyEntity();
|
||||
repository.save(dummyEntity2);
|
||||
|
||||
Example<DummyEntity> example = Example.of(createDummyEntity());
|
||||
|
||||
long count = repository.count(example);
|
||||
assertThat(count).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void countByExampleShouldGetNone() {
|
||||
|
||||
DummyEntity dummyEntity1 = createDummyEntity();
|
||||
dummyEntity1.setFlag(true);
|
||||
|
||||
repository.save(dummyEntity1);
|
||||
|
||||
Example<DummyEntity> example = Example.of(new DummyEntity("NotExisting"));
|
||||
|
||||
long count = repository.count(example);
|
||||
|
||||
assertThat(count).isNotNull().isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void countByExampleComplex() {
|
||||
|
||||
final Instant pointInTime = Instant.now().minusSeconds(10000);
|
||||
|
||||
final DummyEntity one = repository.save(createDummyEntity());
|
||||
|
||||
DummyEntity two = createDummyEntity();
|
||||
two.setName("Diego");
|
||||
two.setPointInTime(pointInTime);
|
||||
two = repository.save(two);
|
||||
|
||||
DummyEntity exampleEntitiy = createDummyEntity();
|
||||
exampleEntitiy.setName("Diego");
|
||||
exampleEntitiy.setPointInTime(pointInTime);
|
||||
|
||||
Example<DummyEntity> example = Example.of(exampleEntitiy);
|
||||
|
||||
long count = repository.count(example);
|
||||
assertThat(count).isOne();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fetchByExampleFluentAllSimple() {
|
||||
String searchName = "Diego";
|
||||
|
||||
Instant now = Instant.now();
|
||||
|
||||
final DummyEntity one = repository.save(createDummyEntity());
|
||||
|
||||
DummyEntity two = createDummyEntity();
|
||||
|
||||
two.setName(searchName);
|
||||
two.setPointInTime(now.minusSeconds(10000));
|
||||
two = repository.save(two);
|
||||
|
||||
DummyEntity third = createDummyEntity();
|
||||
third.setName(searchName);
|
||||
third.setPointInTime(now.minusSeconds(200000));
|
||||
third = repository.save(third);
|
||||
|
||||
DummyEntity exampleEntitiy = createDummyEntity();
|
||||
exampleEntitiy.setName(searchName);
|
||||
|
||||
Example<DummyEntity> example = Example.of(exampleEntitiy);
|
||||
|
||||
List<DummyEntity> matches = repository.findBy(example, p -> p.sortBy(Sort.by("pointInTime").descending()).all());
|
||||
assertThat(matches).hasSize(2).contains(two, third);
|
||||
assertThat(matches.get(0)).isEqualTo(two);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fetchByExampleFluentCountSimple() {
|
||||
String searchName = "Diego";
|
||||
|
||||
Instant now = Instant.now();
|
||||
|
||||
final DummyEntity one = repository.save(createDummyEntity());
|
||||
|
||||
DummyEntity two = createDummyEntity();
|
||||
|
||||
two.setName(searchName);
|
||||
two.setPointInTime(now.minusSeconds(10000));
|
||||
two = repository.save(two);
|
||||
|
||||
DummyEntity third = createDummyEntity();
|
||||
third.setName(searchName);
|
||||
third.setPointInTime(now.minusSeconds(200000));
|
||||
third = repository.save(third);
|
||||
|
||||
DummyEntity exampleEntitiy = createDummyEntity();
|
||||
exampleEntitiy.setName(searchName);
|
||||
|
||||
Example<DummyEntity> example = Example.of(exampleEntitiy);
|
||||
|
||||
Long matches = repository.findBy(example, FluentQuery.FetchableFluentQuery::count);
|
||||
assertThat(matches).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fetchByExampleFluentOnlyInstantFirstSimple() {
|
||||
String searchName = "Diego";
|
||||
|
||||
Instant now = Instant.now();
|
||||
|
||||
final DummyEntity one = repository.save(createDummyEntity());
|
||||
|
||||
DummyEntity two = createDummyEntity();
|
||||
|
||||
two.setName(searchName);
|
||||
two.setPointInTime(now.minusSeconds(10000));
|
||||
two = repository.save(two);
|
||||
|
||||
DummyEntity third = createDummyEntity();
|
||||
third.setName(searchName);
|
||||
third.setPointInTime(now.minusSeconds(200000));
|
||||
third = repository.save(third);
|
||||
|
||||
DummyEntity exampleEntitiy = createDummyEntity();
|
||||
exampleEntitiy.setName(searchName);
|
||||
|
||||
Example<DummyEntity> example = Example.of(exampleEntitiy);
|
||||
|
||||
Optional<DummyEntity> matches = repository.findBy(example,
|
||||
p -> p.sortBy(Sort.by("pointInTime").descending()).first());
|
||||
assertThat(matches).contains(two);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fetchByExampleFluentOnlyInstantOneValueError() {
|
||||
String searchName = "Diego";
|
||||
|
||||
Instant now = Instant.now();
|
||||
|
||||
final DummyEntity one = repository.save(createDummyEntity());
|
||||
|
||||
DummyEntity two = createDummyEntity();
|
||||
|
||||
two.setName(searchName);
|
||||
two.setPointInTime(now.minusSeconds(10000));
|
||||
two = repository.save(two);
|
||||
|
||||
DummyEntity third = createDummyEntity();
|
||||
third.setName(searchName);
|
||||
third.setPointInTime(now.minusSeconds(200000));
|
||||
third = repository.save(third);
|
||||
|
||||
DummyEntity exampleEntitiy = createDummyEntity();
|
||||
exampleEntitiy.setName(searchName);
|
||||
|
||||
Example<DummyEntity> example = Example.of(exampleEntitiy);
|
||||
|
||||
assertThatThrownBy(() -> repository.findBy(example, p -> p.sortBy(Sort.by("pointInTime").descending()).one()))
|
||||
.isInstanceOf(IncorrectResultSizeDataAccessException.class).hasMessageContaining("expected 1, actual 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fetchByExampleFluentOnlyInstantOneValueSimple() {
|
||||
String searchName = "Diego";
|
||||
|
||||
Instant now = Instant.now();
|
||||
|
||||
final DummyEntity one = repository.save(createDummyEntity());
|
||||
|
||||
DummyEntity two = createDummyEntity();
|
||||
|
||||
two.setName(searchName);
|
||||
two.setPointInTime(now.minusSeconds(10000));
|
||||
two = repository.save(two);
|
||||
|
||||
DummyEntity exampleEntitiy = createDummyEntity();
|
||||
exampleEntitiy.setName(searchName);
|
||||
|
||||
Example<DummyEntity> example = Example.of(exampleEntitiy);
|
||||
|
||||
Optional<DummyEntity> match = repository.findBy(example, p -> p.sortBy(Sort.by("pointInTime").descending()).one());
|
||||
|
||||
assertThat(match).contains(two);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fetchByExampleFluentOnlyInstantOneValueAsSimple() {
|
||||
String searchName = "Diego";
|
||||
|
||||
Instant now = Instant.now();
|
||||
|
||||
final DummyEntity one = repository.save(createDummyEntity());
|
||||
|
||||
DummyEntity two = createDummyEntity();
|
||||
|
||||
two.setName(searchName);
|
||||
two.setPointInTime(now.minusSeconds(10000));
|
||||
two = repository.save(two);
|
||||
|
||||
DummyEntity exampleEntitiy = createDummyEntity();
|
||||
exampleEntitiy.setName(searchName);
|
||||
|
||||
Example<DummyEntity> example = Example.of(exampleEntitiy);
|
||||
|
||||
Optional<DummyProjectExample> match = repository.findBy(example, p -> p.as(DummyProjectExample.class).one());
|
||||
|
||||
assertThat(match.get().getName()).contains(two.getName());
|
||||
}
|
||||
|
||||
private Instant createDummyBeforeAndAfterNow() {
|
||||
|
||||
Instant now = Instant.now();
|
||||
@@ -730,7 +1293,11 @@ public class JdbcRepositoryIntegrationTests {
|
||||
return now;
|
||||
}
|
||||
|
||||
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
|
||||
interface DummyProjectExample {
|
||||
String getName();
|
||||
}
|
||||
|
||||
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long>, QueryByExampleExecutor<DummyEntity> {
|
||||
|
||||
@Lock(LockMode.PESSIMISTIC_WRITE)
|
||||
List<DummyEntity> findAllByName(String name);
|
||||
|
||||
@@ -64,6 +64,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
* @author Evgeni Dimitrov
|
||||
* @author Fei Dong
|
||||
* @author Chirag Tailor
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = TestConfiguration.class)
|
||||
@@ -175,7 +176,8 @@ public class EnableJdbcRepositoriesIntegrationTests {
|
||||
|
||||
private static class DummyRepositoryBaseClass<T, ID> implements CrudRepository<T, ID> {
|
||||
|
||||
DummyRepositoryBaseClass(JdbcAggregateTemplate template, PersistentEntity<?, ?> persistentEntity) {
|
||||
DummyRepositoryBaseClass(JdbcAggregateTemplate template, PersistentEntity<?, ?> persistentEntity,
|
||||
JdbcConverter converter) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
|
||||
import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
|
||||
/**
|
||||
@@ -36,11 +37,12 @@ public class SimpleJdbcRepositoryUnitTests {
|
||||
|
||||
@Mock JdbcAggregateOperations operations;
|
||||
@Mock RelationalPersistentEntity<Sample> entity;
|
||||
@Mock JdbcConverter converter;
|
||||
|
||||
@Test // DATAJDBC-252
|
||||
public void saveReturnsEntityProducedByOperations() {
|
||||
|
||||
SimpleJdbcRepository<Sample, Object> repository = new SimpleJdbcRepository<>(operations, entity);
|
||||
SimpleJdbcRepository<Sample, Object> repository = new SimpleJdbcRepository<>(operations, entity,converter);
|
||||
|
||||
Sample expected = new Sample();
|
||||
doReturn(expected).when(operations).save(any());
|
||||
|
||||
Reference in New Issue
Block a user