DATAJDBC-123 - MyBatis integration on DbAction level.

If MyBatis-spring is available and a SqlSessionFactory in the Application Context we look for matching mapped sql statements instead of using the default generated SQL.

Introduced DataAccessStrategy as a new abstraction level, operating on a single entity. JdbcEntityOperations only contain operations related to complete Aggregates. Thereby also solving DATAJDBC-132.

Integration tests ending in HsqlIntegrationTest will only get executed using HsqlDb.

Related issue: DATAJDBC-132.
This commit is contained in:
Jens Schauder
2017-08-28 15:05:30 +02:00
committed by Greg Turnquist
parent fd2ae05795
commit 806bb24ff5
30 changed files with 1989 additions and 544 deletions

View File

@@ -0,0 +1,113 @@
/*
* 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 java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
import org.springframework.data.mapping.PropertyPath;
/**
* Delegates each methods to the {@link DataAccessStrategy}s passed to the constructor in turn until the first that does
* not throw an exception.
*
* @author Jens Schauder
*/
public class CascadingDataAccessStrategy implements DataAccessStrategy {
private final List<DataAccessStrategy> strategies;
public CascadingDataAccessStrategy(List<DataAccessStrategy> strategies) {
this.strategies = new ArrayList<>(strategies);
}
@Override
public <T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
collectVoid(das -> das.insert(instance, domainType, additionalParameters));
}
@Override
public <S> void update(S instance, Class<S> domainType) {
collectVoid(das -> das.update(instance, domainType));
}
@Override
public void delete(Object id, Class<?> domainType) {
collectVoid(das -> das.delete(id, domainType));
}
@Override
public void delete(Object rootId, PropertyPath propertyPath) {
collectVoid(das -> das.delete(rootId, propertyPath));
}
@Override
public <T> void deleteAll(Class<T> domainType) {
collectVoid(das -> das.deleteAll(domainType));
}
@Override
public <T> void deleteAll(PropertyPath propertyPath) {
collectVoid(das -> das.deleteAll(propertyPath));
}
@Override
public long count(Class<?> domainType) {
return collect(das -> das.count(domainType));
}
@Override
public <T> T findById(Object id, Class<T> domainType) {
return collect(das -> das.findById(id, domainType));
}
@Override
public <T> Iterable<T> findAll(Class<T> domainType) {
return collect(das -> das.findAll(domainType));
}
@Override
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
return collect(das -> das.findAllById(ids, domainType));
}
@Override
public <T> Iterable<T> findAllByProperty(Object rootId, JdbcPersistentProperty property) {
return collect(das -> das.findAllByProperty(rootId, property));
}
@Override
public <T> boolean existsById(Object id, Class<T> domainType) {
return collect(das -> das.existsById(id, domainType));
}
private <T> T collect(Function<DataAccessStrategy, T> function) {
return strategies.stream().collect(new FunctionCollector<>(function));
}
private void collectVoid(Consumer<DataAccessStrategy> consumer) {
collect(das -> {
consumer.accept(das);
return null;
});
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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 java.util.Map;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
import org.springframework.data.mapping.PropertyPath;
/**
* Abstraction for accesses to the database that should be implementable with a single SQL statement and relates to a single entity as opposed to {@link JdbcEntityOperations} which provides interactions related to complete aggregates.
*
* @author Jens Schauder
*/
public interface DataAccessStrategy {
<T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters);
<S> void update(S instance, Class<S> domainType);
void delete(Object id, Class<?> domainType);
/** Deletes all entities reachable via {@literal propertyPath} from the instance identified by {@literal rootId}.
*
* @param rootId Id of the root object on which the {@literal propertyPath} is based.
* @param propertyPath Leading from the root object to the entities to be deleted.
*/
void delete(Object rootId, PropertyPath propertyPath);
<T> void deleteAll(Class<T> domainType);
/** Deletes all entities reachable via {@literal propertyPath} from any instance.
*
* @param propertyPath Leading from the root object to the entities to be deleted.
*/
<T> void deleteAll(PropertyPath propertyPath);
long count(Class<?> domainType);
<T> T findById(Object id, Class<T> domainType);
<T> Iterable<T> findAll(Class<T> domainType);
<T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType);
/**
* Finds all entities reachable via {@literal property} from the instance identified by {@literal rootId}.
*
* @param rootId Id of the root object on which the {@literal propertyPath} is based.
* @param property Leading from the root object to the entities to be found.
*/
<T> Iterable<T> findAllByProperty(Object rootId, JdbcPersistentProperty property);
<T> boolean existsById(Object id, Class<T> domainType);
}

View File

