Skip to content

Commit ce9ade6

Browse files
maciejwalkowiakschauder
authored andcommitted
DATAJDBC-263 - Publish events for @query annotated methods.
Original pull request: #94.
1 parent 9e1d394 commit ce9ade6

File tree

5 files changed

+105
-10
lines changed

5 files changed

+105
-10
lines changed

src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.lang.reflect.Method;
1919

20+
import org.springframework.context.ApplicationEventPublisher;
2021
import org.springframework.data.jdbc.core.DataAccessStrategy;
2122
import org.springframework.data.jdbc.core.EntityRowMapper;
2223
import org.springframework.data.jdbc.repository.RowMapperMap;
@@ -43,6 +44,7 @@
4344
*/
4445
class JdbcQueryLookupStrategy implements QueryLookupStrategy {
4546

47+
private final ApplicationEventPublisher publisher;
4648
private final RelationalMappingContext context;
4749
private final RelationalConverter converter;
4850
private final DataAccessStrategy accessStrategy;
@@ -53,19 +55,22 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
5355
* Creates a new {@link JdbcQueryLookupStrategy} for the given {@link RelationalMappingContext},
5456
* {@link DataAccessStrategy} and {@link RowMapperMap}.
5557
*
58+
* @param publisher must not be {@literal null}.
5659
* @param context must not be {@literal null}.
5760
* @param converter must not be {@literal null}.
5861
* @param accessStrategy must not be {@literal null}.
5962
* @param rowMapperMap must not be {@literal null}.
6063
*/
61-
JdbcQueryLookupStrategy(RelationalMappingContext context, RelationalConverter converter,
64+
JdbcQueryLookupStrategy(ApplicationEventPublisher publisher, RelationalMappingContext context, RelationalConverter converter,
6265
DataAccessStrategy accessStrategy, RowMapperMap rowMapperMap, NamedParameterJdbcOperations operations) {
6366

67+
Assert.notNull(publisher, "Publisher must not be null!");
6468
Assert.notNull(context, "RelationalMappingContext must not be null!");
6569
Assert.notNull(converter, "RelationalConverter must not be null!");
6670
Assert.notNull(accessStrategy, "DataAccessStrategy must not be null!");
6771
Assert.notNull(rowMapperMap, "RowMapperMap must not be null!");
6872

73+
this.publisher = publisher;
6974
this.context = context;
7075
this.converter = converter;
7176
this.accessStrategy = accessStrategy;
@@ -85,7 +90,7 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repository
8590

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

88-
return new JdbcRepositoryQuery(queryMethod, operations, rowMapper);
93+
return new JdbcRepositoryQuery(publisher, context, queryMethod, operations, rowMapper);
8994
}
9095

9196
private RowMapper<?> createRowMapper(JdbcQueryMethod queryMethod) {

src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,6 @@ protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable QueryLo
133133
throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key));
134134
}
135135

136-
return Optional.of(new JdbcQueryLookupStrategy(context, converter, accessStrategy, rowMapperMap, operations));
136+
return Optional.of(new JdbcQueryLookupStrategy(publisher, context, converter, accessStrategy, rowMapperMap, operations));
137137
}
138138
}

src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQuery.java

+44-4
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@
1616
package org.springframework.data.jdbc.repository.support;
1717

1818
import org.springframework.beans.BeanUtils;
19+
import org.springframework.context.ApplicationEventPublisher;
1920
import org.springframework.dao.EmptyResultDataAccessException;
2021
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
22+
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
23+
import org.springframework.data.relational.core.mapping.event.AfterLoadEvent;
24+
import org.springframework.data.relational.core.mapping.event.Identifier;
2125
import org.springframework.data.repository.query.RepositoryQuery;
2226
import org.springframework.jdbc.core.RowMapper;
2327
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
@@ -26,40 +30,51 @@
2630
import org.springframework.util.Assert;
2731
import org.springframework.util.StringUtils;
2832

