Skip to content

DATAJDBC-155 - Simplified construction of JdbcRepositoryFactory. #54

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jdbc</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<version>1.0.0.DATAJDBC-155-SNAPSHOT</version>

<name>Spring Data JDBC</name>
<description>Spring Data module for JDBC repositories.</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
private final JdbcMappingContext context;
private final DataAccessStrategy accessStrategy;

public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, NamedParameterJdbcOperations operations,
JdbcMappingContext context, DataAccessStrategy accessStrategy) {
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, JdbcMappingContext context,
DataAccessStrategy accessStrategy) {

this.sqlGeneratorSource = sqlGeneratorSource;
this.operations = operations;
this.operations = context.getTemplate();
this.context = context;
this.accessStrategy = accessStrategy;
}
Expand All @@ -69,11 +69,10 @@ public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, NamedPar
* Creates a {@link DefaultDataAccessStrategy} which references it self for resolution of recursive data accesses.
* Only suitable if this is the only access strategy in use.
*/
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, NamedParameterJdbcOperations operations,
JdbcMappingContext context) {
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, JdbcMappingContext context) {

this.sqlGeneratorSource = sqlGeneratorSource;
this.operations = operations;
this.operations = context.getTemplate();
this.context = context;
this.accessStrategy = this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,22 @@
*/
package org.springframework.data.jdbc.mybatis;

import static java.util.Arrays.*;

import java.util.Collections;
import java.util.Map;

import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.data.jdbc.core.CascadingDataAccessStrategy;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
import org.springframework.data.jdbc.core.DelegatingDataAccessStrategy;
import org.springframework.data.jdbc.core.SqlGeneratorSource;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
import org.springframework.data.mapping.PropertyPath;

import java.util.Collections;
import java.util.Map;

/**
* {@link DataAccessStrategy} implementation based on MyBatis. Each method gets mapped to a statement. The name of the
* statement gets constructed as follows: The namespace is based on the class of the entity plus the suffix "Mapper".
Expand All @@ -41,11 +48,43 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {

private final SqlSession sqlSession;

/**
* Create a {@link DataAccessStrategy} that first checks for queries defined by MyBatis and if it doesn't find one
* used a {@link DefaultDataAccessStrategy}
*
* @param context
* @param sqlSession
* @return
*/
public static DataAccessStrategy createCombinedAccessStrategy(JdbcMappingContext context, SqlSession sqlSession) {

// the DefaultDataAccessStrategy needs a reference to the returned DataAccessStrategy. This creates a dependency
// cycle. In order to create it, we need something that allows to defer closing the cycle until all the elements are
// created. That is the purpose of the DelegatingAccessStrategy.
DelegatingDataAccessStrategy delegatingDataAccessStrategy = new DelegatingDataAccessStrategy();
MyBatisDataAccessStrategy myBatisDataAccessStrategy = new MyBatisDataAccessStrategy(sqlSession);

CascadingDataAccessStrategy cascadingDataAccessStrategy = new CascadingDataAccessStrategy(
asList(myBatisDataAccessStrategy, delegatingDataAccessStrategy));

DefaultDataAccessStrategy defaultDataAccessStrategy = new DefaultDataAccessStrategy( //
new SqlGeneratorSource(context), //
context, //
cascadingDataAccessStrategy);

delegatingDataAccessStrategy.setDelegate(defaultDataAccessStrategy);

return cascadingDataAccessStrategy;
}

/**
* Constructs a {@link DataAccessStrategy} based on MyBatis.
* <p>
* Use a {@link SqlSessionTemplate} for {@link SqlSession} or a similar implementation tying the session to the
* proper transaction.
* Use a {@link SqlSessionTemplate} for {@link SqlSession} or a similar implementation tying the session to the proper
* transaction. Note that the resulting {@link DataAccessStrategy} only handles MyBatis. It does not include the
* functionality of the {@link org.springframework.data.jdbc.core.DefaultDataAccessStrategy} which one normally still
* wants. Use {@link #createCombinedAccessStrategy(JdbcMappingContext, SqlSession)} to create such a
* {@link DataAccessStrategy}.
*
* @param sqlSession Must be non {@literal null}.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,20 @@
import org.springframework.data.jdbc.mapping.model.ConversionCustomizer;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.NamingStrategy;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

/**
* Beans that must be registered for Spring Data JDBC to work.
*
* @author Greg Turnquist
* @author Jens Schauder
*/
@Configuration
public class JdbcConfiguration {

@Bean
JdbcMappingContext jdbcMappingContext(NamedParameterJdbcTemplate template, Optional<NamingStrategy> namingStrategy,
JdbcMappingContext jdbcMappingContext(NamedParameterJdbcOperations template, Optional<NamingStrategy> namingStrategy,
Optional<ConversionCustomizer> conversionCustomizer) {

return new JdbcMappingContext(namingStrategy.orElse(NamingStrategy.INSTANCE), template,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.util.Assert;

/**
* Creates repository implementation based on JDBC.
*
* @author Jens Schauder
* @author Greg Turnquist
* @author Christoph Strobl
* @since 2.0
*/
public class JdbcRepositoryFactory extends RepositoryFactorySupport {
Expand Down Expand Up @@ -89,7 +91,12 @@ protected Optional<QueryLookupStrategy> getQueryLookupStrategy(QueryLookupStrate
return Optional.of(new JdbcQueryLookupStrategy(evaluationContextProvider, context, accessStrategy, rowMapperMap));
}

/**
* @param rowMapperMap must not be {@literal null} consider {@link RowMapperMap#EMPTY} instead.
*/
public void setRowMapperMap(RowMapperMap rowMapperMap) {

Assert.notNull(rowMapperMap, "RowMapperMap must not be null!");
this.rowMapperMap = rowMapperMap;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
import org.springframework.data.jdbc.core.SqlGeneratorSource;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.repository.RowMapperMap;
import org.springframework.data.repository.Repository;
Expand All @@ -34,6 +36,7 @@
*
* @author Jens Schauder
* @author Greg Turnquist
* @author Christoph Strobl
* @since 2.0
*/
public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> //
Expand Down Expand Up @@ -65,10 +68,7 @@ protected RepositoryFactorySupport doCreateRepositoryFactory() {

JdbcRepositoryFactory jdbcRepositoryFactory = new JdbcRepositoryFactory(publisher, mappingContext,
dataAccessStrategy);

if (rowMapperMap != null) {
jdbcRepositoryFactory.setRowMapperMap(rowMapperMap);
}
jdbcRepositoryFactory.setRowMapperMap(rowMapperMap);

return jdbcRepositoryFactory;
}
Expand All @@ -80,11 +80,18 @@ protected void setMappingContext(JdbcMappingContext mappingContext) {
this.mappingContext = mappingContext;
}

@Autowired
/**
* @param dataAccessStrategy can be {@literal null}.
*/
@Autowired(required = false)
public void setDataAccessStrategy(DataAccessStrategy dataAccessStrategy) {
this.dataAccessStrategy = dataAccessStrategy;
}

/**
* @param rowMapperMap can be {@literal null}. {@link #afterPropertiesSet()} defaults to {@link RowMapperMap#EMPTY} if
* {@literal null}.
*/
@Autowired(required = false)
public void setRowMapperMap(RowMapperMap rowMapperMap) {
this.rowMapperMap = rowMapperMap;
Expand All @@ -93,8 +100,19 @@ public void setRowMapperMap(RowMapperMap rowMapperMap) {
@Override
public void afterPropertiesSet() {

Assert.notNull(this.dataAccessStrategy, "DataAccessStrategy must not be null!");
Assert.notNull(this.mappingContext, "MappingContext must not be null!");
Assert.state(this.mappingContext != null, "MappingContext is required and must not be null!");

if (dataAccessStrategy == null) {

dataAccessStrategy = new DefaultDataAccessStrategy( //
new SqlGeneratorSource(mappingContext), //
mappingContext);
}

if (rowMapperMap == null) {
rowMapperMap = RowMapperMap.EMPTY;
}

super.afterPropertiesSet();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ public class DefaultDataAccessStrategyUnitTests {

DefaultDataAccessStrategy accessStrategy = new DefaultDataAccessStrategy( //
new SqlGeneratorSource(context), //
jdbcOperations, //
context //
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/
package org.springframework.data.jdbc.mybatis;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.*;

import junit.framework.AssertionFailedError;

Expand All @@ -30,6 +30,8 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.repository.CrudRepository;
Expand All @@ -39,8 +41,6 @@
import org.springframework.test.context.junit4.rules.SpringMethodRule;
import org.springframework.transaction.annotation.Transactional;

import javax.net.ssl.SSLSocketFactory;

/**
* Tests the integration with Mybatis.
*
Expand Down Expand Up @@ -83,8 +83,8 @@ SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory factory) {
}

@Bean
MyBatisDataAccessStrategy dataAccessStrategy(SqlSession sqlSession) {
return new MyBatisDataAccessStrategy(sqlSession);
DataAccessStrategy dataAccessStrategy(JdbcMappingContext context, SqlSession sqlSession) {
return MyBatisDataAccessStrategy.createCombinedAccessStrategy(context, sqlSession);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,12 @@ public class SimpleJdbcRepositoryEventsUnitTests {
@Before
public void before() {

final JdbcMappingContext context = new JdbcMappingContext(mock(NamedParameterJdbcOperations.class));
final JdbcMappingContext context = new JdbcMappingContext(createIdGeneratingOperations());
JdbcRepositoryFactory factory = new JdbcRepositoryFactory( //
publisher, //
context, //
new DefaultDataAccessStrategy( //
new SqlGeneratorSource(context), //
createIdGeneratingOperations(), //
context //
) //
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,49 @@
/*
* Copyright 2017-2018 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.repository.support;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.repository.RowMapperMap;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.Repository;
import org.springframework.test.util.ReflectionTestUtils;

/**
* Tests the dependency injection for {@link JdbcRepositoryFactoryBean}.
*
* @author Jens Schauder
* @author Greg Turnquist
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class JdbcRepositoryFactoryBeanUnitTests {

JdbcRepositoryFactoryBean<DummyEntityRepository, DummyEntity, Long> factoryBean;

@Mock ListableBeanFactory beanFactory;
@Mock Repository<?, ?> repository;
@Mock DataAccessStrategy dataAccessStrategy;
@Mock JdbcMappingContext mappingContext;

Expand All @@ -55,6 +70,25 @@ public void requiresListableBeanFactory() {
factoryBean.setBeanFactory(mock(BeanFactory.class));
}

@Test(expected = IllegalStateException.class) // DATAJDBC-155
public void afterPropertiesThowsExceptionWhenNoMappingContextSet() {

factoryBean.setMappingContext(null);
factoryBean.afterPropertiesSet();
}

@Test // DATAJDBC-155
public void afterPropertiesSetDefaultsNullablePropertiesCorrectly() {

factoryBean.setMappingContext(mappingContext);
factoryBean.afterPropertiesSet();

assertThat(factoryBean.getObject()).isNotNull();
assertThat(ReflectionTestUtils.getField(factoryBean, "dataAccessStrategy"))
.isInstanceOf(DefaultDataAccessStrategy.class);
assertThat(ReflectionTestUtils.getField(factoryBean, "rowMapperMap")).isEqualTo(RowMapperMap.EMPTY);
}

private static class DummyEntity {
@Id private Long id;
}
Expand Down
Loading