From 94d89605581f6b578456a486b0bd74920ef8b8a4 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 10 Mar 2017 13:39:20 +0100 Subject: [PATCH] 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. --- lombok.config | 2 + pom.xml | 55 ------ .../mapping/context/JdbcMappingContext.java | 13 +- .../jdbc/mapping/event/AfterCreation.java | 35 ++++ .../mapping/event/AfterCreationEvent.java | 36 ---- .../data/jdbc/mapping/event/AfterDelete.java | 39 +++++ .../jdbc/mapping/event/AfterDeleteEvent.java | 36 ---- ...AfterInsertEvent.java => AfterInsert.java} | 14 +- .../{AfterSaveEvent.java => AfterSave.java} | 17 +- ...AfterUpdateEvent.java => AfterUpdate.java} | 14 +- .../data/jdbc/mapping/event/BeforeDelete.java | 37 ++++ .../jdbc/mapping/event/BeforeDeleteEvent.java | 38 ----- ...foreInsertEvent.java => BeforeInsert.java} | 16 +- ...BeforeUpdateEvent.java => BeforeSave.java} | 14 +- .../jdbc/mapping/event/BeforeSaveEvent.java | 37 ---- .../data/jdbc/mapping/event/BeforeUpdate.java | 35 ++++ .../data/jdbc/mapping/event/Identifier.java | 65 +++++++ .../data/jdbc/mapping/event/JdbcEvent.java | 45 +++-- .../mapping/event/JdbcEventWithEntity.java | 31 ++++ .../jdbc/mapping/event/JdbcEventWithId.java | 33 ++++ .../event/JdbcEventWithIdAndEntity.java | 36 ++++ .../data/jdbc/mapping/event/WithEntity.java | 30 ++++ .../data/jdbc/mapping/event/WithId.java | 33 ++++ .../model/BasicJdbcPersistentProperty.java | 60 +++++++ .../mapping/model/JdbcPersistentEntity.java | 26 +-- .../mapping/model/JdbcPersistentProperty.java | 33 +--- .../data/jdbc/repository/EntityRowMapper.java | 11 +- .../EventPublishingEntityRowMapper.java | 31 ++-- .../jdbc/repository/SimpleJdbcRepository.java | 143 +++++++++------- .../data/jdbc/repository/SqlGenerator.java | 1 + .../repository/UnableToSetIdException.java | 31 ++++ .../BasicJdbcPersistentEntityInformation.java | 42 +++++ .../JdbcPersistentEntityInformation.java | 11 +- .../support/JdbcRepositoryFactory.java | 24 +-- .../EventPublishingEntityRowMapperTest.java | 28 ++-- ...epositoryIdGenerationIntegrationTests.java | 125 +++++++------- .../JdbcRepositoryIntegrationTests.java | 158 ++++++++++-------- .../SimpleJdbcRepositoryEventsUnitTests.java | 69 +++++--- ...sitory-id-generation-integration-tests.sql | 4 +- 39 files changed, 924 insertions(+), 584 deletions(-) create mode 100644 lombok.config create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java delete mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreationEvent.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java delete mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java rename src/main/java/org/springframework/data/jdbc/mapping/event/{AfterInsertEvent.java => AfterInsert.java} (65%) rename src/main/java/org/springframework/data/jdbc/mapping/event/{AfterSaveEvent.java => AfterSave.java} (63%) rename src/main/java/org/springframework/data/jdbc/mapping/event/{AfterUpdateEvent.java => AfterUpdate.java} (65%) create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDelete.java delete mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java rename src/main/java/org/springframework/data/jdbc/mapping/event/{BeforeInsertEvent.java => BeforeInsert.java} (60%) rename src/main/java/org/springframework/data/jdbc/mapping/event/{BeforeUpdateEvent.java => BeforeSave.java} (65%) delete mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdate.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithEntity.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithIdAndEntity.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/WithEntity.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/WithId.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java create mode 100644 src/main/java/org/springframework/data/jdbc/repository/UnableToSetIdException.java create mode 100644 src/main/java/org/springframework/data/jdbc/repository/support/BasicJdbcPersistentEntityInformation.java diff --git a/lombok.config b/lombok.config new file mode 100644 index 00000000..e50c7ea4 --- /dev/null +++ b/lombok.config @@ -0,0 +1,2 @@ +lombok.nonNull.exceptionType = IllegalArgumentException +lombok.log.fieldName = LOG diff --git a/pom.xml b/pom.xml index 4ada1017..cef9f314 100644 --- a/pom.xml +++ b/pom.xml @@ -128,61 +128,6 @@ org.apache.maven.plugins maven-surefire-plugin 2.12 - - - org.springframework - spring-instrument - ${spring} - runtime - - - org.hsqldb - hsqldb - ${hsqldb1} - runtime - - - - - default-test - - - **/* - - - - - unit-tests - - test - - test - - - **/*UnitTests.java - - - - - integration-tests - - test - - test - - - **/*IntegrationTests.java - **/*Tests.java - - - **/*UnitTests.java -\ - - -javaagent:${settings.localRepository}/org/jacoco/org.jacoco.agent/${jacoco}/org.jacoco.agent-${jacoco}-runtime.jar=destfile=${jacoco.destfile} - - - - org.apache.maven.plugins diff --git a/src/main/java/org/springframework/data/jdbc/mapping/context/JdbcMappingContext.java b/src/main/java/org/springframework/data/jdbc/mapping/context/JdbcMappingContext.java index e9fd47d3..a2cb0207 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/context/JdbcMappingContext.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/context/JdbcMappingContext.java @@ -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, JdbcPersistentProperty> { - @Override protected JdbcPersistentEntity createPersistentEntity(TypeInformation 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); } } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java new file mode 100644 index 00000000..9515ff80 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java @@ -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); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreationEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreationEvent.java deleted file mode 100644 index 1e96c440..00000000 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreationEvent.java +++ /dev/null @@ -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 type of the entity and the argument of the {@code idProvider} - */ - public AfterCreationEvent(T instance, Function idProvider) { - super(instance, idProvider); - } -} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java new file mode 100644 index 00000000..73c889b0 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java @@ -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 instance) { + super(id, instance); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java deleted file mode 100644 index 982ebbc2..00000000 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java +++ /dev/null @@ -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 type of the entity and the argument of the {@code idProvider} - */ - public AfterDeleteEvent(T instance, Function idProvider) { - super(instance, idProvider); - } -} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsertEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsert.java similarity index 65% rename from src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsertEvent.java rename to src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsert.java index aa733f91..9a6f4145 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsertEvent.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsert.java @@ -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 type of the entity and the argument of the {@code idProvider} */ - public AfterInsertEvent(T instance, Function idProvider) { - super(instance, idProvider); + public AfterInsert(Specified id, Object instance) { + super(id, instance); } } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSaveEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java similarity index 63% rename from src/main/java/org/springframework/data/jdbc/mapping/event/AfterSaveEvent.java rename to src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java index bf25700f..e029e1aa 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSaveEvent.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java @@ -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 type of the entity and the argument of the {@code idProvider} */ - AfterSaveEvent(T instance, Function idProvider) { - super(instance, idProvider); + AfterSave(Specified id, Object instance) { + super(id, instance); } } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdateEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdate.java similarity index 65% rename from src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdateEvent.java rename to src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdate.java index 075fde2b..b95cd093 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdateEvent.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdate.java @@ -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 type of the entity and the argument of the {@code idProvider} */ - public AfterUpdateEvent(T instance, Function idProvider) { - super(instance, idProvider); + public AfterUpdate(Specified id, Object instance) { + super(id, instance); } } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDelete.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDelete.java new file mode 100644 index 00000000..4d221b2e --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDelete.java @@ -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 BeforeDelete(Specified id, Optional entity) { + super(id, entity); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java deleted file mode 100644 index 1e9a7899..00000000 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java +++ /dev/null @@ -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 type of the entity and the argument of the {@code idProvider} - */ - public BeforeDeleteEvent(T instance, Function idProvider) { - super(instance, idProvider); - } -} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsertEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java similarity index 60% rename from src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsertEvent.java rename to src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java index 45ecba98..c290cd44 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsertEvent.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java @@ -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 type of the entity and the argument of the {@code idProvider} */ - public BeforeInsertEvent(T instance, Function idProvider) { - super(instance, idProvider); + public BeforeInsert(Object instance) { + super(Unset.UNSET, instance); } } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdateEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java similarity index 65% rename from src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdateEvent.java rename to src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java index 543cbcd7..bf20430d 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdateEvent.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java @@ -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 type of the entity and the argument of the {@code idProvider} */ - public BeforeUpdateEvent(T instance, Function idProvider) { - super(instance, idProvider); + BeforeSave(Identifier id, Object instance) { + super(id, instance); } } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java deleted file mode 100644 index 626c6f03..00000000 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java +++ /dev/null @@ -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 type of the entity and the argument of the {@code idProvider} - */ - BeforeSaveEvent(T instance, Function idProvider) { - super(instance, idProvider); - } -} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdate.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdate.java new file mode 100644 index 00000000..72ad695d --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdate.java @@ -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); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java b/src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java new file mode 100644 index 00000000..492cc1ed --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java @@ -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 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 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 getOptionalValue() { + return Optional.of(value); + } + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java index ad0df42f..59a1911d 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java @@ -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; - - JdbcEvent(T instance, Function 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 optionalEntity; + + public JdbcEvent(Identifier id, Optional 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(); } + } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithEntity.java b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithEntity.java new file mode 100644 index 00000000..c103dd6e --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithEntity.java @@ -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)); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java new file mode 100644 index 00000000..fa0c61d6 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java @@ -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 entity) { + super(id, entity); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithIdAndEntity.java b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithIdAndEntity.java new file mode 100644 index 00000000..819ef896 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithIdAndEntity.java @@ -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)); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/WithEntity.java b/src/main/java/org/springframework/data/jdbc/mapping/event/WithEntity.java new file mode 100644 index 00000000..a465fd54 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/WithEntity.java @@ -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")); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/WithId.java b/src/main/java/org/springframework/data/jdbc/mapping/event/WithId.java new file mode 100644 index 00000000..108a419a --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/WithId.java @@ -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(); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java b/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java new file mode 100644 index 00000000..a5edb0d0 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java @@ -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 + 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 owner, // + SimpleTypeHolder simpleTypeHolder // + ) { + super(field, propertyDescriptor, owner, simpleTypeHolder); + } + + @Override + protected Association createAssociation() { + return null; + } + + public String getColumnName() { + return getName(); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java index bbe2d69c..1c3a342f 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java @@ -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 extends BasicPersistentEntity { - private String tableName; - private String idColumn; + private final String tableName; public JdbcPersistentEntity(TypeInformation 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(); } } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java index 841d64c6..e63cd4fa 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java @@ -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 { +public interface JdbcPersistentProperty extends PersistentProperty { - /** - * 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 owner, SimpleTypeHolder simpleTypeHolder) { - super(field, propertyDescriptor, owner, simpleTypeHolder); - } - - @Override - protected Association createAssociation() { - return null; - } - - public String getColumnName() { - return getName(); - } + String getColumnName(); } diff --git a/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java index b6043521..7d6e1904 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java +++ b/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java @@ -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 implements RowMapper { @@ -48,7 +50,7 @@ class EntityRowMapper implements RowMapper { T t = createInstance(rs); - entity.doWithProperties((PropertyHandler) property -> { + entity.doWithProperties((PropertyHandler) property -> { setProperty(rs, t, property); }); @@ -57,12 +59,15 @@ class EntityRowMapper implements RowMapper { private T createInstance(ResultSet rs) { return instantiator.createInstance(entity, new ParameterValueProvider() { + @SuppressWarnings("unchecked") @Override public T getParameterValue(PreferredConstructor.Parameter 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 implements RowMapper { throw new RuntimeException(String.format("Couldn't set property %s.", property.getName()), e); } } -} \ No newline at end of file +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java index b499296c..1721c787 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java +++ b/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java @@ -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 implements RowMapper { +@RequiredArgsConstructor +public class EventPublishingEntityRowMapper implements RowMapper { private final RowMapper delegate; - private final JdbcPersistentEntity entity; + private final JdbcPersistentEntityInformation 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 delegate,JdbcPersistentEntity 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; } diff --git a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java index 04aaf467..ebff6a34 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java +++ b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java @@ -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 - implements CrudRepository, ApplicationEventPublisherAware { +public class SimpleJdbcRepository implements CrudRepository { - private final JdbcPersistentEntity 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 persistentEntity; private final JdbcPersistentEntityInformation entityInformation; private final NamedParameterJdbcOperations operations; private final SqlGenerator sql; @@ -54,18 +63,19 @@ public class SimpleJdbcRepository private final EntityRowMapper entityRowMapper; private final ApplicationEventPublisher publisher; - public SimpleJdbcRepository(JdbcPersistentEntity persistentEntity, NamedParameterJdbcOperations jdbcOperations, ApplicationEventPublisher publisher) { + public SimpleJdbcRepository(JdbcPersistentEntity 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(persistentEntity); + this.persistentEntity = persistentEntity; + this.entityInformation = new BasicJdbcPersistentEntityInformation<>(persistentEntity); this.operations = jdbcOperations; this.publisher = publisher; - entityRowMapper = new EntityRowMapper(persistentEntity); + entityRowMapper = new EntityRowMapper<>(persistentEntity); sql = new SqlGenerator(persistentEntity); } @@ -84,29 +94,23 @@ public class SimpleJdbcRepository @Override public Iterable save(Iterable entities) { - entities.forEach(this::save); + List 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 @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 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 operations.getJdbcOperations().update(sql.getDeleteAll()); } - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - - } - private Map getPropertyMap(final S instance) { Map parameters = new HashMap<>(); - this.entity.doWithProperties(new PropertyHandler() { - @Override - public void doWithPersistentProperty(JdbcPersistentProperty persistentProperty) { - parameters.put(persistentProperty.getColumnName(), entity.getPropertyAccessor(instance).getProperty(persistentProperty)); - } - }); + this.persistentEntity.doWithProperties((PropertyHandler) // + property -> parameters.put( // + property.getColumnName(), // + persistentEntity.getPropertyAccessor(instance).getProperty(property)) // + ); return parameters; } private 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 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 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 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 optionalEntity) { + + publisher.publishEvent(new BeforeDelete(specifiedId, optionalEntity)); + operations.update(sql.getDeleteById(), new MapSqlParameterSource("id", specifiedId.getValue())); + publisher.publishEvent(new AfterDelete(specifiedId, optionalEntity)); } private 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)); } } diff --git a/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java b/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java index 2d613d78..5036b979 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java +++ b/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java @@ -24,6 +24,7 @@ import org.springframework.data.mapping.PropertyHandler; /** * @author Jens Schauder + * @since 2.0 */ class SqlGenerator { diff --git a/src/main/java/org/springframework/data/jdbc/repository/UnableToSetIdException.java b/src/main/java/org/springframework/data/jdbc/repository/UnableToSetIdException.java new file mode 100644 index 00000000..1bbb0126 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/UnableToSetIdException.java @@ -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); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/BasicJdbcPersistentEntityInformation.java b/src/main/java/org/springframework/data/jdbc/repository/support/BasicJdbcPersistentEntityInformation.java new file mode 100644 index 00000000..ea22fcca --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/support/BasicJdbcPersistentEntityInformation.java @@ -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 extends PersistentEntityInformation + implements JdbcPersistentEntityInformation { + + private final JdbcPersistentEntity persistentEntity; + + public BasicJdbcPersistentEntityInformation(JdbcPersistentEntity persistentEntity) { + super(persistentEntity); + + this.persistentEntity = persistentEntity; + } + + @Override + public void setId(T instance, Object value) { + persistentEntity.getPropertyAccessor(instance).setProperty(persistentEntity.getIdProperty(), value); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcPersistentEntityInformation.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcPersistentEntityInformation.java index 97664acf..53df278c 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcPersistentEntityInformation.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcPersistentEntityInformation.java @@ -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 extends PersistentEntityInformation { +public interface JdbcPersistentEntityInformation extends EntityInformation { - public JdbcPersistentEntityInformation(JdbcPersistentEntity persistentEntity) { - super(persistentEntity); - } + void setId(T instance, Object value); } diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java index f2e9b895..2b92c331 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java @@ -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 EntityInformation getEntityInformation(Class aClass) { - return new JdbcPersistentEntityInformation<>((JdbcPersistentEntity) context.getPersistentEntity(aClass)); + return new BasicJdbcPersistentEntityInformation<>((JdbcPersistentEntity) 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 diff --git a/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java b/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java index 44113c48..9fefea38 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java +++ b/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java @@ -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 entity = mock(JdbcPersistentEntity.class); - private ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class); + RowMapper rowMapperDelegate = mock(RowMapper.class); + JdbcPersistentEntityInformation 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 rowMapper = new EventPublishingEntityRowMapper<>( - rowMapperDelegate, - entity, + EventPublishingEntityRowMapper 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; } -} \ No newline at end of file +} diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java index f537f6d0..9a1cf50d 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java @@ -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()); + PrimitiveIdEntity reloadedEntity = primitiveIdRepository.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); - } - - - 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 { @@ -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 { @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); + } + } } diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java index 8a90a9c5..9bebfc09 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -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(); + @Autowired NamedParameterJdbcTemplate template; - private final NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(db); - - 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 { Iterable 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 { Iterable 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 { 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 { 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 { 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 { 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 { assertThat(repository.findAll()).isEmpty(); } - @Test // DATAJDBC-98 public void update() { @@ -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 { @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); + } + + } } diff --git a/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java b/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java index 36c06e17..6c34bd93 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java @@ -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 { @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() { + @Override + public Integer answer(InvocationOnMock invocation) throws Throwable { + HashMap 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 { 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 { 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 { diff --git a/src/test/resources/org.springframework.data.jdbc.repository/jdbc-repository-id-generation-integration-tests.sql b/src/test/resources/org.springframework.data.jdbc.repository/jdbc-repository-id-generation-integration-tests.sql index 0e008210..c7a764b2 100644 --- a/src/test/resources/org.springframework.data.jdbc.repository/jdbc-repository-id-generation-integration-tests.sql +++ b/src/test/resources/org.springframework.data.jdbc.repository/jdbc-repository-id-generation-integration-tests.sql @@ -1,3 +1,5 @@ -- noinspection SqlNoDataSourceInspectionForFile -CREATE TABLE ReadOnlyIdEntity (ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(100)) \ No newline at end of file +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)) \ No newline at end of file