Browse Source

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.
pull/6/merge
Jens Schauder 9 years ago committed by Oliver Gierke
parent
commit
94d8960558
  1. 2
      lombok.config
  2. 55
      pom.xml
  3. 13
      src/main/java/org/springframework/data/jdbc/mapping/context/JdbcMappingContext.java
  4. 35
      src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java
  5. 36
      src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreationEvent.java
  6. 39
      src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java
  7. 36
      src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java
  8. 14
      src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsert.java
  9. 17
      src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java
  10. 14
      src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdate.java
  11. 37
      src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDelete.java
  12. 38
      src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java
  13. 16
      src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java
  14. 14
      src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java
  15. 37
      src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java
  16. 35
      src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdate.java
  17. 65
      src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java
  18. 45
      src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java
  19. 31
      src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithEntity.java
  20. 33
      src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java
  21. 36
      src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithIdAndEntity.java
  22. 30
      src/main/java/org/springframework/data/jdbc/mapping/event/WithEntity.java
  23. 33
      src/main/java/org/springframework/data/jdbc/mapping/event/WithId.java
  24. 60
      src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java
  25. 26
      src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java
  26. 33
      src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java
  27. 11
      src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java
  28. 31
      src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java
  29. 143
      src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java
  30. 1
      src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java
  31. 31
      src/main/java/org/springframework/data/jdbc/repository/UnableToSetIdException.java
  32. 42
      src/main/java/org/springframework/data/jdbc/repository/support/BasicJdbcPersistentEntityInformation.java
  33. 11
      src/main/java/org/springframework/data/jdbc/repository/support/JdbcPersistentEntityInformation.java
  34. 24
      src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java
  35. 28
      src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java
  36. 125
      src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java
  37. 158
      src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java
  38. 69
      src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java
  39. 4
      src/test/resources/org.springframework.data.jdbc.repository/jdbc-repository-id-generation-integration-tests.sql

2
lombok.config

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
lombok.nonNull.exceptionType = IllegalArgumentException
lombok.log.fieldName = LOG

55
pom.xml

