DATAJDBC-99 - Polishing.

Introduced interface for JdbcPersistentEntity to be consistent with other store implementations.

Introduced JdbcPersistentEntityInformation.getRequiredId(…) to return a value and throw an exception if it can't be obtained.

Added serialVersionUIDs to event implementations. Extracted Unset and SpecifiedIdentifier into top level classes to be able to reduce visibility. Introduced Specified interface and factory methods on Identifier to be able to create Identifier instances in all ways needed (specified / optional).

Extracted JdbcEvent interface from the previously thus named class to be able to let WithId and WithEntity extend that interface to avoid the cast in the default method of WithId. WithId now simply redeclares getId returning Specified so that the guarantees of returned object type simply adapt as soon as event types also implement that interface. Reduced the visibility of SimpleJdbcEvent as listeners could now just refer to JdbcEvent. Switched to a nullable field for the entity in SimpleJdbcEvent as ApplicationEvent already implies potential serializability and Optional is not Serializable.

Reorganized integration test setup to use configuration classes over FactoryBean implementations. Switched to AssertJ for test assertions.

Renamed UnableToSetIdException to UnableToSetId. A bit of Javadoc here and there. Formatting. No abbreviated variable names.

Original pull request: #5.
This commit is contained in:
Oliver Gierke
2017-04-25 17:57:24 +02:00
parent f0eaf862cf
commit 1af9146f4d
51 changed files with 1044 additions and 630 deletions

View File

