Skip to content
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 @@ -6,7 +6,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jdbc</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<version>1.0.0.DATAJDBC-131-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 @@ -29,7 +29,7 @@ public interface DataAccessStrategy {

<T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters);

<S> void update(S instance, Class<S> domainType);
<T> void update(T instance, Class<T> domainType);

void delete(Object id, Class<?> domainType);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.support.GeneratedKeyHolder;
Expand Down Expand Up @@ -71,7 +72,6 @@ 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,
Expand Down Expand Up @@ -192,15 +192,19 @@ public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
return operations.query(findAllInListSql, parameter, getEntityRowMapper(domainType));
}

@SuppressWarnings("unchecked")
@Override
public <T> Iterable<T> findAllByProperty(Object rootId, JdbcPersistentProperty property) {

Class<?> actualType = property.getActualType();
String findAllByProperty = sql(actualType).getFindAllByProperty(property.getReverseColumnName());
String findAllByProperty = sql(actualType).getFindAllByProperty(property.getReverseColumnName(),
property.getKeyColumn());

MapSqlParameterSource parameter = new MapSqlParameterSource(property.getReverseColumnName(), rootId);

return (Iterable<T>) operations.query(findAllByProperty, parameter, getEntityRowMapper(actualType));
return (Iterable<T>)operations.query(findAllByProperty, parameter, property.isQualified() //
? getMapEntityRowMapper(property) //
: getEntityRowMapper(actualType));
}

@Override
Expand Down Expand Up @@ -287,7 +291,12 @@ private <T> EntityRowMapper<T> getEntityRowMapper(Class<T> domainType) {
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), conversions, context, accessStrategy);
}

private RowMapper getMapEntityRowMapper(JdbcPersistentProperty property) {
return new MapEntityRowMapper(getEntityRowMapper(property.getActualType()), property.getKeyColumn());
}

