diff --git a/src/main/java/org/springframework/data/jdbc/core/CascadingDataAccessStrategy.java b/src/main/java/org/springframework/data/jdbc/core/CascadingDataAccessStrategy.java index d33d9f8a..65ae92d7 100644 --- a/src/main/java/org/springframework/data/jdbc/core/CascadingDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/jdbc/core/CascadingDataAccessStrategy.java @@ -65,7 +65,7 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy { } @Override - public 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 collect(Function function) { - return strategies.stream().collect(new FunctionCollector(function)); + return strategies.stream().collect(new FunctionCollector<>(function)); } private void collectVoid(Consumer consumer) { diff --git a/src/main/java/org/springframework/data/jdbc/core/DataAccessStrategy.java b/src/main/java/org/springframework/data/jdbc/core/DataAccessStrategy.java index 834e8eb2..308b4c88 100644 --- a/src/main/java/org/springframework/data/jdbc/core/DataAccessStrategy.java +++ b/src/main/java/org/springframework/data/jdbc/core/DataAccessStrategy.java @@ -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 the type of the instance. + */ void insert(T instance, Class domainType, Map 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 the type of the instance to save. + */ void update(T instance, Class 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 type of the domain type. + */ void deleteAll(Class 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}. */ - 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 the type of the entity. + * @return Might return {@code null}. + */ + @Nullable T findById(Object id, Class domainType); + /** + * Loads alls entities of the given type. + * + * @param domainType the type of entities to load. Must not be {@code null}. + * @param the type of entities to load. + * @return Guaranteed to be not {@code null}. + */ Iterable findAll(Class 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 type of entities to load. + * @return the loaded entities. Guaranteed to be not {@code null}. + */ Iterable findAllById(Iterable ids, Class domainType); /** @@ -68,5 +131,13 @@ public interface DataAccessStrategy { */ Iterable 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 the type of the entity. + * @return {@code true} if a matching row exists, otherwise {@code false}. + */ boolean existsById(Object id, Class domainType); } diff --git a/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java b/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java index 10b95a26..5dfefca7 100644 --- a/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java @@ -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 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 findById(Object id, Class 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) 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 Iterable findAll(Class domainType) { - return operations.query(sql(domainType).getFindAll(), getEntityRowMapper(domainType)); + return operations.query(sql(domainType).getFindAll(), (RowMapper) getEntityRowMapper(domainType)); } /* * (non-Javadoc) * @see org.springframework.data.jdbc.core.DataAccessStrategy#findAllById(java.lang.Iterable, java.lang.Class) */ + @SuppressWarnings("unchecked") @Override public Iterable findAllById(Iterable ids, Class 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) getEntityRowMapper(domainType)); } /* @@ -242,9 +254,10 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { MapSqlParameterSource parameter = new MapSqlParameterSource(property.getReverseColumnName(), rootId); - return (Iterable) operations.query(findAllByProperty, parameter, property.isMap() // - ? getMapEntityRowMapper(property) // - : getEntityRowMapper(actualType)); + return operations.query(findAllByProperty, parameter, // + (RowMapper) (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 MapSqlParameterSource getPropertyMap(final S instance, JdbcPersistentEntity persistentEntity) { @@ -277,6 +294,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { } @SuppressWarnings("unchecked") + @Nullable private ID getIdValueOrNull(S instance, JdbcPersistentEntity 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 boolean isIdPropertyNullOrScalarZero(ID idValue, JdbcPersistentEntity persistentEntity) { + private static boolean isIdPropertyNullOrScalarZero(@Nullable ID idValue, + JdbcPersistentEntity 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 keys = holder.getKeys(); + return Optional.ofNullable(keys == null ? null : keys.get(persistentEntity.getIdColumn())); } } - public EntityRowMapper getEntityRowMapper(Class 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 MapSqlParameterSource createIdParameterSource(Object id, Class 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) context.getRequiredPersistentEntity(domainType); } - private V convert(Object from, Class to) { + @Nullable + private V convert(@Nullable Object from, Class to) { if (from == null) { return null; diff --git a/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java b/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java index bc60dbcd..ed8dd1d4 100644 --- a/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java +++ b/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java @@ -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(); } diff --git a/src/main/java/org/springframework/data/jdbc/core/DelegatingDataAccessStrategy.java b/src/main/java/org/springframework/data/jdbc/core/DelegatingDataAccessStrategy.java index 5cd7f2cf..03b3b20d 100644 --- a/src/main/java/org/springframework/data/jdbc/core/DelegatingDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/jdbc/core/DelegatingDataAccessStrategy.java @@ -58,7 +58,7 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy { } @Override - public void deleteAll(PropertyPath propertyPath) { + public void deleteAll(PropertyPath propertyPath) { delegate.deleteAll(propertyPath); } diff --git a/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java index 1160a363..9c8a626e 100644 --- a/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java +++ b/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java @@ -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 implements RowMapper { * @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 implements RowMapper { 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 allByProperty = accessStrategy.findAllByProperty(id, property); propertyAccessor.setProperty(property, ITERABLE_OF_ENTRY_TO_MAP_CONVERTER.convert(allByProperty)); @@ -105,6 +109,7 @@ public class EntityRowMapper implements RowMapper { * @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 implements RowMapper { } } + @Nullable private S readEntityFrom(ResultSet rs, PersistentProperty property) { String prefix = property.getName() + "_"; @@ -166,7 +172,9 @@ public class EntityRowMapper implements RowMapper { @Override public T getParameterValue(Parameter 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()); diff --git a/src/main/java/org/springframework/data/jdbc/core/IterableOfEntryToMapConverter.java b/src/main/java/org/springframework/data/jdbc/core/IterableOfEntryToMapConverter.java index 874147ab..cf896fd3 100644 --- a/src/main/java/org/springframework/data/jdbc/core/IterableOfEntryToMapConverter.java +++ b/src/main/java/org/springframework/data/jdbc/core/IterableOfEntryToMapConverter.java @@ -33,6 +33,7 @@ import org.springframework.util.Assert; */ class IterableOfEntryToMapConverter implements ConditionalConverter, Converter, Map> { + @SuppressWarnings("unchecked") @Nullable @Override public Map convert(Iterable source) { @@ -58,7 +59,7 @@ class IterableOfEntryToMapConverter implements ConditionalConverter, ConverterDomain Type. * @@ -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 the type of the aggregate root. + */ 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 the type of the aggregate root. + */ void deleteById(Object id, Class domainType); - void delete(T entity, Class 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 the type of the aggregate root. + */ + void delete(T aggregateRoot, Class 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 the type of the aggregate root. + * @return the loaded aggregate. Might return {@code null}. + */ + @Nullable T findById(Object id, Class 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 the type of the aggregate roots. Must not be {@code null}. + * @return Guaranteed to be not {@code null}. + */ Iterable findAllById(Iterable ids, Class domainType); + /** + * Load all aggregates of a given type. + * + * @param domainType the type of the aggregate roots. Must not be {@code null}. + * @param the type of the aggregate roots. Must not be {@code null}. + * @return Guaranteed to be not {@code null}. + */ Iterable findAll(Class 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 the type of the aggregate root. + * @return whether the aggregate exists. + */ boolean existsById(Object id, Class domainType); } diff --git a/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java b/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java index eb9c1b65..a362280b 100644 --- a/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java +++ b/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java @@ -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 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 void delete(S entity, Class domainType) { + public void delete(S aggregateRoot, Class 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); diff --git a/src/main/java/org/springframework/data/jdbc/core/MapEntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/core/MapEntityRowMapper.java index ccc310eb..6baacc36 100644 --- a/src/main/java/org/springframework/data/jdbc/core/MapEntityRowMapper.java +++ b/src/main/java/org/springframework/data/jdbc/core/MapEntityRowMapper.java @@ -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 implements RowMapper> { private final RowMapper 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 delegate, String keyColumn) { this.delegate = delegate; this.keyColumn = keyColumn; } - @Nullable + @NonNull @Override public Map.Entry mapRow(ResultSet rs, int rowNum) throws SQLException { return new HashMap.SimpleEntry<>(rs.getObject(keyColumn), delegate.mapRow(rs, rowNum)); diff --git a/src/main/java/org/springframework/data/jdbc/core/SelectBuilder.java b/src/main/java/org/springframework/data/jdbc/core/SelectBuilder.java index bd7f68fc..8cf42961 100644 --- a/src/main/java/org/springframework/data/jdbc/core/SelectBuilder.java +++ b/src/main/java/org/springframework/data/jdbc/core/SelectBuilder.java @@ -36,29 +36,60 @@ class SelectBuilder { private final List joins = new ArrayList<>(); private final List 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 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 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 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() { diff --git a/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java b/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java index 31babd24..e05fd31e 100644 --- a/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java +++ b/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java @@ -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; diff --git a/src/main/java/org/springframework/data/jdbc/core/conversion/AggregateChange.java b/src/main/java/org/springframework/data/jdbc/core/conversion/AggregateChange.java index a9c37ba6..6098070d 100644 --- a/src/main/java/org/springframework/data/jdbc/core/conversion/AggregateChange.java +++ b/src/main/java/org/springframework/data/jdbc/core/conversion/AggregateChange.java @@ -49,7 +49,19 @@ public class AggregateChange { 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 } } diff --git a/src/main/java/org/springframework/data/jdbc/core/conversion/DbAction.java b/src/main/java/org/springframework/data/jdbc/core/conversion/DbAction.java index 64b2bfc4..b585ddbb 100644 --- a/src/main/java/org/springframework/data/jdbc/core/conversion/DbAction.java +++ b/src/main/java/org/springframework/data/jdbc/core/conversion/DbAction.java @@ -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 { /** * {@link Class} of the entity of which the database representation is affected by this action. */ - private final Class entityType; + final Class 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 { */ private final DbAction dependingOn; - private DbAction(Class entityType, T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) { + private DbAction(Class 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 { this.dependingOn = dependingOn; } - public static Insert 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 the type of the entity to be inserted. + * @return a {@link DbAction} representing the insert. + */ + public static Insert insert(T entity, JdbcPropertyPath propertyPath, @Nullable DbAction dependingOn) { return new Insert<>(entity, propertyPath, dependingOn); } - public static Update 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 the type of the entity to be updated. + * @return a {@link DbAction} representing the update. + */ + public static Update update(T entity, JdbcPropertyPath propertyPath, @Nullable DbAction dependingOn) { return new Update<>(entity, propertyPath, dependingOn); } - public static Delete delete(Object id, Class 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 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 Delete delete(Object id, Class type, @Nullable T entity, + @Nullable JdbcPropertyPath propertyPath, @Nullable DbAction dependingOn) { return new Delete<>(id, type, entity, propertyPath, dependingOn); } - public static DeleteAll deleteAll(Class 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 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 DeleteAll deleteAll(Class type, @Nullable JdbcPropertyPath propertyPath, + @Nullable DbAction dependingOn) { return new DeleteAll<>(type, propertyPath, dependingOn); } @@ -116,7 +163,7 @@ public abstract class DbAction { abstract static class InsertOrUpdate extends DbAction { @SuppressWarnings("unchecked") - InsertOrUpdate(T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) { + InsertOrUpdate(T entity, JdbcPropertyPath propertyPath, @Nullable DbAction dependingOn) { super((Class) entity.getClass(), entity, propertyPath, dependingOn); } } @@ -128,7 +175,7 @@ public abstract class DbAction { */ public static class Insert extends InsertOrUpdate { - 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 { */ public static class Update extends InsertOrUpdate { - 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 { */ private final Object rootId; - private Delete(Object rootId, Class type, T entity, JdbcPropertyPath propertyPath, DbAction dependingOn) { + private Delete(@Nullable Object rootId, Class type, @Nullable T entity, @Nullable JdbcPropertyPath propertyPath, + @Nullable DbAction dependingOn) { super(type, entity, propertyPath, dependingOn); @@ -191,7 +239,7 @@ public abstract class DbAction { */ public static class DeleteAll extends DbAction { - private DeleteAll(Class entityType, JdbcPropertyPath propertyPath, DbAction dependingOn) { + private DeleteAll(Class entityType, @Nullable JdbcPropertyPath propertyPath, @Nullable DbAction dependingOn) { super(entityType, null, propertyPath, dependingOn); } diff --git a/src/main/java/org/springframework/data/jdbc/core/conversion/DbActionExecutionException.java b/src/main/java/org/springframework/data/jdbc/core/conversion/DbActionExecutionException.java index 5d65c247..d44ebcaf 100644 --- a/src/main/java/org/springframework/data/jdbc/core/conversion/DbActionExecutionException.java +++ b/src/main/java/org/springframework/data/jdbc/core/conversion/DbActionExecutionException.java @@ -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", // diff --git a/src/main/java/org/springframework/data/jdbc/core/conversion/Interpreter.java b/src/main/java/org/springframework/data/jdbc/core/conversion/Interpreter.java index 9d190dc7..e9a56684 100644 --- a/src/main/java/org/springframework/data/jdbc/core/conversion/Interpreter.java +++ b/src/main/java/org/springframework/data/jdbc/core/conversion/Interpreter.java @@ -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 the type of entity to work on. + */ void interpret(Update update); void interpret(Insert insert); diff --git a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityDeleteWriter.java b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityDeleteWriter.java index 8050fbde..25bc53eb 100644 --- a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityDeleteWriter.java +++ b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityDeleteWriter.java @@ -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 void deleteById(Object id, AggregateChange 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 // + )); } } diff --git a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriter.java b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriter.java index 99cae919..df280b26 100644 --- a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriter.java +++ b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriter.java @@ -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 persistentEntity = context.getRequiredPersistentEntity(type); - if (persistentEntity.isNew(o)) { + if (persistentEntity.isNew(aggregateRoot)) { - Insert insert = DbAction.insert(o, propertyPath, dependingOn); + Insert 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 update = DbAction.update(o, propertyPath, dependingOn); + Update 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 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 referencedEntity(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) { @@ -155,6 +158,7 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport { return singlePropertyAsStream(p, propertyAccessor); } + @SuppressWarnings("unchecked") private Stream collectionPropertyAsStream(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) { @@ -165,6 +169,7 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport { : ((Collection) property).stream(); } + @SuppressWarnings("unchecked") private Stream 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 mapPropertyAsStream(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) { Object property = propertyAccessor.getProperty(p); diff --git a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterSupport.java b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterSupport.java index 40a1d42d..64a2a645 100644 --- a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterSupport.java +++ b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterSupport.java @@ -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}. @@ -24,23 +25,26 @@ import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; * @author Jens Schauder * @since 1.0 */ -abstract class JdbcEntityWriterSupport implements EntityWriter { +abstract class JdbcEntityWriterSupport implements EntityWriter> { 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))); } } diff --git a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcPropertyPath.java b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcPropertyPath.java index e5b85a46..e09ec636 100644 --- a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcPropertyPath.java +++ b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcPropertyPath.java @@ -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() { diff --git a/src/main/java/org/springframework/data/jdbc/core/conversion/package-info.java b/src/main/java/org/springframework/data/jdbc/core/conversion/package-info.java new file mode 100644 index 00000000..db476569 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/core/conversion/package-info.java @@ -0,0 +1,7 @@ +/** + * @author Jens Schauder + */ +@NonNullApi +package org.springframework.data.jdbc.core.conversion; + +import org.springframework.lang.NonNullApi; diff --git a/src/main/java/org/springframework/data/jdbc/core/mapping/BasicJdbcPersistentProperty.java b/src/main/java/org/springframework/data/jdbc/core/mapping/BasicJdbcPersistentProperty.java index b1012441..c53c5c20 100644 --- a/src/main/java/org/springframework/data/jdbc/core/mapping/BasicJdbcPersistentProperty.java +++ b/src/main/java/org/springframework/data/jdbc/core/mapping/BasicJdbcPersistentProperty.java @@ -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, Class> javaToDbType = new LinkedHashMap<>(); - private final JdbcMappingContext context; - - private final Lazy> columnName; static { javaToDbType.put(Enum.class, String.class); @@ -53,6 +51,9 @@ class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProperty> columnName; + /** * Creates a new {@link AnnotationBasedPersistentProperty}. * @@ -85,6 +86,7 @@ class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProperty context.getNamingStrategy().getColumnName(this)); } @@ -123,15 +125,16 @@ class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProperty persistentEntity = context.getPersistentEntity(type); diff --git a/src/main/java/org/springframework/data/jdbc/core/mapping/ConversionCustomizer.java b/src/main/java/org/springframework/data/jdbc/core/mapping/ConversionCustomizer.java index 639bf30e..b823a444 100644 --- a/src/main/java/org/springframework/data/jdbc/core/mapping/ConversionCustomizer.java +++ b/src/main/java/org/springframework/data/jdbc/core/mapping/ConversionCustomizer.java @@ -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); } diff --git a/src/main/java/org/springframework/data/jdbc/core/mapping/JdbcMappingContext.java b/src/main/java/org/springframework/data/jdbc/core/mapping/JdbcMappingContext.java index 56aad706..2fe859c4 100644 --- a/src/main/java/org/springframework/data/jdbc/core/mapping/JdbcMappingContext.java +++ b/src/main/java/org/springframework/data/jdbc/core/mapping/JdbcMappingContext.java @@ -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 referencedEntities(Class rootType, PropertyPath path) { + public List referencedEntities(Class rootType, @Nullable PropertyPath path) { List paths = new ArrayList<>(); @@ -142,12 +151,4 @@ public class JdbcMappingContext extends AbstractMappingContext getColumnType(); @@ -45,10 +46,12 @@ public interface JdbcPersistentProperty extends PersistentProperty instance, AggregateChange change) { super(id, instance, change); diff --git a/src/main/java/org/springframework/data/jdbc/core/mapping/event/AfterSaveEvent.java b/src/main/java/org/springframework/data/jdbc/core/mapping/event/AfterSaveEvent.java index ffa7cc2e..fb827a9a 100644 --- a/src/main/java/org/springframework/data/jdbc/core/mapping/event/AfterSaveEvent.java +++ b/src/main/java/org/springframework/data/jdbc/core/mapping/event/AfterSaveEvent.java @@ -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); diff --git a/src/main/java/org/springframework/data/jdbc/core/mapping/event/BeforeDeleteEvent.java b/src/main/java/org/springframework/data/jdbc/core/mapping/event/BeforeDeleteEvent.java index f2034a02..f879f901 100644 --- a/src/main/java/org/springframework/data/jdbc/core/mapping/event/BeforeDeleteEvent.java +++ b/src/main/java/org/springframework/data/jdbc/core/mapping/event/BeforeDeleteEvent.java @@ -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 diff --git a/src/main/java/org/springframework/data/jdbc/core/mapping/event/BeforeSaveEvent.java b/src/main/java/org/springframework/data/jdbc/core/mapping/event/BeforeSaveEvent.java index 2b62127f..8e777c3e 100644 --- a/src/main/java/org/springframework/data/jdbc/core/mapping/event/BeforeSaveEvent.java +++ b/src/main/java/org/springframework/data/jdbc/core/mapping/event/BeforeSaveEvent.java @@ -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); diff --git a/src/main/java/org/springframework/data/jdbc/core/mapping/event/Identifier.java b/src/main/java/org/springframework/data/jdbc/core/mapping/event/Identifier.java index 7ab52e12..7e2c7668 100644 --- a/src/main/java/org/springframework/data/jdbc/core/mapping/event/Identifier.java +++ b/src/main/java/org/springframework/data/jdbc/core/mapping/event/Identifier.java @@ -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 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 getOptionalValue(); + Optional getOptionalValue(); /** * A specified identifier that exposes a definitely present identifier value. diff --git a/src/main/java/org/springframework/data/jdbc/core/mapping/event/JdbcEvent.java b/src/main/java/org/springframework/data/jdbc/core/mapping/event/JdbcEvent.java index de66270f..cdabab25 100644 --- a/src/main/java/org/springframework/data/jdbc/core/mapping/event/JdbcEvent.java +++ b/src/main/java/org/springframework/data/jdbc/core/mapping/event/JdbcEvent.java @@ -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 getOptionalEntity(); diff --git a/src/main/java/org/springframework/data/jdbc/core/mapping/event/JdbcEventWithEntity.java b/src/main/java/org/springframework/data/jdbc/core/mapping/event/JdbcEventWithEntity.java index 6e7fe136..9e0bef83 100644 --- a/src/main/java/org/springframework/data/jdbc/core/mapping/event/JdbcEventWithEntity.java +++ b/src/main/java/org/springframework/data/jdbc/core/mapping/event/JdbcEventWithEntity.java @@ -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); } } diff --git a/src/main/java/org/springframework/data/jdbc/core/mapping/event/JdbcEventWithId.java b/src/main/java/org/springframework/data/jdbc/core/mapping/event/JdbcEventWithId.java index 00ad2b35..48f15d10 100644 --- a/src/main/java/org/springframework/data/jdbc/core/mapping/event/JdbcEventWithId.java +++ b/src/main/java/org/springframework/data/jdbc/core/mapping/event/JdbcEventWithId.java @@ -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 entity, AggregateChange change) { + public JdbcEventWithId(Specified id, Optional entity, @Nullable AggregateChange change) { super(id, entity, change); diff --git a/src/main/java/org/springframework/data/jdbc/core/mapping/event/JdbcEventWithIdAndEntity.java b/src/main/java/org/springframework/data/jdbc/core/mapping/event/JdbcEventWithIdAndEntity.java index b40a215e..672c59cc 100644 --- a/src/main/java/org/springframework/data/jdbc/core/mapping/event/JdbcEventWithIdAndEntity.java +++ b/src/main/java/org/springframework/data/jdbc/core/mapping/event/JdbcEventWithIdAndEntity.java @@ -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); } } diff --git a/src/main/java/org/springframework/data/jdbc/core/mapping/event/SimpleJdbcEvent.java b/src/main/java/org/springframework/data/jdbc/core/mapping/event/SimpleJdbcEvent.java index f06ac86d..6ed75070 100644 --- a/src/main/java/org/springframework/data/jdbc/core/mapping/event/SimpleJdbcEvent.java +++ b/src/main/java/org/springframework/data/jdbc/core/mapping/event/SimpleJdbcEvent.java @@ -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 entity, AggregateChange change) { + SimpleJdbcEvent(Identifier id, Optional entity, @Nullable AggregateChange change) { super(id); diff --git a/src/main/java/org/springframework/data/jdbc/core/mapping/event/SpecifiedIdentifier.java b/src/main/java/org/springframework/data/jdbc/core/mapping/event/SpecifiedIdentifier.java index f23ad100..92cd82e7 100644 --- a/src/main/java/org/springframework/data/jdbc/core/mapping/event/SpecifiedIdentifier.java +++ b/src/main/java/org/springframework/data/jdbc/core/mapping/event/SpecifiedIdentifier.java @@ -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 getOptionalValue() { + public Optional getOptionalValue() { return Optional.of(value); } } diff --git a/src/main/java/org/springframework/data/jdbc/core/mapping/event/package-info.java b/src/main/java/org/springframework/data/jdbc/core/mapping/event/package-info.java new file mode 100644 index 00000000..f6780356 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/core/mapping/event/package-info.java @@ -0,0 +1,7 @@ +/** + * @author Jens Schauder + */ +@NonNullApi +package org.springframework.data.jdbc.core.mapping.event; + +import org.springframework.lang.NonNullApi; diff --git a/src/main/java/org/springframework/data/jdbc/core/mapping/package-info.java b/src/main/java/org/springframework/data/jdbc/core/mapping/package-info.java new file mode 100644 index 00000000..20856e12 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/core/mapping/package-info.java @@ -0,0 +1,7 @@ +/** + * @author Jens Schauder + */ +@NonNullApi +package org.springframework.data.jdbc.core.mapping; + +import org.springframework.lang.NonNullApi; diff --git a/src/main/java/org/springframework/data/jdbc/core/package-info.java b/src/main/java/org/springframework/data/jdbc/core/package-info.java new file mode 100644 index 00000000..5be2bd7c --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/core/package-info.java @@ -0,0 +1,7 @@ +/** + * @author Jens Schauder + */ +@NonNullApi +package org.springframework.data.jdbc.core; + +import org.springframework.lang.NonNullApi; \ No newline at end of file diff --git a/src/main/java/org/springframework/data/jdbc/domain/support/package-info.java b/src/main/java/org/springframework/data/jdbc/domain/support/package-info.java new file mode 100644 index 00000000..bcf2b5b5 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/domain/support/package-info.java @@ -0,0 +1,7 @@ +/** + * @author Jens Schauder + */ +@NonNullApi +package org.springframework.data.jdbc.domain.support; + +import org.springframework.lang.NonNullApi; diff --git a/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisContext.java b/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisContext.java index c7ea64bc..e1f7a609 100644 --- a/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisContext.java +++ b/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisContext.java @@ -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 additonalValues; - public MyBatisContext(Object id, Object instance, Class domainType, Map additonalValues) { + public MyBatisContext(@Nullable Object id, @Nullable Object instance, Class domainType, Map 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); } diff --git a/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java b/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java index 55ffdbf7..1c37824c 100644 --- a/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java @@ -161,7 +161,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy { } @Override - public void deleteAll(PropertyPath propertyPath) { + public void deleteAll(PropertyPath propertyPath) { Class baseType = propertyPath.getOwningType().getType(); Class leafType = propertyPath.getLeafProperty().getTypeInformation().getType(); diff --git a/src/main/java/org/springframework/data/jdbc/mybatis/package-info.java b/src/main/java/org/springframework/data/jdbc/mybatis/package-info.java new file mode 100644 index 00000000..7f8df957 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mybatis/package-info.java @@ -0,0 +1,7 @@ +/** + * @author Jens Schauder + */ +@NonNullApi +package org.springframework.data.jdbc.mybatis; + +import org.springframework.lang.NonNullApi; diff --git a/src/main/java/org/springframework/data/jdbc/repository/RowMapperMap.java b/src/main/java/org/springframework/data/jdbc/repository/RowMapperMap.java index 2fd0d988..938a0f6b 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/RowMapperMap.java +++ b/src/main/java/org/springframework/data/jdbc/repository/RowMapperMap.java @@ -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 RowMapper rowMapperFor(Class type); } diff --git a/src/main/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMap.java b/src/main/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMap.java index c91764bd..948dcafc 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMap.java +++ b/src/main/java/org/springframework/data/jdbc/repository/config/ConfigurableRowMapperMap.java @@ -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 the type to be produced by the returned {@link RowMapper}. + * @return Guaranteed to be not {@code null}. + */ @SuppressWarnings("unchecked") + @Nullable public RowMapper rowMapperFor(Class type) { + Assert.notNull(type, "Type must not be null"); + RowMapper candidate = (RowMapper) rowMappers.get(type); if (candidate == null) { diff --git a/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditing.java b/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditing.java index 11d49777..c7dea615 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditing.java +++ b/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditing.java @@ -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 ""; diff --git a/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java b/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java index 1a28b3dc..911bff51 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java +++ b/src/main/java/org/springframework/data/jdbc/repository/config/JdbcConfiguration.java @@ -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. diff --git a/src/main/java/org/springframework/data/jdbc/repository/config/package-info.java b/src/main/java/org/springframework/data/jdbc/repository/config/package-info.java new file mode 100644 index 00000000..b0c68ed0 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/config/package-info.java @@ -0,0 +1,7 @@ +/** + * @author Jens Schauder + */ +@NonNullApi +package org.springframework.data.jdbc.repository.config; + +import org.springframework.lang.NonNullApi; diff --git a/src/main/java/org/springframework/data/jdbc/repository/package-info.java b/src/main/java/org/springframework/data/jdbc/repository/package-info.java new file mode 100644 index 00000000..e0ba4798 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/package-info.java @@ -0,0 +1,7 @@ +/** + * @author Jens Schauder + */ +@NonNullApi +package org.springframework.data.jdbc.repository; + +import org.springframework.lang.NonNullApi; diff --git a/src/main/java/org/springframework/data/jdbc/repository/query/package-info.java b/src/main/java/org/springframework/data/jdbc/repository/query/package-info.java new file mode 100644 index 00000000..64fdddb2 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/query/package-info.java @@ -0,0 +1,7 @@ +/** + * @author Jens Schauder + */ +@NonNullApi +package org.springframework.data.jdbc.repository.query; + +import org.springframework.lang.NonNullApi; diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethod.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethod.java index 548e4e2f..f0cdac75 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethod.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryMethod.java @@ -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 getMergedAnnotationAttribute(String attribute) { Query queryAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Query.class); diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java index e711e1e7..322b62ed 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java @@ -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) 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 getQueryLookupStrategy(QueryLookupStrategy.Key key, + protected Optional getQueryLookupStrategy(@Nullable QueryLookupStrategy.Key key, QueryMethodEvaluationContextProvider evaluationContextProvider) { if (key != null // diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java index 95caf7ee..f6bab3b8 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java @@ -75,8 +75,6 @@ public class JdbcRepositoryFactoryBean, S, ID extend /** * Creates the actual {@link RepositoryFactorySupport} instance. - * - * @return */ @Override protected RepositoryFactorySupport doCreateRepositoryFactory() { diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQuery.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQuery.java index 153aefaf..4ad11af0 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQuery.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQuery.java @@ -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(); diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/package-info.java b/src/main/java/org/springframework/data/jdbc/repository/support/package-info.java new file mode 100644 index 00000000..5e05c690 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/support/package-info.java @@ -0,0 +1,7 @@ +/** + * @author Jens Schauder + */ +@NonNullApi +package org.springframework.data.jdbc.repository.support; + +import org.springframework.lang.NonNullApi; diff --git a/src/test/java/org/springframework/data/jdbc/core/mapping/BasicJdbcPersistentPropertyUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/mapping/BasicJdbcPersistentPropertyUnitTests.java index 0177771f..812f5f46 100644 --- a/src/test/java/org/springframework/data/jdbc/core/mapping/BasicJdbcPersistentPropertyUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/mapping/BasicJdbcPersistentPropertyUnitTests.java @@ -24,11 +24,6 @@ import java.time.ZonedDateTime; import java.util.Date; import org.junit.Test; -import org.springframework.data.jdbc.core.mapping.BasicJdbcPersistentProperty; -import org.springframework.data.jdbc.core.mapping.Column; -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.mapping.PropertyHandler; /** diff --git a/src/test/java/org/springframework/data/jdbc/core/mapping/JdbcPersistentEntityImplUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/mapping/JdbcPersistentEntityImplUnitTests.java index d2d74f54..353e81c8 100644 --- a/src/test/java/org/springframework/data/jdbc/core/mapping/JdbcPersistentEntityImplUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/mapping/JdbcPersistentEntityImplUnitTests.java @@ -18,10 +18,6 @@ package org.springframework.data.jdbc.core.mapping; import static org.assertj.core.api.Assertions.*; import org.junit.Test; -import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; -import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntity; -import org.springframework.data.jdbc.core.mapping.JdbcPersistentEntityImpl; -import org.springframework.data.jdbc.core.mapping.Table; /** * Unit tests for {@link JdbcPersistentEntityImpl}. diff --git a/src/test/java/org/springframework/data/jdbc/core/mapping/event/IdentifierTest.java b/src/test/java/org/springframework/data/jdbc/core/mapping/event/IdentifierTest.java new file mode 100644 index 00000000..7f992b44 --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/core/mapping/event/IdentifierTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.core.mapping.event; + +import org.junit.Test; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link Identifier} + * + * @author Jens Schauder + */ +public class IdentifierTest { + + @SuppressWarnings("unchecked") + @Test + public void specifiedOffersTheIdentifierValue() { + + Identifier.Specified identifier = Identifier.of("x"); + + assertThat(identifier.getValue()).isEqualTo("x"); + assertThat((Optional) identifier.getOptionalValue()).contains("x"); + } + + @Test + public void indentifierOfNullHasEmptyValue(){ + + Identifier identifier = Identifier.ofNullable(null); + + assertThat(identifier.getOptionalValue()).isEmpty(); + } + + @SuppressWarnings("unchecked") + @Test + public void indentifierOfXHasValueX(){ + + Identifier identifier = Identifier.ofNullable("x"); + + assertThat((Optional) identifier.getOptionalValue()).hasValue("x"); + } +} \ No newline at end of file diff --git a/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java index 52a67d72..68e5980a 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java @@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import org.assertj.core.api.SoftAssertions; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -232,7 +233,7 @@ public class EnableJdbcAuditingHsqlIntegrationTests { return new NamingStrategy() { - public String getTableName(Class type) { + public String getTableName(@NotNull Class type) { return "DummyEntity"; } }; diff --git a/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationHsqlIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationHsqlIntegrationTests.java index 9fc48924..2c5df7db 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationHsqlIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationHsqlIntegrationTests.java @@ -37,6 +37,7 @@ import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; +import org.springframework.lang.Nullable; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.rules.SpringClassRule; @@ -289,6 +290,7 @@ public class QueryAnnotationHsqlIntegrationTests { Optional findByNameAsOptional(@Param("name") String name); // DATAJDBC-172 + @Nullable @Query("SELECT * FROM DUMMY_ENTITY WHERE name = :name") DummyEntity findByNameAsEntity(@Param("name") String name);