@@ -16,31 +16,38 @@
package org.springframework.data.jdbc.mapping.context;
import org.springframework.data.jdbc.mapping.model.BasicJdbcPersistentProperty;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityImpl;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
import org.springframework.data.mapping.context.AbstractMappingContext;
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;
/**
* {@link MappingContext} implementation for JDBC.
*
* @author Jens Schauder
* @since 2.0
*/
public class JdbcMappingContext extends AbstractMappingContext<JdbcPersistentEntity<?>, JdbcPersistentProperty> {
public class JdbcMappingContext extends AbstractMappingContext<JdbcPersistentEntityImpl<?>, JdbcPersistentProperty> {
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation)
*/
@Override
protected <T> JdbcPersistentEntity createPersistentEntity(TypeInformation<T> typeInformation) {
return new JdbcPersistentEntity(typeInformation);
protected <T> JdbcPersistentEntityImpl<?> createPersistentEntity(TypeInformation<T> typeInformation) {
return new JdbcPersistentEntityImpl<>(typeInformation);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentProperty(org.springframework.data.mapping.model.Property, org.springframework.data.mapping.model.MutablePersistentEntity, org.springframework.data.mapping.model.SimpleTypeHolder)
*/
@Override
protected JdbcPersistentProperty createPersistentProperty( //
Property property, //
JdbcPersistentEntity<?> owner, //
SimpleTypeHolder simpleTypeHolder //
) {
protected JdbcPersistentProperty createPersistentProperty(Property property, JdbcPersistentEntityImpl<?> owner,
SimpleTypeHolder simpleTypeHolder) {
return new BasicJdbcPersistentProperty(property, owner, simpleTypeHolder);
}
}

View File

@@ -26,6 +26,8 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
*/
public class AfterCreation extends JdbcEventWithIdAndEntity {
private static final long serialVersionUID = -4185777271143436728L;
/**
* @param id of the entity
* @param entity the newly instantiated entity.

View File

@@ -28,6 +28,8 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
*/
public class AfterDelete extends JdbcEventWithId {
private static final long serialVersionUID = 3594807189931141582L;
/**
* @param id of the entity.
* @param instance the deleted entity if it is available.

View File

@@ -25,6 +25,8 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
*/
public class AfterInsert extends AfterSave {
private static final long serialVersionUID = 1312645717283677063L;
/**
* @param id identifier of the entity triggering the event.
* @param instance the newly inserted entity.

View File

@@ -25,6 +25,8 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
*/
public class AfterSave extends JdbcEventWithIdAndEntity {
private static final long serialVersionUID = 8982085767296982848L;
/**
* @param id identifier of
* @param instance the newly saved entity.

View File

@@ -25,6 +25,8 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
*/
public class AfterUpdate extends AfterSave {
private static final long serialVersionUID = -1765706904721563399L;
/**
* @param id of the entity
* @param instance the updated entity.

View File

@@ -27,6 +27,8 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
*/
public class BeforeDelete extends JdbcEventWithId {
private static final long serialVersionUID = -5483432053368496651L;
/**
* @param id the id of the entity
* @param entity the entity about to get deleted. Might be empty.

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.data.jdbc.mapping.event;
import org.springframework.data.jdbc.mapping.event.Identifier.Unset;
/**
* Gets published before an entity gets inserted into the database. When the id-property of the entity must get set
* manually, an event listener for this event may do so. <br>
@@ -27,6 +25,8 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Unset;
*/
public class BeforeInsert extends BeforeSave {
private static final long serialVersionUID = -5350552051337308870L;
/**
* @param instance the entity about to get inserted.
*/

View File

@@ -23,6 +23,8 @@ package org.springframework.data.jdbc.mapping.event;
*/
public class BeforeSave extends JdbcEventWithEntity {
private static final long serialVersionUID = -6996874391990315443L;
/**
* @param id of the entity to be saved.
* @param instance the entity about to get saved.

View File

@@ -25,11 +25,27 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
*/
public class BeforeUpdate extends BeforeSave implements WithId {
private static final long serialVersionUID = 794561215071613972L;
private final Specified id;
/**
* @param id of the entity about to get updated
* @param instance the entity about to get updated.
*/
public BeforeUpdate(Specified id, Object instance) {
super(id, instance);
this.id = id;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.mapping.event.JdbcEvent#getId()
*/
@Override
public Specified getId() {
return id;
}
}

View File

@@ -15,11 +15,10 @@
*/
package org.springframework.data.jdbc.mapping.event;
import lombok.Data;
import lombok.NonNull;
import java.util.Optional;
import org.springframework.util.Assert;
/**
* Wrapper for an identifier of an entity. Might either be a {@link Specified} or {@link Unset#UNSET}
*
@@ -28,36 +27,53 @@ import java.util.Optional;
*/
public interface Identifier {
static Identifier fromNullable(Object value) {
return (value != null) ? new Specified(value) : Unset.UNSET;
}
Optional<Object> getOptionalValue();
/**
* An unset identifier. Always returns {@link Optional#empty()} as value.
* Creates a new {@link Specified} identifier for the given, non-null value.
*
* @param identifier must not be {@literal null}.
* @return will never be {@literal null}.
*/
enum Unset implements Identifier {
UNSET {
@Override
public Optional<Object> getOptionalValue() {
return Optional.empty();
}
}
static Specified of(Object identifier) {
Assert.notNull(identifier, "Identifier must not be null!");
return SpecifiedIdentifier.of(identifier);
}
/**
* An {@link Identifier} guaranteed to have a non empty value. Since it is guaranteed to exist the value can get
* access directly.
* Creates a new {@link Identifier} for the given optional source value.
*
* @param identifier must not be {@literal null}.
* @return
*/
@Data
class Specified implements Identifier {
static Identifier of(Optional<? extends Object> identifier) {
@NonNull private final Object value;
Assert.notNull(identifier, "Identifier must not be null!");
@Override
public Optional<Object> getOptionalValue() {
return Optional.of(value);
return identifier.map(it -> (Identifier) Identifier.of(it)).orElse(Unset.UNSET);
}
/**
* Returns the identifier value.
*
* @return will never be {@literal null}.
*/
Optional<? extends Object> getOptionalValue();
/**
* A specified identifier that exposes a definitely present identifier value.
*
* @author Oliver Gierke
*/
interface Specified extends Identifier {
/**
* Returns the identifier value.
*
* @return will never be {@literal null}.
*/
default Object getValue() {
return getOptionalValue().orElseThrow(() -> new IllegalStateException("Should not happen!"));
}
}
}

View File

@@ -15,40 +15,26 @@
*/
package org.springframework.data.jdbc.mapping.event;
import lombok.Getter;
import java.util.Optional;
import org.springframework.context.ApplicationEvent;
/**
* The common superclass for all events published by JDBC repositories. {@link #getSource} contains the
* {@link Identifier} of the entity triggering the event.
*
* @author Jens Schauder
* @since 2.0
* @author Oliver Gierke
*/
@Getter
public class JdbcEvent extends ApplicationEvent {
/**
* The optional entity for which this event was published. Might be empty in cases of delete events where only the
* identifier was passed to the delete method.
*/
private final Optional<Object> optionalEntity;
JdbcEvent(Identifier id, Optional<Object> optionalEntity) {
super(id);
this.optionalEntity = optionalEntity;
}
public interface JdbcEvent {
/**
* The identifier of the entity, triggering this event. Also available via {@link #getSource()}.
*
* @return the source of the event as an {@link Identifier}.
*/
public Identifier getId() {
return (Identifier) getSource();
}
Identifier getId();
/**
* Returns the entity the event was triggered for.
*
* @return will never be {@literal null}.
*/
Optional<Object> getOptionalEntity();
}

View File

@@ -18,12 +18,14 @@ package org.springframework.data.jdbc.mapping.event;
import java.util.Optional;
/**
* A {@link JdbcEvent} which is guaranteed to have an entity.
* A {@link SimpleJdbcEvent} which is guaranteed to have an entity.
*
* @author Jens Schauder
* @since 2.0
*/
public class JdbcEventWithEntity extends JdbcEvent implements WithEntity {
public class JdbcEventWithEntity extends SimpleJdbcEvent implements WithEntity {
private static final long serialVersionUID = 4891455396602090638L;
public JdbcEventWithEntity(Identifier id, Object entity) {
super(id, Optional.of(entity));

View File

@@ -20,14 +20,30 @@ import java.util.Optional;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
* A {@link JdbcEvent} guaranteed to have an identifier.
* A {@link SimpleJdbcEvent} guaranteed to have an identifier.
*
* @author Jens Schauder
* @since 2.0
*/
public class JdbcEventWithId extends JdbcEvent implements WithId {
public class JdbcEventWithId extends SimpleJdbcEvent implements WithId {
private static final long serialVersionUID = -8071323168471611098L;
private final Specified id;
public JdbcEventWithId(Specified id, Optional<Object> entity) {
super(id, entity);
this.id = id;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.mapping.event.JdbcEvent#getId()
*/
@Override
public Specified getId() {
return id;
}
}

View File

@@ -22,13 +22,15 @@ import java.util.Optional;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
* A {@link JdbcEvent} which is guaranteed to have an identifier and an entity.
* A {@link SimpleJdbcEvent} which is guaranteed to have an identifier and an entity.
*
* @author Jens Schauder
* @since 2.0
*/
@Getter
public class JdbcEventWithIdAndEntity extends JdbcEvent implements WithId, WithEntity {
public class JdbcEventWithIdAndEntity extends JdbcEventWithId implements WithEntity {
private static final long serialVersionUID = -3194462549552515519L;
public JdbcEventWithIdAndEntity(Specified id, Object entity) {
super(id, Optional.of(entity));

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.mapping.event;
import java.util.Optional;
import org.springframework.context.ApplicationEvent;
/**
* The common superclass for all events published by JDBC repositories. {@link #getSource} contains the
* {@link Identifier} of the entity triggering the event.
*
* @author Jens Schauder
* @author Oliver Gierke
* @since 2.0
*/
class SimpleJdbcEvent extends ApplicationEvent implements JdbcEvent {
private static final long serialVersionUID = -1798807778668751659L;
private final Object entity;
SimpleJdbcEvent(Identifier id, Optional<Object> entity) {
super(id);
this.entity = entity.orElse(null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.mapping.event.JdbcEvent#getId()
*/
@Override
public Identifier getId() {
return (Identifier) getSource();
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.mapping.event.JdbcEvent#getOptionalEntity()
*/
@Override
public Optional<Object> getOptionalEntity() {
return Optional.ofNullable(entity);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.mapping.event;
import lombok.Value;
import java.util.Optional;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
* Simple value object for {@link Specified}.
*
* @author Jens Schauder
* @author Oliver Gierke
* @since 2.0
*/
@Value(staticConstructor = "of")
class SpecifiedIdentifier implements Specified {
Object value;
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.mapping.event.Identifier#getOptionalValue()
*/
@Override
public Optional<? extends Object> getOptionalValue() {
return Optional.of(value);
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.mapping.event;
import java.util.Optional;
/**
* An unset identifier. Always returns {@link Optional#empty()} as value.
*
* @author Jens Schaude
* @author Oliver Gierke
* @since 2.0
*/
enum Unset implements Identifier {
UNSET;
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.mapping.event.Identifier#getOptionalValue()
*/
@Override
public Optional<Object> getOptionalValue() {
return Optional.empty();
}
}

View File

@@ -16,16 +16,18 @@
package org.springframework.data.jdbc.mapping.event;
/**
* Interface for {@link JdbcEvent}s which are guaranteed to have an entity. Allows direct access to that entity, without
* going through an {@link java.util.Optional}
* Interface for {@link SimpleJdbcEvent}s which are guaranteed to have an entity. Allows direct access to that entity,
* without going through an {@link java.util.Optional}
*
* @author Jens Schauder
* @since 2.0
*/
public interface WithEntity {
public interface WithEntity extends JdbcEvent {
/**
* @return will never be {@literal null}.
*/
default Object getEntity() {
return ((JdbcEvent) this).getOptionalEntity()
.orElseThrow(() -> new IllegalStateException("Entity must not be NULL"));
return getOptionalEntity().orElseThrow(() -> new IllegalStateException("Entity must not be NULL"));
}
}

View File

@@ -18,15 +18,16 @@ package org.springframework.data.jdbc.mapping.event;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
* Interface for {@link JdbcEvent}s which are guaranteed to have a {@link Specified} identifier. Offers direct access to
* the {@link Specified} identifier.
* Interface for {@link SimpleJdbcEvent}s which are guaranteed to have a {@link Specified} identifier. Offers direct
* access to the {@link Specified} identifier.
*
* @author Jens Schauder
* @since 2.0
*/
public interface WithId {
public interface WithId extends JdbcEvent {
default Specified getSpecifiedId() {
return (Specified) ((JdbcEvent) this).getId();
}
/**
* Events with an identifier will always return a {@link Specified} one.
*/
Specified getId();
}

View File

@@ -35,21 +35,26 @@ public class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProper
*
* @param property must not be {@literal null}.
* @param owner must not be {@literal null}.
* @param simpleTypeHolder
* @param simpleTypeHolder must not be {@literal null}.
*/
public BasicJdbcPersistentProperty( //
Property property, //
PersistentEntity<?, JdbcPersistentProperty> owner, //
SimpleTypeHolder simpleTypeHolder //
) {
public BasicJdbcPersistentProperty(Property property, PersistentEntity<?, JdbcPersistentProperty> owner,
SimpleTypeHolder simpleTypeHolder) {
super(property, owner, simpleTypeHolder);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.AbstractPersistentProperty#createAssociation()
*/
@Override
protected Association<JdbcPersistentProperty> createAssociation() {
return null;
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty#getColumnName()
*/
public String getColumnName() {
return getName();
}

View File

@@ -15,31 +15,26 @@
*/
package org.springframework.data.jdbc.mapping.model;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.util.TypeInformation;
import org.springframework.data.mapping.PersistentEntity;
/**
* Meta data a repository might need for implementing persistence operations for instances of type {@code T}
*
* @author Jens Schauder
* @author Oliver Gierke
* @since 2.0
*/
public class JdbcPersistentEntity<T> extends BasicPersistentEntity<T, JdbcPersistentProperty> {
public interface JdbcPersistentEntity<T> extends PersistentEntity<T, JdbcPersistentProperty> {
private final String tableName;
/**
* Returns the name of the table backing the given entity.
*
* @return
*/
String getTableName();
public JdbcPersistentEntity(TypeInformation<T> information) {
super(information);
tableName = getType().getSimpleName();
}
public String getTableName() {
return tableName;
}
public String getIdColumn() {
return getRequiredIdProperty().getName();
}
/**
* Returns the column representing the identifier.
*
* @return will never be {@literal null}.
*/
String getIdColumn();
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.mapping.model;
import lombok.Getter;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.util.TypeInformation;
/**
* Meta data a repository might need for implementing persistence operations for instances of type {@code T}
*
* @author Jens Schauder
* @since 2.0
*/
public class JdbcPersistentEntityImpl<T> extends BasicPersistentEntity<T, JdbcPersistentProperty>
implements JdbcPersistentEntity<T> {
private final @Getter String tableName;
/**
* Creates a new {@link JdbcPersistentEntityImpl} for the given {@link TypeInformation}.
*
* @param information must not be {@literal null}.
*/
public JdbcPersistentEntityImpl(TypeInformation<T> information) {
super(information);
tableName = getType().getSimpleName();
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity#getIdColumn()
*/
@Override
public String getIdColumn() {
return getRequiredIdProperty().getName();
}
}

View File

@@ -18,10 +18,18 @@ package org.springframework.data.jdbc.mapping.model;
import org.springframework.data.mapping.PersistentProperty;
/**
* A {@link PersistentProperty} for JDBC.
*
* @author Jens Schauder
* @author Oliver Gierke
* @since 2.0
*/
public interface JdbcPersistentProperty extends PersistentProperty<JdbcPersistentProperty> {
/**
* Returns the name of the column backing that property.
*
* @return
*/
String getColumnName();
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.jdbc.repository;
import lombok.RequiredArgsConstructor;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Optional;
@@ -26,8 +28,10 @@ import org.springframework.data.convert.EntityInstantiator;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.jdbc.core.RowMapper;
@@ -36,65 +40,68 @@ import org.springframework.jdbc.core.RowMapper;
* Maps a ResultSet to an entity of type {@code T}
*
* @author Jens Schauder
* @author Oliver Gierke
* @since 2.0
*/
@RequiredArgsConstructor
class EntityRowMapper<T> implements RowMapper<T> {
private final JdbcPersistentEntity<T> entity;
private final EntityInstantiator instantiator = new ClassGeneratingEntityInstantiator();
private final ConversionService conversions = new DefaultConversionService();
EntityRowMapper(JdbcPersistentEntity<T> entity) {
this.entity = entity;
}
/*
* (non-Javadoc)
* @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
*/
@Override
public T mapRow(ResultSet rs, int rowNum) throws SQLException {
public T mapRow(ResultSet resultSet, int rowNumber) throws SQLException {
T t = createInstance(rs);
T result = createInstance(resultSet);
entity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) property -> {
setProperty(rs, t, property);
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(result);
ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor(accessor, conversions);
propertyAccessor.setProperty(property, readFrom(resultSet, property));
});
return t;
return result;
}
private T createInstance(ResultSet rs) {
return instantiator.createInstance(entity, new ResultSetParameterValueProvider(rs));
return instantiator.createInstance(entity, ResultSetParameterValueProvider.of(rs, conversions));
}
private void setProperty(ResultSet rs, T t, PersistentProperty property) {
private static Optional<Object> readFrom(ResultSet resultSet, PersistentProperty<?> property) {
try {
Object converted = conversions.convert(rs.getObject(property.getName()), property.getType());
entity.getPropertyAccessor(t).setProperty(property, Optional.of(converted));
} catch (Exception e) {
throw new RuntimeException(String.format("Couldn't set property %s.", property.getName()), e);
return Optional.ofNullable(resultSet.getObject(property.getName()));
} catch (SQLException o_O) {
throw new MappingException(String.format("Could not read property %s from result set!", property), o_O);
}
}
private class ResultSetParameterValueProvider implements ParameterValueProvider<JdbcPersistentProperty> {
@RequiredArgsConstructor(staticName = "of")
private static class ResultSetParameterValueProvider implements ParameterValueProvider<JdbcPersistentProperty> {
private final ResultSet rs;
ResultSetParameterValueProvider(ResultSet rs) {
this.rs = rs;
}
private final ResultSet resultSet;
private final ConversionService conversionService;
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.ParameterValueProvider#getParameterValue(org.springframework.data.mapping.PreferredConstructor.Parameter)
*/
@Override
public <S> Optional<S> getParameterValue(Parameter<S, JdbcPersistentProperty> parameter) {
return parameter.getName().map(name -> {
try {
return conversions.convert(rs.getObject(name), parameter.getType().getType());
} catch (SQLException e) {
throw new MappingException( //
String.format("Couldn't read column %s from ResultSet.", name) //
);
try {
return conversionService.convert(resultSet.getObject(name), parameter.getType().getType());
} catch (SQLException o_O) {
throw new MappingException(String.format("Couldn't read column %s from ResultSet.", name), o_O);
}
});
}

View File

@@ -15,15 +15,15 @@
*/
package org.springframework.data.jdbc.repository;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.jdbc.mapping.event.AfterCreation;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
import org.springframework.data.jdbc.mapping.event.Identifier;
import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation;
import org.springframework.jdbc.core.RowMapper;
@@ -35,18 +35,22 @@ import org.springframework.jdbc.core.RowMapper;
* @since 2.0
*/
@RequiredArgsConstructor
public class EventPublishingEntityRowMapper<T, ID extends Serializable> implements RowMapper<T> {
public class EventPublishingEntityRowMapper<T> implements RowMapper<T> {
private final RowMapper<T> delegate;
private final JdbcPersistentEntityInformation<T, ID> entityInformation;
private final ApplicationEventPublisher publisher;
private final @NonNull RowMapper<T> delegate;
private final @NonNull JdbcPersistentEntityInformation<T, ?> entityInformation;
private final @NonNull ApplicationEventPublisher publisher;
/*
* (non-Javadoc)
* @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
*/
@Override
public T mapRow(ResultSet resultSet, int i) throws SQLException {
T instance = delegate.mapRow(resultSet, i);
publisher.publishEvent(new AfterCreation(new Specified(entityInformation.getId(instance)), instance));
publisher.publishEvent(new AfterCreation(Identifier.of(entityInformation.getRequiredId(instance)), instance));
return instance;
}

View File

@@ -22,7 +22,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.convert.ConversionService;
@@ -35,13 +34,16 @@ import org.springframework.data.jdbc.mapping.event.AfterUpdate;
import org.springframework.data.jdbc.mapping.event.BeforeDelete;
import org.springframework.data.jdbc.mapping.event.BeforeInsert;
import org.springframework.data.jdbc.mapping.event.BeforeUpdate;
import org.springframework.data.jdbc.mapping.event.Identifier;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityImpl;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
import org.springframework.data.jdbc.repository.support.BasicJdbcPersistentEntityInformation;
import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.util.Streamable;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.support.GeneratedKeyHolder;
@@ -66,11 +68,15 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
private final ApplicationEventPublisher publisher;
private final ConversionService conversions = new DefaultConversionService();
public SimpleJdbcRepository( //
JdbcPersistentEntity<T> persistentEntity, //
NamedParameterJdbcOperations jdbcOperations, //
ApplicationEventPublisher publisher //
) {
/**
* Creates a new {@link SimpleJdbcRepository} for the given {@link JdbcPersistentEntityImpl}
*
* @param persistentEntity
* @param jdbcOperations
* @param publisher
*/
public SimpleJdbcRepository(JdbcPersistentEntity<T> persistentEntity, NamedParameterJdbcOperations jdbcOperations,
ApplicationEventPublisher publisher) {
Assert.notNull(persistentEntity, "PersistentEntity must not be null.");
Assert.notNull(jdbcOperations, "JdbcOperations must not be null.");
@@ -85,6 +91,10 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
this.sql = new SqlGenerator(persistentEntity);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#save(S)
*/
@Override
public <S extends T> S save(S instance) {
@@ -97,6 +107,10 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
return instance;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable)
*/
@Override
public <S extends T> Iterable<S> save(Iterable<S> entities) {
@@ -105,6 +119,10 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
return savedEntities;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findOne(java.io.Serializable)
*/
@Override
public Optional<T> findOne(ID id) {
@@ -112,43 +130,69 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
.ofNullable(operations.queryForObject(sql.getFindOne(), new MapSqlParameterSource("id", id), entityRowMapper));
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#exists(java.io.Serializable)
*/
@Override
public boolean exists(ID id) {
return operations.queryForObject(sql.getExists(), new MapSqlParameterSource("id", id), Boolean.class);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findAll()
*/
@Override
public Iterable<T> findAll() {
return operations.query(sql.getFindAll(), entityRowMapper);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable)
*/
@Override
public Iterable<T> findAll(Iterable<ID> ids) {
return operations.query(sql.getFindAllInList(), new MapSqlParameterSource("ids", ids), entityRowMapper);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#count()
*/
@Override
public long count() {
return operations.getJdbcOperations().queryForObject(sql.getCount(), Long.class);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable)
*/
@Override
public void delete(ID id) {
doDelete(new Specified(id), Optional.empty());
doDelete(Identifier.of(id), Optional.empty());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.lang.Object)
*/
@Override
public void delete(T instance) {
doDelete(new Specified(entityInformation.getId(instance).orElse(null)), Optional.of(instance));
doDelete(Identifier.of(entityInformation.getRequiredId(instance)), Optional.of(instance));
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable)
*/
@Override
public void delete(Iterable<? extends T> entities) {
List<ID> idList = StreamSupport.stream(entities.spliterator(), false) //
.map(e -> entityInformation.getId(e)
.orElseThrow(() -> new IllegalArgumentException(String.format("Can't obtain id for %s", e)))) //
List<ID> idList = Streamable.of(entities).stream() //
.map(e -> entityInformation.getRequiredId(e)) //
.collect(Collectors.toList());
MapSqlParameterSource sqlParameterSource = new MapSqlParameterSource("ids", idList);
@@ -189,7 +233,7 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
throw new IllegalStateException(String.format(ENTITY_NEW_AFTER_INSERT, persistentEntity));
}
publisher.publishEvent(new AfterInsert(new Specified(entityInformation.getId(instance)), instance));
publisher.publishEvent(new AfterInsert(Identifier.of(entityInformation.getRequiredId(instance)), instance));
}
private <S extends T> ID getIdValueOrNull(S instance) {
@@ -211,27 +255,26 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
try {
Object idValueFromJdbc = getIdFromHolder(holder);
if (idValueFromJdbc != null) {
getIdFromHolder(holder).ifPresent(it -> {
Class<?> targetType = persistentEntity.getRequiredIdProperty().getType();
Object converted = convert(idValueFromJdbc, targetType);
Object converted = convert(it, targetType);
entityInformation.setId(instance, Optional.of(converted));
}
});
} catch (NonTransientDataAccessException e) {
throw new UnableToSetIdException("Unable to set id of " + instance, e);
throw new UnableToSetId("Unable to set id of " + instance, e);
}
}
private Object getIdFromHolder(KeyHolder holder) {
private Optional<Object> getIdFromHolder(KeyHolder holder) {
try {
// mysql just returns one value with a special name
return holder.getKey();
// MySQL just returns one value with a special name
return Optional.ofNullable(holder.getKey());
} catch (InvalidDataAccessApiUsageException e) {
// postgress returns a value for each column
return holder.getKeys().get(persistentEntity.getIdColumn());
// Postgres returns a value for each column
return Optional.ofNullable(holder.getKeys().get(persistentEntity.getIdColumn()));
}
}
@@ -249,7 +292,7 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
private <S extends T> void doUpdate(S instance) {
Specified specifiedId = new Specified(entityInformation.getId(instance));
Specified specifiedId = Identifier.of(entityInformation.getRequiredId(instance));
publisher.publishEvent(new BeforeUpdate(specifiedId, instance));
operations.update(sql.getUpdate(), getPropertyMap(instance));
publisher.publishEvent(new AfterUpdate(specifiedId, instance));

View File

@@ -51,6 +51,7 @@ class SqlGenerator {
private final List<String> nonIdPropertyNames = new ArrayList<>();
SqlGenerator(JdbcPersistentEntity<?> entity) {
this.entity = entity;
initPropertyNames();
@@ -72,6 +73,7 @@ class SqlGenerator {
}
private <T> void initPropertyNames() {
entity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) p -> {
propertyNames.add(p.getName());
if (!entity.isIdProperty(p)) {
@@ -143,8 +145,7 @@ class SqlGenerator {
private String createInsertSql() {
String insertTemplate = "insert into %s (%s) values (%s)";
String tableColumns = nonIdPropertyNames.stream().collect(Collectors.joining(", "));
String tableColumns = String.join(", ", nonIdPropertyNames);
String parameterNames = nonIdPropertyNames.stream().collect(Collectors.joining(", :", ":", ""));
return String.format(insertTemplate, entity.getTableName(), tableColumns, parameterNames);
@@ -154,7 +155,8 @@ class SqlGenerator {
String updateTemplate = "update %s set %s where %s = :%s";
String setClause = propertyNames.stream().map(n -> String.format("%s = :%s", n, n))
String setClause = propertyNames.stream()//
.map(n -> String.format("%s = :%s", n, n))//
.collect(Collectors.joining(", "));
return String.format(updateTemplate, entity.getTableName(), setClause, entity.getIdColumn(), entity.getIdColumn());

View File

@@ -23,9 +23,11 @@ import org.springframework.dao.NonTransientDataAccessException;
* @author Jens Schauder
* @since 2.0
*/
public class UnableToSetIdException extends NonTransientDataAccessException {
public class UnableToSetId extends NonTransientDataAccessException {
public UnableToSetIdException(String message, Throwable cause) {
private static final long serialVersionUID = 3285001352389420376L;
public UnableToSetId(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -31,11 +31,16 @@ public class BasicJdbcPersistentEntityInformation<T, ID extends Serializable> ex
private final JdbcPersistentEntity<T> persistentEntity;
public BasicJdbcPersistentEntityInformation(JdbcPersistentEntity<T> persistentEntity) {
super(persistentEntity);
this.persistentEntity = persistentEntity;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation#setId(java.lang.Object, java.util.Optional)
*/
@Override
public void setId(T instance, Optional<Object> value) {
persistentEntity.getPropertyAccessor(instance).setProperty(persistentEntity.getRequiredIdProperty(), value);

View File

@@ -27,4 +27,16 @@ import org.springframework.data.repository.core.EntityInformation;
public interface JdbcPersistentEntityInformation<T, ID extends Serializable> extends EntityInformation<T, ID> {
void setId(T instance, Optional<Object> value);
/**
* Returns the identifier of the given entity or throws and exception if it can't be obtained.
*
* @param entity must not be {@literal null}.
* @return the identifier of the given entity
* @throws IllegalArgumentException in case no identifier can be obtained for the given entity.
*/
default ID getRequiredId(T entity) {
return getId(entity).orElseThrow(() -> new IllegalArgumentException(
String.format("Could not obtain required identifier from entity %s!", entity)));
}
}