@@ -0,0 +1,320 @@
/*
* 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 java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.data.convert.Jsr310Converters;
import org.springframework.data.jdbc.mapping.model.BasicJdbcPersistentEntityInformation;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityInformation;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
import org.springframework.data.jdbc.support.JdbcUtil;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.util.Assert;
/**
* Generates and executes actual SQL statements.
*
* @author Jens Schauder
*/
public class DefaultDataAccessStrategy implements DataAccessStrategy {
private static final String ENTITY_NEW_AFTER_INSERT = "Entity [%s] still 'new' after insert. Please set either"
+ " the id property in a BeforeInsert event handler, or ensure the database creates a value and your "
+ "JDBC driver returns it.";
private final SqlGeneratorSource sqlGeneratorSource;
private final NamedParameterJdbcOperations operations;
private final JdbcMappingContext context;
private final ConversionService conversions = getDefaultConversionService();
private final DataAccessStrategy accessStrategy;
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, NamedParameterJdbcOperations operations,
JdbcMappingContext context, DataAccessStrategy accessStrategy) {
this.sqlGeneratorSource = sqlGeneratorSource;
this.operations = operations;
this.context = context;
this.accessStrategy = accessStrategy;
}
/**
* 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) {
this.sqlGeneratorSource = sqlGeneratorSource;
this.operations = operations;
this.context = context;
this.accessStrategy = this;
}
@Override
public <T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
KeyHolder holder = new GeneratedKeyHolder();
JdbcPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
JdbcPersistentEntityInformation<T, ?> entityInformation = context
.getRequiredPersistentEntityInformation(domainType);
MapSqlParameterSource parameterSource = getPropertyMap(instance, persistentEntity);
Object idValue = getIdValueOrNull(instance, persistentEntity);
JdbcPersistentProperty idProperty = persistentEntity.getRequiredIdProperty();
parameterSource.addValue(idProperty.getColumnName(), convert(idValue, idProperty.getColumnType()),
JdbcUtil.sqlTypeFor(idProperty.getColumnType()));
additionalParameters.forEach(parameterSource::addValue);
operations.update(sql(domainType).getInsert(idValue == null, additionalParameters.keySet()), parameterSource,
holder);
setIdFromJdbc(instance, holder, persistentEntity);
if (entityInformation.isNew(instance)) {
throw new IllegalStateException(String.format(ENTITY_NEW_AFTER_INSERT, persistentEntity));
}
}
@Override
public <S> void update(S instance, Class<S> domainType) {
JdbcPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType);
operations.update(sql(domainType).getUpdate(), getPropertyMap(instance, persistentEntity));
}
@Override
public void delete(Object id, Class<?> domainType) {
String deleteByIdSql = sql(domainType).getDeleteById();
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
operations.update(deleteByIdSql, parameter);
}
@Override
public void delete(Object rootId, PropertyPath propertyPath) {
JdbcPersistentEntity<?> rootEntity = context.getRequiredPersistentEntity(propertyPath.getOwningType());
JdbcPersistentProperty referencingProperty = rootEntity.getRequiredPersistentProperty(propertyPath.getSegment());
Assert.notNull(referencingProperty, "No property found matching the PropertyPath " + propertyPath);
String format = sql(rootEntity.getType()).createDeleteByPath(propertyPath);
HashMap<String, Object> parameters = new HashMap<>();
parameters.put("rootId", rootId);
operations.update(format, parameters);
}
@Override
public <T> void deleteAll(Class<T> domainType) {
operations.getJdbcOperations().update(sql(domainType).createDeleteAllSql(null));
}
@Override
public <T> void deleteAll(PropertyPath propertyPath) {
operations.getJdbcOperations().update(sql(propertyPath.getOwningType().getType()).createDeleteAllSql(propertyPath));
}
@SuppressWarnings("ConstantConditions")
@Override
public long count(Class<?> domainType) {
return operations.getJdbcOperations().queryForObject(sql(domainType).getCount(), Long.class);
}
@Override
public <T> T findById(Object id, Class<T> domainType) {
String findOneSql = sql(domainType).getFindOne();
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
try {
return operations.queryForObject(findOneSql, parameter, getEntityRowMapper(domainType));
} catch (EmptyResultDataAccessException e) {
return null;
}
}
@Override
public <T> Iterable<T> findAll(Class<T> domainType) {
return operations.query(sql(domainType).getFindAll(), getEntityRowMapper(domainType));
}
@Override
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
String findAllInListSql = sql(domainType).getFindAllInList();
Class<?> targetType = getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType();
MapSqlParameterSource parameter = new MapSqlParameterSource( //
"ids", //
StreamSupport.stream(ids.spliterator(), false) //
.map(id -> convert(id, targetType)) //
.collect(Collectors.toList()) //
);
return operations.query(findAllInListSql, parameter, getEntityRowMapper(domainType));
}
@Override
public <T> Iterable<T> findAllByProperty(Object rootId, JdbcPersistentProperty property) {
Class<?> actualType = property.getActualType();
String findAllByProperty = sql(actualType).getFindAllByProperty(property.getReverseColumnName());
MapSqlParameterSource parameter = new MapSqlParameterSource(property.getReverseColumnName(), rootId);
return (Iterable<T>) operations.query(findAllByProperty, parameter, getEntityRowMapper(actualType));
}
@Override
public <T> boolean existsById(Object id, Class<T> domainType) {
String existsSql = sql(domainType).getExists();
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
return operations.queryForObject(existsSql, parameter, Boolean.class);
}
private static GenericConversionService getDefaultConversionService() {
DefaultConversionService conversionService = new DefaultConversionService();
Jsr310Converters.getConvertersToRegister().forEach(conversionService::addConverter);
return conversionService;
}
private <S> MapSqlParameterSource getPropertyMap(final S instance, JdbcPersistentEntity<S> persistentEntity) {
MapSqlParameterSource parameters = new MapSqlParameterSource();
persistentEntity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) property -> {
if (!property.isEntity()) {
Object value = persistentEntity.getPropertyAccessor(instance).getProperty(property);
Object convertedValue = convert(value, property.getColumnType());
parameters.addValue(property.getColumnName(), convertedValue, JdbcUtil.sqlTypeFor(property.getColumnType()));
}
});
return parameters;
}
private <S, ID> ID getIdValueOrNull(S instance, JdbcPersistentEntity<S> persistentEntity) {
EntityInformation<S, ID> entityInformation = new BasicJdbcPersistentEntityInformation<>(persistentEntity);
ID idValue = entityInformation.getId(instance);
return isIdPropertySimpleTypeAndValueZero(idValue, persistentEntity) ? null : idValue;
}
private <S, ID> boolean isIdPropertySimpleTypeAndValueZero(ID idValue, JdbcPersistentEntity<S> persistentEntity) {
JdbcPersistentProperty idProperty = persistentEntity.getIdProperty();
return idValue == null //
|| idProperty == null //
|| (idProperty.getType() == int.class && idValue.equals(0)) //
|| (idProperty.getType() == long.class && idValue.equals(0L));
}
private <S> void setIdFromJdbc(S instance, KeyHolder holder, JdbcPersistentEntity<S> persistentEntity) {
JdbcPersistentEntityInformation<S, ?> entityInformation = new BasicJdbcPersistentEntityInformation<>(
persistentEntity);
try {
getIdFromHolder(holder, persistentEntity).ifPresent(it -> {
Class<?> targetType = persistentEntity.getRequiredIdProperty().getType();
Object converted = convert(it, targetType);
entityInformation.setId(instance, converted);
});
} catch (NonTransientDataAccessException e) {
throw new UnableToSetId("Unable to set id of " + instance, e);
}
}
private <S> Optional<Object> getIdFromHolder(KeyHolder holder, JdbcPersistentEntity<S> persistentEntity) {
try {
// MySQL just returns one value with a special name
return Optional.ofNullable(holder.getKey());
} catch (InvalidDataAccessApiUsageException e) {
// Postgres returns a value for each column
return Optional.ofNullable(holder.getKeys().get(persistentEntity.getIdColumn()));
}
}
private <T> EntityRowMapper<T> getEntityRowMapper(Class<T> domainType) {
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), conversions, context, accessStrategy);
}
private <T> MapSqlParameterSource createIdParameterSource(Object id, Class<T> domainType) {
return new MapSqlParameterSource("id",
convert(id, getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType()));
}
@SuppressWarnings("unchecked")
private <S> JdbcPersistentEntity<S> getRequiredPersistentEntity(Class<S> domainType) {
return (JdbcPersistentEntity<S>) context.getRequiredPersistentEntity(domainType);
}
private <V> V convert(Object from, Class<V> to) {
if (from == null) {
return null;
}
JdbcPersistentEntity<?> persistentEntity = context.getPersistentEntity(from.getClass());
Object id = persistentEntity == null ? null : persistentEntity.getIdentifierAccessor(from).getIdentifier();
return conversions.convert(id == null ? from : id, to);
}
private SqlGenerator sql(Class<?> domainType) {
return sqlGeneratorSource.getSqlGenerator(domainType);
}
}

View File

@@ -28,7 +28,7 @@ import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
/**
* {@link Interpreter} for {@link DbAction}s using a {@link JdbcEntityTemplate} for performing actual database
* {@link Interpreter} for {@link DbAction}s using a {@link DataAccessStrategy} for performing actual database
* interactions.
*
* @author Jens Schauder
@@ -36,16 +36,45 @@ import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
class DefaultJdbcInterpreter implements Interpreter {
private final JdbcMappingContext context;
private final JdbcEntityTemplate template;
private final DataAccessStrategy accessStrategy;
DefaultJdbcInterpreter(JdbcMappingContext context, JdbcEntityTemplate template) {
DefaultJdbcInterpreter(JdbcMappingContext context, DataAccessStrategy accessStrategy) {
this.context = context;
this.template = template;
this.accessStrategy = accessStrategy;
}
@Override
public <T> void interpret(Insert<T> insert) {
accessStrategy.insert(insert.getEntity(), insert.getEntityType(), createAdditionalColumnValues(insert));
}
@Override
public <T> void interpret(Update<T> update) {
accessStrategy.update(update.getEntity(), update.getEntityType());
}
@Override
public <T> void interpret(Delete<T> delete) {
if (delete.getPropertyPath() == null) {
accessStrategy.delete(delete.getRootId(), delete.getEntityType());
} else {
accessStrategy.delete(delete.getRootId(), delete.getPropertyPath());
}
}
@Override
public <T> void interpret(DeleteAll<T> delete) {
if (delete.getEntityType() == null) {
accessStrategy.deleteAll(delete.getPropertyPath());
} else {
accessStrategy.deleteAll(delete.getEntityType());
}
}
private <T> Map<String, Object> createAdditionalColumnValues(Insert<T> insert) {
Map<String, Object> additionalColumnValues = new HashMap<>();
DbAction dependingOn = insert.getDependingOn();
@@ -59,28 +88,7 @@ class DefaultJdbcInterpreter implements Interpreter {
additionalColumnValues.put(columnName, identifier);
}
template.insert(insert.getEntity(), insert.getEntityType(), additionalColumnValues);
return additionalColumnValues;
}
@Override
public <T> void interpret(Update<T> update) {
template.update(update.getEntity(), update.getEntityType());
}
@Override
public <T> void interpret(Delete<T> delete) {
if (delete.getPropertyPath() == null) {
template.doDelete(delete.getRootId(), delete.getEntityType());
} else {
template.doDelete(delete.getRootId(), delete.getPropertyPath());
}
}
@Override
public <T> void interpret(DeleteAll<T> delete) {
template.doDeleteAll(delete.getEntityType(), delete.getPropertyPath());
}
}

View File

@@ -0,0 +1,112 @@
/*
* 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 java.util.Map;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.util.Assert;
/**
* delegates all method calls to an instance set after construction. This is useful for {@link DataAccessStrategy}s with
* cyclical dependencies.
*
* @author Jens Schauder
*/
public class DelegatingDataAccessStrategy implements DataAccessStrategy {
private DataAccessStrategy delegate;
@Override
public <T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
delegate.insert(instance, domainType, additionalParameters);
}
@Override
public <S> void update(S instance, Class<S> domainType) {
delegate.update(instance, domainType);
}
@Override
public void delete(Object rootId, PropertyPath propertyPath) {
delegate.delete(rootId, propertyPath);
}
@Override
public void delete(Object id, Class<?> domainType) {
delegate.delete(id, domainType);
}
@Override
public <T> void deleteAll(Class<T> domainType) {
delegate.deleteAll(domainType);
}
@Override
public <T> void deleteAll(PropertyPath propertyPath) {
delegate.deleteAll(propertyPath);
}
@Override
public long count(Class<?> domainType) {
return delegate.count(domainType);
}
@Override
public <T> T findById(Object id, Class<T> domainType) {
Assert.notNull(delegate, "Delegate is null");
return delegate.findById(id, domainType);
}
@Override
public <T> Iterable<T> findAll(Class<T> domainType) {
return delegate.findAll(domainType);
}
@Override
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
return delegate.findAllById(ids, domainType);
}
@Override
public <T> Iterable<T> findAllByProperty(Object rootId, JdbcPersistentProperty property) {
Assert.notNull(delegate, "Delegate is null");
return delegate.findAllByProperty(rootId, property);
}
@Override
public <T> boolean existsById(Object id, Class<T> domainType) {
return delegate.existsById(id, domainType);
}
/**
* Must be called exactly once before calling any of the other methods.
*
* @param delegate Must not be {@literal null}
*/
public void setDelegate(DataAccessStrategy delegate) {
Assert.isNull(this.delegate, "The delegate must be set exactly once");
Assert.notNull(delegate, "The delegate must not be set to null");
this.delegate = delegate;
}
}

