Skip to content

Commit b044a13

Browse files
gregturnschauder
authored andcommitted
DATAJDBC-151 - Reform Spring Data JDBC to stop autoconfiguration
In the past, Spring Data JDBC performed autoconfiguration such as gleaning whether or not MyBatis is on the classpath, and also whether or not certain other beans exist. This commit removes such flexible settings and instead wires up a JdbcMappingContext seeking an optional NamingStrategy and optional ConversionCustomizer. The other required beans will alert the end user if they don't exist. All relevant test cases are updated to inject the proper components. All autoconfiguration is being moved outside Spring Data JDBC, to eventually join Spring Boot after being shook out as an independent module.
1 parent 0b2708f commit b044a13

File tree

8 files changed

+177
-444
lines changed

8 files changed

+177
-444
lines changed

src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,19 @@
3232
* repositories by default.
3333
*
3434
* @author Jens Schauder
35+
* @author Greg Turnquist
3536
* @since 2.0
3637
*/
3738
@Target(ElementType.TYPE)
3839
@Retention(RetentionPolicy.RUNTIME)
3940
@Documented
4041
@Inherited
41-
@Import(JdbcRepositoriesRegistrar.class)
42+
@Import({JdbcRepositoriesRegistrar.class, JdbcConfiguration.class})
4243
public @interface EnableJdbcRepositories {
4344

4445
/**
4546
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.:
46-
* {@code @EnableJpaRepositories("org.my.pkg")} instead of {@code @EnableJpaRepositories(basePackages="org.my.pkg")}.
47+
* {@code @EnableJdbcRepositories("org.my.pkg")} instead of {@code @EnableJdbcRepositories(basePackages="org.my.pkg")}.
4748
*/
4849
String[] value() default {};
4950

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.repository.config;
17+
18+
import java.util.Optional;
19+
20+
import org.springframework.context.annotation.Bean;
21+
import org.springframework.context.annotation.Configuration;
22+
import org.springframework.data.jdbc.mapping.model.ConversionCustomizer;
23+
import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy;
24+
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
25+
import org.springframework.data.jdbc.mapping.model.NamingStrategy;
26+
27+
/**
28+
* Beans that must be registered for Spring Data JDBC to work.
29+
*
30+
* @author Greg Turnquist
31+
*/
32+
@Configuration
33+
public class JdbcConfiguration {
34+
35+
@Bean
36+
JdbcMappingContext jdbcMappingContext(Optional<NamingStrategy> namingStrategy,
37+
Optional<ConversionCustomizer> conversionCustomizer) {
38+
39+
return new JdbcMappingContext(
40+
namingStrategy.orElse(new DefaultNamingStrategy()),
41+
conversionCustomizer.orElse(conversionService -> {}));
42+
}
43+
}

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

Lines changed: 30 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -16,35 +16,16 @@
1616
package org.springframework.data.jdbc.repository.support;
1717

1818
import java.io.Serializable;
19-
import java.util.List;
20-
import java.util.Map;
21-
import java.util.Optional;
22-
import java.util.stream.Collectors;
23-
import java.util.stream.Stream;
2419

25-
import javax.sql.DataSource;
26-
27-
import org.apache.ibatis.session.SqlSessionFactory;
28-
import org.springframework.context.ApplicationContext;
20+
import org.springframework.beans.factory.annotation.Autowired;
2921
import org.springframework.context.ApplicationEventPublisher;
30-
import org.springframework.data.jdbc.core.CascadingDataAccessStrategy;
22+
import org.springframework.context.ApplicationEventPublisherAware;
3123
import org.springframework.data.jdbc.core.DataAccessStrategy;
32-
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
33-
import org.springframework.data.jdbc.core.DelegatingDataAccessStrategy;
34-
import org.springframework.data.jdbc.core.SqlGeneratorSource;
35-
import org.springframework.data.jdbc.mapping.model.ConversionCustomizer;
36-
import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy;
3724
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
38-
import org.springframework.data.jdbc.mapping.model.NamingStrategy;
39-
import org.springframework.data.jdbc.mybatis.MyBatisDataAccessStrategy;
4025
import org.springframework.data.repository.Repository;
4126
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
4227
import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport;
43-
import org.springframework.data.util.Optionals;
44-
import org.springframework.jdbc.core.JdbcOperations;
45-
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
46-
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
47-
import org.springframework.util.ClassUtils;
28+
import org.springframework.util.Assert;
4829

4930
/**
5031
* Special adapter for Springs {@link org.springframework.beans.factory.FactoryBean} interface to allow easy setup of
@@ -55,141 +36,50 @@
5536
* @since 2.0
5637
*/
5738
public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> //
58-
extends TransactionalRepositoryFactoryBeanSupport<T, S, ID> {
59-
60-
private static final String NO_NAMED_PARAMETER_JDBC_OPERATION_ERROR_MESSAGE = //
61-
"No unique NamedParameterJdbcOperation could be found, " //
62-
+ "nor JdbcOperations or DataSource to construct one from.";
63-
64-
private static final String NAMED_PARAMETER_JDBC_OPERATIONS_BEAN_NAME = "namedParameterJdbcTemplate";
65-
private static final String JDBC_OPERATIONS_BEAN_NAME = "jdbcTemplate";
66-
private static final String DATA_SOURCE_BEAN_NAME = "dataSource";
67-
private static final String NAMING_STRATEGY_BEAN_NAME = "namingStrategy";
68-
private static final String SQL_SESSION_FACTORY_BEAN_NAME = "sqlSessionFactory";
69-
private static final String CONVERSION_CUSTOMIZER_BEAN_NAME = "conversionCustomizer";
39+
extends TransactionalRepositoryFactoryBeanSupport<T, S, ID> implements ApplicationEventPublisherAware {
7040

71-
private final ApplicationEventPublisher applicationEventPublisher;
72-
private final ApplicationContext applicationContext;
73-
74-
JdbcRepositoryFactoryBean(Class<? extends T> repositoryInterface, ApplicationEventPublisher applicationEventPublisher,
75-
ApplicationContext applicationContext) {
41+
private ApplicationEventPublisher publisher;
42+
private JdbcMappingContext mappingContext;
43+
private DataAccessStrategy dataAccessStrategy;
7644

45+
JdbcRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
7746
super(repositoryInterface);
78-
this.applicationEventPublisher = applicationEventPublisher;
79-
this.applicationContext = applicationContext;
8047
}
8148

8249
@Override
83-
protected RepositoryFactorySupport doCreateRepositoryFactory() {
84-
85-
final JdbcMappingContext context = new JdbcMappingContext(findOrCreateNamingStrategy(), findOrCreateConversionCustomizer());
86-
87-
return new JdbcRepositoryFactory(applicationEventPublisher, context, createDataAccessStrategy(context));
50+
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
51+
52+
super.setApplicationEventPublisher(publisher);
53+
this.publisher = publisher;
8854
}
8955

9056
/**
91-
* <p>
92-
* Create the {@link DataAccessStrategy}, by combining all applicable strategies into one.
93-
* </p>
94-
* <p>
95-
* The challenge is that the {@link DefaultDataAccessStrategy} when used for reading needs a
96-
* {@link DataAccessStrategy} for loading referenced entities (see.
97-
* {@link DefaultDataAccessStrategy#getEntityRowMapper(Class)}. But it should use all configured
98-
* {@link DataAccessStrategy}s for this. This creates a cyclic dependency. In order to build this the
99-
* {@link DefaultDataAccessStrategy} gets passed in a {@link DelegatingDataAccessStrategy} which at the end gets set
100-
* to the full {@link CascadingDataAccessStrategy}.
101-
* </p>
57+
* Creates the actual {@link RepositoryFactorySupport} instance.
58+
*
59+
* @return
10260
*/
103-
private CascadingDataAccessStrategy createDataAccessStrategy(JdbcMappingContext context) {
104-
105-
DelegatingDataAccessStrategy delegatingDataAccessStrategy = new DelegatingDataAccessStrategy();
106-
107-
List<DataAccessStrategy> accessStrategies = Stream.of( //
108-
createMyBatisDataAccessStrategy(), //
109-
createDefaultAccessStrategy(context, delegatingDataAccessStrategy) //
110-
) //
111-
.filter(Optional::isPresent) //
112-
.map(Optional::get) //
113-
.collect(Collectors.toList());
114-
115-
CascadingDataAccessStrategy strategy = new CascadingDataAccessStrategy(accessStrategies);
116-
delegatingDataAccessStrategy.setDelegate(strategy);
117-
118-
return strategy;
119-
}
120-
121-
private Optional<DataAccessStrategy> createMyBatisDataAccessStrategy() {
122-
123-
String myBatisSqlSessionFactoryClassName = "org.apache.ibatis.session.SqlSessionFactory";
124-
ClassLoader classLoader = this.getClass().getClassLoader();
125-
126-
if (!ClassUtils.isPresent(myBatisSqlSessionFactoryClassName, classLoader)) {
127-
return Optional.empty();
128-
}
129-
130-
try {
131-
132-
return getBean(classLoader.loadClass(myBatisSqlSessionFactoryClassName), SQL_SESSION_FACTORY_BEAN_NAME)
133-
// note that the cast to SqlSessionFactory happens in a lambda, which is basically a separate class
134-
// thus it won't get loaded if this code path doesn't get executed.
135-
.map(ssf -> new MyBatisDataAccessStrategy((SqlSessionFactory) ssf));
136-
} catch (ClassNotFoundException e) {
137-
throw new IllegalStateException("Detected MyBatis on classpath but failed to load the class " + myBatisSqlSessionFactoryClassName);
138-
}
139-
}
140-
141-
private Optional<DataAccessStrategy> createDefaultAccessStrategy(JdbcMappingContext context,
142-
DelegatingDataAccessStrategy delegatingDataAccessStrategy) {
143-
144-
return Optional.of(new DefaultDataAccessStrategy(new SqlGeneratorSource(context), findOrCreateJdbcOperations(),
145-
context, delegatingDataAccessStrategy));
146-
}
147-
148-
private NamedParameterJdbcOperations findOrCreateJdbcOperations() {
149-
150-
return Optionals.firstNonEmpty( //
151-
this::getNamedParameterJdbcOperations, //
152-
() -> getJdbcOperations().map(NamedParameterJdbcTemplate::new), //
153-
() -> getDataSource().map(NamedParameterJdbcTemplate::new)) //
154-
.orElseThrow(() -> new IllegalStateException(NO_NAMED_PARAMETER_JDBC_OPERATION_ERROR_MESSAGE));
155-
}
156-
157-
private NamingStrategy findOrCreateNamingStrategy() {
158-
return getNamingStrategy().orElse(new DefaultNamingStrategy());
159-
}
160-
161-
private ConversionCustomizer findOrCreateConversionCustomizer() {
162-
return getConversionCustomizer().orElse(conversionService->{});
163-
}
164-
165-
private Optional<NamedParameterJdbcOperations> getNamedParameterJdbcOperations() {
166-
return getBean(NamedParameterJdbcOperations.class, NAMED_PARAMETER_JDBC_OPERATIONS_BEAN_NAME);
61+
@Override
62+
protected RepositoryFactorySupport doCreateRepositoryFactory() {
63+
return new JdbcRepositoryFactory(publisher, mappingContext, dataAccessStrategy);
16764
}
16865

169-
private Optional<JdbcOperations> getJdbcOperations() {
170-
return getBean(JdbcOperations.class, JDBC_OPERATIONS_BEAN_NAME);
171-
}
66+
@Autowired
67+
protected void setMappingContext(JdbcMappingContext mappingContext) {
17268

173-
private Optional<DataSource> getDataSource() {
174-
return getBean(DataSource.class, DATA_SOURCE_BEAN_NAME);
69+
super.setMappingContext(mappingContext);
70+
this.mappingContext = mappingContext;
17571
}
17672

177-
private Optional<NamingStrategy> getNamingStrategy() {
178-
return getBean(NamingStrategy.class, NAMING_STRATEGY_BEAN_NAME);
73+
@Autowired
74+
public void setDataAccessStrategy(DataAccessStrategy dataAccessStrategy) {
75+
this.dataAccessStrategy = dataAccessStrategy;
17976
}
18077

181-
private Optional<ConversionCustomizer> getConversionCustomizer() {
182-
return getBean(ConversionCustomizer.class, CONVERSION_CUSTOMIZER_BEAN_NAME);
183-
}
184-
185-
private <R> Optional<R> getBean(Class<R> type, String name) {
186-
187-
Map<String, R> beansOfType = applicationContext.getBeansOfType(type);
188-
189-
if (beansOfType.size() == 1) {
190-
return beansOfType.values().stream().findFirst();
191-
}
78+
@Override
79+
public void afterPropertiesSet() {
19280

193-
return Optional.ofNullable(beansOfType.get(name));
81+
Assert.notNull(this.dataAccessStrategy, "DataAccessStrategy must not be null!");
82+
Assert.notNull(this.mappingContext, "MappingContext must not be null!");
83+
super.afterPropertiesSet();
19484
}
19585
}

src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,8 @@
2929
import org.springframework.beans.factory.annotation.Autowired;
3030
import org.springframework.context.annotation.Bean;
3131
import org.springframework.context.annotation.Import;
32-
import org.springframework.data.jdbc.repository.JdbcRepositoryIdGenerationIntegrationTests.TestConfiguration;
3332
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
34-
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
33+
import org.springframework.data.jdbc.testing.TestConfiguration;
3534
import org.springframework.data.repository.CrudRepository;
3635
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
3736
import org.springframework.test.context.ContextConfiguration;
@@ -43,6 +42,7 @@
4342
* Tests the integration with Mybatis.
4443
*
4544
* @author Jens Schauder
45+
* @author Greg Turnquist
4646
*/
4747
@ContextConfiguration
4848
@Transactional
@@ -53,8 +53,6 @@ public class MyBatisHsqlIntegrationTests {
5353
@EnableJdbcRepositories(considerNestedRepositories = true)
5454
static class Config {
5555

56-
@Autowired JdbcRepositoryFactory factory;
57-
5856
@Bean
5957
Class<?> testClass() {
6058
return MyBatisHsqlIntegrationTests.class;
@@ -75,6 +73,11 @@ SqlSessionFactoryBean createSessionFactory(EmbeddedDatabase db) {
7573

7674
return sqlSessionFactoryBean;
7775
}
76+
77+
@Bean
78+
MyBatisDataAccessStrategy dataAccessStrategy(SqlSessionFactory factory) {
79+
return new MyBatisDataAccessStrategy(factory);
80+
}
7881
}
7982

8083
@ClassRule public static final SpringClassRule classRule = new SpringClassRule();

src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,21 @@
2626
import org.junit.Rule;
2727
import org.junit.Test;
2828
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.beans.factory.annotation.Qualifier;
2930
import org.springframework.context.annotation.Bean;
3031
import org.springframework.context.annotation.ComponentScan;
3132
import org.springframework.context.annotation.Configuration;
3233
import org.springframework.context.annotation.Import;
3334
import org.springframework.data.annotation.Id;
35+
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
36+
import org.springframework.data.jdbc.core.SqlGeneratorSource;
3437
import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy;
38+
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
3539
import org.springframework.data.jdbc.mapping.model.NamingStrategy;
3640
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
3741
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
3842
import org.springframework.data.repository.CrudRepository;
43+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
3944
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
4045
import org.springframework.test.context.ContextConfiguration;
4146
import org.springframework.test.context.junit4.rules.SpringClassRule;
@@ -61,6 +66,11 @@ Class<?> testClass() {
6166
return JdbcRepositoryIdGenerationIntegrationTests.class;
6267
}
6368

69+
@Bean
70+
DefaultDataAccessStrategy defaultDataAccessStrategy(JdbcMappingContext context,
71+
@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations operations) {
72+
return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), operations, context);
73+
}
6474
}
6575

6676
@ClassRule public static final SpringClassRule classRule = new SpringClassRule();
@@ -122,7 +132,7 @@ static class PrimitiveIdEntity {
122132
@Configuration
123133
@ComponentScan("org.springframework.data.jdbc.testing")
124134
@EnableJdbcRepositories(considerNestedRepositories = true)
125-
public static class TestConfiguration {
135+
static class TestConfiguration {
126136

127137
@Bean
128138
Class<?> testClass() {

0 commit comments

Comments
 (0)