DATAJDBC-137 - Fixes warnings and improves Javadoc.

All packages now use @NoNullApi.
All warnings related to that fixed, except a few cases where upstream annotations are simply wrong.
Added null checks.
Fixed generic types where possible.
Improved Javadoc.
Code Formatting.
This commit is contained in:
Jens Schauder
2018-06-07 15:42:19 +02:00
parent b9c6b8b943
commit 0a0e774129
62 changed files with 717 additions and 197 deletions

View File

@@ -65,7 +65,7 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy {
}
@Override
public <T> void deleteAll(PropertyPath propertyPath) {
public void deleteAll(PropertyPath propertyPath) {
collectVoid(das -> das.deleteAll(propertyPath));
}
@@ -100,7 +100,7 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy {
}
private <T> T collect(Function<DataAccessStrategy, T> function) {
return strategies.stream().collect(new FunctionCollector<T>(function));
return strategies.stream().collect(new FunctionCollector<>(function));
}
private void collectVoid(Consumer<DataAccessStrategy> consumer) {

View File

@@ -19,45 +19,108 @@ import java.util.Map;
import org.springframework.data.jdbc.core.mapping.JdbcPersistentProperty;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.lang.Nullable;
/**
* 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.
* Abstraction for accesses to the database that should be implementable with a single SQL statement per method and
* relates to a single entity as opposed to {@link JdbcAggregateOperations} which provides interactions related to
* complete aggregates.
*
* @author Jens Schauder
* @since 1.0
*/
public interface DataAccessStrategy {
/**
* Inserts a the data of a single entity. Referenced entities don't get handled.
*
* @param instance the instance to be stored. Must not be {@code null}.
* @param domainType the type of the instance. Must not be {@code null}.
* @param additionalParameters name-value pairs of additional parameters. Especially ids of parent entities that need
* to get referenced are contained in this map. Must not be {@code null}.
* @param <T> the type of the instance.
*/
<T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters);
/**
* Updates the data of a single entity in the database. Referenced entities don't get handled.
*
* @param instance the instance to save. Must not be {@code null}.
* @param domainType the type of the instance to save. Must not be {@code null}.
* @param <T> the type of the instance to save.
*/
<T> void update(T instance, Class<T> domainType);
/**
* deletes a single row identified by the id, from the table identified by the domainType. Does not handle cascading
* deletes.
*
* @param id the id of the row to be deleted. Must not be {@code null}.
* @param domainType the type of entity to be deleted. Implicitely determines the table to operate on. Must not be
* {@code null}.
*/
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.
* @param rootId Id of the root object on which the {@literal propertyPath} is based. Must not be {@code null}.
* @param propertyPath Leading from the root object to the entities to be deleted. Must not be {@code null}.
*/
void delete(Object rootId, PropertyPath propertyPath);
/**
* Deletes all entities of the given domain type.
*
* @param domainType the domain type for which to delete all entries. Must not be {@code null}.
* @param <T> type of the domain type.
*/
<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.
* @param propertyPath Leading from the root object to the entities to be deleted. Must not be {@code null}.
*/
<T> void deleteAll(PropertyPath propertyPath);
void deleteAll(PropertyPath propertyPath);
/**
* Counts the rows in the table representing the given domain type.
*
* @param domainType the domain type for which to count the elements. Must not be {@code null}.
* @return the count. Guaranteed to be not {@code null}.
*/
long count(Class<?> domainType);
/**
* Loads a single entity identified by type and id.
*
* @param id the id of the entity to load. Must not be {@code null}.
* @param domainType the domain type of the entity. Must not be {@code null}.
* @param <T> the type of the entity.
* @return Might return {@code null}.
*/
@Nullable
<T> T findById(Object id, Class<T> domainType);
/**
* Loads alls entities of the given type.
*
* @param domainType the type of entities to load. Must not be {@code null}.
* @param <T> the type of entities to load.
* @return Guaranteed to be not {@code null}.
*/
<T> Iterable<T> findAll(Class<T> domainType);
/**
* Loads all entities that match one of the ids passed as an argument. It is not guaranteed that the number of ids
* passed in matches the number of entities returned.
*
* @param ids the Ids of the entities to load. Must not be {@code null}.
* @param domainType the type of entities to laod. Must not be {@code null}.
* @param <T> type of entities to load.
* @return the loaded entities. Guaranteed to be not {@code null}.
*/
<T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType);
/**
@@ -68,5 +131,13 @@ public interface DataAccessStrategy {
*/
<T> Iterable<T> findAllByProperty(Object rootId, JdbcPersistentProperty property);
/**
* returns if a row with the given id exists for the given type.
*
* @param id the id of the entity for which to check. Must not be {@code null}.
* @param domainType the type of the entity to check for. Must not be {@code null}.
* @param <T> the type of the entity.
* @return {@code true} if a matching row exists, otherwise {@code false}.
*/
<T> boolean existsById(Object id, Class<T> domainType);
}

View File