View File

@@ -48,16 +48,16 @@ class EntityRowMapper<T> implements RowMapper<T> {
private final EntityInstantiator instantiator = new ClassGeneratingEntityInstantiator();
private final ConversionService conversions;
private final JdbcMappingContext context;
private final JdbcEntityOperations template;
private final DataAccessStrategy accessStrategy;
private final JdbcPersistentProperty idProperty;
public EntityRowMapper(JdbcPersistentEntity<T> entity, ConversionService conversions, JdbcMappingContext context,
JdbcEntityOperations template) {
DataAccessStrategy accessStrategy) {
this.entity = entity;
this.conversions = conversions;
this.context = context;
this.template = template;
this.accessStrategy = accessStrategy;
idProperty = entity.getRequiredIdProperty();
}
@@ -79,7 +79,7 @@ class EntityRowMapper<T> implements RowMapper<T> {
for (JdbcPersistentProperty property : entity) {
if (Set.class.isAssignableFrom(property.getType())) {
propertyAccessor.setProperty(property, template.findAllByProperty(id, property));
propertyAccessor.setProperty(property, accessStrategy.findAllByProperty(id, property));
} else {
propertyAccessor.setProperty(property, readFrom(resultSet, property, ""));
}

View File

@@ -0,0 +1,127 @@
/*
* 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 java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import org.springframework.dao.DataAccessException;
/**
* {@link Collector} which invokes functions on the elements of a {@link java.util.stream.Stream} containing
* {@link DataAccessStrategy}s until one function completes without throwing an exception. If all invocations throw
* exceptions this {@link Collector} throws itself an exception, gathering all exceptions thrown.
*
* @author Jens Schauder
*/
class FunctionCollector<T> implements Collector<DataAccessStrategy, FunctionCollector<T>.ResultOrException, T> {
private final Function<DataAccessStrategy, T> method;
FunctionCollector(Function<DataAccessStrategy, T> method) {
this.method = method;
}
@Override
public Supplier<ResultOrException> supplier() {
return ResultOrException::new;
}
@Override
public BiConsumer<ResultOrException, DataAccessStrategy> accumulator() {
return (roe, das) -> {
if (!roe.hasResult()) {
try {
roe.setResult(method.apply(das));
} catch (Exception ex) {
roe.add(ex);
}
}
};
}
@Override
public BinaryOperator<ResultOrException> combiner() {
return (roe1, roe2) -> {
throw new UnsupportedOperationException("Can't combine method calls");
};
}
@Override
public Function<ResultOrException, T> finisher() {
return roe -> {
if (roe.hasResult)
return roe.result;
else
throw new CombinedDataAccessException("Failed to perform data access with all available strategies",
Collections.unmodifiableList(roe.exceptions));
};
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
/**
* Stores intermediate results. I.e. a list of exceptions caught so far, any actual result and the fact, if there
* actually is an result.
*/
class ResultOrException {
private T result;
private final List<Exception> exceptions = new LinkedList<>();
private boolean hasResult = false;
private boolean hasResult() {
return hasResult;
}
private void setResult(T result) {
this.result = result;
hasResult = true;
}
public void add(Exception ex) {
exceptions.add(ex);
}
}
static class CombinedDataAccessException extends DataAccessException {
CombinedDataAccessException(String message, List<Exception> exceptions) {
super(combineMessage(message, exceptions), exceptions.get(exceptions.size() - 1));
}
private static String combineMessage(String message, List<Exception> exceptions) {
return message + exceptions.stream().map(Exception::getMessage).collect(Collectors.joining("\n\t", "\n\t", ""));
}
}
}

View File

@@ -15,10 +15,6 @@
*/
package org.springframework.data.jdbc.core;
import java.util.Map;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
/**
* Specifies a operations one can perform on a database, based on an <em>Domain Type</em>.
*
@@ -28,10 +24,6 @@ public interface JdbcEntityOperations {
<T> void save(T instance, Class<T> domainType);
<T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameter);
<T> void update(T instance, Class<T> domainType);
<T> void deleteById(Object id, Class<T> domainType);
<T> void delete(T entity, Class<T> domainType);
@@ -46,8 +38,6 @@ public interface JdbcEntityOperations {
<T> Iterable<T> findAll(Class<T> domainType);
<T> Iterable<T> findAllByProperty(Object id, JdbcPersistentProperty property);
<T> boolean existsById(Object id, Class<T> domainType);
}

View File

@@ -15,19 +15,9 @@
*/
package org.springframework.data.jdbc.core;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.data.convert.Jsr310Converters;
import org.springframework.data.jdbc.core.conversion.AggregateChange;
import org.springframework.data.jdbc.core.conversion.AggregateChange.Kind;
import org.springframework.data.jdbc.core.conversion.Interpreter;
@@ -39,61 +29,35 @@ import org.springframework.data.jdbc.mapping.event.BeforeDelete;
import org.springframework.data.jdbc.mapping.event.BeforeSave;
import org.springframework.data.jdbc.mapping.event.Identifier;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
import org.springframework.data.jdbc.mapping.model.BasicJdbcPersistentEntityInformation;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityInformation;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
import org.springframework.data.jdbc.support.JdbcUtil;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.util.Assert;
/**
* {@link JdbcEntityOperations} implementation, storing complete entities including references in a JDBC data store.
* {@link JdbcEntityOperations} implementation, storing aggregates in and obtaining them from a JDBC data store.
*
* @author Jens Schauder
*/
public class JdbcEntityTemplate implements JdbcEntityOperations {
private static final String ENTITY_NEW_AFTER_INSERT = "Entity [%s] still 'new' after insert. Please set either"
+ " the id property in a BeforeInsert event handler, or ensure the database creates a value and your "
+ "JDBC driver returns it.";
private final ApplicationEventPublisher publisher;
private final NamedParameterJdbcOperations operations;
private final JdbcMappingContext context;
private final ConversionService conversions = getDefaultConversionService();
private final Interpreter interpreter;
private final SqlGeneratorSource sqlGeneratorSource;
private final JdbcEntityWriter jdbcEntityWriter;
private final JdbcEntityDeleteWriter jdbcEntityDeleteWriter;
public JdbcEntityTemplate(ApplicationEventPublisher publisher, NamedParameterJdbcOperations operations,
JdbcMappingContext context) {
private final DataAccessStrategy accessStrategy;
public JdbcEntityTemplate(ApplicationEventPublisher publisher, JdbcMappingContext context,
DataAccessStrategy dataAccessStrategy) {
this.publisher = publisher;
this.operations = operations;
this.context = context;
this.jdbcEntityWriter = new JdbcEntityWriter(this.context);
this.jdbcEntityDeleteWriter = new JdbcEntityDeleteWriter(this.context);
this.sqlGeneratorSource = new SqlGeneratorSource(this.context);
this.interpreter = new DefaultJdbcInterpreter(this.context, this);
}
private static GenericConversionService getDefaultConversionService() {
DefaultConversionService conversionService = new DefaultConversionService();
Jsr310Converters.getConvertersToRegister().forEach(conversionService::addConverter);
return conversionService;
this.jdbcEntityWriter = new JdbcEntityWriter(context);
this.jdbcEntityDeleteWriter = new JdbcEntityDeleteWriter(context);
this.accessStrategy = dataAccessStrategy;
this.interpreter = new DefaultJdbcInterpreter(context, accessStrategy);
}
@Override
@@ -119,94 +83,29 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
));
}
@Override
public <T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
KeyHolder holder = new GeneratedKeyHolder();
JdbcPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
JdbcPersistentEntityInformation<T, ?> entityInformation = context
.getRequiredPersistentEntityInformation(domainType);
MapSqlParameterSource parameterSource = getPropertyMap(instance, persistentEntity);
Object idValue = getIdValueOrNull(instance, persistentEntity);
JdbcPersistentProperty idProperty = persistentEntity.getRequiredIdProperty();
parameterSource.addValue(idProperty.getColumnName(), convert(idValue, idProperty.getColumnType()),
JdbcUtil.sqlTypeFor(idProperty.getColumnType()));
additionalParameters.forEach(parameterSource::addValue);
operations.update(sql(domainType).getInsert(idValue == null, additionalParameters.keySet()), parameterSource,
holder);
setIdFromJdbc(instance, holder, persistentEntity);
if (entityInformation.isNew(instance)) {
throw new IllegalStateException(String.format(ENTITY_NEW_AFTER_INSERT, persistentEntity));
}
}
@Override
public <S> void update(S instance, Class<S> domainType) {
JdbcPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType);
operations.update(sql(domainType).getUpdate(), getPropertyMap(instance, persistentEntity));
}
@SuppressWarnings("ConstantConditions")
@Override
public long count(Class<?> domainType) {
return operations.getJdbcOperations().queryForObject(sql(domainType).getCount(), Long.class);
return accessStrategy.count(domainType);
}
@Override
public <T> T findById(Object id, Class<T> domainType) {
String findOneSql = sql(domainType).getFindOne();
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
return operations.queryForObject(findOneSql, parameter, getEntityRowMapper(domainType));
return accessStrategy.findById(id, domainType);
}
@Override
public <T> boolean existsById(Object id, Class<T> domainType) {
String existsSql = sql(domainType).getExists();
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
return operations.queryForObject(existsSql, parameter, Boolean.class);
return accessStrategy.existsById(id, domainType);
}
@Override
public <T> Iterable<T> findAll(Class<T> domainType) {
return operations.query(sql(domainType).getFindAll(), getEntityRowMapper(domainType));
return accessStrategy.findAll(domainType);
}
@Override
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
String findAllInListSql = sql(domainType).getFindAllInList();
Class<?> targetType = getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType();
MapSqlParameterSource parameter = new MapSqlParameterSource( //
"ids", //
StreamSupport.stream(ids.spliterator(), false) //
.map(id -> convert(id, targetType)) //
.collect(Collectors.toList()) //
);
return operations.query(findAllInListSql, parameter, getEntityRowMapper(domainType));
}
@Override
public <T> Iterable<T> findAllByProperty(Object id, JdbcPersistentProperty property) {
Class<?> actualType = property.getActualType();
String findAllByProperty = sql(actualType).getFindAllByProperty(property.getReverseColumnName());
MapSqlParameterSource parameter = new MapSqlParameterSource(property.getReverseColumnName(), id);
return (Iterable<T>) operations.query(findAllByProperty, parameter, getEntityRowMapper(actualType));
return accessStrategy.findAllById(ids, domainType);
}
@Override
@@ -240,30 +139,6 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
change.executeWith(interpreter);
publisher.publishEvent(new AfterDelete(specifiedId, optionalEntity, change));
}
void doDelete(Object rootId, PropertyPath propertyPath) {
JdbcPersistentEntity<?> rootEntity = context.getRequiredPersistentEntity(propertyPath.getOwningType());
JdbcPersistentProperty referencingProperty = rootEntity.getRequiredPersistentProperty(propertyPath.getSegment());
Assert.notNull(referencingProperty, "No property found matching the PropertyPath " + propertyPath);
String format = sql(rootEntity.getType()).createDeleteByPath(propertyPath);
HashMap<String, Object> parameters = new HashMap<>();
parameters.put("rootId", rootId);
operations.update(format, parameters);
}
void doDelete(Object id, Class<?> domainType) {
String deleteByIdSql = sql(domainType).getDeleteById();
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
operations.update(deleteByIdSql, parameter);
}
private <T> AggregateChange createChange(T instance) {
@@ -286,110 +161,4 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
jdbcEntityDeleteWriter.write(null, aggregateChange);
return aggregateChange;
}
private <T> MapSqlParameterSource createIdParameterSource(Object id, Class<T> domainType) {
return new MapSqlParameterSource("id",
convert(id, getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType()));
}
private <S> MapSqlParameterSource getPropertyMap(final S instance, JdbcPersistentEntity<S> persistentEntity) {
MapSqlParameterSource parameters = new MapSqlParameterSource();
persistentEntity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) property -> {
if (!property.isEntity()) {
Object value = persistentEntity.getPropertyAccessor(instance).getProperty(property);
Object convertedValue = convert(value, property.getColumnType());
parameters.addValue(property.getColumnName(), convertedValue, JdbcUtil.sqlTypeFor(property.getColumnType()));
}
});
return parameters;
}
private <S, ID> ID getIdValueOrNull(S instance, JdbcPersistentEntity<S> persistentEntity) {
EntityInformation<S, ID> entityInformation = new BasicJdbcPersistentEntityInformation<>(persistentEntity);
ID idValue = entityInformation.getId(instance);
return isIdPropertySimpleTypeAndValueZero(idValue, persistentEntity) ? null : idValue;
}
private <S> void setIdFromJdbc(S instance, KeyHolder holder, JdbcPersistentEntity<S> persistentEntity) {
JdbcPersistentEntityInformation<S, ?> entityInformation = new BasicJdbcPersistentEntityInformation<>(
persistentEntity);
try {
getIdFromHolder(holder, persistentEntity).ifPresent(it -> {
Class<?> targetType = persistentEntity.getRequiredIdProperty().getType();
Object converted = convert(it, targetType);
entityInformation.setId(instance, converted);
});
} catch (NonTransientDataAccessException e) {
throw new UnableToSetId("Unable to set id of " + instance, e);
}
}
private <S> Optional<Object> getIdFromHolder(KeyHolder holder, JdbcPersistentEntity<S> persistentEntity) {
try {
// MySQL just returns one value with a special name
return Optional.ofNullable(holder.getKey());
} catch (InvalidDataAccessApiUsageException e) {
// Postgres returns a value for each column
return Optional.ofNullable(holder.getKeys().get(persistentEntity.getIdColumn()));
}
}
private <V> V convert(Object from, Class<V> to) {
if (from == null) {
return null;
}
JdbcPersistentEntity<?> persistentEntity = context.getPersistentEntity(from.getClass());
Object id = persistentEntity == null ? null : persistentEntity.getIdentifierAccessor(from).getIdentifier();
return conversions.convert(id == null ? from : id, to);
}
private <S, ID> boolean isIdPropertySimpleTypeAndValueZero(ID idValue, JdbcPersistentEntity<S> persistentEntity) {
JdbcPersistentProperty idProperty = persistentEntity.getIdProperty();
return idValue == null //
|| idProperty == null //
|| (idProperty.getType() == int.class && idValue.equals(0)) //
|| (idProperty.getType() == long.class && idValue.equals(0L));
}
@SuppressWarnings("unchecked")
private <S> JdbcPersistentEntity<S> getRequiredPersistentEntity(Class<S> domainType) {
return (JdbcPersistentEntity<S>) context.getRequiredPersistentEntity(domainType);
}
private SqlGenerator sql(Class<?> domainType) {
return sqlGeneratorSource.getSqlGenerator(domainType);
}
private <T> EntityRowMapper<T> getEntityRowMapper(Class<T> domainType) {
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), conversions, context, this);
}
<T> void doDeleteAll(Class<T> domainType, PropertyPath propertyPath) {
operations.getJdbcOperations()
.update(sql(propertyPath == null ? domainType : propertyPath.getOwningType().getType())
.createDeleteAllSql(propertyPath));
}
public NamedParameterJdbcOperations getOperations() {
return operations;
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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.mybatis;
import java.util.Map;
/**
* {@link MyBatisContext} instances get passed to MyBatis mapped statements as arguments, making Ids, instances, domainType and other attributes available to the statements.
*
* All methods might return {@literal null} depending on the kind of values available on invocation.
* @author Jens Schauder
*/
public class MyBatisContext {
private final Object id;
private final Object instance;
private final Class domainType;
private final Map<String, Object> additonalValues;
public MyBatisContext(Object id, Object instance, Class domainType, Map<String, Object> additonalValues) {
this.id = id;
this.instance = instance;
this.domainType = domainType;
this.additonalValues = additonalValues;
}
public Object getId() {
return id;
}
public Object getInstance() {
return instance;
}
public Class getDomainType() {
return domainType;
}
public Object get(String key) {
return additonalValues.get(key);
}
}

View File

@@ -0,0 +1,144 @@
/*
* 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.mybatis;
import java.util.Collections;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
import org.springframework.data.mapping.PropertyPath;
/**
* {@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". This is then followed by the method name separated by a dot.
*
* For methods taking a {@link PropertyPath} as argument, the relevant entity is that of the root of the path, and the path itself gets as dot separated String appended to the statement name.
*
* Each statement gets an instance of {@link MyBatisContext}, which at least has the entityType set.
*
* For methods taking a {@link PropertyPath} the entityTyoe if the context is set to the class of the leaf type.
*
* @author Jens Schauder
*/
public class MyBatisDataAccessStrategy implements DataAccessStrategy {
private static final String MAPPER_SUFFIX = "Mapper";
private final SqlSessionFactory sqlSessionFactory;
public MyBatisDataAccessStrategy(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public <T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
sqlSession().insert(mapper(domainType) + ".insert",
new MyBatisContext(null, instance, domainType, additionalParameters));
}
@Override
public <S> void update(S instance, Class<S> domainType) {
sqlSession().update(mapper(domainType) + ".update",
new MyBatisContext(null, instance, domainType, Collections.emptyMap()));
}
@Override
public void delete(Object id, Class<?> domainType) {
sqlSession().delete(mapper(domainType) + ".delete",
new MyBatisContext(id, null, domainType, Collections.emptyMap()));
}
@Override
public void delete(Object rootId, PropertyPath propertyPath) {
sqlSession().delete(mapper(propertyPath.getOwningType().getType()) + ".delete." + propertyPath.toDotPath(),
new MyBatisContext(rootId, null, propertyPath.getLeafProperty().getTypeInformation().getType(),
Collections.emptyMap()));
}
@Override
public <T> void deleteAll(Class<T> domainType) {
sqlSession().delete( //
mapper(domainType) + ".deleteAll", //
new MyBatisContext(null, null, domainType, Collections.emptyMap()) //
);
}
@Override
public <T> void deleteAll(PropertyPath propertyPath) {
Class baseType = propertyPath.getOwningType().getType();
Class leaveType = propertyPath.getLeafProperty().getTypeInformation().getType();
sqlSession().delete( //
mapper(baseType) + ".deleteAll." + propertyPath.toDotPath(), //
new MyBatisContext(null, null, leaveType, Collections.emptyMap()) //
);
}
@Override
public <T> T findById(Object id, Class<T> domainType) {
return sqlSession().selectOne(mapper(domainType) + ".findById",
new MyBatisContext(id, null, domainType, Collections.emptyMap()));
}
@Override
public <T> Iterable<T> findAll(Class<T> domainType) {
return sqlSession().selectList(mapper(domainType) + ".findAll",
new MyBatisContext(null, null, domainType, Collections.emptyMap()));
}
@Override
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
return sqlSession().selectList(mapper(domainType) + ".findAllById",
new MyBatisContext(ids, null, domainType, Collections.emptyMap()));
}
@Override
public <T> Iterable<T> findAllByProperty(Object rootId, JdbcPersistentProperty property) {
return sqlSession().selectList(mapper(property.getOwner().getType()) + ".findAllByProperty." + property.getName(),
new MyBatisContext(rootId, null, property.getType(), Collections.emptyMap()));
}
@Override
public <T> boolean existsById(Object id, Class<T> domainType) {
return sqlSession().selectOne(mapper(domainType) + ".existsById",
new MyBatisContext(id, null, domainType, Collections.emptyMap()));
}
@Override
public long count(Class<?> domainType) {
return sqlSession().selectOne(mapper(domainType) + ".count");
}
private String mapper(Class<?> domainType) {
return domainType.getName() + MAPPER_SUFFIX;
}
private SqlSession sqlSession() {
return sqlSessionFactory.openSession();
}
}

