DATAJDBC-99 - Polishing.

Events now use the id as source. The id is encapsulated in a value object to support null ids in BeforeInsert events. Entity references in events are now Optionals. Dropped redundant Event suffix from events. Added "@since 2.0" to classes. Replaced constructors and getters with Lombok annotations. Simplified pom.xml. Extracted interface JdbcPersistentEntity and JdbcPersistentProperty. Integration tests use the Spring Test framework. Fixed test for entities with primitive id type.

Original pull request: #5.
This commit is contained in:
Jens Schauder
2017-03-10 13:39:20 +01:00
committed by Oliver Gierke
parent 25fa2d423e
commit 94d8960558
39 changed files with 924 additions and 584 deletions

View File

@@ -17,6 +17,8 @@ package org.springframework.data.jdbc.mapping.context;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import org.springframework.data.jdbc.mapping.model.BasicJdbcPersistentProperty;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
import org.springframework.data.mapping.context.AbstractMappingContext;
@@ -25,17 +27,22 @@ import org.springframework.data.util.TypeInformation;
/**
* @author Jens Schauder
* @since 2.0
*/
public class JdbcMappingContext extends AbstractMappingContext<JdbcPersistentEntity<?>, JdbcPersistentProperty> {
@Override
protected <T> JdbcPersistentEntity createPersistentEntity(TypeInformation<T> typeInformation) {
return new JdbcPersistentEntity(typeInformation);
}
@Override
protected JdbcPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, JdbcPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) {
return new JdbcPersistentProperty(field, descriptor, owner, simpleTypeHolder);
protected JdbcPersistentProperty createPersistentProperty( //
Field field, //
PropertyDescriptor descriptor, //
JdbcPersistentEntity owner, //
SimpleTypeHolder simpleTypeHolder //
) {
return new BasicJdbcPersistentProperty(field, descriptor, owner, simpleTypeHolder);
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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 org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
* Gets published after instantiation and setting of all the properties of an entity. This allows to do some postprocessing of entities.
*
* @author Jens Schauder
* @since 2.0
*/
public class AfterCreation extends JdbcEventWithIdAndEntity {
/**
* @param id of the entity
* @param entity the newly instantiated entity.
*/
public AfterCreation(Specified id, Object entity) {
super(id, entity);
}
}

View File

@@ -1,36 +0,0 @@
/*
* 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.function.Function;
/**
* gets published after instantiation and setting of all the properties of an entity. This allows to do some
* postprocessing of entities.
*
* @author Jens Schauder
*/
public class AfterCreationEvent extends JdbcEvent{
/**
* @param instance the newly instantiated entity.
* @param idProvider a function providing the id, for the instance.
* @param <T> type of the entity and the argument of the {@code idProvider}
*/
public <T> AfterCreationEvent(T instance, Function<T, Object> idProvider) {
super(instance, idProvider);
}
}

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;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
* Gets published after deletion of an entity. It will have a {@link Specified} identifier.
*
* If the entity is empty or not depends on the delete method used.
*
* @author Jens Schauder
* @since 2.0
*/
public class AfterDelete extends JdbcEventWithId{
/**
* @param id of the entity.
* @param instance the deleted entity if it is available.
*/
public AfterDelete(Specified id, Optional<Object> instance) {
super(id, instance);
}
}

View File

@@ -1,36 +0,0 @@
/*
* 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.function.Function;
/**
* get published after deletion of an entity. The source might contain the Id or the actual entity, depending on the
* {@code delete(...)} method used.
*
* @author Jens Schauder
*/
public class AfterDeleteEvent extends JdbcEvent{
/**
* @param instance the deleted entity.
* @param idProvider a function providing the id, for the instance.
* @param <T> type of the entity and the argument of the {@code idProvider}
*/
public <T> AfterDeleteEvent(T instance, Function<T, Object> idProvider) {
super(instance, idProvider);
}
}

View File

@@ -15,21 +15,21 @@
*/
package org.springframework.data.jdbc.mapping.event;
import java.util.function.Function;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
* gets published after an entity got inserted into the database.
* Gets published after an entity got inserted into the database.
*
* @author Jens Schauder
* @since 2.0
*/
public class AfterInsertEvent extends AfterSaveEvent {
public class AfterInsert extends AfterSave {
/**
* @param id identifier of the entity triggering the event.
* @param instance the newly inserted entity.
* @param idProvider a function providing the id, for the instance.
* @param <T> type of the entity and the argument of the {@code idProvider}
*/
public <T> AfterInsertEvent(T instance, Function<T, Object> idProvider) {
super(instance, idProvider);
public AfterInsert(Specified id, Object instance) {
super(id, instance);
}
}

View File

@@ -15,20 +15,23 @@
*/
package org.springframework.data.jdbc.mapping.event;
import java.util.function.Function;
import java.util.Optional;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
* subclasses of this get published after a new instance or a changed instance was saved in the database
* Subclasses of this get published after a new instance or a changed instance was saved in the database.
*
* @author Jens Schauder
* @since 2.0
*/
public class AfterSaveEvent extends JdbcEvent{
public class AfterSave extends JdbcEventWithIdAndEntity {
/**
* @param id identifier of
* @param instance the newly saved entity.
* @param idProvider a function providing the id, for the instance.
* @param <T> type of the entity and the argument of the {@code idProvider}
*/
<T> AfterSaveEvent(T instance, Function<T, Object> idProvider) {
super(instance, idProvider);
AfterSave(Specified id, Object instance) {
super(id, instance);
}
}

View File

@@ -15,21 +15,21 @@
*/
package org.springframework.data.jdbc.mapping.event;
import java.util.function.Function;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
* gets published after an entity was updated in the database.
* Gets published after an entity was updated in the database.
*
* @author Jens Schauder
* @since 2.0
*/
public class AfterUpdateEvent extends AfterSaveEvent {
public class AfterUpdate extends AfterSave {
/**
* @param id of the entity
* @param instance the updated entity.
* @param idProvider a function providing the id, for the instance.
* @param <T> type of the entity and the argument of the {@code idProvider}
*/
public <T> AfterUpdateEvent(T instance, Function<T, Object> idProvider) {
super(instance, idProvider);
public AfterUpdate(Specified id, Object instance) {
super(id, instance);
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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.data.jdbc.mapping.event.Identifier.Specified;
/**
* Gets published when an entity is about to get deleted.
*
* @author Jens Schauder
* @since 2.0
*/
public class BeforeDelete extends JdbcEventWithId {
/**
* @param id the id of the entity
* @param entity the entity about to get deleted. Might be empty.
*/
public <T> BeforeDelete(Specified id, Optional<Object> entity) {
super(id, entity);
}
}

View File

@@ -1,38 +0,0 @@
/*
* 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.function.Function;
import org.springframework.context.ApplicationEvent;
/**
* gets published when an entity is about to get deleted. {@link ApplicationEvent#getSource()} might contain either the
* entity or the id of the entity, depending on which delete method was used.
*
* @author Jens Schauder
*/
public class BeforeDeleteEvent extends JdbcEvent {
/**
* @param instance the entity about to get deleted. Might be {@literal NULL}
* @param idProvider a function providing the id, for the instance. Must provide a not {@literal NULL} id, when called with {@link #instance}
* @param <T> type of the entity and the argument of the {@code idProvider}
*/
public <T> BeforeDeleteEvent(T instance, Function<T, Object> idProvider) {
super(instance, idProvider);
}
}

View File

@@ -15,23 +15,23 @@
*/
package org.springframework.data.jdbc.mapping.event;
import java.util.function.Function;
import org.springframework.data.jdbc.mapping.event.Identifier.Unset;
/**
* gets published before an entity gets inserted into the database.
* 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.
*
* When the id-property of the entity must get set manually, an event listener for this event may do so.
* The {@link Identifier} is {@link org.springframework.data.jdbc.mapping.event.Identifier.Unset#UNSET}
*
* @author Jens Schauder
* @since 2.0
*/
public class BeforeInsertEvent extends BeforeSaveEvent {
public class BeforeInsert extends BeforeSave {
/**
* @param instance the entity about to get inserted.
* @param idProvider a function providing the id, for the instance.
* @param <T> type of the entity and the argument of the {@code idProvider}
*/
public <T> BeforeInsertEvent(T instance, Function<T, Object> idProvider) {
super(instance, idProvider);
public BeforeInsert(Object instance) {
super(Unset.UNSET, instance);
}
}

View File

@@ -15,21 +15,21 @@
*/
package org.springframework.data.jdbc.mapping.event;
import java.util.function.Function;
import java.util.Optional;
/**
* gets published before an entity gets updated in the database.
* Subclasses of this get published before an entity gets saved to the database.
*
* @author Jens Schauder
* @since 2.0
*/
public class BeforeUpdateEvent extends BeforeSaveEvent {
public class BeforeSave extends JdbcEventWithEntity {
/**
* @param id of the entity to be saved.
* @param instance the entity about to get saved.
* @param idProvider a function providing the id, for the instance.
* @param <T> type of the entity and the argument of the {@code idProvider}
*/
public <T> BeforeUpdateEvent(T instance, Function<T, Object> idProvider) {
super(instance, idProvider);
BeforeSave(Identifier id, Object instance) {
super(id, instance);
}
}

View File

@@ -1,37 +0,0 @@
/*
* 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.function.Function;
import org.springframework.context.ApplicationEvent;
/**
* subclasses of this get published before an entity gets saved to the database.
*
* @author Jens Schauder
*/
public class BeforeSaveEvent extends JdbcEvent {
/**
* @param instance the entity about to get saved.
* @param idProvider a function providing the id, for the instance.
* @param <T> type of the entity and the argument of the {@code idProvider}
*/
<T> BeforeSaveEvent(T instance, Function<T, Object> idProvider) {
super(instance, idProvider);
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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 org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/**
* Gets published before an entity gets updated in the database.
*
* @author Jens Schauder
* @since 2.0
*/
public class BeforeUpdate extends BeforeSave implements WithId {
/**
* @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);
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.Data;
import lombok.NonNull;
import java.util.Optional;
/**
* Wrapper for an identifier of an entity. Might either be a {@link Specified} or {@link Unset#UNSET}
*
* @author Jens Schauder
* @since 2.0
*/
public interface Identifier {
Optional<Object> getOptionalValue();
static Identifier fromNullable(Object value) {
return (value != null) ? new Specified(value) : Unset.UNSET;
}
/**
* An unset identifier. Always returns {@link Optional#empty()} as value.
*/
enum Unset implements Identifier {
UNSET {
@Override
public Optional<Object> getOptionalValue() {
return Optional.empty();
}
}
}
/**
* An {@link Identifier} guaranteed to have a non empty value.
*
* Since it is guaranteed to exist the value can get access directly.
*/
@Data
class Specified implements Identifier {
@NonNull
private final Object value;
@Override
public Optional<Object> getOptionalValue() {
return Optional.of(value);
}
}
}

View File

@@ -15,48 +15,43 @@
*/
package org.springframework.data.jdbc.mapping.event;
import java.util.function.Function;
import java.util.Optional;
import org.springframework.context.ApplicationEvent;
import lombok.Getter;
/**
* is the common superclass for all events published by JDBC repositories.
*
* It is recommendet not to use the {@link #getSource()} since it may contain the entity if it was available, when the
* event was published, or in case of delete events only the Id.
*
* Use the dedicated methods {@link #getId()} or {@link #getInstance()} instead. Note that the later might be
* {@literal NULL} in the cases mentioned above.
* 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
*/
@Getter
public class JdbcEvent extends ApplicationEvent {
private final Object id;
private final Object instance;
<T> JdbcEvent(T instance, Function<T, Object> idProvider) {
super(instance == null ? idProvider.apply(instance) : instance);
this.instance = instance;
this.id = idProvider.apply(instance);
}
/**
* the entity for which this event was publish. Might be {@literal NULL} in cases of delete events where only the id
* The optional entity for which this event was published. Might be empty in cases of delete events where only the identifier
* was provided to the delete method.
*
* @return instance of the entity triggering this event.
* @return The entity triggering this event or empty.
*/
public Object getInstance() {
return instance;
private final Optional<Object> optionalEntity;
public JdbcEvent(Identifier id, Optional<Object> optionalEntity) {
super(id);
this.optionalEntity = optionalEntity;
}
/**
* the id of the entity, triggering this event. Guaranteed not to be {@literal NULL}.
* The identifier of the entity, triggering this event. Also available via
* {@link #getSource()}.
*
* @return
*/
public Object getId() {
return id;
public Identifier getId() {
return (Identifier) getSource();
}
}

View File

@@ -0,0 +1,31 @@
/*
* 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;
/**
* A {@link JdbcEvent} which is guaranteed to have an entity.
*
* @author Jens Schauder
* @since 2.0
*/
public class JdbcEventWithEntity extends JdbcEvent implements WithEntity {
public JdbcEventWithEntity(Identifier id, Object entity) {
super(id, Optional.of(entity));
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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.data.jdbc.mapping.event.Identifier.Specified;
/**
* A {@link JdbcEvent} guaranteed to have an identifier.
*
* @author Jens Schauder
* @since 2.0
*/
public class JdbcEventWithId extends JdbcEvent implements WithId{
public JdbcEventWithId(Specified id, Optional<Object> entity) {
super(id, entity);
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.Getter;
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.
*
* @author Jens Schauder
* @since 2.0
*/
@Getter
public class JdbcEventWithIdAndEntity extends JdbcEvent implements WithId, WithEntity {
public JdbcEventWithIdAndEntity(Specified id, Object entity) {
super(id, Optional.of(entity));
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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;
/**
* 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}
*
* @author Jens Schauder
* @since 2.0
*/
public interface WithEntity {
default Object getEntity() {
return ((JdbcEvent) this).getOptionalEntity()
.orElseThrow(() -> new IllegalStateException("Entity must not be NULL"));
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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 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.
*
* @author Jens Schauder
* @since 2.0
*/
public interface WithId {
default Specified getSpecifiedId(){
return (Specified) ((JdbcEvent)this).getId();
}
}

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.model;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
import org.springframework.data.mapping.model.SimpleTypeHolder;
/**
* Meta data about a property to be used by repository implementations.
*
* @author Jens Schauder
* @since 2.0
*/
public class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProperty<JdbcPersistentProperty>
implements JdbcPersistentProperty {
/**
* Creates a new {@link AnnotationBasedPersistentProperty}.
*
* @param field must not be {@literal null}.
* @param propertyDescriptor can be {@literal null}.
* @param owner must not be {@literal null}.
* @param simpleTypeHolder
*/
public BasicJdbcPersistentProperty( //
Field field, //
PropertyDescriptor propertyDescriptor, //
PersistentEntity<?, JdbcPersistentProperty> owner, //
SimpleTypeHolder simpleTypeHolder //
) {
super(field, propertyDescriptor, owner, simpleTypeHolder);
}
@Override
protected Association<JdbcPersistentProperty> createAssociation() {
return null;
}
public String getColumnName() {
return getName();
}
}

View File

@@ -20,38 +20,26 @@ 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 JdbcPersistentEntity<T> extends BasicPersistentEntity<T, JdbcPersistentProperty> {
private String tableName;
private String idColumn;
private final String tableName;
public JdbcPersistentEntity(TypeInformation<T> information) {
super(information);
tableName = getType().getSimpleName();
}
public String getTableName() {
if (tableName == null)
tableName = getType().getSimpleName();
return tableName;
}
public String getIdColumn() {
if (idColumn == null)
idColumn = getIdProperty().getName();
return idColumn;
}
public Object getIdValue(T instance) {
return getPropertyAccessor(instance).getProperty(getIdProperty());
}
public void setId(T instance, Object value) {
getPropertyAccessor(instance).setProperty(getIdProperty(),value);
return getIdProperty().getName();
}
}

View File

@@ -15,38 +15,13 @@
*/
package org.springframework.data.jdbc.mapping.model;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.mapping.PersistentProperty;
/**
* meta data about a property to be used by repository implementations.
*
* @author Jens Schauder
* @since 2.0
*/
public class JdbcPersistentProperty extends AnnotationBasedPersistentProperty<JdbcPersistentProperty> {
public interface JdbcPersistentProperty extends PersistentProperty<JdbcPersistentProperty> {
/**
* Creates a new {@link AnnotationBasedPersistentProperty}.
*
* @param field must not be {@literal null}.
* @param propertyDescriptor can be {@literal null}.
* @param owner must not be {@literal null}.
* @param simpleTypeHolder
*/
public JdbcPersistentProperty(Field field, PropertyDescriptor propertyDescriptor, PersistentEntity<?, JdbcPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
super(field, propertyDescriptor, owner, simpleTypeHolder);
}
@Override
protected Association<JdbcPersistentProperty> createAssociation() {
return null;
}
public String getColumnName() {
return getName();
}
String getColumnName();
}

View File

@@ -17,6 +17,7 @@ package org.springframework.data.jdbc.repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.data.convert.ClassGeneratingEntityInstantiator;
import org.springframework.data.convert.EntityInstantiator;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
@@ -32,6 +33,7 @@ import org.springframework.jdbc.core.RowMapper;
* maps a ResultSet to an entity of type {@code T}
*
* @author Jens Schauder
* @since 2.0
*/
class EntityRowMapper<T> implements RowMapper<T> {
@@ -48,7 +50,7 @@ class EntityRowMapper<T> implements RowMapper<T> {
T t = createInstance(rs);
entity.doWithProperties((PropertyHandler) property -> {
entity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) property -> {
setProperty(rs, t, property);
});
@@ -57,12 +59,15 @@ class EntityRowMapper<T> implements RowMapper<T> {
private T createInstance(ResultSet rs) {
return instantiator.createInstance(entity, new ParameterValueProvider<JdbcPersistentProperty>() {
@SuppressWarnings("unchecked")
@Override
public <T> T getParameterValue(PreferredConstructor.Parameter<T, JdbcPersistentProperty> parameter) {
try {
return (T) rs.getObject(parameter.getName());
} catch (SQLException e) {
throw new MappingException(String.format("Couldn't read column %s from ResultSet.", parameter.getName()));
throw new MappingException( //
String.format("Couldn't read column %s from ResultSet.", parameter.getName()) //
);
}
}
});
@@ -76,4 +81,4 @@ class EntityRowMapper<T> implements RowMapper<T> {
throw new RuntimeException(String.format("Couldn't set property %s.", property.getName()), e);
}
}
}
}

View File

@@ -15,44 +15,37 @@
*/
package org.springframework.data.jdbc.repository;
import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.jdbc.mapping.event.AfterCreationEvent;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
import org.springframework.data.jdbc.mapping.event.AfterCreation;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation;
import org.springframework.jdbc.core.RowMapper;
import lombok.RequiredArgsConstructor;
/**
* a RowMapper that publishes events after a delegate, did the actual work of mapping a {@link ResultSet} to an entity.
* a RowMapper that publishes events after a delegate, did the actual work of mapping a {@link ResultSet} to an entityInformation.
*
* @author Jens Schauder
* @since 2.0
*/
public class EventPublishingEntityRowMapper<T> implements RowMapper<T> {
@RequiredArgsConstructor
public class EventPublishingEntityRowMapper<T, ID extends Serializable> implements RowMapper<T> {
private final RowMapper<T> delegate;
private final JdbcPersistentEntity<T> entity;
private final JdbcPersistentEntityInformation<T, ID> entityInformation;
private final ApplicationEventPublisher publisher;
/**
*
* @param delegate does the actuall mapping.
* @param entity provides functionality to create ids from entities
* @param publisher used for event publishing after the mapping.
*/
EventPublishingEntityRowMapper(RowMapper<T> delegate,JdbcPersistentEntity<T> entity, ApplicationEventPublisher publisher) {
this.delegate = delegate;
this.entity = entity;
this.publisher = publisher;
}
@Override
public T mapRow(ResultSet resultSet, int i) throws SQLException {
T instance = delegate.mapRow(resultSet, i);
publisher.publishEvent(new AfterCreationEvent(instance, entity::getIdValue));
publisher.publishEvent(new AfterCreation(new Specified(entityInformation.getId(instance)), instance));
return instance;
}

View File

@@ -16,21 +16,26 @@
package org.springframework.data.jdbc.repository;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
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.context.ApplicationEventPublisherAware;
import org.springframework.data.jdbc.mapping.event.AfterDeleteEvent;
import org.springframework.data.jdbc.mapping.event.AfterInsertEvent;
import org.springframework.data.jdbc.mapping.event.AfterUpdateEvent;
import org.springframework.data.jdbc.mapping.event.BeforeDeleteEvent;
import org.springframework.data.jdbc.mapping.event.BeforeInsertEvent;
import org.springframework.data.jdbc.mapping.event.BeforeUpdateEvent;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.data.jdbc.mapping.event.AfterDelete;
import org.springframework.data.jdbc.mapping.event.AfterInsert;
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.Specified;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
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;
@@ -42,11 +47,15 @@ import org.springframework.util.Assert;
/**
* @author Jens Schauder
* @since 2.0
*/
public class SimpleJdbcRepository<T, ID extends Serializable>
implements CrudRepository<T, ID>, ApplicationEventPublisherAware {
public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRepository<T, ID> {
private final JdbcPersistentEntity<T> entity;
private static final String ENTITY_NEW_AFTER_INSERT = "Entity [%s] still 'new' after insert. Please set either"
+ " the id property in a before insert event handler, or ensure the database creates a value and your "
+ "JDBC driver returns it.";
private final JdbcPersistentEntity<T> persistentEntity;
private final JdbcPersistentEntityInformation<T, ID> entityInformation;
private final NamedParameterJdbcOperations operations;
private final SqlGenerator sql;
@@ -54,18 +63,19 @@ public class SimpleJdbcRepository<T, ID extends Serializable>
private final EntityRowMapper<T> entityRowMapper;
private final ApplicationEventPublisher publisher;
public SimpleJdbcRepository(JdbcPersistentEntity<T> persistentEntity, NamedParameterJdbcOperations jdbcOperations, ApplicationEventPublisher 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.");
Assert.notNull(publisher, "Publisher must not be null.");
this.entity = persistentEntity;
this.entityInformation = new JdbcPersistentEntityInformation<T, ID>(persistentEntity);
this.persistentEntity = persistentEntity;
this.entityInformation = new BasicJdbcPersistentEntityInformation<>(persistentEntity);
this.operations = jdbcOperations;
this.publisher = publisher;
entityRowMapper = new EntityRowMapper<T>(persistentEntity);
entityRowMapper = new EntityRowMapper<>(persistentEntity);
sql = new SqlGenerator(persistentEntity);
}
@@ -84,29 +94,23 @@ public class SimpleJdbcRepository<T, ID extends Serializable>
@Override
public <S extends T> Iterable<S> save(Iterable<S> entities) {
entities.forEach(this::save);
List<S> savedEntities = new ArrayList<>();
return entities;
entities.forEach(e -> savedEntities.add(save(e)));
return savedEntities;
}
@Override
public T findOne(ID id) {
return operations.queryForObject(
sql.getFindOne(),
new MapSqlParameterSource("id", id),
entityRowMapper
);
return operations.queryForObject(sql.getFindOne(), new MapSqlParameterSource("id", id), entityRowMapper);
}
@Override
public boolean exists(ID id) {
return operations.queryForObject(
sql.getExists(),
new MapSqlParameterSource("id", id),
Boolean.class
);
return operations.queryForObject(sql.getExists(), new MapSqlParameterSource("id", id), Boolean.class);
}
@Override
@@ -126,27 +130,19 @@ public class SimpleJdbcRepository<T, ID extends Serializable>
@Override
public void delete(ID id) {
doDelete(id, null);
doDelete(new Specified(id), Optional.empty());
}
@Override
public void delete(T instance) {
doDelete((ID) entity.getIdValue(instance), instance);
doDelete(new Specified(entityInformation.getId(instance)), Optional.of(instance));
}
@Override
public void delete(Iterable<? extends T> entities) {
operations.update(
sql.getDeleteByList(),
new MapSqlParameterSource("ids",
StreamSupport
.stream(entities.spliterator(), false)
.map(entity::getIdValue)
.collect(Collectors.toList())
)
);
operations.update(sql.getDeleteByList(), new MapSqlParameterSource("ids", StreamSupport
.stream(entities.spliterator(), false).map(entityInformation::getId).collect(Collectors.toList())));
}
@Override
@@ -154,52 +150,73 @@ public class SimpleJdbcRepository<T, ID extends Serializable>
operations.getJdbcOperations().update(sql.getDeleteAll());
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
}
private <S extends T> Map<String, Object> getPropertyMap(final S instance) {
Map<String, Object> parameters = new HashMap<>();
this.entity.doWithProperties(new PropertyHandler<JdbcPersistentProperty>() {
@Override
public void doWithPersistentProperty(JdbcPersistentProperty persistentProperty) {
parameters.put(persistentProperty.getColumnName(), entity.getPropertyAccessor(instance).getProperty(persistentProperty));
}
});
this.persistentEntity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) //
property -> parameters.put( //
property.getColumnName(), //
persistentEntity.getPropertyAccessor(instance).getProperty(property)) //
);
return parameters;
}
private <S extends T> void doInsert(S instance) {
publisher.publishEvent(new BeforeInsertEvent(instance, entity::getIdValue));
publisher.publishEvent(new BeforeInsert(instance));
KeyHolder holder = new GeneratedKeyHolder();
operations.update(
sql.getInsert(),
new MapSqlParameterSource(getPropertyMap(instance)),
holder);
Map<String, Object> propertyMap = getPropertyMap(instance);
propertyMap.put(persistentEntity.getIdColumn(), getIdValueOrNull(instance));
entity.setId(instance, holder.getKey());
operations.update(sql.getInsert(), new MapSqlParameterSource(propertyMap), holder);
setIdFromJdbc(instance, holder);
publisher.publishEvent(new AfterInsertEvent(instance, entity::getIdValue));
if (entityInformation.isNew(instance)) {
throw new IllegalStateException(String.format(ENTITY_NEW_AFTER_INSERT, persistentEntity));
}
publisher.publishEvent(new AfterInsert(new Specified(entityInformation.getId(instance)), instance));
}
private void doDelete(ID id, Object instance) {
private <S extends T> ID getIdValueOrNull(S instance) {
publisher.publishEvent(new BeforeDeleteEvent(instance, o -> id));
operations.update(sql.getDeleteById(), new MapSqlParameterSource("id", id));
publisher.publishEvent(new AfterDeleteEvent(instance, o -> id));
ID idValue = entityInformation.getId(instance);
return isIdPropertySimpleTypeAndValueZero(idValue) ? null : idValue;
}
private boolean isIdPropertySimpleTypeAndValueZero(ID idValue) {
return (persistentEntity.getIdProperty().getType() == int.class && idValue.equals(0))
|| (persistentEntity.getIdProperty().getType() == long.class && idValue.equals(0L));
}
private <S extends T> void setIdFromJdbc(S instance, KeyHolder holder) {
try {
Number idValueFromJdbc = holder.getKey();
if (idValueFromJdbc != null) {
entityInformation.setId(instance, idValueFromJdbc);
}
} catch (NonTransientDataAccessException e) {
throw new UnableToSetIdException("Unable to set id of " + instance, e);
}
}
private void doDelete(Specified specifiedId, Optional<Object> optionalEntity) {
publisher.publishEvent(new BeforeDelete(specifiedId, optionalEntity));
operations.update(sql.getDeleteById(), new MapSqlParameterSource("id", specifiedId.getValue()));
publisher.publishEvent(new AfterDelete(specifiedId, optionalEntity));
}
private <S extends T> void doUpdate(S instance) {
publisher.publishEvent(new BeforeUpdateEvent(instance, entity::getIdValue));
Specified specifiedId = new Specified(entityInformation.getId(instance));
publisher.publishEvent(new BeforeUpdate(specifiedId, instance));
operations.update(sql.getUpdate(), getPropertyMap(instance));
publisher.publishEvent(new AfterUpdateEvent(instance, entity::getIdValue));
publisher.publishEvent(new AfterUpdate(specifiedId, instance));
}
}

View File

@@ -24,6 +24,7 @@ import org.springframework.data.mapping.PropertyHandler;
/**
* @author Jens Schauder
* @since 2.0
*/
class SqlGenerator {

View File

@@ -0,0 +1,31 @@
/*
* 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.repository;
import org.springframework.dao.NonTransientDataAccessException;
/**
* Signals failure to set the id property of an entity.
*
* @author Jens Schauder
* @since 2.0
*/
public class UnableToSetIdException extends NonTransientDataAccessException {
public UnableToSetIdException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.repository.support;
import java.io.Serializable;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
import org.springframework.data.repository.core.support.PersistentEntityInformation;
/**
* @author Jens Schauder
* @since 2.0
*/
public class BasicJdbcPersistentEntityInformation<T, ID extends Serializable> extends PersistentEntityInformation<T, ID>
implements JdbcPersistentEntityInformation<T, ID> {
private final JdbcPersistentEntity<T> persistentEntity;
public BasicJdbcPersistentEntityInformation(JdbcPersistentEntity<T> persistentEntity) {
super(persistentEntity);
this.persistentEntity = persistentEntity;
}
@Override
public void setId(T instance, Object value) {
persistentEntity.getPropertyAccessor(instance).setProperty(persistentEntity.getIdProperty(), value);
}
}

View File

@@ -16,15 +16,14 @@
package org.springframework.data.jdbc.repository.support;
import java.io.Serializable;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
import org.springframework.data.repository.core.support.PersistentEntityInformation;
import org.springframework.data.repository.core.EntityInformation;
/**
* @author Jens Schauder
* @since 2.0
*/
public class JdbcPersistentEntityInformation<T, ID extends Serializable> extends PersistentEntityInformation<T, ID> {
public interface JdbcPersistentEntityInformation<T, ID extends Serializable> extends EntityInformation<T, ID> {
public JdbcPersistentEntityInformation(JdbcPersistentEntity<T> persistentEntity) {
super(persistentEntity);
}
void setId(T instance, Object value);
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.jdbc.repository.support;
import lombok.RequiredArgsConstructor;
import java.io.Serializable;
import org.springframework.context.ApplicationEventPublisher;
@@ -29,34 +31,26 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
/**
* @author Jens Schauder
* @since 2.0
*/
@RequiredArgsConstructor
public class JdbcRepositoryFactory extends RepositoryFactorySupport {
private final JdbcMappingContext context = new JdbcMappingContext();
private final ApplicationEventPublisher publisher;
private final NamedParameterJdbcOperations jdbcOperations;
private final ApplicationEventPublisher publisher;
public JdbcRepositoryFactory(
ApplicationEventPublisher publisher,
NamedParameterJdbcOperations jdbcOperations
) {
this.publisher = publisher;
this.jdbcOperations = jdbcOperations;
}
@SuppressWarnings("unchecked")
@Override
public <T, ID extends Serializable> EntityInformation<T, ID> getEntityInformation(Class<T> aClass) {
return new JdbcPersistentEntityInformation<>((JdbcPersistentEntity<T>) context.getPersistentEntity(aClass));
return new BasicJdbcPersistentEntityInformation<>((JdbcPersistentEntity<T>) context.getPersistentEntity(aClass));
}
@Override
protected Object getTargetRepository(RepositoryInformation repositoryInformation) {
return new SimpleJdbcRepository(
context.getPersistentEntity(repositoryInformation.getDomainType()),
jdbcOperations,
publisher);
JdbcPersistentEntity<?> persistentEntity = context.getPersistentEntity(repositoryInformation.getDomainType());
return new SimpleJdbcRepository<>(persistentEntity, jdbcOperations, publisher);
}
@Override