@@ -41,6 +41,7 @@ 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.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -88,6 +89,9 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
JdbcPersistentProperty idProperty = persistentEntity.getIdProperty();
if (idValue != null) {
Assert.notNull(idProperty, "Since we have a non-null idValue, we must have an idProperty as well.");
additionalParameters.put(idProperty.getColumnName(), convert(idValue, idProperty.getColumnType()));
}
@@ -167,7 +171,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
* @see org.springframework.data.jdbc.core.DataAccessStrategy#deleteAll(org.springframework.data.mapping.PropertyPath)
*/
@Override
public <T> void deleteAll(PropertyPath propertyPath) {
public void deleteAll(PropertyPath propertyPath) {
operations.getJdbcOperations().update(sql(propertyPath.getOwningType().getType()).createDeleteAllSql(propertyPath));
}
@@ -177,13 +181,19 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
*/
@Override
public long count(Class<?> domainType) {
return operations.getJdbcOperations().queryForObject(sql(domainType).getCount(), Long.class);
Long result = operations.getJdbcOperations().queryForObject(sql(domainType).getCount(), Long.class);
Assert.notNull(result, "The result of a count query must not be null.");
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findById(java.lang.Object, java.lang.Class)
*/
@SuppressWarnings("unchecked")
@Override
public <T> T findById(Object id, Class<T> domainType) {
@@ -191,7 +201,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
try {
return operations.queryForObject(findOneSql, parameter, getEntityRowMapper(domainType));
return operations.queryForObject(findOneSql, parameter, (RowMapper<T>) getEntityRowMapper(domainType));
} catch (EmptyResultDataAccessException e) {
return null;
}
@@ -201,15 +211,17 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAll(java.lang.Class)
*/
@SuppressWarnings("unchecked")
@Override
public <T> Iterable<T> findAll(Class<T> domainType) {
return operations.query(sql(domainType).getFindAll(), getEntityRowMapper(domainType));
return operations.query(sql(domainType).getFindAll(), (RowMapper<T>) getEntityRowMapper(domainType));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllById(java.lang.Iterable, java.lang.Class)
*/
@SuppressWarnings("unchecked")
@Override
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
@@ -223,7 +235,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
.collect(Collectors.toList()) //
);
return operations.query(findAllInListSql, parameter, getEntityRowMapper(domainType));
return operations.query(findAllInListSql, parameter, (RowMapper<T>) getEntityRowMapper(domainType));
}
/*
@@ -242,9 +254,10 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
MapSqlParameterSource parameter = new MapSqlParameterSource(property.getReverseColumnName(), rootId);
return (Iterable<T>) operations.query(findAllByProperty, parameter, property.isMap() //
? getMapEntityRowMapper(property) //
: getEntityRowMapper(actualType));
return operations.query(findAllByProperty, parameter, //
(RowMapper<T>) (property.isMap() //
? this.getMapEntityRowMapper(property) //
: this.getEntityRowMapper(actualType)));
}
/*
@@ -257,7 +270,11 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
String existsSql = sql(domainType).getExists();
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
return operations.queryForObject(existsSql, parameter, Boolean.class);
Boolean result = operations.queryForObject(existsSql, parameter, Boolean.class);
Assert.notNull(result, "The result of an exists query must not be null");
return result;
}
private <S> MapSqlParameterSource getPropertyMap(final S instance, JdbcPersistentEntity<S> persistentEntity) {
@@ -277,6 +294,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
}
@SuppressWarnings("unchecked")
@Nullable
private <S, ID> ID getIdValueOrNull(S instance, JdbcPersistentEntity<S> persistentEntity) {
ID idValue = (ID) persistentEntity.getIdentifierAccessor(instance).getIdentifier();
@@ -284,7 +302,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
return isIdPropertyNullOrScalarZero(idValue, persistentEntity) ? null : idValue;
}
private static <S, ID> boolean isIdPropertyNullOrScalarZero(ID idValue, JdbcPersistentEntity<S> persistentEntity) {
private static <S, ID> boolean isIdPropertyNullOrScalarZero(@Nullable ID idValue,
JdbcPersistentEntity<S> persistentEntity) {
JdbcPersistentProperty idProperty = persistentEntity.getIdProperty();
return idValue == null //
@@ -319,22 +338,29 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
return Optional.ofNullable(holder.getKey());
} catch (InvalidDataAccessApiUsageException e) {
// Postgres returns a value for each column
return Optional.ofNullable(holder.getKeys().get(persistentEntity.getIdColumn()));
Map<String, Object> keys = holder.getKeys();
return Optional.ofNullable(keys == null ? null : keys.get(persistentEntity.getIdColumn()));
}
}
public <T> EntityRowMapper<T> getEntityRowMapper(Class<T> domainType) {
public EntityRowMapper<?> getEntityRowMapper(Class<?> domainType) {
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), context, instantiators, accessStrategy);
}
private RowMapper getMapEntityRowMapper(JdbcPersistentProperty property) {
return new MapEntityRowMapper(getEntityRowMapper(property.getActualType()), property.getKeyColumn());
@SuppressWarnings("unchecked")
private RowMapper<?> getMapEntityRowMapper(JdbcPersistentProperty property) {
String keyColumn = property.getKeyColumn();
Assert.notNull(keyColumn, () -> "keyColumn must not be null for " + property);
return new MapEntityRowMapper<>(getEntityRowMapper(property.getActualType()), keyColumn);
}
private <T> MapSqlParameterSource createIdParameterSource(Object id, Class<T> domainType) {
return new MapSqlParameterSource("id",
convert(id, getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType()));
Class<?> columnType = getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType();
return new MapSqlParameterSource("id", convert(id, columnType));
}
@SuppressWarnings("unchecked")
@@ -342,7 +368,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
return (JdbcPersistentEntity<S>) context.getRequiredPersistentEntity(domainType);
}
private <V> V convert(Object from, Class<V> to) {
@Nullable
private <V> V convert(@Nullable Object from, Class<V> to) {
if (from == null) {
return null;

View File

@@ -27,6 +27,7 @@ import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity;
import org.springframework.data.jdbc.core.conversion.Interpreter;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -103,6 +104,7 @@ class DefaultJdbcInterpreter implements Interpreter {
additionalColumnValues.put(columnName, identifier);
}
@Nullable
private Object getIdFromEntityDependingOn(DbAction dependingOn, JdbcPersistentEntity<?> persistentEntity) {
return persistentEntity.getIdentifierAccessor(dependingOn.getEntity()).getIdentifier();
}

View File

@@ -58,7 +58,7 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy {
}
@Override
public <T> void deleteAll(PropertyPath propertyPath) {
public void deleteAll(PropertyPath propertyPath) {
delegate.deleteAll(propertyPath);
}

View File

@@ -35,9 +35,13 @@ import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Maps a ResultSet to an entity of type {@code T}, including entities referenced.
* Maps a {@link ResultSet} to an entity of type {@code T}, including entities referenced.
*
* This {@link RowMapper} might trigger additional SQL statements in order to load other members of the same aggregate.
*
* @author Jens Schauder
* @author Oliver Gierke
@@ -72,7 +76,7 @@ public class EntityRowMapper<T> implements RowMapper<T> {
* @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
*/
@Override
public T mapRow(ResultSet resultSet, int rowNumber) throws SQLException {
public T mapRow(ResultSet resultSet, int rowNumber) {
T result = createInstance(entity, resultSet, "");
@@ -83,9 +87,9 @@ public class EntityRowMapper<T> implements RowMapper<T> {
for (JdbcPersistentProperty property : entity) {
if (property.isCollectionLike()) {
if (property.isCollectionLike() && id != null) {
propertyAccessor.setProperty(property, accessStrategy.findAllByProperty(id, property));
} else if (property.isMap()) {
} else if (property.isMap() && id != null) {
Iterable<Object> allByProperty = accessStrategy.findAllByProperty(id, property);
propertyAccessor.setProperty(property, ITERABLE_OF_ENTRY_TO_MAP_CONVERTER.convert(allByProperty));
@@ -105,6 +109,7 @@ public class EntityRowMapper<T> implements RowMapper<T> {
* @param prefix to be used for all column names accessed by this method. Must not be {@code null}.
* @return the value read from the {@link ResultSet}. May be {@code null}.
*/
@Nullable
private Object readFrom(ResultSet resultSet, JdbcPersistentProperty property, String prefix) {
try {
@@ -120,6 +125,7 @@ public class EntityRowMapper<T> implements RowMapper<T> {
}
}
@Nullable
private <S> S readEntityFrom(ResultSet rs, PersistentProperty<?> property) {
String prefix = property.getName() + "_";
@@ -166,7 +172,9 @@ public class EntityRowMapper<T> implements RowMapper<T> {
@Override
public <T> T getParameterValue(Parameter<T, JdbcPersistentProperty> parameter) {
String column = prefix + entity.getRequiredPersistentProperty(parameter.getName()).getColumnName();
String parameterName = parameter.getName();
Assert.notNull(parameterName, "A constructor parameter name must not be null to be used with Spring Data JDBC");
String column = prefix + entity.getRequiredPersistentProperty(parameterName).getColumnName();
try {
return conversionService.convert(resultSet.getObject(column), parameter.getType().getType());

View File

@@ -33,6 +33,7 @@ import org.springframework.util.Assert;
*/
class IterableOfEntryToMapConverter implements ConditionalConverter, Converter<Iterable<?>, Map<?, ?>> {
@SuppressWarnings("unchecked")
@Nullable
@Override
public Map<?, ?> convert(Iterable<?> source) {
@@ -58,7 +59,7 @@ class IterableOfEntryToMapConverter implements ConditionalConverter, Converter<I
*
* @param sourceType {@link TypeDescriptor} to convert from.
* @param targetType {@link TypeDescriptor} to convert to.
* @return
* @return if the sourceType can be converted to a Map.
*/
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.jdbc.core;
import org.springframework.lang.Nullable;
/**
* Specifies a operations one can perform on a database, based on an <em>Domain Type</em>.
*
@@ -23,21 +25,84 @@ package org.springframework.data.jdbc.core;
*/
public interface JdbcAggregateOperations {
/**
* Saves an instance of an aggregate, including all the members of the aggregate.
*
* @param instance the aggregate root of the aggregate to be saved. Must not be {@code null}.
* @param <T> the type of the aggregate root.
*/
<T> void save(T instance);
/**
* Deletes a single Aggregate including all entities contained in that aggregate.
*
* @param id the id of the aggregate root of the aggregate to be deleted. Must not be {@code null}.
* @param domainType the type of the aggregate root.
* @param <T> the type of the aggregate root.
*/
<T> void deleteById(Object id, Class<T> domainType);
<T> void delete(T entity, Class<T> domainType);
/**
* Delete an aggregate identified by it's aggregate root.
*
* @param aggregateRoot to delete. Must not be {@code null}.
* @param domainType the type of the aggregate root. Must not be {@code null}.
* @param <T> the type of the aggregate root.
*/
<T> void delete(T aggregateRoot, Class<T> domainType);
/**
* Delete all aggregates of a given type.
*
* @param domainType type of the aggregate roots to be deleted. Must not be {@code null}.
*/
void deleteAll(Class<?> domainType);
/**
* Counts the number of aggregates of a given type.
*
* @param domainType the type of the aggregates to be counted.
* @return the number of instances stored in the database. Guaranteed to be not {@code null}.
*/
long count(Class<?> domainType);
/**
* Load an aggregate from the database.
*
* @param id the id of the aggregate to load. Must not be {@code null}.
* @param domainType the type of the aggregate root. Must not be {@code null}.
* @param <T> the type of the aggregate root.
* @return the loaded aggregate. Might return {@code null}.
*/
@Nullable
<T> T findById(Object id, Class<T> domainType);
/**
* Load all aggregates of a given type that are identified by the given ids.
*
* @param ids of the aggregate roots identifying the aggregates to load. Must not be {@code null}.
* @param domainType the type of the aggregate roots. Must not be {@code null}.
* @param <T> the type of the aggregate roots. Must not be {@code null}.
* @return Guaranteed to be not {@code null}.
*/
<T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType);
/**
* Load all aggregates of a given type.
*
* @param domainType the type of the aggregate roots. Must not be {@code null}.
* @param <T> the type of the aggregate roots. Must not be {@code null}.
* @return Guaranteed to be not {@code null}.
*/
<T> Iterable<T> findAll(Class<T> domainType);
/**
* Checks if an aggregate identified by type and id exists in the database.
*
* @param id the id of the aggregate root.
* @param domainType the type of the aggregate root.
* @param <T> the type of the aggregate root.
* @return whether the aggregate exists.
*/
<T> boolean existsById(Object id, Class<T> domainType);
}

View File

@@ -33,6 +33,7 @@ import org.springframework.data.jdbc.core.mapping.event.BeforeSaveEvent;
import org.springframework.data.jdbc.core.mapping.event.Identifier;
import org.springframework.data.jdbc.core.mapping.event.Identifier.Specified;
import org.springframework.data.mapping.IdentifierAccessor;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -68,7 +69,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
@Override
public <T> void save(T instance) {
Assert.notNull(instance, "Agggregate instance must not be null!");
Assert.notNull(instance, "Aggregate instance must not be null!");
JdbcPersistentEntity<?> entity = context.getRequiredPersistentEntity(instance.getClass());
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(instance);
@@ -83,8 +84,12 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
change.executeWith(interpreter);
Object identifier = identifierAccessor.getIdentifier();
Assert.notNull(identifier, "After saving the identifier must not be null");
publisher.publishEvent(new AfterSaveEvent( //
Identifier.of(identifierAccessor.getIdentifier()), //
Identifier.of(identifier), //
instance, //
change //
));
@@ -127,12 +132,12 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
}
@Override
public <S> void delete(S entity, Class<S> domainType) {
public <S> void delete(S aggregateRoot, Class<S> domainType) {
IdentifierAccessor identifierAccessor = context.getRequiredPersistentEntity(domainType)
.getIdentifierAccessor(entity);
.getIdentifierAccessor(aggregateRoot);
deleteTree(identifierAccessor.getRequiredIdentifier(), entity, domainType);
deleteTree(identifierAccessor.getRequiredIdentifier(), aggregateRoot, domainType);
}
@Override
@@ -147,7 +152,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
change.executeWith(interpreter);
}
private void deleteTree(Object id, Object entity, Class<?> domainType) {
private void deleteTree(Object id, @Nullable Object entity, Class<?> domainType) {
AggregateChange change = createDeletingChange(id, entity, domainType);
@@ -169,7 +174,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
}
@SuppressWarnings("unchecked")
private AggregateChange createDeletingChange(Object id, Object entity, Class<?> domainType) {
private AggregateChange createDeletingChange(Object id, @Nullable Object entity, Class<?> domainType) {
AggregateChange<?> aggregateChange = new AggregateChange(Kind.DELETE, domainType, entity);
jdbcEntityDeleteWriter.write(id, aggregateChange);
@@ -187,7 +192,7 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
for (T e : all) {
JdbcPersistentEntity<?> entity = context.getPersistentEntity(e.getClass());
JdbcPersistentEntity<?> entity = context.getRequiredPersistentEntity(e.getClass());
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(e);
publishAfterLoad(identifierAccessor.getRequiredIdentifier(), e);

View File

@@ -21,7 +21,7 @@ import java.util.HashMap;
import java.util.Map;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.Nullable;
import org.springframework.lang.NonNull;
/**
* A {@link RowMapper} that maps a row to a {@link Map.Entry} so an {@link Iterable} of those can be converted to a
@@ -36,13 +36,17 @@ class MapEntityRowMapper<T> implements RowMapper<Map.Entry<Object, T>> {
private final RowMapper<T> delegate;
private final String keyColumn;
/**
* @param delegate rowmapper used as a delegate for obtaining the map values.
* @param keyColumn the name of the key column.
*/
MapEntityRowMapper(RowMapper<T> delegate, String keyColumn) {
this.delegate = delegate;
this.keyColumn = keyColumn;
}
@Nullable
@NonNull
@Override
public Map.Entry<Object, T> mapRow(ResultSet rs, int rowNum) throws SQLException {
return new HashMap.SimpleEntry<>(rs.getObject(keyColumn), delegate.mapRow(rs, rowNum));

View File

@@ -36,29 +36,60 @@ class SelectBuilder {
private final List<Join> joins = new ArrayList<>();
private final List<WhereCondition> conditions = new ArrayList<>();
/**
* Creates a {@link SelectBuilder} using the given table name.
*
* @param tableName the table name. Must not be {@code null}.
*/
SelectBuilder(String tableName) {
this.tableName = tableName;
}
/**
* Adds a column to the select list.
*
* @param columnSpec a function that specifies the column to add. The passed in {@link Column.ColumnBuilder} allows to
* specify details like alias and the source table. Must not be {@code null}.
* @return {@code this}.
*/
SelectBuilder column(Function<Column.ColumnBuilder, Column.ColumnBuilder> columnSpec) {
columns.add(columnSpec.apply(Column.builder()).build());
return this;
}
/**
* Adds a where clause to the select
*
* @param whereSpec a function specifying the details of the where clause by manipulating the passed in
* {@link WhereConditionBuilder}. Must not be {@code null}.
* @return {@code this}.
*/
SelectBuilder where(Function<WhereConditionBuilder, WhereConditionBuilder> whereSpec) {
conditions.add(whereSpec.apply(new WhereConditionBuilder(this)).build());
conditions.add(whereSpec.apply(new WhereConditionBuilder()).build());
return this;
}
/**
* Adds a join to the select.
*
* @param joinSpec a function specifying the details of the join by manipulating the passed in
* {@link Join.JoinBuilder}. Must not be {@code null}.
* @return {@code this}.
*/
SelectBuilder join(Function<Join.JoinBuilder, Join.JoinBuilder> joinSpec) {
joins.add(joinSpec.apply(Join.builder()).build());
return this;
}
/**
* Builds the actual SQL statement.
*
* @return a SQL statement. Guaranteed to be not {@code null}.
*/
String build() {
return selectFrom() + joinClause() + whereClause();
@@ -73,7 +104,7 @@ class SelectBuilder {
return conditions.stream() //
.map(WhereCondition::toSql) //
.collect(Collectors.joining("AND", " WHERE ", "") //
);
);
}
private String joinClause() {
@@ -102,14 +133,11 @@ class SelectBuilder {
private String fromTable;
private String fromColumn;
private final SelectBuilder selectBuilder;
private String operation = "=";
private String expression;
WhereConditionBuilder(SelectBuilder selectBuilder) {
this.selectBuilder = selectBuilder;
}
WhereConditionBuilder() {}
WhereConditionBuilder eq() {

View File

@@ -15,16 +15,6 @@
*/
package org.springframework.data.jdbc.core;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity;
import org.springframework.data.jdbc.core.mapping.JdbcPersistentProperty;
import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.StreamUtils;
import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
@@ -34,6 +24,17 @@ import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity;
import org.springframework.data.jdbc.core.mapping.JdbcPersistentProperty;
import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.StreamUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Generates SQL statements to be used by {@link SimpleJdbcRepository}
*
@@ -81,10 +82,21 @@ class SqlGenerator {
});
}
/**
* Returns a query for selecting all simple properties of an entitty, including those for one-to-one relationhships.
* Results are filtered using an {@code IN}-clause on the id column.
*
* @return a SQL statement. Guaranteed to be not {@code null}.
*/
String getFindAllInList() {
return findAllInListSql.get();
}
/**
* Returns a query for selecting all simple properties of an entitty, including those for one-to-one relationhships.
*
* @return a SQL statement. Guaranteed to be not {@code null}.
*/
String getFindAll() {
return findAllSql.get();
}
@@ -96,17 +108,19 @@ class SqlGenerator {
* 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.
* @param ordered whether the SQL statement should include an ORDER BY for the keyColumn. If this is {@literal true}, the keyColumn must not be {@literal null}.
* @param keyColumn if the property is of type {@link Map} this column contains the map key.
* @param ordered whether the SQL statement should include an ORDER BY for the keyColumn. If this is {@code true},
* the keyColumn must not be {@code null}.
* @return a SQL String.
*/
String getFindAllByProperty(String columnName, String keyColumn, boolean ordered) {
String getFindAllByProperty(String columnName, @Nullable String keyColumn, boolean ordered) {
Assert.isTrue(keyColumn != null || !ordered, "If the SQL statement should be ordered a keyColumn to order by must be provided.");
Assert.isTrue(keyColumn != null || !ordered,
"If the SQL statement should be ordered a keyColumn to order by must be provided.");
String baseSelect = (keyColumn != null) //
? createSelectBuilder().column(cb -> cb.tableAlias(entity.getTableName()).column(keyColumn).as(keyColumn))
.build()
.build()
: getFindAll();
String orderBy = ordered ? " ORDER BY " + keyColumn : "";
@@ -170,7 +184,7 @@ class SqlGenerator {
if (!property.isEntity() //
|| Collection.class.isAssignableFrom(property.getType()) //
|| Map.class.isAssignableFrom(property.getType()) //
) {
) {
continue;
}
@@ -266,7 +280,7 @@ class SqlGenerator {
return String.format("DELETE FROM %s WHERE %s = :id", entity.getTableName(), entity.getIdColumn());
}
String createDeleteAllSql(PropertyPath path) {
String createDeleteAllSql(@Nullable PropertyPath path) {
if (path == null) {
return String.format("DELETE FROM %s", entity.getTableName());
@@ -301,7 +315,7 @@ class SqlGenerator {
return String.format("DELETE FROM %s WHERE %s", entityToDelete.getTableName(), condition);
}
private String cascadeConditions(String innerCondition, PropertyPath path) {
private String cascadeConditions(String innerCondition, @Nullable PropertyPath path) {
if (path == null) {
return innerCondition;

View File

@@ -49,7 +49,19 @@ public class AggregateChange<T> {
actions.add(action);
}
/**
* The kind of action to be performed on an aggregate.
*/
public enum Kind {
SAVE, DELETE
/**
* A {@code SAVE} of an aggregate typically involves an {@code insert} or {@code update} on the aggregate root plus
* {@code insert}s, {@code update}s, and {@code delete}s on the other elements of an aggregate.
*/
SAVE,
/**
* A {@code DELETE} of an aggregate typically involves a {@code delete} on all contained entities.
*/
DELETE
}
}

View File

@@ -21,6 +21,7 @@ import lombok.ToString;
import java.util.HashMap;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -36,7 +37,7 @@ public abstract class DbAction<T> {
/**
* {@link Class} of the entity of which the database representation is affected by this action.
*/
private final Class<T> entityType;
final Class<T> entityType;
/**
* The entity of which the database representation is affected by this action. Might be {@literal null}.
@@ -62,7 +63,10 @@ public abstract class DbAction<T> {
*/
private final DbAction dependingOn;
private DbAction(Class<T> entityType, T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) {
private DbAction(Class<T> entityType, @Nullable T entity, @Nullable JdbcPropertyPath propertyPath,
@Nullable DbAction dependingOn) {
Assert.notNull(entityType, "entityType must not be null");
this.entityType = entityType;
this.entity = entity;
@@ -70,20 +74,63 @@ public abstract class DbAction<T> {
this.dependingOn = dependingOn;
}
public static <T> Insert<T> insert(T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) {
/**
* Creates an {@link Insert} action for the given parameters.
*
* @param entity the entity to insert into the database. Must not be {@code null}.
* @param propertyPath property path from the aggregate root to the entity to be saved. Must not be {@code null}.
* @param dependingOn a {@link DbAction} the to be created insert may depend on. Especially the insert of a parent
* entity. May be {@code null}.
* @param <T> the type of the entity to be inserted.
* @return a {@link DbAction} representing the insert.
*/
public static <T> Insert<T> insert(T entity, JdbcPropertyPath propertyPath, @Nullable DbAction dependingOn) {
return new Insert<>(entity, propertyPath, dependingOn);
}
public static <T> Update<T> update(T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) {
/**
* Creates an {@link Update} action for the given parameters.
*
* @param entity the entity to update in the database. Must not be {@code null}.
* @param propertyPath property path from the aggregate root to the entity to be saved. Must not be {@code null}.
* @param dependingOn a {@link DbAction} the to be created update may depend on. Especially the insert of a parent
* entity. May be {@code null}.
* @param <T> the type of the entity to be updated.
* @return a {@link DbAction} representing the update.
*/
public static <T> Update<T> update(T entity, JdbcPropertyPath propertyPath, @Nullable DbAction dependingOn) {
return new Update<>(entity, propertyPath, dependingOn);
}
public static <T> Delete<T> delete(Object id, Class<T> type, T entity, JdbcPropertyPath propertyPath,
DbAction dependingOn) {
/**
* Creates a {@link Delete} action for the given parameters.
*
* @param id the id of the aggregate root for which referenced entities to be deleted. May not be {@code null}.
* @param type the type of the entity to be deleted. Must not be {@code null}.
* @param entity the aggregate roo to be deleted. May be {@code null}.
* @param propertyPath the property path from the aggregate root to the entity to be deleted.
* @param dependingOn an action this action might depend on.
* @param <T> the type of the entity to be deleted.
* @return a {@link DbAction} representing the deletion of the entity with given type and id.
*/
public static <T> Delete<T> delete(Object id, Class<T> type, @Nullable T entity,
@Nullable JdbcPropertyPath propertyPath, @Nullable DbAction dependingOn) {
return new Delete<>(id, type, entity, propertyPath, dependingOn);
}
public static <T> DeleteAll<T> deleteAll(Class<T> type, JdbcPropertyPath propertyPath, DbAction dependingOn) {
/**
* Creates a {@link DeleteAll} action for the given parameters.
*
* @param type the type of entities to be deleted. May be {@code null}.
* @param propertyPath the property path describing the relation of the entity to be deleted to the aggregate it
* belongs to. May be {@code null} for the aggregate root.
* @param dependingOn an action this action might depend on. May be {@code null}.
* @param <T> the type of the entity to be deleted.
* @return a {@link DbAction} representing the deletion of all entities of a given type belonging to a specific type
* of aggregate root.
*/
public static <T> DeleteAll<T> deleteAll(Class<T> type, @Nullable JdbcPropertyPath propertyPath,
@Nullable DbAction dependingOn) {
return new DeleteAll<>(type, propertyPath, dependingOn);
}
@@ -116,7 +163,7 @@ public abstract class DbAction<T> {
abstract static class InsertOrUpdate<T> extends DbAction<T> {
@SuppressWarnings("unchecked")
InsertOrUpdate(T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) {
InsertOrUpdate(T entity, JdbcPropertyPath propertyPath, @Nullable DbAction dependingOn) {
super((Class<T>) entity.getClass(), entity, propertyPath, dependingOn);
}
}
@@ -128,7 +175,7 @@ public abstract class DbAction<T> {
*/
public static class Insert<T> extends InsertOrUpdate<T> {
private Insert(T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) {
private Insert(T entity, JdbcPropertyPath propertyPath, @Nullable DbAction dependingOn) {
super(entity, propertyPath, dependingOn);
}
@@ -145,7 +192,7 @@ public abstract class DbAction<T> {
*/
public static class Update<T> extends InsertOrUpdate<T> {
private Update(T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) {
private Update(T entity, JdbcPropertyPath propertyPath, @Nullable DbAction dependingOn) {
super(entity, propertyPath, dependingOn);
}
@@ -169,7 +216,8 @@ public abstract class DbAction<T> {
*/
private final Object rootId;
private Delete(Object rootId, Class<T> type, T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) {
private Delete(@Nullable Object rootId, Class<T> type, @Nullable T entity, @Nullable JdbcPropertyPath propertyPath,
@Nullable DbAction dependingOn) {
super(type, entity, propertyPath, dependingOn);
@@ -191,7 +239,7 @@ public abstract class DbAction<T> {
*/
public static class DeleteAll<T> extends DbAction<T> {
private DeleteAll(Class<T> entityType, JdbcPropertyPath propertyPath, DbAction dependingOn) {
private DeleteAll(Class<T> entityType, @Nullable JdbcPropertyPath propertyPath, @Nullable DbAction dependingOn) {
super(entityType, null, propertyPath, dependingOn);
}

View File

@@ -23,6 +23,11 @@ package org.springframework.data.jdbc.core.conversion;
* @since 1.0
*/
public class DbActionExecutionException extends RuntimeException {
/**
* @param action the {@link DbAction} which triggered the exception. Must not be {@code null}.
* @param cause the underlying exception. May not be {@code null}.
*/
public DbActionExecutionException(DbAction<?> action, Throwable cause) {
super( //
String.format("Failed to execute %s for instance %s of type %s with path %s", //

View File

@@ -21,11 +21,22 @@ import org.springframework.data.jdbc.core.conversion.DbAction.Insert;
import org.springframework.data.jdbc.core.conversion.DbAction.Update;
/**
* An {@link Interpreter} gets called by a {@link AggregateChange} for each {@link DbAction} and is tasked with
* executing that action against a database. While the {@link DbAction} is just an abstract representation of a database
* action it's the task of an interpreter to actually execute it. This typically involves creating some SQL and running
* it using JDBC, but it may also use some third party technology like MyBatis or jOOQ to do this.
*
* @author Jens Schauder
* @since 1.0
*/
public interface Interpreter {
/**
* Interpret an {@link Update}. Interpreting normally means "executing".
*
* @param update the {@link Update} to be executed
* @param <T> the type of entity to work on.
*/
<T> void interpret(Update<T> update);
<T> void interpret(Insert<T> insert);

View File

@@ -16,10 +16,11 @@
package org.springframework.data.jdbc.core.conversion;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.lang.Nullable;
/**
* Converts an entity that is about to be deleted into {@link DbAction}s inside a {@link AggregateChange} that need to be
* executed against the database to recreate the appropriate state in the database.
* Converts an entity that is about to be deleted into {@link DbAction}s inside a {@link AggregateChange} that need to
* be executed against the database to recreate the appropriate state in the database.
*
* @author Jens Schauder
* @since 1.0
@@ -30,8 +31,16 @@ public class JdbcEntityDeleteWriter extends JdbcEntityWriterSupport {
super(context);
}
/**
* Fills the provided {@link AggregateChange} with the necessary {@link DbAction}s to delete the aggregate root
* identified by {@code id}. If {@code id} is {@code null} it is interpreted as "Delete all aggregates of the type
* indicated by the aggregateChange".
*
* @param id May be {@code null}.
* @param aggregateChange Must not be {@code null}.
*/
@Override
public void write(Object id, AggregateChange aggregateChange) {
public void write(@Nullable Object id, AggregateChange<?> aggregateChange) {
if (id == null) {
deleteAll(aggregateChange);
@@ -40,7 +49,7 @@ public class JdbcEntityDeleteWriter extends JdbcEntityWriterSupport {
}
}
private void deleteAll(AggregateChange aggregateChange) {
private void deleteAll(AggregateChange<?> aggregateChange) {
context.referencedEntities(aggregateChange.getEntityType(), null)
.forEach(p -> aggregateChange.addAction(DbAction.deleteAll(p.getLeafType(), new JdbcPropertyPath(p), null)));
@@ -48,10 +57,16 @@ public class JdbcEntityDeleteWriter extends JdbcEntityWriterSupport {
aggregateChange.addAction(DbAction.deleteAll(aggregateChange.getEntityType(), null, null));
}
private void deleteById(Object id, AggregateChange aggregateChange) {
private <T> void deleteById(Object id, AggregateChange<T> aggregateChange) {
deleteReferencedEntities(id, aggregateChange);
aggregateChange.addAction(DbAction.delete(id, aggregateChange.getEntityType(), aggregateChange.getEntity(), null, null));
aggregateChange.addAction(DbAction.delete( //
id, //
aggregateChange.getEntityType(), //
aggregateChange.getEntity(), //
null, //
null //
));
}
}

View File

@@ -52,24 +52,27 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport {
this.context = context;
}
/**
* Converts an aggregate represented by its aggregate root into a list of {@link DbAction}s and adds them to the
* {@link AggregateChange} passed in as an argument.
*
* @param aggregateRoot the aggregate root to be written to the {@link AggregateChange} Must not be {@code null}.
* @param aggregateChange the {@link AggregateChange} to which the {@link DbAction}s get written.
*/
@Override
public void write(Object o, AggregateChange aggregateChange) {
write(o, aggregateChange, null);
}
public void write(Object aggregateRoot, AggregateChange aggregateChange) {
private void write(Object o, AggregateChange aggregateChange, DbAction dependingOn) {
Class<?> type = o.getClass();
Class<?> type = aggregateRoot.getClass();
JdbcPropertyPath propertyPath = JdbcPropertyPath.from("", type);
PersistentEntity<?, JdbcPersistentProperty> persistentEntity = context.getRequiredPersistentEntity(type);
if (persistentEntity.isNew(o)) {
if (persistentEntity.isNew(aggregateRoot)) {
Insert<Object> insert = DbAction.insert(o, propertyPath, dependingOn);
Insert<Object> insert = DbAction.insert(aggregateRoot, propertyPath, null);
aggregateChange.addAction(insert);
referencedEntities(o) //
referencedEntities(aggregateRoot) //
.forEach( //
propertyAndValue -> //
insertReferencedEntities( //
@@ -77,19 +80,19 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport {
aggregateChange, //
propertyPath.nested(propertyAndValue.property.getName()), //
insert) //
);
);
} else {
JdbcPersistentEntity<?> entity = context.getPersistentEntity(type);
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(o);
JdbcPersistentEntity<?> entity = context.getRequiredPersistentEntity(type);
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(aggregateRoot);
deleteReferencedEntities(identifierAccessor.getRequiredIdentifier(), aggregateChange);
Update<Object> update = DbAction.update(o, propertyPath, dependingOn);
Update<Object> update = DbAction.update(aggregateRoot, propertyPath, null);
aggregateChange.addAction(update);
referencedEntities(o).forEach(propertyAndValue -> insertReferencedEntities(propertyAndValue, aggregateChange,
propertyPath.nested(propertyAndValue.property.getName()), update));
referencedEntities(aggregateRoot).forEach(propertyAndValue -> insertReferencedEntities(propertyAndValue,
aggregateChange, propertyPath.nested(propertyAndValue.property.getName()), update));
}
}
@@ -113,7 +116,7 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport {
aggregateChange, //
propertyPath.nested(pav.property.getName()), //
dependingOn) //
);
);
}
private Stream<PropertyAndValue> referencedEntities(Object o) {
@@ -125,7 +128,7 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport {
.flatMap( //
p -> referencedEntity(p, persistentEntity.getPropertyAccessor(o)) //
.map(e -> new PropertyAndValue(p, e)) //
);
);
}
private Stream<Object> referencedEntity(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) {
@@ -155,6 +158,7 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport {
return singlePropertyAsStream(p, propertyAccessor);
}
@SuppressWarnings("unchecked")
private Stream<Object> collectionPropertyAsStream(JdbcPersistentProperty p,
PersistentPropertyAccessor propertyAccessor) {
@@ -165,6 +169,7 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport {
: ((Collection<Object>) property).stream();
}
@SuppressWarnings("unchecked")
private Stream<Object> listPropertyAsStream(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) {
Object property = propertyAccessor.getProperty(p);
@@ -180,6 +185,7 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport {
return listProperty.stream().map(e -> new KeyValue(index.getAndIncrement(), e));
}
@SuppressWarnings("unchecked")
private Stream<Object> mapPropertyAsStream(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) {
Object property = propertyAccessor.getProperty(p);

View File

@@ -17,6 +17,7 @@ package org.springframework.data.jdbc.core.conversion;
import org.springframework.data.convert.EntityWriter;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.util.Assert;
/**
* Common infrastructure needed by different implementations of {@link EntityWriter}<Object, AggregateChange>.
@@ -24,23 +25,26 @@ import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
* @author Jens Schauder
* @since 1.0
*/
abstract class JdbcEntityWriterSupport implements EntityWriter<Object, AggregateChange> {
abstract class JdbcEntityWriterSupport implements EntityWriter<Object, AggregateChange<?>> {
protected final JdbcMappingContext context;
JdbcEntityWriterSupport(JdbcMappingContext context) {
Assert.notNull(context, "Context must not be null");
this.context = context;
}
/**
* add {@link org.springframework.data.jdbc.core.conversion.DbAction.Delete} actions to the {@link AggregateChange} for
* deleting all referenced entities.
* add {@link org.springframework.data.jdbc.core.conversion.DbAction.Delete} actions to the {@link AggregateChange}
* for deleting all referenced entities.
*
* @param id id of the aggregate root, of which the referenced entities get deleted.
* @param aggregateChange the change object to which the actions should get added. Must not be {@literal null}
* @param aggregateChange the change object to which the actions should get added. Must not be {@code null}
*/
void deleteReferencedEntities(Object id, AggregateChange aggregateChange) {
void deleteReferencedEntities(Object id, AggregateChange<?> aggregateChange) {
context.referencedEntities(aggregateChange.getEntityType(), null)
.forEach(p -> aggregateChange.addAction(DbAction.delete(id, p.getLeafType(), null, new JdbcPropertyPath(p), null)));
context.referencedEntities(aggregateChange.getEntityType(), null).forEach(
p -> aggregateChange.addAction(DbAction.delete(id, p.getLeafType(), null, new JdbcPropertyPath(p), null)));
}
}

View File

@@ -16,13 +16,12 @@
package org.springframework.data.jdbc.core.conversion;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A replacement for {@link org.springframework.data.mapping.PropertyPath} as long as it doesn't support objects with
* empty path.
*
* See https://jira.spring.io/browse/DATACMNS-1204.
* empty path. See https://jira.spring.io/browse/DATACMNS-1204.
*
* @author Jens Schauder
* @since 1.0
@@ -34,12 +33,16 @@ public class JdbcPropertyPath {
JdbcPropertyPath(PropertyPath path) {
Assert.notNull(path, "path must not be null if rootType is not set");
this.path = path;
this.rootType = null;
}
private JdbcPropertyPath(Class<?> type) {
Assert.notNull(type, "type must not be null if path is not set");
this.path = null;
this.rootType = type;
}
@@ -54,7 +57,10 @@ public class JdbcPropertyPath {
}
public JdbcPropertyPath nested(String name) {
return path == null ? new JdbcPropertyPath(PropertyPath.from(name, rootType)) : new JdbcPropertyPath(path.nested(name));
return path == null ? //
new JdbcPropertyPath(PropertyPath.from(name, rootType)) //
: new JdbcPropertyPath(path.nested(name));
}
public PropertyPath getPath() {

View File

@@ -0,0 +1,7 @@
/**
* @author Jens Schauder
*/
@NonNullApi
package org.springframework.data.jdbc.core.conversion;
import org.springframework.lang.NonNullApi;

View File

@@ -29,6 +29,7 @@ import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.Lazy;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -43,9 +44,6 @@ class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProperty<Jdbc
implements JdbcPersistentProperty {
private static final Map<Class<?>, Class<?>> javaToDbType = new LinkedHashMap<>();
private final JdbcMappingContext context;
private final Lazy<Optional<String>> columnName;
static {
javaToDbType.put(Enum.class, String.class);
@@ -53,6 +51,9 @@ class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProperty<Jdbc
javaToDbType.put(Temporal.class, Date.class);
}
private final JdbcMappingContext context;
private final Lazy<Optional<String>> columnName;
/**
* Creates a new {@link AnnotationBasedPersistentProperty}.
*
@@ -85,6 +86,7 @@ class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProperty<Jdbc
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.mapping.model.JdbcPersistentProperty#getColumnName()
*/
@Override
public String getColumnName() {
return columnName.get().orElseGet(() -> context.getNamingStrategy().getColumnName(this));
}
@@ -123,15 +125,16 @@ class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProperty<Jdbc
return isMap() || isListLike();
}
private boolean isListLike() {
return isCollectionLike() && !Set.class.isAssignableFrom(this.getType());
}
@Override
public boolean isOrdered() {
return isListLike();
}
private boolean isListLike() {
return isCollectionLike() && !Set.class.isAssignableFrom(this.getType());
}
@Nullable
private Class columnTypeIfEntity(Class type) {
JdbcPersistentEntity<?> persistentEntity = context.getPersistentEntity(type);

View File

@@ -18,12 +18,23 @@ package org.springframework.data.jdbc.core.mapping;
import org.springframework.core.convert.support.GenericConversionService;
/**
* Interface to register custom conversions.
*
* @author Jens Schauder
* @since 1.0
*/
public interface ConversionCustomizer {
public static ConversionCustomizer NONE = __ -> {};
/**
* Noop instance to be used as a default.
*/
ConversionCustomizer NONE = __ -> {};
/**
* Gets called in order to allow the customization of the {@link org.springframework.core.convert.ConversionService}.
* Typically used by registering additional conversions.
*
* @param conversions the conversions that get customized.
*/
void customize(GenericConversionService conversions);
}

View File

@@ -37,6 +37,7 @@ import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -57,8 +58,8 @@ public class JdbcMappingContext extends AbstractMappingContext<JdbcPersistentEnt
));
@Getter private final NamingStrategy namingStrategy;
private final GenericConversionService conversions = getDefaultConversionService();
@Getter private SimpleTypeHolder simpleTypeHolder;
private GenericConversionService conversions = getDefaultConversionService();
/**
* Creates a new {@link JdbcMappingContext}.
@@ -88,6 +89,14 @@ public class JdbcMappingContext extends AbstractMappingContext<JdbcPersistentEnt
setSimpleTypeHolder(new SimpleTypeHolder(CUSTOM_SIMPLE_TYPES, true));
}
private static GenericConversionService getDefaultConversionService() {
DefaultConversionService conversionService = new DefaultConversionService();
Jsr310Converters.getConvertersToRegister().forEach(conversionService::addConverter);
return conversionService;
}
@Override
public void setSimpleTypeHolder(SimpleTypeHolder simpleTypes) {
super.setSimpleTypeHolder(simpleTypes);
@@ -98,7 +107,7 @@ public class JdbcMappingContext extends AbstractMappingContext<JdbcPersistentEnt
* returns all {@link PropertyPath}s reachable from the root type in the order needed for deleting, i.e. the deepest
* reference first.
*/
public List<PropertyPath> referencedEntities(Class<?> rootType, PropertyPath path) {
public List<PropertyPath> referencedEntities(Class<?> rootType, @Nullable PropertyPath path) {
List<PropertyPath> paths = new ArrayList<>();
@@ -142,12 +151,4 @@ public class JdbcMappingContext extends AbstractMappingContext<JdbcPersistentEnt
public ConversionService getConversions() {
return conversions;
}
private static GenericConversionService getDefaultConversionService() {
DefaultConversionService conversionService = new DefaultConversionService();
Jsr310Converters.getConvertersToRegister().forEach(conversionService::addConverter);
return conversionService;
}
}

View File

@@ -18,6 +18,9 @@ package org.springframework.data.jdbc.core.mapping;
import org.springframework.data.mapping.model.MutablePersistentEntity;
/**
* A {@link org.springframework.data.mapping.PersistentEntity} interface with additional methods for JDBC/RDBMS related
* metadata.
*
* @author Jens Schauder
* @author Oliver Gierke
* @since 1.0

View File

@@ -16,9 +16,10 @@
package org.springframework.data.jdbc.core.mapping;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.lang.Nullable;
/**
* A {@link PersistentProperty} for JDBC.
* A {@link PersistentProperty} with methods for additional JDBC/RDBMS related meta data.
*
* @author Jens Schauder
* @author Oliver Gierke
@@ -36,7 +37,7 @@ public interface JdbcPersistentProperty extends PersistentProperty<JdbcPersisten
/**
* The type to be used to store this property in the database.
*
* @return a {@link Class} that is suitable for usage with JDBC drivers
* @return a {@link Class} that is suitable for usage with JDBC drivers.
*/
Class<?> getColumnType();
@@ -45,10 +46,12 @@ public interface JdbcPersistentProperty extends PersistentProperty<JdbcPersisten
String getReverseColumnName();
@Nullable
String getKeyColumn();
/**
* Returns if this property is a qualified property, i.e. a property referencing multiple elements that can get picked by a key or an index.
* Returns if this property is a qualified property, i.e. a property referencing multiple elements that can get picked
* by a key or an index.
*/
boolean isQualified();

View File

@@ -62,7 +62,8 @@ public interface NamingStrategy {
}
/**
* Defaults to return the given {@link JdbcPersistentProperty}'s name with the parts of a camel case name separated by '_';
* Defaults to return the given {@link JdbcPersistentProperty}'s name with the parts of a camel case name separated by
* '_';
*/
default String getColumnName(JdbcPersistentProperty property) {
@@ -83,7 +84,7 @@ public interface NamingStrategy {
*/
default String getReverseColumnName(JdbcPersistentProperty property) {
Assert.notNull(property,"Property must not be null.");
Assert.notNull(property, "Property must not be null.");
return property.getOwner().getTableName();
}

View File

@@ -34,7 +34,8 @@ public class AfterDeleteEvent extends JdbcEventWithId {
/**
* @param id of the entity.
* @param instance the deleted entity if it is available.
* @param change the {@link AggregateChange} encoding the planned actions to be performed on the database.
* @param change the {@link AggregateChange} encoding the actions that were performed on the database as part of the
* delete operation.
*/
public AfterDeleteEvent(Specified id, Optional<Object> instance, AggregateChange change) {
super(id, instance, change);

View File

@@ -19,7 +19,7 @@ import org.springframework.data.jdbc.core.conversion.AggregateChange;
import org.springframework.data.jdbc.core.mapping.event.Identifier.Specified;
/**
* Subclasses of this get published after a new instance or a changed instance was saved in the database.
* Gets published after a new instance or a changed instance was saved in the database.
*
* @author Jens Schauder
* @since 1.0
@@ -29,9 +29,9 @@ public class AfterSaveEvent extends JdbcEventWithIdAndEntity {
private static final long serialVersionUID = 8982085767296982848L;
/**
* @param id identifier of
* @param instance the newly saved entity.
* @param change the {@link AggregateChange} encoding the planned actions to be performed on the database.
* @param id identifier of the saved entity.
* @param instance the saved entity.
* @param change the {@link AggregateChange} encoding the actions performed on the database as part of the delete.
*/
public AfterSaveEvent(Specified id, Object instance, AggregateChange change) {
super(id, instance, change);

View File

@@ -21,7 +21,8 @@ import org.springframework.data.jdbc.core.conversion.AggregateChange;
import org.springframework.data.jdbc.core.mapping.event.Identifier.Specified;
/**
* Gets published when an entity is about to get deleted.
* Gets published when an entity is about to get deleted. The contained {@link AggregateChange} is mutable and may be
* changed in order to change the actions that get performed on the database as part of the delete operation.
*
* @author Jens Schauder
* @since 1.0

View File

@@ -18,7 +18,8 @@ package org.springframework.data.jdbc.core.mapping.event;
import org.springframework.data.jdbc.core.conversion.AggregateChange;
/**
* Subclasses of this get published before an entity gets saved to the database.
* Gets published before an entity gets saved to the database. The contained {@link AggregateChange} is mutable and may
* be changed in order to change the actions that get performed on the database as part of the save operation.
*
* @author Jens Schauder
* @since 1.0
@@ -30,7 +31,7 @@ public class BeforeSaveEvent extends JdbcEventWithEntity {
/**
* @param id of the entity to be saved.
* @param instance the entity about to get saved.
* @param change
* @param change the {@link AggregateChange} that is going to get applied to the database.
*/
public BeforeSaveEvent(Identifier id, Object instance, AggregateChange change) {
super(id, instance, change);

View File

@@ -17,6 +17,7 @@ package org.springframework.data.jdbc.core.mapping.event;
import java.util.Optional;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -40,29 +41,22 @@ public interface Identifier {
return SpecifiedIdentifier.of(identifier);
}
static Identifier ofNullable(Object identifier) {
return identifier == null ? Unset.UNSET : of(identifier);
}
/**
* Creates a new {@link Identifier} for the given optional source value.
* Produces an {@link Identifier} of appropriate type depending the argument being {@code null} or not.
*
* @param identifier must not be {@literal null}.
* @return
* @param identifier May be {@code null}.
* @return an {@link Identifier}.
*/
static Identifier of(Optional<? extends Object> identifier) {
Assert.notNull(identifier, "Identifier must not be null!");
return identifier.map(it -> (Identifier) Identifier.of(it)).orElse(Unset.UNSET);
static Identifier ofNullable(@Nullable Object identifier) {
return identifier == null ? Unset.UNSET : of(identifier);
}
/**
* Returns the identifier value.
*
* @return will never be {@literal null}.
* @return will never be {@code null}.
*/
Optional<? extends Object> getOptionalValue();
Optional<?> getOptionalValue();
/**
* A specified identifier that exposes a definitely present identifier value.

View File

@@ -18,6 +18,8 @@ package org.springframework.data.jdbc.core.mapping.event;
import java.util.Optional;
/**
* an event signalling JDBC processing. It offers access to an {@link Identifier} of the aggregate root affected by the
* event.
*
* @author Oliver Gierke
* @since 1.0
@@ -25,16 +27,16 @@ import java.util.Optional;
public interface JdbcEvent {
/**
* The identifier of the entity, triggering this event. Also available via {@link #getSource()}.
* The identifier of the aggregate root, triggering this event.
*
* @return the source of the event as an {@link Identifier}.
* @return the source of the event as an {@link Identifier}. Guaranteed to be not {@code null}.
*/
Identifier getId();
/**
* Returns the entity the event was triggered for.
* Returns the aggregate root the event was triggered for.
*
* @return will never be {@literal null}.
* @return will never be {@code null}.
*/
Optional<Object> getOptionalEntity();

View File

@@ -29,7 +29,7 @@ public class JdbcEventWithEntity extends SimpleJdbcEvent implements WithEntity {
private static final long serialVersionUID = 4891455396602090638L;
public JdbcEventWithEntity(Identifier id, Object entity, AggregateChange change) {
JdbcEventWithEntity(Identifier id, Object entity, AggregateChange change) {
super(id, Optional.of(entity), change);
}
}

View File

@@ -19,6 +19,7 @@ import java.util.Optional;
import org.springframework.data.jdbc.core.conversion.AggregateChange;
import org.springframework.data.jdbc.core.mapping.event.Identifier.Specified;
import org.springframework.lang.Nullable;
/**
* A {@link SimpleJdbcEvent} guaranteed to have an identifier.
@@ -32,7 +33,7 @@ public class JdbcEventWithId extends SimpleJdbcEvent implements WithId {
private final Specified id;
public JdbcEventWithId(Specified id, Optional<Object> entity, AggregateChange change) {
public JdbcEventWithId(Specified id, Optional<Object> entity, @Nullable AggregateChange change) {
super(id, entity, change);

View File

@@ -21,6 +21,7 @@ import java.util.Optional;
import org.springframework.data.jdbc.core.conversion.AggregateChange;
import org.springframework.data.jdbc.core.mapping.event.Identifier.Specified;
import org.springframework.lang.Nullable;
/**
* A {@link SimpleJdbcEvent} which is guaranteed to have an identifier and an entity.
@@ -33,7 +34,7 @@ public class JdbcEventWithIdAndEntity extends JdbcEventWithId implements WithEnt
private static final long serialVersionUID = -3194462549552515519L;
public JdbcEventWithIdAndEntity(Specified id, Object entity, AggregateChange change) {
public JdbcEventWithIdAndEntity(Specified id, Object entity, @Nullable AggregateChange change) {
super(id, Optional.of(entity), change);
}
}

View File

@@ -19,6 +19,7 @@ import java.util.Optional;
import org.springframework.context.ApplicationEvent;
import org.springframework.data.jdbc.core.conversion.AggregateChange;
import org.springframework.lang.Nullable;
/**
* The common superclass for all events published by JDBC repositories. {@link #getSource} contains the
@@ -35,7 +36,7 @@ class SimpleJdbcEvent extends ApplicationEvent implements JdbcEvent {
private final Object entity;
private final AggregateChange change;
SimpleJdbcEvent(Identifier id, Optional<Object> entity, AggregateChange change) {
SimpleJdbcEvent(Identifier id, Optional<Object> entity, @Nullable AggregateChange change) {
super(id);

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.jdbc.core.mapping.event;
import lombok.NonNull;
import lombok.Value;
import java.util.Optional;
@@ -31,14 +32,14 @@ import org.springframework.data.jdbc.core.mapping.event.Identifier.Specified;
@Value(staticConstructor = "of")
class SpecifiedIdentifier implements Specified {
Object value;
@NonNull Object value;
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.mapping.event.Identifier#getOptionalValue()
*/
@Override
public Optional<? extends Object> getOptionalValue() {
public Optional<?> getOptionalValue() {
return Optional.of(value);
}
}

View File

@@ -0,0 +1,7 @@
/**
* @author Jens Schauder
*/
@NonNullApi
package org.springframework.data.jdbc.core.mapping.event;
import org.springframework.lang.NonNullApi;

View File

@@ -0,0 +1,7 @@
/**
* @author Jens Schauder
*/
@NonNullApi
package org.springframework.data.jdbc.core.mapping;
import org.springframework.lang.NonNullApi;

View File

@@ -0,0 +1,7 @@
/**
* @author Jens Schauder
*/
@NonNullApi
package org.springframework.data.jdbc.core;
import org.springframework.lang.NonNullApi;

View File

@@ -0,0 +1,7 @@
/**
* @author Jens Schauder
*/
@NonNullApi
package org.springframework.data.jdbc.domain.support;
import org.springframework.lang.NonNullApi;

View File

@@ -17,10 +17,12 @@ package org.springframework.data.jdbc.mybatis;
import java.util.Map;
import org.springframework.lang.Nullable;
/**
* {@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.
* {@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
* @since 1.0
@@ -32,7 +34,7 @@ public class MyBatisContext {
private final Class domainType;
private final Map<String, Object> additonalValues;
public MyBatisContext(Object id, Object instance, Class domainType, Map<String, Object> additonalValues) {
public MyBatisContext(@Nullable Object id, @Nullable Object instance, Class domainType, Map<String, Object> additonalValues) {
this.id = id;
this.instance = instance;
@@ -40,18 +42,43 @@ public class MyBatisContext {
this.additonalValues = additonalValues;
}
/**
* The ID of the entity to query/act upon.
*
* @return Might return {@code null}.
*/
@Nullable
public Object getId() {
return id;
}
/**
* The entity to act upon. This is {@code null} for queries, since the object doesn't exist before the query.
*
* @return Might return {@code null}.
*/
@Nullable
public Object getInstance() {
return instance;
}
/**
* The domain type of the entity to query or act upon.
*
* @return Might return {@code null}.
*/
@Nullable
public Class getDomainType() {
return domainType;
}
/**
* Returns a value for the given key. Used to communicate ids of parent entities.
*
* @param key Must not be {@code null}.
* @return Might return {@code null}.
*/
@Nullable
public Object get(String key) {
return additonalValues.get(key);
}

View File

@@ -161,7 +161,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
}
@Override
public <T> void deleteAll(PropertyPath propertyPath) {
public void deleteAll(PropertyPath propertyPath) {
Class<?> baseType = propertyPath.getOwningType().getType();
Class<?> leafType = propertyPath.getLeafProperty().getTypeInformation().getType();

View File

@@ -0,0 +1,7 @@
/**
* @author Jens Schauder
*/
@NonNullApi
package org.springframework.data.jdbc.mybatis;
import org.springframework.lang.NonNullApi;

View File

@@ -16,6 +16,7 @@
package org.springframework.data.jdbc.repository;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.Nullable;
/**
* A map from a type to a {@link RowMapper} to be used for extracting that type from {@link java.sql.ResultSet}s.
@@ -39,5 +40,6 @@ public interface RowMapperMap {
}
};
@Nullable
<T> RowMapper<? extends T> rowMapperFor(Class<T> type);
}

View File

@@ -20,6 +20,8 @@ import java.util.Map;
import org.springframework.data.jdbc.repository.RowMapperMap;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* A {@link RowMapperMap} that allows for registration of {@link RowMapper}s via a fluent Api.
@@ -42,9 +44,21 @@ public class ConfigurableRowMapperMap implements RowMapperMap {
return this;
}
/**
* Returs a {@link RowMapper} for the given type if such a {@link RowMapper} is present. If an exact match is found
* that is returned. If not a {@link RowMapper} is returned that produces subtypes of the requested type. If no such
* {@link RowMapper} is found the method returns {@code null}.
*
* @param type the type to be produced by the returned {@link RowMapper}. Must not be {@code null}.
* @param <T> the type to be produced by the returned {@link RowMapper}.
* @return Guaranteed to be not {@code null}.
*/
@SuppressWarnings("unchecked")
@Nullable
public <T> RowMapper<? extends T> rowMapperFor(Class<T> type) {
Assert.notNull(type, "Type must not be null");
RowMapper<? extends T> candidate = (RowMapper<? extends T>) rowMappers.get(type);
if (candidate == null) {

View File

@@ -43,22 +43,17 @@ public @interface EnableJdbcAuditing {
/**
* Configures the {@link AuditorAware} bean to be used to lookup the current principal.
*
* @return
* @see AuditorAware
*/
String auditorAwareRef() default "";
/**
* Configures whether the creation and modification dates are set.
*
* @return
*/
boolean setDates() default true;
/**
* Configures whether the entity shall be marked as modified on creation.
*
* @return
*/
boolean modifyOnCreate() default true;
@@ -66,7 +61,6 @@ public @interface EnableJdbcAuditing {
* Configures a {@link DateTimeProvider} bean name that allows customizing the {@link java.time.LocalDateTime} to be
* used for setting creation and modification dates.
*
* @return
* @see DateTimeProvider
*/
String dateTimeProviderRef() default "";

View File

@@ -22,8 +22,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.data.jdbc.core.mapping.ConversionCustomizer;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.core.mapping.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.

View File

@@ -0,0 +1,7 @@
/**
* @author Jens Schauder
*/
@NonNullApi
package org.springframework.data.jdbc.repository.config;
import org.springframework.lang.NonNullApi;

View File

@@ -0,0 +1,7 @@
/**
* @author Jens Schauder
*/
@NonNullApi
package org.springframework.data.jdbc.repository;
import org.springframework.lang.NonNullApi;

View File

@@ -0,0 +1,7 @@
/**
* @author Jens Schauder
*/
@NonNullApi
package org.springframework.data.jdbc.repository.query;
import org.springframework.lang.NonNullApi;

View File

@@ -60,6 +60,7 @@ public class JdbcQueryMethod extends QueryMethod {
*
* @return May be {@code null}.
*/
@Nullable
public Class<?> getRowMapperClass() {
return getMergedAnnotationAttribute("rowMapperClass");
}
@@ -75,6 +76,7 @@ public class JdbcQueryMethod extends QueryMethod {
}
@SuppressWarnings("unchecked")
@Nullable
private <T> T getMergedAnnotationAttribute(String attribute) {
Query queryAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Query.class);

View File

@@ -32,6 +32,7 @@ import org.springframework.data.repository.core.support.RepositoryFactorySupport
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -102,6 +103,10 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
JdbcPersistentEntity<?> entity = context.getPersistentEntity(aClass);
if (entity == null) {
return null;
}
return (EntityInformation<T, ID>) new PersistentEntityInformation<>(entity);
}
@@ -131,7 +136,7 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key, org.springframework.data.repository.query.EvaluationContextProvider)
*/
@Override
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(QueryLookupStrategy.Key key,
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable QueryLookupStrategy.Key key,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
if (key != null //

View File

@@ -75,8 +75,6 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
/**
* Creates the actual {@link RepositoryFactorySupport} instance.
*
* @return
*/
@Override
protected RepositoryFactorySupport doCreateRepositoryFactory() {

View File

@@ -22,6 +22,7 @@ import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -51,7 +52,7 @@ class JdbcRepositoryQuery implements RepositoryQuery {
* @param defaultRowMapper can be {@literal null} (only in case of a modifying query).
*/
JdbcRepositoryQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
RowMapper<?> defaultRowMapper) {
@Nullable RowMapper<?> defaultRowMapper) {
Assert.notNull(queryMethod, "Query method must not be null!");
Assert.notNull(operations, "NamedParameterJdbcOperations must not be null!");
@@ -127,7 +128,8 @@ class JdbcRepositoryQuery implements RepositoryQuery {
return parameters;
}
private static RowMapper<?> createRowMapper(JdbcQueryMethod queryMethod, RowMapper<?> defaultRowMapper) {
@Nullable
private static RowMapper<?> createRowMapper(JdbcQueryMethod queryMethod, @Nullable RowMapper<?> defaultRowMapper) {
Class<?> rowMapperClass = queryMethod.getRowMapperClass();

View File

@@ -0,0 +1,7 @@
/**
* @author Jens Schauder
*/
@NonNullApi
package org.springframework.data.jdbc.repository.support;
import org.springframework.lang.NonNullApi;