DATAJDBC-263 - Publish events for @Query annotated methods.

Original pull request: #94.
This commit is contained in:
Maciej Walkowiak
2018-10-11 22:50:39 +02:00
committed by Jens Schauder
parent 9e1d39486e
commit ce9ade6d7c
5 changed files with 105 additions and 10 deletions

View File

@@ -17,6 +17,7 @@ package org.springframework.data.jdbc.repository.support;
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;
@@ -43,6 +44,7 @@ import org.springframework.util.Assert;
*/
class JdbcQueryLookupStrategy implements QueryLookupStrategy {
private final ApplicationEventPublisher publisher;
private final RelationalMappingContext context;
private final RelationalConverter converter;
private final DataAccessStrategy accessStrategy;
@@ -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;
@@ -85,7 +90,7 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
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) {

View File

@@ -133,6 +133,6 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
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));
}
}

View File

@@ -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;
@@ -26,6 +30,8 @@ import org.springframework.lang.Nullable;
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.
@@ -33,11 +39,14 @@ import org.springframework.util.StringUtils;
* @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;
@@ -45,14 +54,18 @@ class JdbcRepositoryQuery implements RepositoryQuery {
/**
* 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!");
@@ -60,6 +73,8 @@ class JdbcRepositoryQuery implements RepositoryQuery {
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);
@@ -84,11 +99,15 @@ class JdbcRepositoryQuery implements RepositoryQuery {
}
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;
}
@@ -136,4 +155,25 @@ class JdbcRepositoryQuery implements RepositoryQuery {
? 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));
}
}
}
}

View File

@@ -25,6 +25,7 @@ import java.text.NumberFormat;
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;
@@ -49,6 +50,7 @@ import org.springframework.jdbc.core.namedparam.SqlParameterSource;
*/
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);
@@ -82,7 +84,7 @@ public class JdbcQueryLookupStrategyUnitTests {
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);

View File

@@ -19,10 +19,14 @@ import static org.mockito.ArgumentMatchers.*;
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;
@@ -42,6 +46,8 @@ public class JdbcRepositoryQueryUnitTests {
RowMapper<?> defaultRowMapper;
JdbcRepositoryQuery query;
NamedParameterJdbcOperations operations;
ApplicationEventPublisher publisher;
RelationalMappingContext context;
@Before
public void setup() throws NoSuchMethodException {
@@ -54,8 +60,10 @@ public class JdbcRepositoryQueryUnitTests {
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
@@ -94,12 +102,40 @@ public class JdbcRepositoryQueryUnitTests {
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.
*/
@@ -113,4 +149,16 @@ public class JdbcRepositoryQueryUnitTests {
return null;
}
}
private static class DummyEntity {
private Long id;
public DummyEntity(Long id) {
this.id = id;
}
Long getId() {
return id;
}
}
}