, S, ID extend
@Override
protected RepositoryFactorySupport doCreateRepositoryFactory() {
- return new JdbcRepositoryFactory(findOrCreateJdbcOperations(), applicationEventPublisher,
- findOrCreateNamingStrategy());
+
+ final JdbcMappingContext context = new JdbcMappingContext(findOrCreateNamingStrategy());
+
+ return new JdbcRepositoryFactory(applicationEventPublisher, context, createDataAccessStrategy(context));
+ }
+
+ /**
+ *
+ * Create the {@link DataAccessStrategy}, by combining all applicable strategies into one.
+ *
+ *
+ * The challenge is that the {@link DefaultDataAccessStrategy} when used for reading needs a
+ * {@link DataAccessStrategy} for loading referenced entities (see.
+ * {@link DefaultDataAccessStrategy#getEntityRowMapper(Class)}. But it should use all configured
+ * {@link DataAccessStrategy}s for this. This creates a cyclic dependency. In order to build this the
+ * {@link DefaultDataAccessStrategy} gets passed in a {@link DelegatingDataAccessStrategy} which at the end gets set
+ * to the full {@link CascadingDataAccessStrategy}.
+ *
+ */
+ private CascadingDataAccessStrategy createDataAccessStrategy(JdbcMappingContext context) {
+
+ DelegatingDataAccessStrategy delegatingDataAccessStrategy = new DelegatingDataAccessStrategy();
+
+ List accessStrategies = Stream.of( //
+ createMyBatisDataAccessStrategy(), //
+ createDefaultAccessStrategy(context, delegatingDataAccessStrategy) //
+ ) //
+ .filter(Optional::isPresent) //
+ .map(Optional::get) //
+ .collect(Collectors.toList());
+
+ CascadingDataAccessStrategy strategy = new CascadingDataAccessStrategy(accessStrategies);
+ delegatingDataAccessStrategy.setDelegate(strategy);
+
+ return strategy;
+ }
+
+ private Optional createMyBatisDataAccessStrategy() {
+
+ if (!ClassUtils.isPresent("org.apache.ibatis.session.SqlSessionFactory", this.getClass().getClassLoader())) {
+ return Optional.empty();
+ }
+
+ return getBean(SqlSessionFactory.class, SQL_SESSION_FACTORY_BEAN_NAME)
+ .map(ssf -> new MyBatisDataAccessStrategy(ssf));
+ }
+
+ private Optional createDefaultAccessStrategy(JdbcMappingContext context,
+ DelegatingDataAccessStrategy delegatingDataAccessStrategy) {
+
+ return Optional.of(new DefaultDataAccessStrategy(new SqlGeneratorSource(context), findOrCreateJdbcOperations(),
+ context, delegatingDataAccessStrategy));
}
private NamedParameterJdbcOperations findOrCreateJdbcOperations() {
diff --git a/src/test/java/org/springframework/data/jdbc/core/CascadingDataAccessStrategyUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/CascadingDataAccessStrategyUnitTests.java
new file mode 100644
index 0000000000..349c419c61
--- /dev/null
+++ b/src/test/java/org/springframework/data/jdbc/core/CascadingDataAccessStrategyUnitTests.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2017 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
+ *
+ * http://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.core;
+
+import static java.util.Arrays.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import junit.framework.AssertionFailedError;
+
+import java.util.Collections;
+
+import org.junit.Test;
+import org.springframework.data.jdbc.core.FunctionCollector.CombinedDataAccessException;
+import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
+
+/**
+ * Unit tests for {@link CascadingDataAccessStrategy}.
+ *
+ * @author Jens Schauder
+ */
+public class CascadingDataAccessStrategyUnitTests {
+
+ int errorIndex = 1;
+ String[] errorMessages = {"Sorry I don't support this method. Please try again later", "Still no luck"};
+
+ DataAccessStrategy alwaysFails = mock(DataAccessStrategy.class, i -> {
+ errorIndex ++;
+ errorIndex %=2;
+ throw new UnsupportedOperationException(errorMessages[errorIndex]);
+ });
+ DataAccessStrategy succeeds = mock(DataAccessStrategy.class);
+ DataAccessStrategy mayNotCall = mock(DataAccessStrategy.class, i -> {
+ throw new AssertionFailedError("this shouldn't have get called");
+ });
+
+
+ @Test // DATAJDBC-123
+ public void findByReturnsFirstSuccess() {
+
+ doReturn("success").when(succeeds).findById(23L, String.class);
+ CascadingDataAccessStrategy access = new CascadingDataAccessStrategy(asList(alwaysFails, succeeds, mayNotCall));
+
+ String byId = access.findById(23L, String.class);
+
+ assertThat(byId).isEqualTo("success");
+ }
+
+ @Test // DATAJDBC-123
+ public void findByFailsIfAllStrategiesFail() {
+
+ CascadingDataAccessStrategy access = new CascadingDataAccessStrategy(asList(alwaysFails, alwaysFails));
+
+ assertThatExceptionOfType(CombinedDataAccessException.class) //
+ .isThrownBy(() -> access.findById(23L, String.class)) //
+ .withMessageContaining("Failed to perform data access with all available strategies") //
+ .withMessageContaining("Sorry I don't support this method") //
+ .withMessageContaining("Still no luck");
+
+ }
+
+ @Test // DATAJDBC-123
+ public void findByPropertyReturnsFirstSuccess() {
+
+ doReturn(Collections.singletonList("success")).when(succeeds).findAllByProperty(eq(23L), any(JdbcPersistentProperty.class));
+ CascadingDataAccessStrategy access = new CascadingDataAccessStrategy(asList(alwaysFails, succeeds, mayNotCall));
+
+ Iterable