From 1af9146f4d2af9eaa2c91cd83916481396f163fd Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 25 Apr 2017 17:57:24 +0200 Subject: [PATCH] DATAJDBC-99 - Polishing. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduced interface for JdbcPersistentEntity to be consistent with other store implementations. Introduced JdbcPersistentEntityInformation.getRequiredId(…) to return a value and throw an exception if it can't be obtained. Added serialVersionUIDs to event implementations. Extracted Unset and SpecifiedIdentifier into top level classes to be able to reduce visibility. Introduced Specified interface and factory methods on Identifier to be able to create Identifier instances in all ways needed (specified / optional). Extracted JdbcEvent interface from the previously thus named class to be able to let WithId and WithEntity extend that interface to avoid the cast in the default method of WithId. WithId now simply redeclares getId returning Specified so that the guarantees of returned object type simply adapt as soon as event types also implement that interface. Reduced the visibility of SimpleJdbcEvent as listeners could now just refer to JdbcEvent. Switched to a nullable field for the entity in SimpleJdbcEvent as ApplicationEvent already implies potential serializability and Optional is not Serializable. Reorganized integration test setup to use configuration classes over FactoryBean implementations. Switched to AssertJ for test assertions. Renamed UnableToSetIdException to UnableToSetId. A bit of Javadoc here and there. Formatting. No abbreviated variable names. Original pull request: #5. --- .../mapping/context/JdbcMappingContext.java | 27 +-- .../jdbc/mapping/event/AfterCreation.java | 2 + .../data/jdbc/mapping/event/AfterDelete.java | 2 + .../data/jdbc/mapping/event/AfterInsert.java | 2 + .../data/jdbc/mapping/event/AfterSave.java | 2 + .../data/jdbc/mapping/event/AfterUpdate.java | 2 + .../data/jdbc/mapping/event/BeforeDelete.java | 2 + .../data/jdbc/mapping/event/BeforeInsert.java | 4 +- .../data/jdbc/mapping/event/BeforeSave.java | 2 + .../data/jdbc/mapping/event/BeforeUpdate.java | 16 ++ .../data/jdbc/mapping/event/Identifier.java | 66 ++++--- .../data/jdbc/mapping/event/JdbcEvent.java | 34 ++-- .../mapping/event/JdbcEventWithEntity.java | 6 +- .../jdbc/mapping/event/JdbcEventWithId.java | 20 ++- .../event/JdbcEventWithIdAndEntity.java | 6 +- .../jdbc/mapping/event/SimpleJdbcEvent.java | 60 +++++++ .../mapping/event/SpecifiedIdentifier.java | 44 +++++ .../data/jdbc/mapping/event/Unset.java | 39 ++++ .../data/jdbc/mapping/event/WithEntity.java | 12 +- .../data/jdbc/mapping/event/WithId.java | 13 +- .../model/BasicJdbcPersistentProperty.java | 19 +- .../mapping/model/JdbcPersistentEntity.java | 35 ++-- .../model/JdbcPersistentEntityImpl.java | 54 ++++++ .../mapping/model/JdbcPersistentProperty.java | 8 + .../data/jdbc/repository/EntityRowMapper.java | 63 ++++--- .../EventPublishingEntityRowMapper.java | 18 +- .../jdbc/repository/SimpleJdbcRepository.java | 91 +++++++--- .../data/jdbc/repository/SqlGenerator.java | 8 +- ...SetIdException.java => UnableToSetId.java} | 6 +- .../BasicJdbcPersistentEntityInformation.java | 5 + .../JdbcPersistentEntityInformation.java | 12 ++ .../EventPublishingEntityRowMapperTest.java | 41 +++-- ...epositoryIdGenerationIntegrationTests.java | 101 +++++------ .../JdbcRepositoryIntegrationTests.java | 168 +++++++----------- .../SimpleJdbcRepositoryEventsUnitTests.java | 120 ++++++------- .../jdbc/testing/DataSourceConfiguration.java | 79 ++++++++ .../jdbc/testing/DataSourceFactoryBean.java | 59 ------ ....java => HsqlDataSourceConfiguration.java} | 26 ++- .../testing/MySqlDataSourceConfiguration.java | 66 +++++++ .../testing/MySqlDataSourceFactoryBean.java | 84 --------- .../PostgresDataSourceConfiguration.java | 55 ++++++ .../PostgresDataSourceFactoryBean.java | 69 ------- .../data/jdbc/testing/TestConfiguration.java | 56 ++++++ .../data/jdbc/testing/TestUtils.java | 42 +++++ src/test/resources/logback.xml | 16 ++ ...itoryIdGenerationIntegrationTests-hsql.sql | 2 +- ...toryIdGenerationIntegrationTests-mysql.sql | 2 +- ...yIdGenerationIntegrationTests-postgres.sql | 2 +- .../JdbcRepositoryIntegrationTests-hsql.sql | 2 +- .../JdbcRepositoryIntegrationTests-mysql.sql | 2 +- ...dbcRepositoryIntegrationTests-postgres.sql | 2 +- 51 files changed, 1044 insertions(+), 630 deletions(-) create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/SimpleJdbcEvent.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/SpecifiedIdentifier.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/Unset.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntityImpl.java rename src/main/java/org/springframework/data/jdbc/repository/{UnableToSetIdException.java => UnableToSetId.java} (81%) create mode 100644 src/test/java/org/springframework/data/jdbc/testing/DataSourceConfiguration.java delete mode 100644 src/test/java/org/springframework/data/jdbc/testing/DataSourceFactoryBean.java rename src/test/java/org/springframework/data/jdbc/testing/{HsqlDataSourceFactoryBean.java => HsqlDataSourceConfiguration.java} (73%) create mode 100644 src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java delete mode 100644 src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceFactoryBean.java create mode 100644 src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceConfiguration.java delete mode 100644 src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceFactoryBean.java create mode 100644 src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java create mode 100644 src/test/java/org/springframework/data/jdbc/testing/TestUtils.java create mode 100644 src/test/resources/logback.xml 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 469a0eeb..9d228ce7 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 @@ -16,31 +16,38 @@ package org.springframework.data.jdbc.mapping.context; import org.springframework.data.jdbc.mapping.model.BasicJdbcPersistentProperty; -import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; +import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityImpl; import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; import org.springframework.data.mapping.context.AbstractMappingContext; +import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.Property; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.util.TypeInformation; /** + * {@link MappingContext} implementation for JDBC. + * * @author Jens Schauder * @since 2.0 */ -public class JdbcMappingContext extends AbstractMappingContext, JdbcPersistentProperty> { +public class JdbcMappingContext extends AbstractMappingContext, JdbcPersistentProperty> { + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation) + */ @Override - protected JdbcPersistentEntity createPersistentEntity(TypeInformation typeInformation) { - return new JdbcPersistentEntity(typeInformation); + protected JdbcPersistentEntityImpl createPersistentEntity(TypeInformation typeInformation) { + return new JdbcPersistentEntityImpl<>(typeInformation); } + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentProperty(org.springframework.data.mapping.model.Property, org.springframework.data.mapping.model.MutablePersistentEntity, org.springframework.data.mapping.model.SimpleTypeHolder) + */ @Override - protected JdbcPersistentProperty createPersistentProperty( // - Property property, // - JdbcPersistentEntity owner, // - SimpleTypeHolder simpleTypeHolder // - ) { + protected JdbcPersistentProperty createPersistentProperty(Property property, JdbcPersistentEntityImpl owner, + SimpleTypeHolder simpleTypeHolder) { return new BasicJdbcPersistentProperty(property, owner, simpleTypeHolder); } - } 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 index abdf582e..b0f2e8ed 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java @@ -26,6 +26,8 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Specified; */ public class AfterCreation extends JdbcEventWithIdAndEntity { + private static final long serialVersionUID = -4185777271143436728L; + /** * @param id of the entity * @param entity the newly instantiated entity. 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 index 4ebca774..f1f24d19 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java @@ -28,6 +28,8 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Specified; */ public class AfterDelete extends JdbcEventWithId { + private static final long serialVersionUID = 3594807189931141582L; + /** * @param id of the entity. * @param instance the deleted entity if it is available. diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsert.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsert.java index 9a6f4145..5b82bd3c 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsert.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsert.java @@ -25,6 +25,8 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Specified; */ public class AfterInsert extends AfterSave { + private static final long serialVersionUID = 1312645717283677063L; + /** * @param id identifier of the entity triggering the event. * @param instance the newly inserted entity. diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java index c83d48dd..9c79e213 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java @@ -25,6 +25,8 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Specified; */ public class AfterSave extends JdbcEventWithIdAndEntity { + private static final long serialVersionUID = 8982085767296982848L; + /** * @param id identifier of * @param instance the newly saved entity. diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdate.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdate.java index b95cd093..cda83304 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdate.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdate.java @@ -25,6 +25,8 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Specified; */ public class AfterUpdate extends AfterSave { + private static final long serialVersionUID = -1765706904721563399L; + /** * @param id of the entity * @param instance the updated entity. 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 index 4d221b2e..ef7d1c0f 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDelete.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDelete.java @@ -27,6 +27,8 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Specified; */ public class BeforeDelete extends JdbcEventWithId { + private static final long serialVersionUID = -5483432053368496651L; + /** * @param id the id of the entity * @param entity the entity about to get deleted. Might be empty. diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java index dcd669dd..37d22270 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java @@ -15,8 +15,6 @@ */ package org.springframework.data.jdbc.mapping.event; -import org.springframework.data.jdbc.mapping.event.Identifier.Unset; - /** * Gets published before an entity gets inserted into the database. When the id-property of the entity must get set * manually, an event listener for this event may do so.
@@ -27,6 +25,8 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Unset; */ public class BeforeInsert extends BeforeSave { + private static final long serialVersionUID = -5350552051337308870L; + /** * @param instance the entity about to get inserted. */ diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java index b6eeb701..ae100d39 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java @@ -23,6 +23,8 @@ package org.springframework.data.jdbc.mapping.event; */ public class BeforeSave extends JdbcEventWithEntity { + private static final long serialVersionUID = -6996874391990315443L; + /** * @param id of the entity to be saved. * @param instance the entity about to get saved. 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 index 72ad695d..57f7a330 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdate.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdate.java @@ -25,11 +25,27 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Specified; */ public class BeforeUpdate extends BeforeSave implements WithId { + private static final long serialVersionUID = 794561215071613972L; + + private final Specified id; + /** * @param id of the entity about to get updated * @param instance the entity about to get updated. */ public BeforeUpdate(Specified id, Object instance) { + super(id, instance); + + this.id = id; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.mapping.event.JdbcEvent#getId() + */ + @Override + public Specified getId() { + return id; } } 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 index 43eb3566..0db46646 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java @@ -15,11 +15,10 @@ */ package org.springframework.data.jdbc.mapping.event; -import lombok.Data; -import lombok.NonNull; - import java.util.Optional; +import org.springframework.util.Assert; + /** * Wrapper for an identifier of an entity. Might either be a {@link Specified} or {@link Unset#UNSET} * @@ -28,36 +27,53 @@ import java.util.Optional; */ public interface Identifier { - static Identifier fromNullable(Object value) { - return (value != null) ? new Specified(value) : Unset.UNSET; - } - - Optional getOptionalValue(); - /** - * An unset identifier. Always returns {@link Optional#empty()} as value. + * Creates a new {@link Specified} identifier for the given, non-null value. + * + * @param identifier must not be {@literal null}. + * @return will never be {@literal null}. */ - enum Unset implements Identifier { - UNSET { - @Override - public Optional getOptionalValue() { - return Optional.empty(); - } - } + static Specified of(Object identifier) { + + Assert.notNull(identifier, "Identifier must not be null!"); + + return SpecifiedIdentifier.of(identifier); } /** - * An {@link Identifier} guaranteed to have a non empty value. Since it is guaranteed to exist the value can get - * access directly. + * Creates a new {@link Identifier} for the given optional source value. + * + * @param identifier must not be {@literal null}. + * @return */ - @Data - class Specified implements Identifier { + static Identifier of(Optional identifier) { - @NonNull private final Object value; + Assert.notNull(identifier, "Identifier must not be null!"); - @Override - public Optional getOptionalValue() { - return Optional.of(value); + return identifier.map(it -> (Identifier) Identifier.of(it)).orElse(Unset.UNSET); + } + + /** + * Returns the identifier value. + * + * @return will never be {@literal null}. + */ + Optional getOptionalValue(); + + /** + * A specified identifier that exposes a definitely present identifier value. + * + * @author Oliver Gierke + */ + interface Specified extends Identifier { + + /** + * Returns the identifier value. + * + * @return will never be {@literal null}. + */ + default Object getValue() { + return getOptionalValue().orElseThrow(() -> new IllegalStateException("Should not happen!")); } } } 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 9c233969..d862517a 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,40 +15,26 @@ */ package org.springframework.data.jdbc.mapping.event; -import lombok.Getter; - import java.util.Optional; -import org.springframework.context.ApplicationEvent; - /** - * The common superclass for all events published by JDBC repositories. {@link #getSource} contains the - * {@link Identifier} of the entity triggering the event. * - * @author Jens Schauder - * @since 2.0 + * @author Oliver Gierke */ -@Getter -public class JdbcEvent extends ApplicationEvent { - - /** - * The optional entity for which this event was published. Might be empty in cases of delete events where only the - * identifier was passed to the delete method. - */ - private final Optional optionalEntity; - - JdbcEvent(Identifier id, Optional optionalEntity) { - super(id); - this.optionalEntity = optionalEntity; - } +public interface JdbcEvent { /** * The identifier of the entity, triggering this event. Also available via {@link #getSource()}. * * @return the source of the event as an {@link Identifier}. */ - public Identifier getId() { - return (Identifier) getSource(); - } + Identifier getId(); + + /** + * Returns the entity the event was triggered for. + * + * @return will never be {@literal null}. + */ + Optional getOptionalEntity(); } 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 index c103dd6e..1d325f6b 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithEntity.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithEntity.java @@ -18,12 +18,14 @@ package org.springframework.data.jdbc.mapping.event; import java.util.Optional; /** - * A {@link JdbcEvent} which is guaranteed to have an entity. + * A {@link SimpleJdbcEvent} which is guaranteed to have an entity. * * @author Jens Schauder * @since 2.0 */ -public class JdbcEventWithEntity extends JdbcEvent implements WithEntity { +public class JdbcEventWithEntity extends SimpleJdbcEvent implements WithEntity { + + private static final long serialVersionUID = 4891455396602090638L; public JdbcEventWithEntity(Identifier id, Object entity) { super(id, Optional.of(entity)); 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 index 5622672e..d5c1ded0 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java @@ -20,14 +20,30 @@ import java.util.Optional; import org.springframework.data.jdbc.mapping.event.Identifier.Specified; /** - * A {@link JdbcEvent} guaranteed to have an identifier. + * A {@link SimpleJdbcEvent} guaranteed to have an identifier. * * @author Jens Schauder * @since 2.0 */ -public class JdbcEventWithId extends JdbcEvent implements WithId { +public class JdbcEventWithId extends SimpleJdbcEvent implements WithId { + + private static final long serialVersionUID = -8071323168471611098L; + + private final Specified id; public JdbcEventWithId(Specified id, Optional entity) { + super(id, entity); + + this.id = id; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.mapping.event.JdbcEvent#getId() + */ + @Override + public Specified getId() { + return id; } } 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 index 819ef896..e9268bf4 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithIdAndEntity.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithIdAndEntity.java @@ -22,13 +22,15 @@ import java.util.Optional; import org.springframework.data.jdbc.mapping.event.Identifier.Specified; /** - * A {@link JdbcEvent} which is guaranteed to have an identifier and an entity. + * A {@link SimpleJdbcEvent} which is guaranteed to have an identifier and an entity. * * @author Jens Schauder * @since 2.0 */ @Getter -public class JdbcEventWithIdAndEntity extends JdbcEvent implements WithId, WithEntity { +public class JdbcEventWithIdAndEntity extends JdbcEventWithId implements WithEntity { + + private static final long serialVersionUID = -3194462549552515519L; public JdbcEventWithIdAndEntity(Specified id, Object entity) { super(id, Optional.of(entity)); diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/SimpleJdbcEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/SimpleJdbcEvent.java new file mode 100644 index 00000000..bf5df81a --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/SimpleJdbcEvent.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.event; + +import java.util.Optional; + +import org.springframework.context.ApplicationEvent; + +/** + * The common superclass for all events published by JDBC repositories. {@link #getSource} contains the + * {@link Identifier} of the entity triggering the event. + * + * @author Jens Schauder + * @author Oliver Gierke + * @since 2.0 + */ +class SimpleJdbcEvent extends ApplicationEvent implements JdbcEvent { + + private static final long serialVersionUID = -1798807778668751659L; + + private final Object entity; + + SimpleJdbcEvent(Identifier id, Optional entity) { + + super(id); + + this.entity = entity.orElse(null); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.mapping.event.JdbcEvent#getId() + */ + @Override + public Identifier getId() { + return (Identifier) getSource(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.mapping.event.JdbcEvent#getOptionalEntity() + */ + @Override + public Optional getOptionalEntity() { + return Optional.ofNullable(entity); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/SpecifiedIdentifier.java b/src/main/java/org/springframework/data/jdbc/mapping/event/SpecifiedIdentifier.java new file mode 100644 index 00000000..a68c97b2 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/SpecifiedIdentifier.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import lombok.Value; + +import java.util.Optional; + +import org.springframework.data.jdbc.mapping.event.Identifier.Specified; + +/** + * Simple value object for {@link Specified}. + * + * @author Jens Schauder + * @author Oliver Gierke + * @since 2.0 + */ +@Value(staticConstructor = "of") +class SpecifiedIdentifier implements Specified { + + Object value; + + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.mapping.event.Identifier#getOptionalValue() + */ + @Override + public Optional getOptionalValue() { + return Optional.of(value); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/Unset.java b/src/main/java/org/springframework/data/jdbc/mapping/event/Unset.java new file mode 100644 index 00000000..0fd30e40 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/Unset.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; + +/** + * An unset identifier. Always returns {@link Optional#empty()} as value. + * + * @author Jens Schaude + * @author Oliver Gierke + * @since 2.0 + */ +enum Unset implements Identifier { + + UNSET; + + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.mapping.event.Identifier#getOptionalValue() + */ + @Override + public Optional getOptionalValue() { + return Optional.empty(); + } +} 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 index 8e4687ee..568a8437 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/WithEntity.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/WithEntity.java @@ -16,16 +16,18 @@ package org.springframework.data.jdbc.mapping.event; /** - * Interface for {@link JdbcEvent}s which are guaranteed to have an entity. Allows direct access to that entity, without - * going through an {@link java.util.Optional} + * Interface for {@link SimpleJdbcEvent}s which are guaranteed to have an entity. Allows direct access to that entity, + * without going through an {@link java.util.Optional} * * @author Jens Schauder * @since 2.0 */ -public interface WithEntity { +public interface WithEntity extends JdbcEvent { + /** + * @return will never be {@literal null}. + */ default Object getEntity() { - return ((JdbcEvent) this).getOptionalEntity() - .orElseThrow(() -> new IllegalStateException("Entity must not be NULL")); + return getOptionalEntity().orElseThrow(() -> new IllegalStateException("Entity must not be NULL")); } } 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 index d6b5fcf1..f5c857dc 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/WithId.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/WithId.java @@ -18,15 +18,16 @@ package org.springframework.data.jdbc.mapping.event; import org.springframework.data.jdbc.mapping.event.Identifier.Specified; /** - * Interface for {@link JdbcEvent}s which are guaranteed to have a {@link Specified} identifier. Offers direct access to - * the {@link Specified} identifier. + * Interface for {@link SimpleJdbcEvent}s which are guaranteed to have a {@link Specified} identifier. Offers direct + * access to the {@link Specified} identifier. * * @author Jens Schauder * @since 2.0 */ -public interface WithId { +public interface WithId extends JdbcEvent { - default Specified getSpecifiedId() { - return (Specified) ((JdbcEvent) this).getId(); - } + /** + * Events with an identifier will always return a {@link Specified} one. + */ + Specified getId(); } 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 index 64f6126e..cda28019 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java @@ -35,21 +35,26 @@ public class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProper * * @param property must not be {@literal null}. * @param owner must not be {@literal null}. - * @param simpleTypeHolder + * @param simpleTypeHolder must not be {@literal null}. */ - public BasicJdbcPersistentProperty( // - Property property, // - PersistentEntity owner, // - SimpleTypeHolder simpleTypeHolder // - ) { + public BasicJdbcPersistentProperty(Property property, PersistentEntity owner, + SimpleTypeHolder simpleTypeHolder) { super(property, owner, simpleTypeHolder); } + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.AbstractPersistentProperty#createAssociation() + */ @Override protected Association createAssociation() { - return null; + throw new UnsupportedOperationException(); } + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty#getColumnName() + */ 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 3102cfde..ee7a75d3 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 @@ -15,31 +15,26 @@ */ package org.springframework.data.jdbc.mapping.model; -import org.springframework.data.mapping.model.BasicPersistentEntity; -import org.springframework.data.util.TypeInformation; +import org.springframework.data.mapping.PersistentEntity; /** - * Meta data a repository might need for implementing persistence operations for instances of type {@code T} - * * @author Jens Schauder + * @author Oliver Gierke * @since 2.0 */ -public class JdbcPersistentEntity extends BasicPersistentEntity { +public interface JdbcPersistentEntity extends PersistentEntity { - private final String tableName; + /** + * Returns the name of the table backing the given entity. + * + * @return + */ + String getTableName(); - public JdbcPersistentEntity(TypeInformation information) { - - super(information); - - tableName = getType().getSimpleName(); - } - - public String getTableName() { - return tableName; - } - - public String getIdColumn() { - return getRequiredIdProperty().getName(); - } + /** + * Returns the column representing the identifier. + * + * @return will never be {@literal null}. + */ + String getIdColumn(); } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntityImpl.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntityImpl.java new file mode 100644 index 00000000..744423f1 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntityImpl.java @@ -0,0 +1,54 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.model; + +import lombok.Getter; + +import org.springframework.data.mapping.model.BasicPersistentEntity; +import org.springframework.data.util.TypeInformation; + +/** + * Meta data a repository might need for implementing persistence operations for instances of type {@code T} + * + * @author Jens Schauder + * @since 2.0 + */ +public class JdbcPersistentEntityImpl extends BasicPersistentEntity + implements JdbcPersistentEntity { + + private final @Getter String tableName; + + /** + * Creates a new {@link JdbcPersistentEntityImpl} for the given {@link TypeInformation}. + * + * @param information must not be {@literal null}. + */ + public JdbcPersistentEntityImpl(TypeInformation information) { + + super(information); + + tableName = getType().getSimpleName(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity#getIdColumn() + */ + @Override + public String getIdColumn() { + return getRequiredIdProperty().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 e63cd4fa..7a8ffc63 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 @@ -18,10 +18,18 @@ package org.springframework.data.jdbc.mapping.model; import org.springframework.data.mapping.PersistentProperty; /** + * A {@link PersistentProperty} for JDBC. + * * @author Jens Schauder + * @author Oliver Gierke * @since 2.0 */ public interface JdbcPersistentProperty extends PersistentProperty { + /** + * Returns the name of the column backing that property. + * + * @return + */ 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 e94af8ca..b529d918 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java +++ b/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java @@ -15,6 +15,8 @@ */ package org.springframework.data.jdbc.repository; +import lombok.RequiredArgsConstructor; + import java.sql.ResultSet; import java.sql.SQLException; import java.util.Optional; @@ -26,8 +28,10 @@ import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.mapping.PropertyHandler; +import org.springframework.data.mapping.model.ConvertingPropertyAccessor; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mapping.model.ParameterValueProvider; import org.springframework.jdbc.core.RowMapper; @@ -36,65 +40,68 @@ import org.springframework.jdbc.core.RowMapper; * Maps a ResultSet to an entity of type {@code T} * * @author Jens Schauder + * @author Oliver Gierke * @since 2.0 */ +@RequiredArgsConstructor class EntityRowMapper implements RowMapper { private final JdbcPersistentEntity entity; private final EntityInstantiator instantiator = new ClassGeneratingEntityInstantiator(); private final ConversionService conversions = new DefaultConversionService(); - EntityRowMapper(JdbcPersistentEntity entity) { - this.entity = entity; - } - + /* + * (non-Javadoc) + * @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int) + */ @Override - public T mapRow(ResultSet rs, int rowNum) throws SQLException { + public T mapRow(ResultSet resultSet, int rowNumber) throws SQLException { - T t = createInstance(rs); + T result = createInstance(resultSet); entity.doWithProperties((PropertyHandler) property -> { - setProperty(rs, t, property); + + PersistentPropertyAccessor accessor = entity.getPropertyAccessor(result); + ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor(accessor, conversions); + + propertyAccessor.setProperty(property, readFrom(resultSet, property)); }); - return t; + return result; } private T createInstance(ResultSet rs) { - - return instantiator.createInstance(entity, new ResultSetParameterValueProvider(rs)); + return instantiator.createInstance(entity, ResultSetParameterValueProvider.of(rs, conversions)); } - private void setProperty(ResultSet rs, T t, PersistentProperty property) { + private static Optional readFrom(ResultSet resultSet, PersistentProperty property) { try { - - Object converted = conversions.convert(rs.getObject(property.getName()), property.getType()); - entity.getPropertyAccessor(t).setProperty(property, Optional.of(converted)); - } catch (Exception e) { - throw new RuntimeException(String.format("Couldn't set property %s.", property.getName()), e); + return Optional.ofNullable(resultSet.getObject(property.getName())); + } catch (SQLException o_O) { + throw new MappingException(String.format("Could not read property %s from result set!", property), o_O); } } - private class ResultSetParameterValueProvider implements ParameterValueProvider { + @RequiredArgsConstructor(staticName = "of") + private static class ResultSetParameterValueProvider implements ParameterValueProvider { - private final ResultSet rs; - - ResultSetParameterValueProvider(ResultSet rs) { - this.rs = rs; - } + private final ResultSet resultSet; + private final ConversionService conversionService; + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.ParameterValueProvider#getParameterValue(org.springframework.data.mapping.PreferredConstructor.Parameter) + */ @Override public Optional getParameterValue(Parameter parameter) { return parameter.getName().map(name -> { - try { - return conversions.convert(rs.getObject(name), parameter.getType().getType()); - } catch (SQLException e) { - throw new MappingException( // - String.format("Couldn't read column %s from ResultSet.", name) // - ); + try { + return conversionService.convert(resultSet.getObject(name), parameter.getType().getType()); + } catch (SQLException o_O) { + throw new MappingException(String.format("Couldn't read column %s from ResultSet.", name), o_O); } }); } 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 9eca27da..24723ff0 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java +++ b/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java @@ -15,15 +15,15 @@ */ package org.springframework.data.jdbc.repository; +import lombok.NonNull; import lombok.RequiredArgsConstructor; -import java.io.Serializable; import java.sql.ResultSet; import java.sql.SQLException; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.jdbc.mapping.event.AfterCreation; -import org.springframework.data.jdbc.mapping.event.Identifier.Specified; +import org.springframework.data.jdbc.mapping.event.Identifier; import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation; import org.springframework.jdbc.core.RowMapper; @@ -35,18 +35,22 @@ import org.springframework.jdbc.core.RowMapper; * @since 2.0 */ @RequiredArgsConstructor -public class EventPublishingEntityRowMapper implements RowMapper { +public class EventPublishingEntityRowMapper implements RowMapper { - private final RowMapper delegate; - private final JdbcPersistentEntityInformation entityInformation; - private final ApplicationEventPublisher publisher; + private final @NonNull RowMapper delegate; + private final @NonNull JdbcPersistentEntityInformation entityInformation; + private final @NonNull ApplicationEventPublisher publisher; + /* + * (non-Javadoc) + * @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int) + */ @Override public T mapRow(ResultSet resultSet, int i) throws SQLException { T instance = delegate.mapRow(resultSet, i); - publisher.publishEvent(new AfterCreation(new Specified(entityInformation.getId(instance)), instance)); + publisher.publishEvent(new AfterCreation(Identifier.of(entityInformation.getRequiredId(instance)), instance)); return instance; } 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 e6109cea..1f585a32 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java +++ b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java @@ -22,7 +22,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; -import java.util.stream.StreamSupport; import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.convert.ConversionService; @@ -35,13 +34,16 @@ import org.springframework.data.jdbc.mapping.event.AfterUpdate; import org.springframework.data.jdbc.mapping.event.BeforeDelete; import org.springframework.data.jdbc.mapping.event.BeforeInsert; import org.springframework.data.jdbc.mapping.event.BeforeUpdate; +import org.springframework.data.jdbc.mapping.event.Identifier; import org.springframework.data.jdbc.mapping.event.Identifier.Specified; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; +import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityImpl; import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; import org.springframework.data.jdbc.repository.support.BasicJdbcPersistentEntityInformation; import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.util.Streamable; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.support.GeneratedKeyHolder; @@ -66,11 +68,15 @@ public class SimpleJdbcRepository implements CrudRep private final ApplicationEventPublisher publisher; private final ConversionService conversions = new DefaultConversionService(); - public SimpleJdbcRepository( // - JdbcPersistentEntity persistentEntity, // - NamedParameterJdbcOperations jdbcOperations, // - ApplicationEventPublisher publisher // - ) { + /** + * Creates a new {@link SimpleJdbcRepository} for the given {@link JdbcPersistentEntityImpl} + * + * @param persistentEntity + * @param jdbcOperations + * @param 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."); @@ -85,6 +91,10 @@ public class SimpleJdbcRepository implements CrudRep this.sql = new SqlGenerator(persistentEntity); } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#save(S) + */ @Override public S save(S instance) { @@ -97,6 +107,10 @@ public class SimpleJdbcRepository implements CrudRep return instance; } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable) + */ @Override public Iterable save(Iterable entities) { @@ -105,6 +119,10 @@ public class SimpleJdbcRepository implements CrudRep return savedEntities; } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#findOne(java.io.Serializable) + */ @Override public Optional findOne(ID id) { @@ -112,43 +130,69 @@ public class SimpleJdbcRepository implements CrudRep .ofNullable(operations.queryForObject(sql.getFindOne(), new MapSqlParameterSource("id", id), entityRowMapper)); } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#exists(java.io.Serializable) + */ @Override public boolean exists(ID id) { - return operations.queryForObject(sql.getExists(), new MapSqlParameterSource("id", id), Boolean.class); } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#findAll() + */ @Override public Iterable findAll() { return operations.query(sql.getFindAll(), entityRowMapper); } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable) + */ @Override public Iterable findAll(Iterable ids) { return operations.query(sql.getFindAllInList(), new MapSqlParameterSource("ids", ids), entityRowMapper); } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#count() + */ @Override public long count() { return operations.getJdbcOperations().queryForObject(sql.getCount(), Long.class); } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable) + */ @Override public void delete(ID id) { - doDelete(new Specified(id), Optional.empty()); + doDelete(Identifier.of(id), Optional.empty()); } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#delete(java.lang.Object) + */ @Override public void delete(T instance) { - doDelete(new Specified(entityInformation.getId(instance).orElse(null)), Optional.of(instance)); + doDelete(Identifier.of(entityInformation.getRequiredId(instance)), Optional.of(instance)); } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable) + */ @Override public void delete(Iterable entities) { - List idList = StreamSupport.stream(entities.spliterator(), false) // - .map(e -> entityInformation.getId(e) - .orElseThrow(() -> new IllegalArgumentException(String.format("Can't obtain id for %s", e)))) // + List idList = Streamable.of(entities).stream() // + .map(e -> entityInformation.getRequiredId(e)) // .collect(Collectors.toList()); MapSqlParameterSource sqlParameterSource = new MapSqlParameterSource("ids", idList); @@ -189,7 +233,7 @@ public class SimpleJdbcRepository implements CrudRep throw new IllegalStateException(String.format(ENTITY_NEW_AFTER_INSERT, persistentEntity)); } - publisher.publishEvent(new AfterInsert(new Specified(entityInformation.getId(instance)), instance)); + publisher.publishEvent(new AfterInsert(Identifier.of(entityInformation.getRequiredId(instance)), instance)); } private ID getIdValueOrNull(S instance) { @@ -211,27 +255,26 @@ public class SimpleJdbcRepository implements CrudRep try { - Object idValueFromJdbc = getIdFromHolder(holder); - if (idValueFromJdbc != null) { + getIdFromHolder(holder).ifPresent(it -> { Class targetType = persistentEntity.getRequiredIdProperty().getType(); - Object converted = convert(idValueFromJdbc, targetType); + Object converted = convert(it, targetType); entityInformation.setId(instance, Optional.of(converted)); - } + }); } catch (NonTransientDataAccessException e) { - throw new UnableToSetIdException("Unable to set id of " + instance, e); + throw new UnableToSetId("Unable to set id of " + instance, e); } } - private Object getIdFromHolder(KeyHolder holder) { + private Optional getIdFromHolder(KeyHolder holder) { try { - // mysql just returns one value with a special name - return holder.getKey(); + // MySQL just returns one value with a special name + return Optional.ofNullable(holder.getKey()); } catch (InvalidDataAccessApiUsageException e) { - // postgress returns a value for each column - return holder.getKeys().get(persistentEntity.getIdColumn()); + // Postgres returns a value for each column + return Optional.ofNullable(holder.getKeys().get(persistentEntity.getIdColumn())); } } @@ -249,7 +292,7 @@ public class SimpleJdbcRepository implements CrudRep private void doUpdate(S instance) { - Specified specifiedId = new Specified(entityInformation.getId(instance)); + Specified specifiedId = Identifier.of(entityInformation.getRequiredId(instance)); publisher.publishEvent(new BeforeUpdate(specifiedId, instance)); operations.update(sql.getUpdate(), getPropertyMap(instance)); publisher.publishEvent(new AfterUpdate(specifiedId, instance)); 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 a67bdd88..89ad5918 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java +++ b/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java @@ -51,6 +51,7 @@ class SqlGenerator { private final List nonIdPropertyNames = new ArrayList<>(); SqlGenerator(JdbcPersistentEntity entity) { + this.entity = entity; initPropertyNames(); @@ -72,6 +73,7 @@ class SqlGenerator { } private void initPropertyNames() { + entity.doWithProperties((PropertyHandler) p -> { propertyNames.add(p.getName()); if (!entity.isIdProperty(p)) { @@ -143,8 +145,7 @@ class SqlGenerator { private String createInsertSql() { String insertTemplate = "insert into %s (%s) values (%s)"; - - String tableColumns = nonIdPropertyNames.stream().collect(Collectors.joining(", ")); + String tableColumns = String.join(", ", nonIdPropertyNames); String parameterNames = nonIdPropertyNames.stream().collect(Collectors.joining(", :", ":", "")); return String.format(insertTemplate, entity.getTableName(), tableColumns, parameterNames); @@ -154,7 +155,8 @@ class SqlGenerator { String updateTemplate = "update %s set %s where %s = :%s"; - String setClause = propertyNames.stream().map(n -> String.format("%s = :%s", n, n)) + String setClause = propertyNames.stream()// + .map(n -> String.format("%s = :%s", n, n))// .collect(Collectors.joining(", ")); return String.format(updateTemplate, entity.getTableName(), setClause, entity.getIdColumn(), entity.getIdColumn()); diff --git a/src/main/java/org/springframework/data/jdbc/repository/UnableToSetIdException.java b/src/main/java/org/springframework/data/jdbc/repository/UnableToSetId.java similarity index 81% rename from src/main/java/org/springframework/data/jdbc/repository/UnableToSetIdException.java rename to src/main/java/org/springframework/data/jdbc/repository/UnableToSetId.java index 1bbb0126..e6e22c1b 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/UnableToSetIdException.java +++ b/src/main/java/org/springframework/data/jdbc/repository/UnableToSetId.java @@ -23,9 +23,11 @@ import org.springframework.dao.NonTransientDataAccessException; * @author Jens Schauder * @since 2.0 */ -public class UnableToSetIdException extends NonTransientDataAccessException { +public class UnableToSetId extends NonTransientDataAccessException { - public UnableToSetIdException(String message, Throwable cause) { + private static final long serialVersionUID = 3285001352389420376L; + + public UnableToSetId(String message, Throwable cause) { super(message, cause); } } 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 index 04178886..7ac1539e 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/BasicJdbcPersistentEntityInformation.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/BasicJdbcPersistentEntityInformation.java @@ -31,11 +31,16 @@ public class BasicJdbcPersistentEntityInformation ex private final JdbcPersistentEntity persistentEntity; public BasicJdbcPersistentEntityInformation(JdbcPersistentEntity persistentEntity) { + super(persistentEntity); this.persistentEntity = persistentEntity; } + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation#setId(java.lang.Object, java.util.Optional) + */ @Override public void setId(T instance, Optional value) { persistentEntity.getPropertyAccessor(instance).setProperty(persistentEntity.getRequiredIdProperty(), 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 62227faf..78ccae92 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 @@ -27,4 +27,16 @@ import org.springframework.data.repository.core.EntityInformation; public interface JdbcPersistentEntityInformation extends EntityInformation { void setId(T instance, Optional value); + + /** + * Returns the identifier of the given entity or throws and exception if it can't be obtained. + * + * @param entity must not be {@literal null}. + * @return the identifier of the given entity + * @throws IllegalArgumentException in case no identifier can be obtained for the given entity. + */ + default ID getRequiredId(T entity) { + return getId(entity).orElseThrow(() -> new IllegalArgumentException( + String.format("Could not obtain required identifier from entity %s!", entity))); + } } 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 3f521eb9..fe6f94ba 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java +++ b/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java @@ -1,14 +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.repository; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import lombok.Data; +import lombok.Value; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Optional; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.mapping.event.AfterCreation; @@ -16,13 +35,17 @@ import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInfo import org.springframework.jdbc.core.RowMapper; /** + * Unit tests for {@link EventPublishingEntityRowMapper}. + * * @author Jens Schauder + * @author Oliver Gierke */ +@RunWith(MockitoJUnitRunner.class) public class EventPublishingEntityRowMapperTest { - RowMapper rowMapperDelegate = mock(RowMapper.class); - JdbcPersistentEntityInformation entityInformation = mock(JdbcPersistentEntityInformation.class); - ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class); + @Mock RowMapper rowMapperDelegate; + @Mock JdbcPersistentEntityInformation entityInformation; + @Mock ApplicationEventPublisher publisher; @Test // DATAJDBC-99 public void eventGetsPublishedAfterInstantiation() throws SQLException { @@ -30,10 +53,8 @@ public class EventPublishingEntityRowMapperTest { when(rowMapperDelegate.mapRow(any(ResultSet.class), anyInt())).thenReturn(new DummyEntity(1L)); when(entityInformation.getId(any())).thenReturn(Optional.of(1L)); - EventPublishingEntityRowMapper rowMapper = new EventPublishingEntityRowMapper<>( // - rowMapperDelegate, // - entityInformation, // - publisher); + EventPublishingEntityRowMapper rowMapper = new EventPublishingEntityRowMapper<>(rowMapperDelegate, + entityInformation, publisher); ResultSet resultSet = mock(ResultSet.class); rowMapper.mapRow(resultSet, 1); @@ -41,8 +62,8 @@ public class EventPublishingEntityRowMapperTest { verify(publisher).publishEvent(isA(AfterCreation.class)); } - @Data + @Value static class DummyEntity { - @Id private final Long Id; + @Id Long Id; } } 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 1d289b77..a615a1eb 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java @@ -15,27 +15,21 @@ */ package org.springframework.data.jdbc.repository; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +import static org.assertj.core.api.Assertions.*; import lombok.Data; - -import java.util.Optional; - -import javax.sql.DataSource; +import lombok.Value; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; 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.jdbc.testing.TestConfiguration; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.test.context.ContextConfiguration; @@ -47,9 +41,31 @@ import org.springframework.test.context.junit4.rules.SpringMethodRule; * * @author Jens Schauder */ -@ContextConfiguration(classes = TestConfiguration.class) +@ContextConfiguration public class JdbcRepositoryIdGenerationIntegrationTests { + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryIdGenerationIntegrationTests.class; + } + + @Bean + ReadOnlyIdEntityRepository readOnlyIdRepository() { + return factory.getRepository(ReadOnlyIdEntityRepository.class); + } + + @Bean + PrimitiveIdEntityRepository primitiveIdRepository() { + return factory.getRepository(PrimitiveIdEntityRepository.class); + } + } + @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); @Rule public SpringMethodRule methodRule = new SpringMethodRule(); @@ -60,17 +76,15 @@ public class JdbcRepositoryIdGenerationIntegrationTests { @Test // DATAJDBC-98 public void idWithoutSetterGetsSet() { - ReadOnlyIdEntity entity = new ReadOnlyIdEntity(null); - entity.setName("Entity Name"); - entity = readOnlyIdrepository.save(entity); + ReadOnlyIdEntity entity = readOnlyIdrepository.save(new ReadOnlyIdEntity(null, "Entity Name")); assertThat(entity.getId()).isNotNull(); - Optional reloadedEntity = readOnlyIdrepository.findOne(entity.getId()); + assertThat(readOnlyIdrepository.findOne(entity.getId())).hasValueSatisfying(it -> { - assertTrue(reloadedEntity.isPresent()); - assertEquals(entity.getId(), reloadedEntity.get().getId()); - assertEquals(entity.getName(), reloadedEntity.get().getName()); + assertThat(it.getId()).isEqualTo(entity.getId()); + assertThat(it.getName()).isEqualTo(entity.getName()); + }); } @Test // DATAJDBC-98 @@ -78,26 +92,26 @@ public class JdbcRepositoryIdGenerationIntegrationTests { PrimitiveIdEntity entity = new PrimitiveIdEntity(0); entity.setName("Entity Name"); - entity = primitiveIdRepository.save(entity); - assertThat(entity.getId()).isNotNull(); - assertThat(entity.getId()).isNotEqualTo(0L); + PrimitiveIdEntity saved = primitiveIdRepository.save(entity); - Optional reloadedEntity = primitiveIdRepository.findOne(entity.getId()); + assertThat(saved.getId()).isNotEqualTo(0L); - assertTrue(reloadedEntity.isPresent()); - assertEquals(entity.getId(), reloadedEntity.get().getId()); - assertEquals(entity.getName(), reloadedEntity.get().getName()); + assertThat(primitiveIdRepository.findOne(saved.getId())).hasValueSatisfying(it -> { + + assertThat(it.getId()).isEqualTo(saved.getId()); + assertThat(it.getName()).isEqualTo(saved.getName()); + }); } - private interface ReadOnlyIdEntityRepository extends CrudRepository {} - private interface PrimitiveIdEntityRepository extends CrudRepository {} - @Data + public interface ReadOnlyIdEntityRepository extends CrudRepository {} + + @Value static class ReadOnlyIdEntity { - @Id private final Long id; + @Id Long id; String name; } @@ -107,33 +121,4 @@ public class JdbcRepositoryIdGenerationIntegrationTests { @Id private final long id; String name; } - - @Configuration - @ComponentScan("org.springframework.data.jdbc.testing") - static class TestConfiguration { - - @Bean - Class testClass() { - return JdbcRepositoryIdGenerationIntegrationTests.class; - } - - @Bean - NamedParameterJdbcTemplate template(DataSource db) { - return new NamedParameterJdbcTemplate(db); - } - - @Bean - ReadOnlyIdEntityRepository readOnlyIdRepository(DataSource 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 611310c6..d0e37d74 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -16,37 +16,27 @@ 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.*; +import static org.assertj.core.api.Assertions.*; import lombok.Data; -import java.util.Optional; - -import javax.sql.DataSource; - import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; 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.jdbc.testing.TestConfiguration; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import org.springframework.jdbc.datasource.DataSourceTransactionManager; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.rules.SpringClassRule; import org.springframework.test.context.junit4.rules.SpringMethodRule; import org.springframework.test.jdbc.JdbcTestUtils; -import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.Transactional; /** @@ -54,61 +44,58 @@ import org.springframework.transaction.annotation.Transactional; * * @author Jens Schauder */ -@ContextConfiguration(classes = TestConfiguration.class) +@ContextConfiguration @Transactional public class JdbcRepositoryIntegrationTests { + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryIntegrationTests.class; + } + + @Bean + DummyEntityRepository dummyEntityRepository() { + return factory.getRepository(DummyEntityRepository.class); + } + } + @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); @Rule public SpringMethodRule methodRule = new SpringMethodRule(); @Autowired NamedParameterJdbcTemplate template; @Autowired DummyEntityRepository repository; - private DummyEntity entity = createDummyEntity(); - - private static DummyEntityRepository createRepository(EmbeddedDatabase db) { - - return new JdbcRepositoryFactory(new NamedParameterJdbcTemplate(db), mock(ApplicationEventPublisher.class)) - .getRepository(DummyEntityRepository.class); - } - - private static DummyEntity createDummyEntity() { - - DummyEntity entity = new DummyEntity(); - entity.setName("Entity Name"); - return entity; - } - @Test // DATAJDBC-95 public void savesAnEntity() { - entity = repository.save(entity); + DummyEntity entity = repository.save(createDummyEntity()); - int count = JdbcTestUtils.countRowsInTableWhere( // - (JdbcTemplate) template.getJdbcOperations(), // - "dummyentity", // - "idProp = " + entity.getIdProp() // - ); - - assertEquals(1, count); + assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummyentity", + "idProp = " + entity.getIdProp())).isEqualTo(1); } @Test // DATAJDBC-95 public void saveAndLoadAnEntity() { - entity = repository.save(entity); + DummyEntity entity = repository.save(createDummyEntity()); - Optional reloadedEntity = repository.findOne(entity.getIdProp()); + assertThat(repository.findOne(entity.getIdProp())).hasValueSatisfying(it -> { - assertTrue(reloadedEntity.isPresent()); - - assertEquals(entity.getIdProp(), reloadedEntity.get().getIdProp()); - assertEquals(entity.getName(), reloadedEntity.get().getName()); + assertThat(it.getIdProp()).isEqualTo(entity.getIdProp()); + assertThat(it.getName()).isEqualTo(entity.getName()); + }); } @Test // DATAJDBC-97 public void savesManyEntities() { + DummyEntity entity = createDummyEntity(); DummyEntity other = createDummyEntity(); repository.save(asList(entity, other)); @@ -121,35 +108,34 @@ public class JdbcRepositoryIntegrationTests { @Test // DATAJDBC-97 public void existsReturnsTrueIffEntityExists() { - entity = repository.save(entity); + DummyEntity entity = repository.save(createDummyEntity()); - assertTrue(repository.exists(entity.getIdProp())); - assertFalse(repository.exists(entity.getIdProp() + 1)); + assertThat(repository.exists(entity.getIdProp())).isTrue(); + assertThat(repository.exists(entity.getIdProp() + 1)).isFalse(); } @Test // DATAJDBC-97 public void findAllFindsAllEntities() { - DummyEntity other = createDummyEntity(); - - other = repository.save(other); - entity = repository.save(entity); + DummyEntity entity = repository.save(createDummyEntity()); + DummyEntity other = repository.save(createDummyEntity()); Iterable all = repository.findAll(); - assertThat(all).extracting("idProp").containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); + assertThat(all)// + .extracting(DummyEntity::getIdProp)// + .containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); } @Test // DATAJDBC-97 public void findAllFindsAllSpecifiedEntities() { - entity = repository.save(entity); - DummyEntity two = repository.save(createDummyEntity()); - DummyEntity three = repository.save(createDummyEntity()); + DummyEntity entity = repository.save(createDummyEntity()); + DummyEntity other = repository.save(createDummyEntity()); - Iterable all = repository.findAll(asList(entity.getIdProp(), three.getIdProp())); - - assertThat(all).extracting("idProp").containsExactlyInAnyOrder(entity.getIdProp(), three.getIdProp()); + assertThat(repository.findAll(asList(entity.getIdProp(), other.getIdProp())))// + .extracting(DummyEntity::getIdProp)// + .containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); } @Test // DATAJDBC-97 @@ -157,7 +143,7 @@ public class JdbcRepositoryIntegrationTests { repository.save(createDummyEntity()); repository.save(createDummyEntity()); - repository.save(entity); + repository.save(createDummyEntity()); assertThat(repository.count()).isEqualTo(3L); } @@ -165,7 +151,7 @@ public class JdbcRepositoryIntegrationTests { @Test // DATAJDBC-97 public void deleteById() { - entity = repository.save(entity); + DummyEntity one = repository.save(createDummyEntity()); DummyEntity two = repository.save(createDummyEntity()); DummyEntity three = repository.save(createDummyEntity()); @@ -173,17 +159,17 @@ public class JdbcRepositoryIntegrationTests { assertThat(repository.findAll()) // .extracting(DummyEntity::getIdProp) // - .containsExactlyInAnyOrder(entity.getIdProp(), three.getIdProp()); + .containsExactlyInAnyOrder(one.getIdProp(), three.getIdProp()); } @Test // DATAJDBC-97 public void deleteByEntity() { - entity = repository.save(entity); + DummyEntity one = repository.save(createDummyEntity()); DummyEntity two = repository.save(createDummyEntity()); DummyEntity three = repository.save(createDummyEntity()); - repository.delete(entity); + repository.delete(one); assertThat(repository.findAll()) // .extracting(DummyEntity::getIdProp) // @@ -193,11 +179,11 @@ public class JdbcRepositoryIntegrationTests { @Test // DATAJDBC-97 public void deleteByList() { - repository.save(entity); + DummyEntity one = repository.save(createDummyEntity()); DummyEntity two = repository.save(createDummyEntity()); DummyEntity three = repository.save(createDummyEntity()); - repository.delete(asList(entity, three)); + repository.delete(asList(one, three)); assertThat(repository.findAll()) // .extracting(DummyEntity::getIdProp) // @@ -207,9 +193,11 @@ public class JdbcRepositoryIntegrationTests { @Test // DATAJDBC-97 public void deleteAll() { - repository.save(entity); repository.save(createDummyEntity()); repository.save(createDummyEntity()); + repository.save(createDummyEntity()); + + assertThat(repository.findAll()).isNotEmpty(); repository.deleteAll(); @@ -219,22 +207,20 @@ public class JdbcRepositoryIntegrationTests { @Test // DATAJDBC-98 public void update() { - entity = repository.save(entity); + DummyEntity entity = repository.save(createDummyEntity()); entity.setName("something else"); + DummyEntity saved = repository.save(entity); - entity = repository.save(entity); - - Optional reloaded = repository.findOne(entity.getIdProp()); - - assertTrue(reloaded.isPresent()); - assertThat(reloaded.get().getName()).isEqualTo(entity.getName()); + assertThat(repository.findOne(entity.getIdProp())).hasValueSatisfying(it -> { + assertThat(it.getName()).isEqualTo(saved.getName()); + }); } @Test // DATAJDBC-98 public void updateMany() { - entity = repository.save(entity); + DummyEntity entity = repository.save(createDummyEntity()); DummyEntity other = repository.save(createDummyEntity()); entity.setName("something else"); @@ -247,7 +233,14 @@ public class JdbcRepositoryIntegrationTests { .containsExactlyInAnyOrder(entity.getName(), other.getName()); } - private interface DummyEntityRepository extends CrudRepository {} + private static DummyEntity createDummyEntity() { + + DummyEntity entity = new DummyEntity(); + entity.setName("Entity Name"); + return entity; + } + + interface DummyEntityRepository extends CrudRepository {} @Data static class DummyEntity { @@ -255,31 +248,4 @@ public class JdbcRepositoryIntegrationTests { String name; @Id private Long idProp; } - - @Configuration - @ComponentScan("org.springframework.data.jdbc.testing") - static class TestConfiguration { - - @Bean - Class testClass() { - return JdbcRepositoryIntegrationTests.class; - } - - @Bean - NamedParameterJdbcTemplate template(DataSource 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 e5accf7b..86b2f56d 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java @@ -1,9 +1,9 @@ package org.springframework.data.jdbc.repository; import static java.util.Arrays.*; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import static org.springframework.util.Assert.*; import lombok.Data; @@ -22,7 +22,7 @@ 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.Identifier; import org.springframework.data.jdbc.mapping.event.JdbcEvent; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.repository.CrudRepository; @@ -35,9 +35,9 @@ import org.springframework.jdbc.support.KeyHolder; */ public class SimpleJdbcRepositoryEventsUnitTests { - private FakePublisher publisher = new FakePublisher(); + FakePublisher publisher = new FakePublisher(); - private DummyEntityRepository repository; + DummyEntityRepository repository; @Before public void before() { @@ -47,7 +47,58 @@ public class SimpleJdbcRepositoryEventsUnitTests { repository = factory.getRepository(DummyEntityRepository.class); } - private NamedParameterJdbcOperations createIdGeneratingOperations() { + @Test // DATAJDBC-99 + public void publishesEventsOnSave() { + + DummyEntity entity = new DummyEntity(23L); + + repository.save(entity); + + assertThat(publisher.events.get(0)).isInstanceOf(BeforeUpdate.class); + assertThat(publisher.events.get(1)).isInstanceOf(AfterUpdate.class); + } + + @Test // DATAJDBC-99 + public void publishesEventsOnSaveMany() { + + DummyEntity entity1 = new DummyEntity(null); + DummyEntity entity2 = new DummyEntity(23L); + + repository.save(asList(entity1, entity2)); + + assertThat(publisher.events.get(0)).isInstanceOf(BeforeInsert.class); + assertThat(publisher.events.get(1)).isInstanceOf(AfterInsert.class); + assertThat(publisher.events.get(2)).isInstanceOf(BeforeUpdate.class); + assertThat(publisher.events.get(3)).isInstanceOf(AfterUpdate.class); + } + + @Test // DATAJDBC-99 + public void publishesEventsOnDelete() { + + DummyEntity entity = new DummyEntity(23L); + + repository.delete(entity); + + assertThat(publisher.events.get(0)).isInstanceOf(BeforeDelete.class); + assertThat(publisher.events.get(1)).isInstanceOf(AfterDelete.class); + + assertThat(publisher.events.get(0).getOptionalEntity()).hasValue(entity); + assertThat(publisher.events.get(1).getOptionalEntity()).hasValue(entity); + + assertThat(publisher.events.get(0).getId()).isEqualTo(Identifier.of(23L)); + assertThat(publisher.events.get(1).getId()).isEqualTo(Identifier.of(23L)); + } + + @Test // DATAJDBC-99 + public void publishesEventsOnDeleteById() { + + repository.delete(23L); + + assertThat(publisher.events.get(0)).isInstanceOf(BeforeDelete.class); + assertThat(publisher.events.get(1)).isInstanceOf(AfterDelete.class); + } + + private static NamedParameterJdbcOperations createIdGeneratingOperations() { Answer setIdInKeyHolder = invocation -> { @@ -65,62 +116,11 @@ public class SimpleJdbcRepositoryEventsUnitTests { return operations; } - @Test // DATAJDBC-99 - public void publishesEventsOnSave() { - - DummyEntity entity = new DummyEntity(23L); - - repository.save(entity); - - 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(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)); - } - - @Test // DATAJDBC-99 - public void publishesEventsOnDelete() { - - DummyEntity entity = new DummyEntity(23L); - - repository.delete(entity); - - isInstanceOf(BeforeDelete.class, publisher.events.get(0)); - isInstanceOf(AfterDelete.class, publisher.events.get(1)); - - assertEquals(entity, publisher.events.get(0).getOptionalEntity().get()); - assertEquals(entity, publisher.events.get(1).getOptionalEntity().get()); - - 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(BeforeDelete.class, publisher.events.get(0)); - isInstanceOf(AfterDelete.class, publisher.events.get(1)); - } - - private interface DummyEntityRepository extends CrudRepository {} + interface DummyEntityRepository extends CrudRepository {} @Data - private static class DummyEntity { - @Id private final Long id; + static class DummyEntity { + private final @Id Long id; } static class FakePublisher implements ApplicationEventPublisher { diff --git a/src/test/java/org/springframework/data/jdbc/testing/DataSourceConfiguration.java b/src/test/java/org/springframework/data/jdbc/testing/DataSourceConfiguration.java new file mode 100644 index 00000000..01db760c --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/testing/DataSourceConfiguration.java @@ -0,0 +1,79 @@ +/* + * 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.testing; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ClassPathResource; +import org.springframework.jdbc.datasource.init.DataSourceInitializer; +import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; + +/** + * Basic configuration expecting subclasses to provide a {@link DataSource} via {@link #createDataSource()} to be + * exposed to the {@link ApplicationContext}. + * + * @author Jens Schauder + * @author Oliver Gierke + */ +@Configuration +abstract class DataSourceConfiguration { + + @Autowired Class testClass; + @Autowired Environment environment; + + @Bean + DataSource dataSource() { + return createDataSource(); + } + + @Bean + DataSourceInitializer initializer() { + + DataSourceInitializer initializer = new DataSourceInitializer(); + initializer.setDataSource(dataSource()); + + String[] activeProfiles = environment.getActiveProfiles(); + String profile = activeProfiles.length == 0 ? "" : activeProfiles[0]; + + ClassPathResource script = new ClassPathResource(TestUtils.createScriptName(testClass, profile)); + ResourceDatabasePopulator populator = new ResourceDatabasePopulator(script); + customizePopulator(populator); + initializer.setDatabasePopulator(populator); + + return initializer; + } + + /** + * Return the {@link DataSource} to be exposed as a Spring bean. + * + * @return + */ + protected abstract DataSource createDataSource(); + + /** + * Callback to customize the {@link ResourceDatabasePopulator} before it will be applied to the {@link DataSource}. It + * will be pre-populated with a SQL script derived from the name of the current test class and the activated Spring + * profile. + * + * @param populator will never be {@literal null}. + */ + protected void customizePopulator(ResourceDatabasePopulator populator) {} +} diff --git a/src/test/java/org/springframework/data/jdbc/testing/DataSourceFactoryBean.java b/src/test/java/org/springframework/data/jdbc/testing/DataSourceFactoryBean.java deleted file mode 100644 index df4cbe5b..00000000 --- a/src/test/java/org/springframework/data/jdbc/testing/DataSourceFactoryBean.java +++ /dev/null @@ -1,59 +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.testing; - -import javax.sql.DataSource; - -import org.springframework.beans.factory.FactoryBean; - -/** - * @author Jens Schauder - */ -abstract class DataSourceFactoryBean implements FactoryBean { - - private final Class testClass; - - DataSourceFactoryBean(Class testClass) { - this.testClass = testClass; - } - - private DataSource createDataSource() { - return create(createScriptName(testClass, scriptSuffix())); - } - - abstract String scriptSuffix(); - - abstract DataSource create(String scriptName); - - private String createScriptName(Class testClass, String databaseType) { - - return String.format( // - "%s/%s-%s.sql", // - testClass.getPackage().getName(), // - testClass.getSimpleName(), // - databaseType.toLowerCase()); - } - - @Override - public DataSource getObject() throws Exception { - return createDataSource(); - } - - @Override - public Class getObjectType() { - return DataSource.class; - } -} diff --git a/src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceFactoryBean.java b/src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceConfiguration.java similarity index 73% rename from src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceFactoryBean.java rename to src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceConfiguration.java index dbe832a0..0b92c63c 100644 --- a/src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceFactoryBean.java +++ b/src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceConfiguration.java @@ -17,36 +17,34 @@ package org.springframework.data.jdbc.testing; import javax.sql.DataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.stereotype.Component; /** + * {@link DataSource} setup for HSQL. + * * @author Jens Schauder + * @author Oliver Gierke */ -@Component +@Configuration @Profile({ "hsql", "default" }) -class HsqlDataSourceFactoryBean extends DataSourceFactoryBean { +class HsqlDataSourceConfiguration { - HsqlDataSourceFactoryBean(Class testClass) { - super(testClass); - } + @Autowired Class context; - @Override - String scriptSuffix() { - return "hsql"; - } - - @Override - DataSource create(String scriptName) { + @Bean + DataSource dataSource() { return new EmbeddedDatabaseBuilder() // .generateUniqueName(true) // .setType(EmbeddedDatabaseType.HSQL) // .setScriptEncoding("UTF-8") // .ignoreFailedDrops(true) // - .addScript(scriptName) // + .addScript(TestUtils.createScriptName(context, "hsql")) // .build(); } } diff --git a/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java b/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java new file mode 100644 index 00000000..2bd81a50 --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java @@ -0,0 +1,66 @@ +/* + * 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.testing; + +import javax.annotation.PostConstruct; +import javax.sql.DataSource; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.core.io.ClassPathResource; +import org.springframework.jdbc.datasource.init.DataSourceInitializer; +import org.springframework.jdbc.datasource.init.DatabasePopulator; +import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; + +import com.mysql.jdbc.jdbc2.optional.MysqlDataSource; + +/** + * {@link DataSource} setup for MySQL. + * + * @author Jens Schauder + * @author Oliver Gierke + */ +@Configuration +@Profile("mysql") +class MySqlDataSourceConfiguration extends DataSourceConfiguration { + + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.testing.DataSourceConfiguration#createDataSource() + */ + protected DataSource createDataSource() { + + MysqlDataSource dataSource = new MysqlDataSource(); + dataSource.setUrl("jdbc:mysql:///test?user=root"); + + return dataSource; + } + + @PostConstruct + public void initDatabase() { + + MysqlDataSource dataSource = new MysqlDataSource(); + dataSource.setUrl("jdbc:mysql:///?user=root"); + + ClassPathResource createScript = new ClassPathResource("create-mysql.sql"); + DatabasePopulator databasePopulator = new ResourceDatabasePopulator(createScript); + + DataSourceInitializer initializer = new DataSourceInitializer(); + initializer.setDatabasePopulator(databasePopulator); + initializer.setDataSource(dataSource); + initializer.afterPropertiesSet(); + } +} diff --git a/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceFactoryBean.java b/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceFactoryBean.java deleted file mode 100644 index 7ec34489..00000000 --- a/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceFactoryBean.java +++ /dev/null @@ -1,84 +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.testing; - -import java.sql.Connection; -import java.sql.SQLException; - -import javax.sql.DataSource; - -import org.springframework.context.annotation.Profile; -import org.springframework.core.io.DefaultResourceLoader; -import org.springframework.jdbc.datasource.init.ScriptUtils; -import org.springframework.stereotype.Component; - -import com.mysql.jdbc.jdbc2.optional.MysqlDataSource; - -/** - * @author Jens Schauder - */ -@Component -@Profile("mysql") -class MySqlDataSourceFactoryBean extends DataSourceFactoryBean { - - public static final String ROOT_URL = "jdbc:mysql:///?user=root"; - public static final String COULDN_T_CREATE_DATABASE = // - "Couldn't create database. Maybe you don't have a MySql database running, reachable at %s"; - private static final DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); - private static final String TEST_URL = "jdbc:mysql:///test?user=root"; - - MySqlDataSourceFactoryBean(Class testClass) { - super(testClass); - } - - @Override - String scriptSuffix() { - return "mysql"; - } - - @Override - DataSource create(String scriptName) { - - createDatabase(); - return setupDatabase(scriptName); - } - - private MysqlDataSource setupDatabase(String scriptName) { - - MysqlDataSource ds = new MysqlDataSource(); - ds.setUrl(TEST_URL); - - try (Connection connection = ds.getConnection()) { - ScriptUtils.executeSqlScript(connection, resourceLoader.getResource(scriptName)); - } catch (SQLException e) { - throw new RuntimeException("Couldn't setup database", e); - } - - return ds; - } - - private void createDatabase() { - - MysqlDataSource initialDs = new MysqlDataSource(); - initialDs.setUrl(ROOT_URL); - - try (Connection connection = initialDs.getConnection()) { - ScriptUtils.executeSqlScript(connection, resourceLoader.getResource("create-mysql.sql")); - } catch (SQLException e) { - throw new RuntimeException(String.format(COULDN_T_CREATE_DATABASE, ROOT_URL), e); - } - } -} diff --git a/src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceConfiguration.java b/src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceConfiguration.java new file mode 100644 index 00000000..6a7f1925 --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceConfiguration.java @@ -0,0 +1,55 @@ +/* + * 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.testing; + +import javax.sql.DataSource; + +import org.postgresql.ds.PGSimpleDataSource; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; + +/** + * {@link DataSource} setup for PostgreSQL + * + * @author Jens Schauder + * @author Oliver Gierke + */ +@Configuration +@Profile("postgres") +public class PostgresDataSourceConfiguration extends DataSourceConfiguration { + + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.testing.DataSourceConfiguration#createDataSource() + */ + protected DataSource createDataSource() { + + PGSimpleDataSource ds = new PGSimpleDataSource(); + ds.setUrl("jdbc:postgresql:///postgres"); + + return ds; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jdbc.testing.DataSourceFactoryBean#customizePopulator(org.springframework.jdbc.datasource.init.ResourceDatabasePopulator) + */ + @Override + protected void customizePopulator(ResourceDatabasePopulator populator) { + populator.setIgnoreFailedDrops(true); + } +} diff --git a/src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceFactoryBean.java b/src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceFactoryBean.java deleted file mode 100644 index 25b3fa7f..00000000 --- a/src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceFactoryBean.java +++ /dev/null @@ -1,69 +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.testing; - -import java.sql.Connection; -import java.sql.SQLException; - -import javax.sql.DataSource; - -import org.postgresql.ds.PGSimpleDataSource; -import org.springframework.context.annotation.Profile; -import org.springframework.core.io.DefaultResourceLoader; -import org.springframework.core.io.support.EncodedResource; -import org.springframework.jdbc.datasource.init.ScriptUtils; -import org.springframework.stereotype.Component; - -/** - * @author Jens Schauder - */ -@Component -@Profile("postgres") -public class PostgresDataSourceFactoryBean extends DataSourceFactoryBean { - - public static final String URL = "jdbc:postgresql:///postgres"; - private static final DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); - - PostgresDataSourceFactoryBean(Class testClass) { - super(testClass); - } - - @Override - protected String scriptSuffix() { - return "postgres"; - } - - @Override - DataSource create(String scriptName) { - return setupDatabase(scriptName); - } - - private DataSource setupDatabase(String scriptName) { - - PGSimpleDataSource ds = new PGSimpleDataSource(); - ds.setUrl(URL); - - try (Connection connection = ds.getConnection()) { - - ScriptUtils.executeSqlScript(connection, new EncodedResource(resourceLoader.getResource(scriptName)), false, true, - "--", ";", "/*", "*/"); - } catch (SQLException e) { - throw new RuntimeException("Couldn't setup database", e); - } - - return ds; - } -} diff --git a/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java b/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java new file mode 100644 index 00000000..0c93cc3f --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java @@ -0,0 +1,56 @@ +/* + * 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.testing; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * Infrastructure configuration for integration tests. + * + * @author Oliver Gierke + */ +@Configuration +@ComponentScan // To pick up configuration classes (per activated profile) +public class TestConfiguration { + + @Autowired DataSource dataSource; + @Autowired ApplicationEventPublisher publisher; + + @Bean + JdbcRepositoryFactory jdbcRepositoryFactory() { + return new JdbcRepositoryFactory(namedParameterJdbcTemplate(), publisher); + } + + @Bean + NamedParameterJdbcTemplate namedParameterJdbcTemplate() { + return new NamedParameterJdbcTemplate(dataSource); + } + + @Bean + PlatformTransactionManager transactionManager() { + return new DataSourceTransactionManager(dataSource); + } +} diff --git a/src/test/java/org/springframework/data/jdbc/testing/TestUtils.java b/src/test/java/org/springframework/data/jdbc/testing/TestUtils.java new file mode 100644 index 00000000..e9e1a5dc --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/testing/TestUtils.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.testing; + +import org.springframework.util.Assert; + +/** + * Utility methods for testing. + * + * @author Oliver Gierke + */ +public interface TestUtils { + + /** + * Returns the name of the SQL script to be loaded for the given test class and database type. + * + * @param testClass must not be {@literal null}. + * @param databaseType must not be {@literal null} or empty. + * @return + */ + public static String createScriptName(Class testClass, String databaseType) { + + Assert.notNull(testClass, "Test class must not be null!"); + Assert.hasText(databaseType, "Database type must not be null or empty!"); + + return String.format("%s/%s-%s.sql", testClass.getPackage().getName(), testClass.getSimpleName(), + databaseType.toLowerCase()); + } +} diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml new file mode 100644 index 00000000..ad5cbef5 --- /dev/null +++ b/src/test/resources/logback.xml @@ -0,0 +1,16 @@ + + + + + + %d %5p %40.40c:%4L - %m%n + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-hsql.sql b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-hsql.sql index 60408124..1618e256 100644 --- a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-hsql.sql +++ b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-hsql.sql @@ -1,4 +1,4 @@ -- noinspection SqlNoDataSourceInspectionForFile 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 +CREATE TABLE PrimitiveIdEntity (ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(100)) diff --git a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-mysql.sql b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-mysql.sql index 2ef12bbf..b02242b2 100644 --- a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-mysql.sql +++ b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-mysql.sql @@ -1,2 +1,2 @@ CREATE TABLE ReadOnlyIdEntity (ID BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100)); -CREATE TABLE PrimitiveIdEntity (ID BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100)); \ No newline at end of file +CREATE TABLE PrimitiveIdEntity (ID BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100)); diff --git a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-postgres.sql b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-postgres.sql index 3c8e7908..267f0b7a 100644 --- a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-postgres.sql +++ b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-postgres.sql @@ -2,4 +2,4 @@ DROP TABLE ReadOnlyIdEntity; DROP TABLE PrimitiveIdEntity; CREATE TABLE ReadOnlyIdEntity (ID SERIAL PRIMARY KEY, NAME VARCHAR(100)); -CREATE TABLE PrimitiveIdEntity (ID SERIAL PRIMARY KEY, NAME VARCHAR(100)); \ No newline at end of file +CREATE TABLE PrimitiveIdEntity (ID SERIAL PRIMARY KEY, NAME VARCHAR(100)); diff --git a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql index db94760d..d6458976 100644 --- a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql +++ b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql @@ -1 +1 @@ -CREATE TABLE dummyentity ( idProp BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100)) \ No newline at end of file +CREATE TABLE dummyentity ( idProp BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100)) diff --git a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql index 3b204a37..89763ecd 100644 --- a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql +++ b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql @@ -1 +1 @@ -CREATE TABLE dummyentity (idProp BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100)); \ No newline at end of file +CREATE TABLE dummyentity (idProp BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100)); diff --git a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql index 64e1d154..25e9c426 100644 --- a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql +++ b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql @@ -1,2 +1,2 @@ DROP TABLE dummyentity; -CREATE TABLE dummyentity (idProp SERIAL PRIMARY KEY, NAME VARCHAR(100)); \ No newline at end of file +CREATE TABLE dummyentity (idProp SERIAL PRIMARY KEY, NAME VARCHAR(100));