View File

@@ -19,7 +19,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.jdbc.core.JdbcEntityOperations;
import org.springframework.data.jdbc.core.JdbcEntityTemplate;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityInformation;
@@ -75,12 +74,7 @@ public class SimpleJdbcRepository<T, ID> implements CrudRepository<T, ID> {
*/
@Override
public Optional<T> findById(ID id) {
try {
return Optional.of(entityOperations.findById(id, entityInformation.getJavaType()));
} catch (EmptyResultDataAccessException ex) {
return Optional.empty();
}
return Optional.ofNullable(entityOperations.findById(id, entityInformation.getJavaType()));
}
/*

View File

@@ -16,18 +16,17 @@
package org.springframework.data.jdbc.repository.support;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.jdbc.core.DataAccessStrategy;
import org.springframework.data.jdbc.core.JdbcEntityTemplate;
import org.springframework.data.jdbc.mapping.model.BasicJdbcPersistentEntityInformation;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityInformation;
import org.springframework.data.jdbc.mapping.model.NamingStrategy;
import org.springframework.data.jdbc.repository.SimpleJdbcRepository;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
/**
* @author Jens Schauder
@@ -37,15 +36,15 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
public class JdbcRepositoryFactory extends RepositoryFactorySupport {
private final JdbcMappingContext context;
private final NamedParameterJdbcOperations jdbcOperations;
private final ApplicationEventPublisher publisher;
private final DataAccessStrategy accessStrategy;
public JdbcRepositoryFactory(NamedParameterJdbcOperations namedParameterJdbcOperations,
ApplicationEventPublisher publisher, NamingStrategy namingStrategy) {
public JdbcRepositoryFactory(ApplicationEventPublisher publisher, JdbcMappingContext context,
DataAccessStrategy dataAccessStrategy) {
this.jdbcOperations = namedParameterJdbcOperations;
this.publisher = publisher;
this.context = new JdbcMappingContext(namingStrategy);
this.context = context;
this.accessStrategy = dataAccessStrategy;
}
@SuppressWarnings("unchecked")
@@ -62,7 +61,7 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
JdbcPersistentEntityInformation persistentEntityInformation = context
.getRequiredPersistentEntityInformation(repositoryInformation.getDomainType());
JdbcEntityTemplate template = new JdbcEntityTemplate(publisher, jdbcOperations, context);
JdbcEntityTemplate template = new JdbcEntityTemplate(publisher, context, accessStrategy);
return new SimpleJdbcRepository<>(template, persistentEntityInformation);
}
@@ -71,4 +70,5 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
protected Class<?> getRepositoryBaseClass(RepositoryMetadata repositoryMetadata) {
return SimpleJdbcRepository.class;
}
}

View File

@@ -16,15 +16,26 @@
package org.springframework.data.jdbc.repository.support;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
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.DefaultNamingStrategy;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
import org.springframework.data.jdbc.mapping.model.NamingStrategy;
import org.springframework.data.jdbc.mybatis.MyBatisDataAccessStrategy;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport;
@@ -32,6 +43,7 @@ import org.springframework.data.util.Optionals;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.util.ClassUtils;
/**
* Special adapter for Springs {@link org.springframework.beans.factory.FactoryBean} interface to allow easy setup of
@@ -52,6 +64,7 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
private static final String JDBC_OPERATIONS_BEAN_NAME = "jdbcTemplate";
private static final String DATA_SOURCE_BEAN_NAME = "dataSource";
private static final String NAMING_STRATEGY_BEAN_NAME = "namingStrategy";
private static final String SQL_SESSION_FACTORY_BEAN_NAME = "sqlSessionFactory";
private final ApplicationEventPublisher applicationEventPublisher;
private final ApplicationContext context;
@@ -66,8 +79,40 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
@Override
protected RepositoryFactorySupport doCreateRepositoryFactory() {
return new JdbcRepositoryFactory(findOrCreateJdbcOperations(), applicationEventPublisher,
findOrCreateNamingStrategy());
final JdbcMappingContext context = new JdbcMappingContext(findOrCreateNamingStrategy());
DelegatingDataAccessStrategy delegatingDataAccessStrategy = new DelegatingDataAccessStrategy();
List<DataAccessStrategy> 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 new JdbcRepositoryFactory(applicationEventPublisher, context, strategy);
}
private Optional<DataAccessStrategy> 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<DataAccessStrategy> createDefaultAccessStrategy(JdbcMappingContext context,
DelegatingDataAccessStrategy delegatingDataAccessStrategy) {
return Optional.of(new DefaultDataAccessStrategy(new SqlGeneratorSource(context), findOrCreateJdbcOperations(),
context, delegatingDataAccessStrategy));
}
private NamedParameterJdbcOperations findOrCreateJdbcOperations() {