DATAJDBC-99 - Polishing.

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<DummyEntity> rowMapperDelegate = mock(RowMapper.class);
JdbcPersistentEntityInformation<DummyEntity, Long> entityInformation = mock(JdbcPersistentEntityInformation.class);
ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class);
@Mock RowMapper<DummyEntity> rowMapperDelegate;
@Mock JdbcPersistentEntityInformation<DummyEntity, Long> 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;
}
}

View File

@@ -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<ReadOnlyIdEntity> 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<PrimitiveIdEntity> 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<ReadOnlyIdEntity, Long> {}
private interface PrimitiveIdEntityRepository extends CrudRepository<PrimitiveIdEntity, Long> {}
@Data
public interface ReadOnlyIdEntityRepository extends CrudRepository<ReadOnlyIdEntity, Long> {}
@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);
}
}
}

View File

@@ -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<DummyEntity> 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<DummyEntity> 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<DummyEntity> 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<DummyEntity> 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<DummyEntity, Long> {}
private static DummyEntity createDummyEntity() {
DummyEntity entity = new DummyEntity();
entity.setName("Entity Name");
return entity;
}
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {}
@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);
}
}
}

View File

@@ -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<Integer> 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<DummyEntity, Long> {}
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {}
@Data
private static class DummyEntity {
@Id private final Long id;
static class DummyEntity {
private final @Id Long id;
}
static class FakePublisher implements ApplicationEventPublisher {

View File

@@ -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) {}
}

View File

@@ -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<DataSource> {
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;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.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());
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %5p %40.40c:%4L - %m%n</pattern>
</encoder>
</appender>
<logger name="org.springframework.data" level="error" />
<root level="error">
<appender-ref ref="console" />
</root>
</configuration>

View File

@@ -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))
CREATE TABLE PrimitiveIdEntity (ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(100))

View File

@@ -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));
CREATE TABLE PrimitiveIdEntity (ID BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100));

View File

@@ -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));
CREATE TABLE PrimitiveIdEntity (ID SERIAL PRIMARY KEY, NAME VARCHAR(100));

View File

@@ -1 +1 @@
CREATE TABLE dummyentity ( idProp BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100))
CREATE TABLE dummyentity ( idProp BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100))

View File

@@ -1 +1 @@
CREATE TABLE dummyentity (idProp BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE dummyentity (idProp BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100));

View File

@@ -1,2 +1,2 @@
DROP TABLE dummyentity;
CREATE TABLE dummyentity (idProp SERIAL PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE dummyentity (idProp SERIAL PRIMARY KEY, NAME VARCHAR(100));