Skip to content

Commit b239991

Browse files
committed
DATAJDBC-384 - Fixed MyBatisDataAccessStrategy.findAllByPath.
findAllByPath now falls back to the older findAllByProperty for better backward compatibility. Also the path is included in the query name used for MyBatis.
1 parent 2f765ce commit b239991

File tree

4 files changed

+117
-7
lines changed

4 files changed

+117
-7
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisContext.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
*/
1616
package org.springframework.data.jdbc.mybatis;
1717

18+
import java.util.Collections;
1819
import java.util.Map;
1920

21+
import org.springframework.data.relational.domain.Identifier;
2022
import org.springframework.lang.Nullable;
2123

2224
/**
@@ -30,18 +32,29 @@ public class MyBatisContext {
3032

3133
private final Object id;
3234
private final Object instance;
35+
private final Identifier identifier;
3336
private final Class domainType;
3437
private final Map<String, Object> additonalValues;
3538

3639
public MyBatisContext(@Nullable Object id, @Nullable Object instance, Class domainType,
3740
Map<String, Object> additonalValues) {
3841

3942
this.id = id;
43+
this.identifier = null;
4044
this.instance = instance;
4145
this.domainType = domainType;
4246
this.additonalValues = additonalValues;
4347
}
4448

49+
public MyBatisContext(Identifier identifier, Object instance, Class<?> domainType) {
50+
51+
this.id = null;
52+
this.identifier = identifier;
53+
this.instance = instance;
54+
this.domainType = domainType;
55+
this.additonalValues = Collections.emptyMap();
56+
}
57+
4558
/**
4659
* The ID of the entity to query/act upon.
4760
*
@@ -52,6 +65,15 @@ public Object getId() {
5265
return id;
5366
}
5467

68+
/**
69+
* The {@link Identifier} for a path to query.
70+
*
71+
* @return Might return {@literal null}.
72+
*/
73+
public Identifier getIdentifier() {
74+
return identifier;
75+
}
76+
5577
/**
5678
* The entity to act upon. This is {@code null} for queries, since the object doesn't exist before the query.
5779
*
@@ -82,4 +104,5 @@ public Class getDomainType() {
82104
public Object get(String key) {
83105
return additonalValues.get(key);
84106
}
107+
85108
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@
2020
import java.util.Collections;
2121
import java.util.Map;
2222

23+
import org.apache.ibatis.exceptions.PersistenceException;
2324
import org.apache.ibatis.session.SqlSession;
2425
import org.mybatis.spring.SqlSessionTemplate;
26+
import org.slf4j.Logger;
27+
import org.slf4j.LoggerFactory;
2528
import org.springframework.data.jdbc.core.convert.CascadingDataAccessStrategy;
2629
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
2730
import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy;
@@ -52,6 +55,8 @@
5255
*/
5356
public class MyBatisDataAccessStrategy implements DataAccessStrategy {
5457

58+
private static final Logger LOG = LoggerFactory.getLogger(MyBatisDataAccessStrategy.class);
59+
5560
private final SqlSession sqlSession;
5661
private NamespaceStrategy namespaceStrategy = NamespaceStrategy.DEFAULT_INSTANCE;
5762

@@ -245,8 +250,19 @@ public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
245250
@Override
246251
public Iterable<Object> findAllByPath(Identifier identifier,
247252
PersistentPropertyPath<RelationalPersistentProperty> path) {
248-
return sqlSession().selectList(namespace(path.getBaseProperty().getOwner().getType()) + ".findAllByPath",
249-
new MyBatisContext(identifier, null, path.getLeafProperty().getType(), Collections.emptyMap()));
253+
254+
String statementName = namespace(path.getBaseProperty().getOwner().getType()) + ".findAllByPath-"
255+
+ path.toDotPath();
256+
257+
try {
258+
return sqlSession().selectList(statementName,
259+
new MyBatisContext(identifier, null, path.getRequiredLeafProperty().getType()));
260+
} catch (PersistenceException pex) {
261+
262+
LOG.debug("Didn't find %s in the MyBatis session. Falling back to findAllByPath", pex);
263+
264+
return DataAccessStrategy.super.findAllByPath(identifier, path);
265+
}
250266
}
251267

252268
/*
@@ -255,6 +271,7 @@ public Iterable<Object> findAllByPath(Identifier identifier,
255271
*/
256272
@Override
257273
public <T> Iterable<T> findAllByProperty(Object rootId, RelationalPersistentProperty property) {
274+
258275
return sqlSession().selectList(
259276
namespace(property.getOwner().getType()) + ".findAllByProperty-" + property.getName(),
260277
new MyBatisContext(rootId, null, property.getType(), Collections.emptyMap()));
@@ -266,6 +283,7 @@ public <T> Iterable<T> findAllByProperty(Object rootId, RelationalPersistentProp
266283
*/
267284
@Override
268285
public <T> boolean existsById(Object id, Class<T> domainType) {
286+
269287
return sqlSession().selectOne(namespace(domainType) + ".existsById",
270288
new MyBatisContext(id, null, domainType, Collections.emptyMap()));
271289
}

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategyUnitTests.java

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,24 @@
1616
package org.springframework.data.jdbc.mybatis;
1717

1818
import static java.util.Arrays.*;
19+
import static java.util.Collections.*;
1920
import static org.assertj.core.api.Assertions.*;
2021
import static org.mockito.Mockito.*;
2122

2223
import java.util.Collections;
2324

25+
import org.apache.ibatis.exceptions.PersistenceException;
2426
import org.apache.ibatis.session.SqlSession;
2527
import org.junit.Before;
2628
import org.junit.Test;
2729
import org.mockito.ArgumentCaptor;
2830
import org.mockito.Mockito;
29-
3031
import org.springframework.data.jdbc.core.PropertyPathTestingUtils;
3132
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
3233
import org.springframework.data.mapping.PersistentPropertyPath;
3334
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
3435
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
36+
import org.springframework.data.relational.domain.Identifier;
3537

3638
/**
3739
* Unit tests for the {@link MyBatisDataAccessStrategy}, mainly ensuring that the correct statements get's looked up.
@@ -48,7 +50,8 @@ public class MyBatisDataAccessStrategyUnitTests {
4850

4951
MyBatisDataAccessStrategy accessStrategy = new MyBatisDataAccessStrategy(session);
5052

51-
PersistentPropertyPath<RelationalPersistentProperty> path = PropertyPathTestingUtils.toPath("one.two", DummyEntity.class, context);
53+
PersistentPropertyPath<RelationalPersistentProperty> path = PropertyPathTestingUtils.toPath("one.two",
54+
DummyEntity.class, context);
5255

5356
@Before
5457
public void before() {
@@ -127,8 +130,8 @@ public void deleteAllByPath() {
127130

128131
accessStrategy.deleteAll(path);
129132

130-
verify(session).delete(
131-
eq("org.springframework.data.jdbc.mybatis.MyBatisDataAccessStrategyUnitTests$DummyEntityMapper.deleteAll-one-two"),
133+
verify(session).delete(eq(
134+
"org.springframework.data.jdbc.mybatis.MyBatisDataAccessStrategyUnitTests$DummyEntityMapper.deleteAll-one-two"),
132135
captor.capture());
133136

134137
assertThat(captor.getValue()) //
@@ -285,6 +288,65 @@ public void findAllByProperty() {
285288
);
286289
}
287290

291+
@SuppressWarnings("unchecked")
292+
@Test // DATAJDBC-384
293+
public void findAllByPath() {
294+
295+
RelationalPersistentProperty property = mock(RelationalPersistentProperty.class, RETURNS_DEEP_STUBS);
296+
PersistentPropertyPath path = mock(PersistentPropertyPath.class, RETURNS_DEEP_STUBS);
297+
298+
when(path.getBaseProperty()).thenReturn(property);
299+
when(property.getOwner().getType()).thenReturn((Class) String.class);
300+
301+
when(path.getRequiredLeafProperty()).thenReturn(property);
302+
when(property.getType()).thenReturn((Class) Number.class);
303+
304+
when(path.toDotPath()).thenReturn("dot.path");
305+
306+
accessStrategy.findAllByPath(Identifier.empty(), path);
307+
308+
verify(session).selectList(eq("java.lang.StringMapper.findAllByPath-dot.path"), captor.capture());
309+
310+
assertThat(captor.getValue()) //
311+
.isNotNull() //
312+
.extracting( //
313+
MyBatisContext::getInstance, //
314+
MyBatisContext::getId, //
315+
MyBatisContext::getIdentifier, //
316+
MyBatisContext::getDomainType, //
317+
c -> c.get("key") //
318+
).containsExactly( //
319+
null, //
320+
null, //
321+
Identifier.empty(), //
322+
Number.class, //
323+
null //
324+
);
325+
}
326+
327+
@SuppressWarnings("unchecked")
328+
@Test // DATAJDBC-384
329+
public void findAllByPathFallsBackToFindAllByProperty() {
330+
331+
RelationalPersistentProperty property = mock(RelationalPersistentProperty.class, RETURNS_DEEP_STUBS);
332+
PersistentPropertyPath path = mock(PersistentPropertyPath.class, RETURNS_DEEP_STUBS);
333+
334+
when(path.getBaseProperty()).thenReturn(property);
335+
when(property.getOwner().getType()).thenReturn((Class) String.class);
336+
337+
when(path.getRequiredLeafProperty()).thenReturn(property);
338+
when(property.getType()).thenReturn((Class) Number.class);
339+
340+
when(path.toDotPath()).thenReturn("dot.path");
341+
342+
when(session.selectList(any(), any())).thenThrow(PersistenceException.class).thenReturn(emptyList());
343+
344+
accessStrategy.findAllByPath(Identifier.empty(), path);
345+
346+
verify(session, times(2)).selectList(any(), any());
347+
348+
}
349+
288350
@Test // DATAJDBC-123
289351
public void existsById() {
290352

src/main/asciidoc/jdbc.adoc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,12 +517,19 @@ Note that the type used for prefixing the statement name is the name of the aggr
517517

518518
`getDomainType`: The type of the entity to load.
519519

520-
| `findAllByProperty-<propertyName>` | Select a set of entities that is referenced by another entity. The type of the referencing entity is used for the prefix. The referenced entities type is used as the suffix. | All `find*` methods.|
520+
| `findAllByProperty-<propertyName>` | Select a set of entities that is referenced by another entity. The type of the referencing entity is used for the prefix. The referenced entities type is used as the suffix. _This method is deprecated. Use `findAllByPath` instead_ | All `find*` methods. If no query is defined for `findAllByPath`|
521521

522522
`getId`: The ID of the entity referencing the entities to be loaded.
523523

524524
`getDomainType`: The type of the entity to load.
525525

526+
527+
| `findAllByPath-<propertyPath>` | Select a set of entities that is referenced by another entity via a property path. | All `find*` methods.|
528+
529+
`getIdentifier`: The `Identifier` holding the id of the aggregate root plus the keys and list indexes of all path elements.
530+
531+
`getDomainType`: The type of the entity to load.
532+
526533
| `count` | Count the number of aggregate root of the type used as prefix | `count` |
527534

528535
`getDomainType`: The type of aggregate roots to count.

0 commit comments

Comments
 (0)