@ -128,61 +128,6 @@ @@ -128,61 +128,6 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
<version>${spring}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>${hsqldb1}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<executions>
<execution>
<id>default-test</id>
<configuration>
<excludes>
<exclude>**/*</exclude>
</excludes>
</configuration>
</execution>
<execution>
<id>unit-tests</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
<configuration>
<includes>
<include>**/*UnitTests.java</include>
</includes>
</configuration>
</execution>
<execution>
<id>integration-tests</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
<configuration>
<includes>
<include>**/*IntegrationTests.java</include>
<include>**/*Tests.java</include>
</includes>
<excludes>
<exclude>**/*UnitTests.java</exclude>
\ </excludes>
<argLine>
-javaagent:${settings.localRepository}/org/jacoco/org.jacoco.agent/${jacoco}/org.jacoco.agent-${jacoco}-runtime.jar=destfile=${jacoco.destfile}
</argLine>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>

13
src/main/java/org/springframework/data/jdbc/mapping/context/JdbcMappingContext.java

@ -17,6 +17,8 @@ package org.springframework.data.jdbc.mapping.context; @@ -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; @@ -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);
}
}

35
src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java

@ -0,0 +1,35 @@ @@ -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);
}
}

36
src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreationEvent.java

@ -1,36 +0,0 @@ @@ -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);
}
}

39
src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java

@ -0,0 +1,39 @@ @@ -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);
}
}

36
src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java

@ -1,36 +0,0 @@ @@ -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);
}
}

14
src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsertEvent.java → src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsert.java

@ -15,21 +15,21 @@ @@ -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);
}
}

17
src/main/java/org/springframework/data/jdbc/mapping/event/AfterSaveEvent.java → src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java

@ -15,20 +15,23 @@ @@ -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);
}
}

14
src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdateEvent.java → src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdate.java

@ -15,21 +15,21 @@ @@ -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);
}
}

37
src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDelete.java

@ -0,0 +1,37 @@ @@ -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);
}
}

38
src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java

@ -1,38 +0,0 @@ @@ -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);
}
}

16
src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsertEvent.java → src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java

@ -15,23 +15,23 @@ @@ -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);
}
}

14
src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdateEvent.java → src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java

@ -15,21 +15,21 @@ @@ -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);
}
}

37
src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java

@ -1,37 +0,0 @@ @@ -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);
}
}

35
src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdate.java

@ -0,0 +1,35 @@ @@ -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);
}
}

65
src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java

@ -0,0 +1,65 @@ @@ -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);
}
}
}

45
src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java

@ -15,48 +15,43 @@ @@ -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();
}
}

31
src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithEntity.java

@ -0,0 +1,31 @@ @@ -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));
}
}

33
src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java

@ -0,0 +1,33 @@ @@ -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);
}
}

36
src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithIdAndEntity.java

@ -0,0 +1,36 @@ @@ -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));
}
}

30
src/main/java/org/springframework/data/jdbc/mapping/event/WithEntity.java

@ -0,0 +1,30 @@ @@ -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"));
}
}

33
src/main/java/org/springframework/data/jdbc/mapping/event/WithId.java

@ -0,0 +1,33 @@ @@ -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();
}
}

60
src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java

@ -0,0 +1,60 @@ @@ -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();
}
}

26
src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java

@ -20,38 +20,26 @@ import org.springframework.data.util.TypeInformation; @@ -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();
}
}

33
src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java

@ -15,38 +15,13 @@ @@ -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> {
/**
* 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 interface JdbcPersistentProperty extends PersistentProperty<JdbcPersistentProperty> {
public String getColumnName() {
return getName();
}
String getColumnName();
}

11
src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java

@ -17,6 +17,7 @@ package org.springframework.data.jdbc.repository; @@ -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; @@ -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> { @@ -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> { @@ -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> { @@ -76,4 +81,4 @@ class EntityRowMapper<T> implements RowMapper<T> {
throw new RuntimeException(String.format("Couldn't set property %s.", property.getName()), e);
}
}
}
}

31
src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java

@ -15,44 +15,37 @@ @@ -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;
}

143
src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java

@ -16,21 +16,26 @@ @@ -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; @@ -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> @@ -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> @@ -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<>();
entities.forEach(e -> savedEntities.add(save(e)));
return entities;
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> @@ -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> @@ -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));
}
}

1
src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java

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

31
src/main/java/org/springframework/data/jdbc/repository/UnableToSetIdException.java

@ -0,0 +1,31 @@ @@ -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);
}
}

42
src/main/java/org/springframework/data/jdbc/repository/support/BasicJdbcPersistentEntityInformation.java

@ -0,0 +1,42 @@ @@ -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);
}
}

11
src/main/java/org/springframework/data/jdbc/repository/support/JdbcPersistentEntityInformation.java

@ -16,15 +16,14 @@ @@ -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);
}

24
src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java

@ -15,6 +15,8 @@ @@ -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; @@ -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

28
src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java

@ -2,46 +2,46 @@ package org.springframework.data.jdbc.repository; @@ -2,46 +2,46 @@ package org.springframework.data.jdbc.repository;
import static org.mockito.Mockito.*;
import lombok.Data;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.junit.Test;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.annotation.Id;
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.repository.support.JdbcPersistentEntityInformation;
import org.springframework.jdbc.core.RowMapper;
import lombok.Data;
/**
* @author Jens Schauder
*/
public class EventPublishingEntityRowMapperTest {
private RowMapper rowMapperDelegate = mock(RowMapper.class);
private JdbcPersistentEntity<DummyEntity> entity = mock(JdbcPersistentEntity.class);
private ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class);
RowMapper rowMapperDelegate = mock(RowMapper.class);
JdbcPersistentEntityInformation<DummyEntity, Long> entityInformation = mock(JdbcPersistentEntityInformation.class);
ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class);
@Test // DATAJDBC-99
public void eventGetsPublishedAfterInstantiation() throws SQLException {
when(entity.getIdValue(any())).thenReturn(1L);
when(entityInformation.getId(any())).thenReturn(1L);
EventPublishingEntityRowMapper<DummyEntity> rowMapper = new EventPublishingEntityRowMapper<>(
rowMapperDelegate,
entity,
EventPublishingEntityRowMapper<DummyEntity, Long> rowMapper = new EventPublishingEntityRowMapper<>( //
rowMapperDelegate, //
entityInformation, //
publisher);
ResultSet resultSet = mock(ResultSet.class);
rowMapper.mapRow(resultSet, 1);
verify(publisher).publishEvent(isA(AfterCreationEvent.class));
verify(publisher).publishEvent(isA(AfterCreation.class));
}
@Data
private static class DummyEntity {
static class DummyEntity {
@Id private final Long Id;
}
}
}

