Skip to content
Closed
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 @@ -17,6 +17,7 @@

import java.lang.reflect.Method;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.core.EntityRowMapper;
import org.springframework.data.jdbc.repository.RowMapperMap;
Expand All @@ -43,6 +44,7 @@
*/
class JdbcQueryLookupStrategy implements QueryLookupStrategy {

private final ApplicationEventPublisher publisher;
private final RelationalMappingContext context;
private final RelationalConverter converter;
private final DataAccessStrategy accessStrategy;
Expand All @@ -53,19 +55,22 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
* Creates a new {@link JdbcQueryLookupStrategy} for the given {@link RelationalMappingContext},
* {@link DataAccessStrategy} and {@link RowMapperMap}.
*
* @param publisher must not be {@literal null}.
* @param context must not be {@literal null}.
* @param converter must not be {@literal null}.
* @param accessStrategy must not be {@literal null}.
* @param rowMapperMap must not be {@literal null}.
*/
JdbcQueryLookupStrategy(RelationalMappingContext context, RelationalConverter converter,
JdbcQueryLookupStrategy(ApplicationEventPublisher publisher, RelationalMappingContext context, RelationalConverter converter,
DataAccessStrategy accessStrategy, RowMapperMap rowMapperMap, NamedParameterJdbcOperations operations) {

Assert.notNull(publisher, "Publisher must not be null!");
Assert.notNull(context, "RelationalMappingContext must not be null!");
Assert.notNull(converter, "RelationalConverter must not be null!");
Assert.notNull(accessStrategy, "DataAccessStrategy must not be null!");
Assert.notNull(rowMapperMap, "RowMapperMap must not be null!");

this.publisher = publisher;
this.context = context;
this.converter = converter;
this.accessStrategy = accessStrategy;
Expand All @@ -85,7 +90,7 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repository

RowMapper<?> rowMapper = queryMethod.isModifyingQuery() ? null : createRowMapper(queryMethod);

return new JdbcRepositoryQuery(queryMethod, operations, rowMapper);
return new JdbcRepositoryQuery(publisher, context, queryMethod, operations, rowMapper);
}

private RowMapper<?> createRowMapper(JdbcQueryMethod queryMethod) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,6 @@ protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable QueryLo
throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key));
}

return Optional.of(new JdbcQueryLookupStrategy(context, converter, accessStrategy, rowMapperMap, operations));
return Optional.of(new JdbcQueryLookupStrategy(publisher, context, converter, accessStrategy, rowMapperMap, operations));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@
package org.springframework.data.jdbc.repository.support;

import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.event.AfterLoadEvent;
import org.springframework.data.relational.core.mapping.event.Identifier;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
Expand All @@ -26,40 +30,51 @@
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.List;