private <T> MapSqlParameterSource createIdParameterSource(Object id, Class<T> domainType) {

return new MapSqlParameterSource("id",
convert(id, getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType()));
}
Expand All @@ -313,5 +322,4 @@ private <V> V convert(Object from, Class<V> to) {
private SqlGenerator sql(Class<?> domainType) {
return sqlGeneratorSource.getSqlGenerator(domainType);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ public <T> void interpret(DeleteAll<T> delete) {
private <T> Map<String, Object> createAdditionalColumnValues(Insert<T> insert) {

Map<String, Object> additionalColumnValues = new HashMap<>();
addDependingOnInformation(insert, additionalColumnValues);
additionalColumnValues.putAll(insert.getAdditionalValues());

return additionalColumnValues;
}

private <T> void addDependingOnInformation(Insert<T> insert, Map<String, Object> additionalColumnValues) {
DbAction dependingOn = insert.getDependingOn();

if (dependingOn != null) {
Expand All @@ -88,7 +95,5 @@ private <T> Map<String, Object> createAdditionalColumnValues(Insert<T> insert) {

additionalColumnValues.put(columnName, identifier);
}
return additionalColumnValues;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Set;

import org.springframework.core.convert.ConversionService;
Expand Down Expand Up @@ -80,6 +81,11 @@ public T mapRow(ResultSet resultSet, int rowNumber) throws SQLException {

if (Set.class.isAssignableFrom(property.getType())) {
propertyAccessor.setProperty(property, accessStrategy.findAllByProperty(id, property));
} else if (Map.class.isAssignableFrom(property.getType())) {

Iterable<Object> allByProperty = accessStrategy.findAllByProperty(id, property);
IterableOfEntryToMapConverter converter = new IterableOfEntryToMapConverter();
propertyAccessor.setProperty(property, converter.convert(allByProperty));
} else {
propertyAccessor.setProperty(property, readFrom(resultSet, property, ""));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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.Map.Entry;

import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalConverter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
* A converter for creating a {@link Map} from an {@link Iterable<Map.Entry>}.
*
* @author Jens Schauder
*/
class IterableOfEntryToMapConverter implements ConditionalConverter, Converter<Iterable, Map> {

@Nullable
@Override
public Map convert(Iterable source) {

HashMap result = new HashMap();

source.forEach(element -> {

if (!(element instanceof Entry)) {
throw new IllegalArgumentException(String.format("Cannot convert %s to Map.Entry", element.getClass()));
}

Entry entry = (Entry) element;
result.put(entry.getKey(), entry.getValue());
});

return result;
}

/**
* Tries to determine if the {@literal sourceType} can be converted to a {@link Map}. If this can not be determined,
* because the sourceTyppe does not contain information about the element type it returns {@literal true}.
*
* @param sourceType {@link TypeDescriptor} to convert from.
* @param targetType {@link TypeDescriptor} to convert to.
* @return
*/
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {

Assert.notNull(sourceType, "Source type must not be null.");
Assert.notNull(targetType, "Target type must not be null.");

if (!sourceType.isAssignableTo(TypeDescriptor.valueOf(Iterable.class)))
return false;

TypeDescriptor elementDescriptor = sourceType.getElementTypeDescriptor();
return elementDescriptor == null || elementDescriptor.isAssignableTo(TypeDescriptor.valueOf(Entry.class));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.Nullable;

/**
* A {@link RowMapper} that maps a row to a {@link Map.Entry} so an {@link Iterable} of those can be converted to a
* {@link Map} using an {@link IterableOfEntryToMapConverter}. Creation of the {@literal value} part of the resulting
* {@link Map.Entry} is delegated to a {@link RowMapper} provided in the constructor.
*
* @author Jens Schauder
*/
class MapEntityRowMapper<T> implements RowMapper<Map.Entry<Object, T>> {

private final RowMapper<T> delegate;
private final String keyColumn;

MapEntityRowMapper(RowMapper<T> delegate, String keyColumn) {

this.delegate = delegate;
this.keyColumn = keyColumn;
}

@Nullable
@Override
public Map.Entry<Object, T> mapRow(ResultSet rs, int rowNum) throws SQLException {
return new HashMap.SimpleEntry<>(rs.getObject(keyColumn), delegate.mapRow(rs, rowNum));
}
}
32 changes: 29 additions & 3 deletions src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -88,8 +89,24 @@ String getFindAll() {
return findAllSql.get();
}

String getFindAllByProperty(String columnName) {
return String.format("%s WHERE %s = :%s", findAllSql.get(), columnName, columnName);
/**
* Returns a query for selecting all simple properties of an entity, including those for one-to-one relationships.
* Results are limited to those rows referencing some other entity using the column specified by
* {@literal columnName}. This is used to select values for a complex property ({@link Set}, {@link Map} ...) based on
* a referencing entity.
*
* @param columnName name of the column of the FK back to the referencing entity.
* @param keyColumn if the property is of type {@link Map} this column contains the map key.
* @return a SQL String.
*/
String getFindAllByProperty(String columnName, String keyColumn) {

String baseSelect = (keyColumn != null) //
? createSelectBuilder().column(cb -> cb.tableAlias(entity.getTableName()).column(keyColumn).as(keyColumn))
.build()
: getFindAll();

return String.format("%s WHERE %s = :%s", baseSelect, columnName, columnName);
}

String getExists() {
Expand Down Expand Up @@ -136,10 +153,19 @@ private SelectBuilder createSelectBuilder() {
return builder;
}

/**
* Adds the columns to the provided {@link SelectBuilder} representing simplem properties, including those from
* one-to-one relationships.
*
* @param builder The {@link SelectBuilder} to be modified.
*/
private void addColumnsAndJoinsForOneToOneReferences(SelectBuilder builder) {

for (JdbcPersistentProperty property : entity) {
if (!property.isEntity() || Collection.class.isAssignableFrom(property.getType())) {
if (!property.isEntity() //
|| Collection.class.isAssignableFrom(property.getType()) //
|| Map.class.isAssignableFrom(property.getType()) //
) {
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
import lombok.Getter;
import lombok.ToString;

import java.util.HashMap;
import java.util.Map;

import org.springframework.data.mapping.PropertyPath;
import org.springframework.util.Assert;

Expand All @@ -40,6 +43,13 @@ public abstract class DbAction<T> {
*/
private final T entity;

/**
* Key-value-pairs to specify additional values to be used with the statement which can't be obtained from the entity,
* nor from {@link DbAction}s {@literal this} depends on. A used case are map keys, which need to be persisted with
* the map value but aren't part of the value.
*/
private final Map<String, Object> additionalValues = new HashMap<>();

/**
* Another action, this action depends on. For example the insert for one entity might need the id of another entity,
* which gets insert before this one. That action would be referenced by this property, so that the id becomes
Expand Down
Loading