125
src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java

@ -17,96 +17,71 @@ package org.springframework.data.jdbc.repository; @@ -17,96 +17,71 @@ package org.springframework.data.jdbc.repository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.*;
import lombok.Data;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.repository.JdbcRepositoryIdGenerationIntegrationTests.TestConfiguration;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.repository.CrudRepository;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import lombok.Data;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* testing special cases for Id generation with JdbcRepositories.
* Testing special cases for id generation with {@link SimpleJdbcRepository}.
*
* @author Jens Schauder
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfiguration.class)
public class JdbcRepositoryIdGenerationIntegrationTests {
private final EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(EmbeddedDatabaseType.HSQL)
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.addScript("org.springframework.data.jdbc.repository/jdbc-repository-id-generation-integration-tests.sql")
.build();
@Autowired NamedParameterJdbcTemplate template;
private final NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(db);
@Autowired ReadOnlyIdEntityRepository readOnlyIdrepository;
private final ReadOnlyIdEntityRepository repository = createRepository(db);
private ReadOnlyIdEntity entity = createDummyEntity();
@After
public void after() {
db.shutdown();
}
@Autowired PrimitiveIdEntityRepository primitiveIdRepository;
@Test // DATAJDBC-98
public void idWithoutSetterGetsSet() {
entity = repository.save(entity);
ReadOnlyIdEntity entity1 = new ReadOnlyIdEntity(null);
entity1.setName("Entity Name");
ReadOnlyIdEntity entity = entity1;
entity = readOnlyIdrepository.save(entity);
assertThat(entity.getId()).isNotNull();
ReadOnlyIdEntity reloadedEntity = repository.findOne(entity.getId());
ReadOnlyIdEntity reloadedEntity = readOnlyIdrepository.findOne(entity.getId());
assertEquals(
entity.getId(),
reloadedEntity.getId());
assertEquals(
entity.getName(),
reloadedEntity.getName());
assertEquals(entity.getId(), reloadedEntity.getId());
assertEquals(entity.getName(), reloadedEntity.getName());
}
@Test // DATAJDBC-98
public void primitiveIdGetsSet() {
entity = repository.save(entity);
PrimitiveIdEntity entity = new PrimitiveIdEntity(0);
entity.setName("Entity Name");
entity = primitiveIdRepository.save(entity);
assertThat(entity.getId()).isNotNull();
ReadOnlyIdEntity reloadedEntity = repository.findOne(entity.getId());
assertEquals(
entity.getId(),
reloadedEntity.getId());
assertEquals(
entity.getName(),
reloadedEntity.getName());
}
private static ReadOnlyIdEntityRepository createRepository(EmbeddedDatabase db) {
return new JdbcRepositoryFactory(
mock(ApplicationEventPublisher.class),
new NamedParameterJdbcTemplate(db)
).getRepository(ReadOnlyIdEntityRepository.class);
}
PrimitiveIdEntity reloadedEntity = primitiveIdRepository.findOne(entity.getId());
private static ReadOnlyIdEntity createDummyEntity() {
ReadOnlyIdEntity entity = new ReadOnlyIdEntity(null);
entity.setName("Entity Name");
return entity;
assertEquals(entity.getId(), reloadedEntity.getId());
assertEquals(entity.getName(), reloadedEntity.getName());
}
private interface ReadOnlyIdEntityRepository extends CrudRepository<ReadOnlyIdEntity, Long> {
@ -116,8 +91,7 @@ public class JdbcRepositoryIdGenerationIntegrationTests { @@ -116,8 +91,7 @@ public class JdbcRepositoryIdGenerationIntegrationTests {
@Data
static class ReadOnlyIdEntity {
@Id
private final Long id;
@Id private final Long id;
String name;
}
@ -128,8 +102,43 @@ public class JdbcRepositoryIdGenerationIntegrationTests { @@ -128,8 +102,43 @@ public class JdbcRepositoryIdGenerationIntegrationTests {
@Data
static class PrimitiveIdEntity {
@Id
private final Long id;
@Id private final long id;
String name;
}
@Configuration
static class TestConfiguration {
@Bean
EmbeddedDatabase dataSource() {
System.out.println(" creating datasource");
return new EmbeddedDatabaseBuilder() //
.generateUniqueName(true) //
.setType(EmbeddedDatabaseType.HSQL) //
.setScriptEncoding("UTF-8") //
.ignoreFailedDrops(true) //
.addScript("org.springframework.data.jdbc.repository/jdbc-repository-id-generation-integration-tests.sql")
.build();
}
@Bean
NamedParameterJdbcTemplate template(EmbeddedDatabase db) {
return new NamedParameterJdbcTemplate(db);
}
@Bean
ReadOnlyIdEntityRepository readOnlyIdRepository(EmbeddedDatabase db) {
return new JdbcRepositoryFactory(new NamedParameterJdbcTemplate(db), mock(ApplicationEventPublisher.class))
.getRepository(ReadOnlyIdEntityRepository.class);
}
@Bean
PrimitiveIdEntityRepository primitiveIdRepository(NamedParameterJdbcTemplate template) {
return new JdbcRepositoryFactory(template, mock(ApplicationEventPublisher.class))
.getRepository(PrimitiveIdEntityRepository.class);
}
}
}

158
src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java

@ -18,91 +18,86 @@ package org.springframework.data.jdbc.repository; @@ -18,91 +18,86 @@ package org.springframework.data.jdbc.repository;
import static java.util.Arrays.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.*;
import lombok.Data;
import javax.sql.DataSource;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.repository.JdbcRepositoryIntegrationTests.TestConfiguration;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.repository.CrudRepository;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import lombok.Data;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
/**
* very simple use cases for creation and usage of JdbcRepositories.
* Very simple use cases for creation and usage of JdbcRepositories.
*
* @author Jens Schauder
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfiguration.class)
@Transactional
public class JdbcRepositoryIntegrationTests {
private final EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(EmbeddedDatabaseType.HSQL)
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.addScript("org.springframework.data.jdbc.repository/jdbc-repository-integration-tests.sql")
.build();
private final NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(db);
@Autowired NamedParameterJdbcTemplate template;
private final DummyEntityRepository repository = createRepository(db);
@Autowired DummyEntityRepository repository;
private DummyEntity entity = createDummyEntity();
@After
public void after() {
db.shutdown();
}
@Test // DATAJDBC-95
public void canSaveAnEntity() {
public void savesAnEntity() {
entity = repository.save(entity);
int count = template.queryForObject(
"SELECT count(*) FROM dummyentity WHERE idProp = :id",
new MapSqlParameterSource("id", entity.getIdProp()),
Integer.class);
int count = template.queryForObject( //
"SELECT count(*) FROM dummyentity WHERE idProp = :id", //
new MapSqlParameterSource("id", entity.getIdProp()), //
Integer.class //
);
assertEquals(
1,
count);
assertEquals(1, count);
}
@Test // DATAJDBC-95
public void canSaveAndLoadAnEntity() {
public void saveAndLoadAnEntity() {
entity = repository.save(entity);
DummyEntity reloadedEntity = repository.findOne(entity.getIdProp());
assertEquals(
entity.getIdProp(),
reloadedEntity.getIdProp());
assertEquals(
entity.getName(),
reloadedEntity.getName());
assertEquals(entity.getIdProp(), reloadedEntity.getIdProp());
assertEquals(entity.getName(), reloadedEntity.getName());
}
@Test // DATAJDBC-97
public void saveMany() {
public void savesManyEntities() {
DummyEntity other = createDummyEntity();
repository.save(asList(entity, other));
assertThat(repository.findAll())
.extracting(DummyEntity::getIdProp)
.containsExactlyInAnyOrder(
entity.getIdProp(),
other.getIdProp()
);
assertThat(repository.findAll()) //
.extracting(DummyEntity::getIdProp) //
.containsExactlyInAnyOrder( //
entity.getIdProp(), other.getIdProp() //
);
}
@Test // DATAJDBC-97
@ -124,8 +119,7 @@ public class JdbcRepositoryIntegrationTests { @@ -124,8 +119,7 @@ public class JdbcRepositoryIntegrationTests {
Iterable<DummyEntity> all = repository.findAll();
assertThat(all).extracting("idProp")
.containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp());
assertThat(all).extracting("idProp").containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp());
}
@Test // DATAJDBC-97
@ -137,12 +131,11 @@ public class JdbcRepositoryIntegrationTests { @@ -137,12 +131,11 @@ public class JdbcRepositoryIntegrationTests {
Iterable<DummyEntity> all = repository.findAll(asList(entity.getIdProp(), three.getIdProp()));
assertThat(all).extracting("idProp")
.containsExactlyInAnyOrder(entity.getIdProp(), three.getIdProp());
assertThat(all).extracting("idProp").containsExactlyInAnyOrder(entity.getIdProp(), three.getIdProp());
}
@Test // DATAJDBC-97
public void count() {
public void countsEntities() {
repository.save(createDummyEntity());
repository.save(createDummyEntity());
@ -151,7 +144,7 @@ public class JdbcRepositoryIntegrationTests { @@ -151,7 +144,7 @@ public class JdbcRepositoryIntegrationTests {
assertThat(repository.count()).isEqualTo(3L);
}
@Test // DATAJDBC-97
@Test // DATAJDBC-97
public void deleteById() {
entity = repository.save(entity);
@ -160,9 +153,11 @@ public class JdbcRepositoryIntegrationTests { @@ -160,9 +153,11 @@ public class JdbcRepositoryIntegrationTests {
repository.delete(two.getIdProp());
assertThat(repository.findAll())
.extracting(DummyEntity::getIdProp)
.containsExactlyInAnyOrder(entity.getIdProp(), three.getIdProp());
assertThat(repository.findAll()) //
.extracting(DummyEntity::getIdProp) //
.containsExactlyInAnyOrder( //
entity.getIdProp(), three.getIdProp() //
);
}
@Test // DATAJDBC-97
@ -174,15 +169,13 @@ public class JdbcRepositoryIntegrationTests { @@ -174,15 +169,13 @@ public class JdbcRepositoryIntegrationTests {
repository.delete(entity);
assertThat(repository.findAll())
.extracting(DummyEntity::getIdProp)
.containsExactlyInAnyOrder(
two.getIdProp(),
three.getIdProp()
);
assertThat(repository.findAll()) //
.extracting(DummyEntity::getIdProp) //
.containsExactlyInAnyOrder( //
two.getIdProp(), three.getIdProp() //
);
}
@Test // DATAJDBC-97
public void deleteByList() {
@ -195,7 +188,7 @@ public class JdbcRepositoryIntegrationTests { @@ -195,7 +188,7 @@ public class JdbcRepositoryIntegrationTests {
assertThat(repository.findAll()).extracting(DummyEntity::getIdProp).containsExactlyInAnyOrder(two.getIdProp());
}
@Test // DATAJDBC-97
@Test // DATAJDBC-97
public void deleteAll() {
repository.save(entity);
@ -207,7 +200,6 @@ public class JdbcRepositoryIntegrationTests { @@ -207,7 +200,6 @@ public class JdbcRepositoryIntegrationTests {
assertThat(repository.findAll()).isEmpty();
}
@Test // DATAJDBC-98
public void update() {
@ -233,18 +225,16 @@ public class JdbcRepositoryIntegrationTests { @@ -233,18 +225,16 @@ public class JdbcRepositoryIntegrationTests {
repository.save(asList(entity, other));
assertThat(repository.findAll())
.extracting(DummyEntity::getName)
.containsExactlyInAnyOrder(entity.getName(), other.getName());
assertThat(repository.findAll()).extracting(DummyEntity::getName).containsExactlyInAnyOrder(entity.getName(),
other.getName());
}
private static DummyEntityRepository createRepository(EmbeddedDatabase db) {
return new JdbcRepositoryFactory(mock(ApplicationEventPublisher.class), new NamedParameterJdbcTemplate(db))
return new JdbcRepositoryFactory(new NamedParameterJdbcTemplate(db), mock(ApplicationEventPublisher.class))
.getRepository(DummyEntityRepository.class);
}
private static DummyEntity createDummyEntity() {
DummyEntity entity = new DummyEntity();
@ -259,8 +249,42 @@ public class JdbcRepositoryIntegrationTests { @@ -259,8 +249,42 @@ public class JdbcRepositoryIntegrationTests {
@Data
static class DummyEntity {
@Id
private Long idProp;
@Id private Long idProp;
String name;
}
@Configuration
static class TestConfiguration {
@Bean
EmbeddedDatabase dataSource() {
System.out.println(" creating datasource");
return new EmbeddedDatabaseBuilder() //
.generateUniqueName(true) //
.setType(EmbeddedDatabaseType.HSQL) //
.setScriptEncoding("UTF-8") //
.ignoreFailedDrops(true) //
.addScript("org.springframework.data.jdbc.repository/jdbc-repository-integration-tests.sql") //
.build();
}
@Bean
NamedParameterJdbcTemplate template(EmbeddedDatabase db) {
return new NamedParameterJdbcTemplate(db);
}
@Bean
DummyEntityRepository readOnlyIdRepository(NamedParameterJdbcTemplate template) {
return new JdbcRepositoryFactory(template, mock(ApplicationEventPublisher.class))
.getRepository(DummyEntityRepository.class);
}
@Bean
PlatformTransactionManager transactionManager(DataSource db) {
return new DataSourceTransactionManager(db);
}
}
}

69
src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java

@ -1,27 +1,33 @@ @@ -1,27 +1,33 @@
package org.springframework.data.jdbc.repository;
import static java.util.Arrays.*;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
import static org.springframework.util.Assert.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.annotation.Id;
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.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.event.JdbcEvent;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.repository.CrudRepository;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.KeyHolder;
import lombok.Data;
@ -36,10 +42,25 @@ public class SimpleJdbcRepositoryEventsUnitTests { @@ -36,10 +42,25 @@ public class SimpleJdbcRepositoryEventsUnitTests {
@Before
public void before() {
JdbcRepositoryFactory factory = new JdbcRepositoryFactory(publisher, mock(NamedParameterJdbcOperations.class));
NamedParameterJdbcOperations operations = createIdGeneratingOperations();
JdbcRepositoryFactory factory = new JdbcRepositoryFactory(operations, publisher);
repository = factory.getRepository(DummyEntityRepository.class);
}
private NamedParameterJdbcOperations createIdGeneratingOperations() {
NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class);
when(operations.update(anyString(), any(SqlParameterSource.class), any(KeyHolder.class))).thenAnswer(new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocation) throws Throwable {
HashMap<String, Object> keys = new HashMap<>();
keys.put("id", 4711L);
invocation.getArgumentAt(2, KeyHolder.class).getKeyList().add(keys);
return 1;
}
});
return operations;
}
@Test // DATAJDBC-99
public void publishesEventsOnSave() {
@ -47,22 +68,24 @@ public class SimpleJdbcRepositoryEventsUnitTests { @@ -47,22 +68,24 @@ public class SimpleJdbcRepositoryEventsUnitTests {
repository.save(entity);
isInstanceOf(BeforeUpdateEvent.class, publisher.events.get(0));
isInstanceOf(AfterUpdateEvent.class, publisher.events.get(1));
isInstanceOf(BeforeUpdate.class, publisher.events.get(0));
isInstanceOf(AfterUpdate.class, publisher.events.get(1));
}
@Test // DATAJDBC-99
public void publishesEventsOnSaveMany() {
DummyEntity entity1 = new DummyEntity(null);
DummyEntity entity2 = new DummyEntity(23L);
repository.save(asList(entity1, entity2));
isInstanceOf(BeforeInsertEvent.class, publisher.events.get(0));
isInstanceOf(AfterInsertEvent.class, publisher.events.get(1));
isInstanceOf(BeforeUpdateEvent.class, publisher.events.get(2));
isInstanceOf(AfterUpdateEvent.class, publisher.events.get(3));
isInstanceOf(BeforeInsert.class, publisher.events.get(0));
isInstanceOf(AfterInsert.class, publisher.events.get(1));
isInstanceOf(BeforeUpdate.class, publisher.events.get(2));
isInstanceOf(AfterUpdate.class, publisher.events.get(3));
}
@ -73,27 +96,25 @@ public class SimpleJdbcRepositoryEventsUnitTests { @@ -73,27 +96,25 @@ public class SimpleJdbcRepositoryEventsUnitTests {
repository.delete(entity);
isInstanceOf(BeforeDeleteEvent.class, publisher.events.get(0));
isInstanceOf(AfterDeleteEvent.class, publisher.events.get(1));
isInstanceOf(BeforeDelete.class, publisher.events.get(0));
isInstanceOf(AfterDelete.class, publisher.events.get(1));
assertEquals(entity, publisher.events.get(0).getInstance());
assertEquals(entity, publisher.events.get(1).getInstance());
assertEquals(entity, publisher.events.get(0).getOptionalEntity().get());
assertEquals(entity, publisher.events.get(1).getOptionalEntity().get());
assertEquals(23L, publisher.events.get(0).getId());
assertEquals(23L, publisher.events.get(1).getId());
assertEquals(new Specified(23L), publisher.events.get(0).getId());
assertEquals(new Specified(23L), publisher.events.get(1).getId());
}
@Test // DATAJDBC-99
public void publishesEventsOnDeleteById() {
repository.delete(23L);
isInstanceOf(BeforeDeleteEvent.class, publisher.events.get(0));
isInstanceOf(AfterDeleteEvent.class, publisher.events.get(1));
isInstanceOf(BeforeDelete.class, publisher.events.get(0));
isInstanceOf(AfterDelete.class, publisher.events.get(1));
}
@Data
private static class DummyEntity {

4
src/test/resources/org.springframework.data.jdbc.repository/jdbc-repository-id-generation-integration-tests.sql

@ -1,3 +1,5 @@ @@ -1,3 +1,5 @@
-- noinspection SqlNoDataSourceInspectionForFile
CREATE TABLE ReadOnlyIdEntity (ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(100))
CREATE TABLE ReadOnlyIdEntity (ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(100))
CREATE TABLE PrimitiveIdEntity (ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(100))
Loading…
Cancel
Save