/**
* A query to be executed based on a repository method, it's annotated SQL query and the arguments provided to the
* method.
*
* @author Jens Schauder
* @author Kazuki Shimizu
* @author Oliver Gierke
* @author Maciej Walkowiak
*/
class JdbcRepositoryQuery implements RepositoryQuery {

private static final String PARAMETER_NEEDS_TO_BE_NAMED = "For queries with named parameters you need to provide names for method parameters. Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters.";

private final ApplicationEventPublisher publisher;
private final RelationalMappingContext context;
private final JdbcQueryMethod queryMethod;
private final NamedParameterJdbcOperations operations;
private final RowMapper<?> rowMapper;

/**
* Creates a new {@link JdbcRepositoryQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext} and
* {@link RowMapper}.
*
*
* @param publisher must not be {@literal null}.
* @param context must not be {@literal null}.
* @param queryMethod must not be {@literal null}.
* @param operations must not be {@literal null}.
* @param defaultRowMapper can be {@literal null} (only in case of a modifying query).
*/
JdbcRepositoryQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
JdbcRepositoryQuery(ApplicationEventPublisher publisher, RelationalMappingContext context, JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
@Nullable RowMapper<?> defaultRowMapper) {

Assert.notNull(publisher, "Publisher must not be null!");
Assert.notNull(context, "Context must not be null!");
Assert.notNull(queryMethod, "Query method must not be null!");
Assert.notNull(operations, "NamedParameterJdbcOperations must not be null!");

if (!queryMethod.isModifyingQuery()) {
Assert.notNull(defaultRowMapper, "RowMapper must not be null!");
}

this.publisher = publisher;
this.context = context;
this.queryMethod = queryMethod;
this.operations = operations;
this.rowMapper = createRowMapper(queryMethod, defaultRowMapper);
Expand All @@ -84,11 +99,15 @@ public Object execute(Object[] objects) {
}

if (queryMethod.isCollectionQuery() || queryMethod.isStreamQuery()) {
return operations.query(query, parameters, rowMapper);
List<?> result = operations.query(query, parameters, rowMapper);
publishAfterLoad(result);
return result;
}

try {
return operations.queryForObject(query, parameters, rowMapper);
Object result = operations.queryForObject(query, parameters, rowMapper);
publishAfterLoad(result);
return result;
} catch (EmptyResultDataAccessException e) {
return null;
}
Expand Down Expand Up @@ -136,4 +155,25 @@ private static RowMapper<?> createRowMapper(JdbcQueryMethod queryMethod, @Nullab
? defaultRowMapper //
: (RowMapper<?>) BeanUtils.instantiateClass(rowMapperClass);
}

private <T> void publishAfterLoad(Iterable<T> all) {

for (T e : all) {
publishAfterLoad(e);
}
}

private <T> void publishAfterLoad(@Nullable T entity) {

if (entity != null && context.hasPersistentEntityFor(entity.getClass())) {
RelationalPersistentEntity<?> e = context.getRequiredPersistentEntity(entity.getClass());
Object identifier = e.getIdentifierAccessor(entity)
.getIdentifier();

if (identifier != null) {
publisher.publishEvent(new AfterLoadEvent(Identifier.of(identifier), entity));
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.repository.RowMapperMap;
import org.springframework.data.jdbc.repository.config.ConfigurableRowMapperMap;
Expand All @@ -49,6 +50,7 @@
*/
public class JdbcQueryLookupStrategyUnitTests {

ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class);
RelationalMappingContext mappingContext = mock(RelationalMappingContext.class, RETURNS_DEEP_STUBS);
RelationalConverter converter = mock(BasicRelationalConverter.class);
DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class);
Expand Down Expand Up @@ -82,7 +84,7 @@ public void typeBasedRowMapperGetsUsedForQuery() {

private RepositoryQuery getRepositoryQuery(String name, RowMapperMap rowMapperMap) {

JdbcQueryLookupStrategy queryLookupStrategy = new JdbcQueryLookupStrategy(mappingContext, converter, accessStrategy,
JdbcQueryLookupStrategy queryLookupStrategy = new JdbcQueryLookupStrategy(publisher, mappingContext, converter, accessStrategy,
rowMapperMap, operations);

return queryLookupStrategy.resolveQuery(getMethod(name), metadata, projectionFactory, namedQueries);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
import static org.mockito.Mockito.*;

import java.sql.ResultSet;
import java.util.Arrays;

import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.event.AfterLoadEvent;
import org.springframework.data.repository.query.DefaultParameters;
import org.springframework.data.repository.query.Parameters;
import org.springframework.jdbc.core.RowMapper;
Expand All @@ -42,6 +46,8 @@ public class JdbcRepositoryQueryUnitTests {
RowMapper<?> defaultRowMapper;
JdbcRepositoryQuery query;
NamedParameterJdbcOperations operations;
ApplicationEventPublisher publisher;
RelationalMappingContext context;

@Before
public void setup() throws NoSuchMethodException {
Expand All @@ -54,8 +60,10 @@ public void setup() throws NoSuchMethodException {

this.defaultRowMapper = mock(RowMapper.class);
this.operations = mock(NamedParameterJdbcOperations.class);
this.publisher = mock(ApplicationEventPublisher.class);
this.context = mock(RelationalMappingContext.class, RETURNS_DEEP_STUBS);

this.query = new JdbcRepositoryQuery(queryMethod, operations, defaultRowMapper);
this.query = new JdbcRepositoryQuery(publisher, context, queryMethod, operations, defaultRowMapper);
}

@Test // DATAJDBC-165
Expand Down Expand Up @@ -94,12 +102,40 @@ public void customRowMapperIsUsedWhenSpecified() {
doReturn("some sql statement").when(queryMethod).getAnnotatedQuery();
doReturn(CustomRowMapper.class).when(queryMethod).getRowMapperClass();

new JdbcRepositoryQuery(queryMethod, operations, defaultRowMapper).execute(new Object[] {});
new JdbcRepositoryQuery(publisher, context, queryMethod, operations, defaultRowMapper).execute(new Object[] {});

verify(operations) //
.queryForObject(anyString(), any(SqlParameterSource.class), isA(CustomRowMapper.class));
}

@Test
public void publishesSingleEventWhenQueryReturnsSingleElement() {

doReturn("some sql statement").when(queryMethod).getAnnotatedQuery();
doReturn(false).when(queryMethod).isCollectionQuery();
doReturn(new DummyEntity(1L)).when(operations).queryForObject(anyString(), any(SqlParameterSource.class), any(RowMapper.class));
doReturn(true).when(context).hasPersistentEntityFor(DummyEntity.class);
when(context.getRequiredPersistentEntity(DummyEntity.class).getIdentifierAccessor(any()).getRequiredIdentifier()).thenReturn("some identifier");

new JdbcRepositoryQuery(publisher, context, queryMethod, operations, defaultRowMapper).execute(new Object[] {});

verify(publisher).publishEvent(any(AfterLoadEvent.class));
}

@Test
public void publishesAsManyEventsAsReturnedEntities() {

doReturn("some sql statement").when(queryMethod).getAnnotatedQuery();
doReturn(true).when(queryMethod).isCollectionQuery();
doReturn(Arrays.asList(new DummyEntity(1L), new DummyEntity(1L))).when(operations).query(anyString(), any(SqlParameterSource.class), any(RowMapper.class));
doReturn(true).when(context).hasPersistentEntityFor(DummyEntity.class);
when(context.getRequiredPersistentEntity(DummyEntity.class).getIdentifierAccessor(any()).getRequiredIdentifier()).thenReturn("some identifier");

new JdbcRepositoryQuery(publisher, context, queryMethod, operations, defaultRowMapper).execute(new Object[] {});

verify(publisher, times(2)).publishEvent(any(AfterLoadEvent.class));
}

/**
* The whole purpose of this method is to easily generate a {@link DefaultParameters} instance during test setup.
*/
Expand All @@ -113,4 +149,16 @@ public Object mapRow(ResultSet rs, int rowNum) {
return null;
}
}

private static class DummyEntity {
private Long id;

public DummyEntity(Long id) {
this.id = id;
}

Long getId() {
return id;
}
}
}