33+
import java.util.List;
34+
2935
/**
3036
* A query to be executed based on a repository method, it's annotated SQL query and the arguments provided to the
3137
* method.
3238
*
3339
* @author Jens Schauder
3440
* @author Kazuki Shimizu
3541
* @author Oliver Gierke
42+
* @author Maciej Walkowiak
3643
*/
3744
class JdbcRepositoryQuery implements RepositoryQuery {
3845

3946
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.";
4047

48+
private final ApplicationEventPublisher publisher;
49+
private final RelationalMappingContext context;
4150
private final JdbcQueryMethod queryMethod;
4251
private final NamedParameterJdbcOperations operations;
4352
private final RowMapper<?> rowMapper;
4453

4554
/**
4655
* Creates a new {@link JdbcRepositoryQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext} and
4756
* {@link RowMapper}.
48-
*
57+
*
58+
* @param publisher must not be {@literal null}.
59+
* @param context must not be {@literal null}.
4960
* @param queryMethod must not be {@literal null}.
5061
* @param operations must not be {@literal null}.
5162
* @param defaultRowMapper can be {@literal null} (only in case of a modifying query).
5263
*/
53-
JdbcRepositoryQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
64+
JdbcRepositoryQuery(ApplicationEventPublisher publisher, RelationalMappingContext context, JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
5465
@Nullable RowMapper<?> defaultRowMapper) {
5566

67+
Assert.notNull(publisher, "Publisher must not be null!");
68+
Assert.notNull(context, "Context must not be null!");
5669
Assert.notNull(queryMethod, "Query method must not be null!");
5770
Assert.notNull(operations, "NamedParameterJdbcOperations must not be null!");
5871

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

76+
this.publisher = publisher;
77+
this.context = context;
6378
this.queryMethod = queryMethod;
6479
this.operations = operations;
6580
this.rowMapper = createRowMapper(queryMethod, defaultRowMapper);
@@ -84,11 +99,15 @@ public Object execute(Object[] objects) {
8499
}
85100

86101
if (queryMethod.isCollectionQuery() || queryMethod.isStreamQuery()) {
87-
return operations.query(query, parameters, rowMapper);
102+
List<?> result = operations.query(query, parameters, rowMapper);
103+
publishAfterLoad(result);
104+
return result;
88105
}
89106

90107
try {
91-
return operations.queryForObject(query, parameters, rowMapper);
108+
Object result = operations.queryForObject(query, parameters, rowMapper);
109+
publishAfterLoad(result);
110+
return result;
92111
} catch (EmptyResultDataAccessException e) {
93112
return null;
94113
}
@@ -136,4 +155,25 @@ private static RowMapper<?> createRowMapper(JdbcQueryMethod queryMethod, @Nullab
136155
? defaultRowMapper //
137156
: (RowMapper<?>) BeanUtils.instantiateClass(rowMapperClass);
138157
}
158+
159+
private <T> void publishAfterLoad(Iterable<T> all) {
160+
161+
for (T e : all) {
162+
publishAfterLoad(e);
163+
}
164+
}
165+
166+
private <T> void publishAfterLoad(@Nullable T entity) {
167+
168+
if (entity != null && context.hasPersistentEntityFor(entity.getClass())) {
169+
RelationalPersistentEntity<?> e = context.getRequiredPersistentEntity(entity.getClass());
170+
Object identifier = e.getIdentifierAccessor(entity)
171+
.getIdentifier();
172+
173+
if (identifier != null) {
174+
publisher.publishEvent(new AfterLoadEvent(Identifier.of(identifier), entity));
175+
}
176+
}
177+
178+
}
139179
}

src/test/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategyUnitTests.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.junit.Before;
2727
import org.junit.Test;
28+
import org.springframework.context.ApplicationEventPublisher;
2829
import org.springframework.data.jdbc.core.DataAccessStrategy;
2930
import org.springframework.data.jdbc.repository.RowMapperMap;
3031
import org.springframework.data.jdbc.repository.config.ConfigurableRowMapperMap;
@@ -49,6 +50,7 @@
4950
*/
5051
public class JdbcQueryLookupStrategyUnitTests {
5152

53+
ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class);
5254
RelationalMappingContext mappingContext = mock(RelationalMappingContext.class, RETURNS_DEEP_STUBS);
5355
RelationalConverter converter = mock(BasicRelationalConverter.class);
5456
DataAccessStrategy accessStrategy = mock(DataAccessStrategy.class);
@@ -82,7 +84,7 @@ public void typeBasedRowMapperGetsUsedForQuery() {
8284

8385
private RepositoryQuery getRepositoryQuery(String name, RowMapperMap rowMapperMap) {
8486

85-
JdbcQueryLookupStrategy queryLookupStrategy = new JdbcQueryLookupStrategy(mappingContext, converter, accessStrategy,
87+
JdbcQueryLookupStrategy queryLookupStrategy = new JdbcQueryLookupStrategy(publisher, mappingContext, converter, accessStrategy,
8688
rowMapperMap, operations);
8789

8890
return queryLookupStrategy.resolveQuery(getMethod(name), metadata, projectionFactory, namedQueries);

src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQueryUnitTests.java

+50-2
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@
1919
import static org.mockito.Mockito.*;
2020

2121
import java.sql.ResultSet;
22+
import java.util.Arrays;
2223

2324
import org.assertj.core.api.Assertions;
2425
import org.junit.Before;
2526
import org.junit.Test;
27+
import org.springframework.context.ApplicationEventPublisher;
28+
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
29+
import org.springframework.data.relational.core.mapping.event.AfterLoadEvent;
2630
import org.springframework.data.repository.query.DefaultParameters;
2731
import org.springframework.data.repository.query.Parameters;
2832
import org.springframework.jdbc.core.RowMapper;
@@ -42,6 +46,8 @@ public class JdbcRepositoryQueryUnitTests {
4246
RowMapper<?> defaultRowMapper;
4347
JdbcRepositoryQuery query;
4448
NamedParameterJdbcOperations operations;
49+
ApplicationEventPublisher publisher;
50+
RelationalMappingContext context;
4551

4652
@Before
4753
public void setup() throws NoSuchMethodException {
@@ -54,8 +60,10 @@ public void setup() throws NoSuchMethodException {
5460

5561
this.defaultRowMapper = mock(RowMapper.class);
5662
this.operations = mock(NamedParameterJdbcOperations.class);
63+
this.publisher = mock(ApplicationEventPublisher.class);
64+
this.context = mock(RelationalMappingContext.class, RETURNS_DEEP_STUBS);
5765

58-
this.query = new JdbcRepositoryQuery(queryMethod, operations, defaultRowMapper);
66+
this.query = new JdbcRepositoryQuery(publisher, context, queryMethod, operations, defaultRowMapper);
5967
}
6068

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

97-
new JdbcRepositoryQuery(queryMethod, operations, defaultRowMapper).execute(new Object[] {});
105+
new JdbcRepositoryQuery(publisher, context, queryMethod, operations, defaultRowMapper).execute(new Object[] {});
98106

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

111+
@Test
112+
public void publishesSingleEventWhenQueryReturnsSingleElement() {
113+
114+
doReturn("some sql statement").when(queryMethod).getAnnotatedQuery();
115+
doReturn(false).when(queryMethod).isCollectionQuery();
116+
doReturn(new DummyEntity(1L)).when(operations).queryForObject(anyString(), any(SqlParameterSource.class), any(RowMapper.class));
117+
doReturn(true).when(context).hasPersistentEntityFor(DummyEntity.class);
118+
when(context.getRequiredPersistentEntity(DummyEntity.class).getIdentifierAccessor(any()).getRequiredIdentifier()).thenReturn("some identifier");
119+
120+
new JdbcRepositoryQuery(publisher, context, queryMethod, operations, defaultRowMapper).execute(new Object[] {});
121+
122+
verify(publisher).publishEvent(any(AfterLoadEvent.class));
123+
}
124+
125+
@Test
126+
public void publishesAsManyEventsAsReturnedEntities() {
127+
128+
doReturn("some sql statement").when(queryMethod).getAnnotatedQuery();
129+
doReturn(true).when(queryMethod).isCollectionQuery();
130+
doReturn(Arrays.asList(new DummyEntity(1L), new DummyEntity(1L))).when(operations).query(anyString(), any(SqlParameterSource.class), any(RowMapper.class));
131+
doReturn(true).when(context).hasPersistentEntityFor(DummyEntity.class);
132+
when(context.getRequiredPersistentEntity(DummyEntity.class).getIdentifierAccessor(any()).getRequiredIdentifier()).thenReturn("some identifier");
133+
134+
new JdbcRepositoryQuery(publisher, context, queryMethod, operations, defaultRowMapper).execute(new Object[] {});
135+
136+
verify(publisher, times(2)).publishEvent(any(AfterLoadEvent.class));
137+
}
138+
103139
/**
104140
* The whole purpose of this method is to easily generate a {@link DefaultParameters} instance during test setup.
105141
*/
@@ -113,4 +149,16 @@ public Object mapRow(ResultSet rs, int rowNum) {
113149
return null;
114150
}
115151
}
152+
153+
private static class DummyEntity {
154+
private Long id;
155+
156+
public DummyEntity(Long id) {
157+
this.id = id;
158+
}
159+
160+
Long getId() {
161+
return id;
162+
}
163+
}
116164
}

0 commit comments

Comments
 (0)