Browse Source

DATAJDBC-105 - Test with multiple databases.

Different databases are now supported by means of Maven Profiles and Spring Profiles. There is one Profile for each supported database. The default Profile is equivalent to hql. There is a Maven Profile all-dbs, which runs the integration tests against all databases.

The databases need to be started outside the build. The build assumes the default configuration as I found it after `brew install <database>`

For now we support the databases mysql, postgres and hsqldb.

In order to make the new databases work setting of properties and reading generated ids was improved to do some simple conversions. This might be considered a first step towards DATAJDBC-104.

The project root contains some basic scripts for starting and stopping databases, as well as running a build against all supported databases. Integration tests using a database now use Rules instead of JUnit runners. This gives more flexibility when adding fancy stuff to the Tests in the form of other Rules.

Related issue: DATAJDBC-104.
Original pull request: #5.
pull/6/merge
Jens Schauder 9 years ago committed by Oliver Gierke
parent
commit
1582a4d475
  1. 78
      pom.xml
  2. 29
      readme.md
  3. 3
      run-tests-against-all-dbs.sh
  4. 3
      src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java
  5. 7
      src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java
  6. 2
      src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java
  7. 5
      src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java
  8. 2
      src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java
  9. 12
      src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java
  10. 21
      src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java
  11. 2
      src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java
  12. 3
      src/main/java/org/springframework/data/jdbc/mapping/event/WithEntity.java
  13. 9
      src/main/java/org/springframework/data/jdbc/mapping/event/WithId.java
  14. 2
      src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java
  15. 17
      src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java
  16. 7
      src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java
  17. 62
      src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java
  18. 78
      src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java
  19. 6
      src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java
  20. 49
      src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java
  21. 96
      src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java
  22. 42
      src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java
  23. 59
      src/test/java/org/springframework/data/jdbc/testing/DataSourceFactoryBean.java
  24. 52
      src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceFactoryBean.java
  25. 84
      src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceFactoryBean.java
  26. 69
      src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceFactoryBean.java
  27. 2
      src/test/resources/create-mysql.sql
  28. 1
      src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-hsql.sql
  29. 2
      src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-mysql.sql
  30. 5
      src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-postgres.sql
  31. 1
      src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql
  32. 1
      src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql
  33. 2
      src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql
  34. 1
      src/test/resources/org.springframework.data.jdbc.repository/jdbc-repository-integration-tests.sql
  35. 7
      start-all-dbs.sh
  36. 7
      stop-all-dbs.sh

78
pom.xml

@ -42,6 +42,50 @@
</plugins> </plugins>
</build> </build>
</profile> </profile>
<profile>
<id>all-dbs</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>mysql-test</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<includes>
<include>**/*IntegrationTests.java</include>
</includes>
<systemPropertyVariables>
<spring.profiles.active>mysql</spring.profiles.active>
</systemPropertyVariables>
</configuration>
</execution>
<execution>
<id>postgres-test</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<includes>
<include>**/*IntegrationTests.java</include>
</includes>
<systemPropertyVariables>
<spring.profiles.active>postgres</spring.profiles.active>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles> </profiles>
<dependencies> <dependencies>
@ -97,10 +141,33 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.0.0</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12</version>
</plugin>
</plugins>
</pluginManagement>
<plugins> <plugins>
<!-- <!--
@ -127,7 +194,16 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>2.12</version> <executions>
<execution>
<id>default-test</id>
<configuration>
<includes>
<include>**/*Tests.java</include>
</includes>
</configuration>
</execution>
</executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>

29
readme.md

@ -9,6 +9,35 @@ The primary goal of the [Spring Data](http://projects.spring.io/spring-data) pro
## Quick Start ## ## Quick Start ##
## Execute Tests ##
### Fast running tests
Fast running tests can executed with a simple
mvn test
This will execute unit tests and integration tests using an in-memory database.
### Running tests with a real database
To run the integration tests against a specific database you nned to have the database running on your local machine and then execute.
mvn test -Dspring.profiles.active=<databasetype>
This will also execute the unit tests.
Currently the following *databasetypes* are available:
* hsql (default, does not need to be running)
* mysql
### Run tests with all databases
mvn test -Pall-dbs
This will execute the unit tests, and all the integration tests with all the databases we currently support for testing. The databases must be running.
## Contributing to Spring Data JDBC ## ## Contributing to Spring Data JDBC ##
Here are some ways for you to get involved in the community: Here are some ways for you to get involved in the community:

3
run-tests-against-all-dbs.sh

@ -0,0 +1,3 @@
#!/bin/sh
./start-all-dbs.sh && mvn clean install -Pall-dbs && ./stop-all-dbs.sh

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

@ -18,7 +18,8 @@ package org.springframework.data.jdbc.mapping.event;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified; import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/** /**
* Gets published after instantiation and setting of all the properties of an entity. This allows to do some postprocessing of entities. * Gets published after instantiation and setting of all the properties of an entity. This allows to do some
* postprocessing of entities.
* *
* @author Jens Schauder * @author Jens Schauder
* @since 2.0 * @since 2.0

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

@ -20,14 +20,13 @@ import java.util.Optional;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified; import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/** /**
* Gets published after deletion of an entity. It will have a {@link Specified} identifier. * Gets published after deletion of an entity. It will have a {@link Specified} identifier. If the entity is empty or
* * not depends on the delete method used.
* If the entity is empty or not depends on the delete method used.
* *
* @author Jens Schauder * @author Jens Schauder
* @since 2.0 * @since 2.0
*/ */
public class AfterDelete extends JdbcEventWithId{ public class AfterDelete extends JdbcEventWithId {
/** /**
* @param id of the entity. * @param id of the entity.

2
src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java

@ -15,8 +15,6 @@
*/ */
package org.springframework.data.jdbc.mapping.event; package org.springframework.data.jdbc.mapping.event;
import java.util.Optional;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified; import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
/** /**

5
src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java

@ -19,9 +19,8 @@ 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 * 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. * manually, an event listener for this event may do so. <br>
* * The {@link Identifier} is {@link Unset#UNSET}
* The {@link Identifier} is {@link org.springframework.data.jdbc.mapping.event.Identifier.Unset#UNSET}
* *
* @author Jens Schauder * @author Jens Schauder
* @since 2.0 * @since 2.0

2
src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java

@ -15,8 +15,6 @@
*/ */
package org.springframework.data.jdbc.mapping.event; package org.springframework.data.jdbc.mapping.event;
import java.util.Optional;
/** /**
* Subclasses of this get published before an entity gets saved to the database. * Subclasses of this get published before an entity gets saved to the database.
* *

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

@ -28,12 +28,12 @@ import java.util.Optional;
*/ */
public interface Identifier { public interface Identifier {
Optional<Object> getOptionalValue();
static Identifier fromNullable(Object value) { static Identifier fromNullable(Object value) {
return (value != null) ? new Specified(value) : Unset.UNSET; return (value != null) ? new Specified(value) : Unset.UNSET;
} }
Optional<Object> getOptionalValue();
/** /**
* An unset identifier. Always returns {@link Optional#empty()} as value. * An unset identifier. Always returns {@link Optional#empty()} as value.
*/ */
@ -47,15 +47,13 @@ public interface Identifier {
} }
/** /**
* An {@link Identifier} guaranteed to have a non empty value. * An {@link Identifier} guaranteed to have a non empty value. Since it is guaranteed to exist the value can get
* * access directly.
* Since it is guaranteed to exist the value can get access directly.
*/ */
@Data @Data
class Specified implements Identifier { class Specified implements Identifier {
@NonNull @NonNull private final Object value;
private final Object value;
@Override @Override
public Optional<Object> getOptionalValue() { public Optional<Object> getOptionalValue() {

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

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

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

@ -25,7 +25,7 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
* @author Jens Schauder * @author Jens Schauder
* @since 2.0 * @since 2.0
*/ */
public class JdbcEventWithId extends JdbcEvent implements WithId{ public class JdbcEventWithId extends JdbcEvent implements WithId {
public JdbcEventWithId(Specified id, Optional<Object> entity) { public JdbcEventWithId(Specified id, Optional<Object> entity) {
super(id, entity); super(id, entity);

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

@ -16,7 +16,8 @@
package org.springframework.data.jdbc.mapping.event; 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 JdbcEvent}s which are guaranteed to have an entity. Allows direct access to that entity, without
* going through an {@link java.util.Optional}
* *
* @author Jens Schauder * @author Jens Schauder
* @since 2.0 * @since 2.0

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

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

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

@ -19,7 +19,7 @@ import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.util.TypeInformation; import org.springframework.data.util.TypeInformation;
/** /**
* meta data a repository might need for implementing persistence operations for instances of type {@code T} * Meta data a repository might need for implementing persistence operations for instances of type {@code T}
* *
* @author Jens Schauder * @author Jens Schauder
* @since 2.0 * @since 2.0

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

@ -18,6 +18,8 @@ package org.springframework.data.jdbc.repository;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.convert.ClassGeneratingEntityInstantiator; import org.springframework.data.convert.ClassGeneratingEntityInstantiator;
import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.convert.EntityInstantiator;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
@ -30,7 +32,7 @@ import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
/** /**
* maps a ResultSet to an entity of type {@code T} * Maps a ResultSet to an entity of type {@code T}
* *
* @author Jens Schauder * @author Jens Schauder
* @since 2.0 * @since 2.0
@ -38,8 +40,8 @@ import org.springframework.jdbc.core.RowMapper;
class EntityRowMapper<T> implements RowMapper<T> { class EntityRowMapper<T> implements RowMapper<T> {
private final JdbcPersistentEntity<T> entity; private final JdbcPersistentEntity<T> entity;
private final EntityInstantiator instantiator = new ClassGeneratingEntityInstantiator(); private final EntityInstantiator instantiator = new ClassGeneratingEntityInstantiator();
private final ConversionService conversions = new DefaultConversionService();
EntityRowMapper(JdbcPersistentEntity<T> entity) { EntityRowMapper(JdbcPersistentEntity<T> entity) {
this.entity = entity; this.entity = entity;
@ -58,13 +60,16 @@ class EntityRowMapper<T> implements RowMapper<T> {
} }
private T createInstance(ResultSet rs) { private T createInstance(ResultSet rs) {
return instantiator.createInstance(entity, new ParameterValueProvider<JdbcPersistentProperty>() { return instantiator.createInstance(entity, new ParameterValueProvider<JdbcPersistentProperty>() {
@SuppressWarnings("unchecked")
@Override @Override
public <T> T getParameterValue(PreferredConstructor.Parameter<T, JdbcPersistentProperty> parameter) { public <T> T getParameterValue(PreferredConstructor.Parameter<T, JdbcPersistentProperty> parameter) {
try { try {
return (T) rs.getObject(parameter.getName()); return conversions.convert(rs.getObject(parameter.getName()), parameter.getType().getType());
} catch (SQLException e) { } catch (SQLException e) {
throw new MappingException( // throw new MappingException( //
String.format("Couldn't read column %s from ResultSet.", parameter.getName()) // String.format("Couldn't read column %s from ResultSet.", parameter.getName()) //
); );
@ -76,7 +81,9 @@ class EntityRowMapper<T> implements RowMapper<T> {
private void setProperty(ResultSet rs, T t, PersistentProperty property) { private void setProperty(ResultSet rs, T t, PersistentProperty property) {
try { try {
entity.getPropertyAccessor(t).setProperty(property, rs.getObject(property.getName()));
Object converted = conversions.convert(rs.getObject(property.getName()), property.getType());
entity.getPropertyAccessor(t).setProperty(property, converted);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(String.format("Couldn't set property %s.", property.getName()), e); throw new RuntimeException(String.format("Couldn't set property %s.", property.getName()), e);
} }

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

@ -15,6 +15,8 @@
*/ */
package org.springframework.data.jdbc.repository; package org.springframework.data.jdbc.repository;
import lombok.RequiredArgsConstructor;
import java.io.Serializable; import java.io.Serializable;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@ -25,10 +27,9 @@ import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation; import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
import lombok.RequiredArgsConstructor;
/** /**
* a RowMapper that publishes events after a delegate, did the actual work of mapping a {@link ResultSet} to an entityInformation. * A {@link RowMapper} that publishes events after a delegate, did the actual work of mapping a {@link ResultSet} to an
* entityInformation.
* *
* @author Jens Schauder * @author Jens Schauder
* @since 2.0 * @since 2.0

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

@ -25,6 +25,9 @@ import java.util.stream.Collectors;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.NonTransientDataAccessException; import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.data.jdbc.mapping.event.AfterDelete; import org.springframework.data.jdbc.mapping.event.AfterDelete;
import org.springframework.data.jdbc.mapping.event.AfterInsert; import org.springframework.data.jdbc.mapping.event.AfterInsert;
@ -59,12 +62,15 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
private final JdbcPersistentEntityInformation<T, ID> entityInformation; private final JdbcPersistentEntityInformation<T, ID> entityInformation;
private final NamedParameterJdbcOperations operations; private final NamedParameterJdbcOperations operations;
private final SqlGenerator sql; private final SqlGenerator sql;
private final EntityRowMapper<T> entityRowMapper; private final EntityRowMapper<T> entityRowMapper;
private final ApplicationEventPublisher publisher; private final ApplicationEventPublisher publisher;
private final ConversionService conversions = new DefaultConversionService();
public SimpleJdbcRepository(JdbcPersistentEntity<T> persistentEntity, NamedParameterJdbcOperations jdbcOperations, public SimpleJdbcRepository( //
ApplicationEventPublisher publisher) { JdbcPersistentEntity<T> persistentEntity, //
NamedParameterJdbcOperations jdbcOperations, //
ApplicationEventPublisher publisher //
) {
Assert.notNull(persistentEntity, "PersistentEntity must not be null."); Assert.notNull(persistentEntity, "PersistentEntity must not be null.");
Assert.notNull(jdbcOperations, "JdbcOperations must not be null."); Assert.notNull(jdbcOperations, "JdbcOperations must not be null.");
@ -75,8 +81,8 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
this.operations = jdbcOperations; this.operations = jdbcOperations;
this.publisher = publisher; this.publisher = publisher;
entityRowMapper = new EntityRowMapper<>(persistentEntity); this.entityRowMapper = new EntityRowMapper<>(persistentEntity);
sql = new SqlGenerator(persistentEntity); this.sql = new SqlGenerator(persistentEntity);
} }
@Override @Override
@ -95,9 +101,7 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
public <S extends T> Iterable<S> save(Iterable<S> entities) { public <S extends T> Iterable<S> save(Iterable<S> entities) {
List<S> savedEntities = new ArrayList<>(); List<S> savedEntities = new ArrayList<>();
entities.forEach(e -> savedEntities.add(save(e))); entities.forEach(e -> savedEntities.add(save(e)));
return savedEntities; return savedEntities;
} }
@ -141,8 +145,12 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
@Override @Override
public void delete(Iterable<? extends T> entities) { public void delete(Iterable<? extends T> entities) {
operations.update(sql.getDeleteByList(), new MapSqlParameterSource("ids", StreamSupport List<ID> idList = StreamSupport.stream(entities.spliterator(), false) //
.stream(entities.spliterator(), false).map(entityInformation::getId).collect(Collectors.toList()))); .map(entityInformation::getId) //
.collect(Collectors.toList());
MapSqlParameterSource sqlParameterSource = new MapSqlParameterSource("ids", idList);
operations.update(sql.getDeleteByList(), sqlParameterSource);
} }
@Override @Override
@ -154,11 +162,11 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
Map<String, Object> parameters = new HashMap<>(); Map<String, Object> parameters = new HashMap<>();
this.persistentEntity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) // this.persistentEntity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) property -> {
property -> parameters.put( //
property.getColumnName(), // Object value = persistentEntity.getPropertyAccessor(instance).getProperty(property);
persistentEntity.getPropertyAccessor(instance).getProperty(property)) // parameters.put(property.getColumnName(), value);
); });
return parameters; return parameters;
} }
@ -195,16 +203,38 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
} }
private <S extends T> void setIdFromJdbc(S instance, KeyHolder holder) { private <S extends T> void setIdFromJdbc(S instance, KeyHolder holder) {
try { try {
Number idValueFromJdbc = holder.getKey();
Object idValueFromJdbc = getIdFromHolder(holder);
if (idValueFromJdbc != null) { if (idValueFromJdbc != null) {
entityInformation.setId(instance, idValueFromJdbc);
Class<?> targetType = persistentEntity.getIdProperty().getType();
Object converted = convert(idValueFromJdbc, targetType);
entityInformation.setId(instance, converted);
} }
} catch (NonTransientDataAccessException e) { } catch (NonTransientDataAccessException e) {
throw new UnableToSetIdException("Unable to set id of " + instance, e); throw new UnableToSetIdException("Unable to set id of " + instance, e);
} }
} }
private Object getIdFromHolder(KeyHolder holder) {
try {
// mysql just returns one value with a special name
return holder.getKey();
} catch (InvalidDataAccessApiUsageException e) {
// postgress returns a value for each column
return holder.getKeys().get(persistentEntity.getIdColumn());
}
}
private <V> V convert(Object idValueFromJdbc, Class<V> targetType) {
return conversions.convert(idValueFromJdbc, targetType);
}
private void doDelete(Specified specifiedId, Optional<Object> optionalEntity) { private void doDelete(Specified specifiedId, Optional<Object> optionalEntity) {
publisher.publishEvent(new BeforeDelete(specifiedId, optionalEntity)); publisher.publishEvent(new BeforeDelete(specifiedId, optionalEntity));

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

@ -17,12 +17,15 @@ package org.springframework.data.jdbc.repository;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collector;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.PropertyHandler;
/** /**
* Generates SQL statements to be used by {@Link SimpleJdbcRepository}
*
* @author Jens Schauder * @author Jens Schauder
* @since 2.0 * @since 2.0
*/ */
@ -36,29 +39,45 @@ class SqlGenerator {
private final String countSql; private final String countSql;
private final String insertSql; private final String insertSql;
private final String updateSql;
private final String deleteByIdSql; private final String deleteByIdSql;
private final String deleteAllSql; private final String deleteAllSql;
private final String deleteByListSql; private final String deleteByListSql;
private final String updateSql;
private final JdbcPersistentEntity<?> entity;
private final List<String> propertyNames = new ArrayList<>(); private final List<String> propertyNames = new ArrayList<>();
private final List<String> nonIdPropertyNames = new ArrayList<>();
SqlGenerator(JdbcPersistentEntity<?> entity) {
this.entity = entity;
initPropertyNames();
<T> SqlGenerator(JdbcPersistentEntity<T> entity) { findOneSql = createFindOneSelectSql();
findAllSql = createFindAllSql();
findAllInListSql = createFindAllInListSql();
entity.doWithProperties((PropertyHandler) persistentProperty -> propertyNames.add(persistentProperty.getName())); existsSql = createExistsSql();
countSql = createCountSql();
findOneSql = createFindOneSelectSql(entity); insertSql = createInsertSql();
findAllSql = createFindAllSql(entity);
findAllInListSql = createFindAllInListSql(entity);
existsSql = createExistsSql(entity); updateSql = createUpdateSql();
countSql = createCountSql(entity);
insertSql = createInsertSql(entity); deleteByIdSql = createDeleteSql();
updateSql = createUpdateSql(entity); deleteAllSql = createDeleteAllSql();
deleteByListSql = createDeleteByListSql();
}
deleteByIdSql = createDeleteSql(entity); private <T> void initPropertyNames() {
deleteAllSql = createDeleteAllSql(entity); entity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) p -> {
deleteByListSql = createDeleteByListSql(entity); propertyNames.add(p.getName());
if (!entity.isIdProperty(p)) {
nonIdPropertyNames.add(p.getName());
}
});
} }
String getFindAllInList() { String getFindAllInList() {
@ -100,55 +119,56 @@ class SqlGenerator {
String getDeleteByList() { String getDeleteByList() {
return deleteByListSql; return deleteByListSql;
} }
private String createFindOneSelectSql(JdbcPersistentEntity<?> entity) {
private String createFindOneSelectSql() {
return String.format("select * from %s where %s = :id", entity.getTableName(), entity.getIdColumn()); return String.format("select * from %s where %s = :id", entity.getTableName(), entity.getIdColumn());
} }
private String createFindAllSql(JdbcPersistentEntity<?> entity) { private String createFindAllSql() {
return String.format("select * from %s", entity.getTableName()); return String.format("select * from %s", entity.getTableName());
} }
private String createFindAllInListSql(JdbcPersistentEntity<?> entity) { private String createFindAllInListSql() {
return String.format(String.format("select * from %s where %s in (:ids)", entity.getTableName(), entity.getIdColumn()), entity.getTableName()); return String.format("select * from %s where %s in (:ids)", entity.getTableName(), entity.getIdColumn());
} }
private String createExistsSql(JdbcPersistentEntity<?> entity) { private String createExistsSql() {
return String.format("select count(*) from %s where %s = :id", entity.getTableName(), entity.getIdColumn()); return String.format("select count(*) from %s where %s = :id", entity.getTableName(), entity.getIdColumn());
} }
private <T> String createCountSql(JdbcPersistentEntity<T> entity) { private <T> String createCountSql() {
return String.format("select count(*) from %s", entity.getTableName()); return String.format("select count(*) from %s", entity.getTableName());
} }
private String createInsertSql(JdbcPersistentEntity<?> entity) { private String createInsertSql() {
String insertTemplate = "insert into %s (%s) values (%s)"; String insertTemplate = "insert into %s (%s) values (%s)";
String tableColumns = propertyNames.stream().collect(Collectors.joining(", ")); String tableColumns = nonIdPropertyNames.stream().collect(Collectors.joining(", "));
String parameterNames = propertyNames.stream().collect(Collectors.joining(", :", ":", "")); String parameterNames = nonIdPropertyNames.stream().collect(Collectors.joining(", :", ":", ""));
return String.format(insertTemplate, entity.getTableName(), tableColumns, parameterNames); return String.format(insertTemplate, entity.getTableName(), tableColumns, parameterNames);
} }
private <T> String createUpdateSql(JdbcPersistentEntity<T> entity) { private <T> String createUpdateSql() {
String updateTemplate = "update %s set %s where %s = :%s"; String updateTemplate = "update %s set %s where %s = :%s";
String setClause = propertyNames.stream().map(n -> String.format("%s = :%s", n, n)).collect(Collectors.joining(", ")); 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()); return String.format(updateTemplate, entity.getTableName(), setClause, entity.getIdColumn(), entity.getIdColumn());
} }
private String createDeleteSql(JdbcPersistentEntity entity) { private String createDeleteSql() {
return String.format("delete from %s where %s = :id", entity.getTableName(), entity.getIdColumn()); return String.format("delete from %s where %s = :id", entity.getTableName(), entity.getIdColumn());
} }
private String createDeleteAllSql(JdbcPersistentEntity entity) { private String createDeleteAllSql() {
return String.format("delete from %s", entity.getTableName()); return String.format("delete from %s", entity.getTableName());
} }
private String createDeleteByListSql(JdbcPersistentEntity entity) { private String createDeleteByListSql() {
return String.format("delete from %s where %s in (:ids)", entity.getTableName(), entity.getIdColumn()); return String.format("delete from %s where %s in (:ids)", entity.getTableName(), entity.getIdColumn());
} }
} }

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

@ -19,16 +19,17 @@ import org.springframework.jdbc.core.RowMapper;
*/ */
public class EventPublishingEntityRowMapperTest { public class EventPublishingEntityRowMapperTest {
RowMapper rowMapperDelegate = mock(RowMapper.class); RowMapper<DummyEntity> rowMapperDelegate = mock(RowMapper.class);
JdbcPersistentEntityInformation<DummyEntity, Long> entityInformation = mock(JdbcPersistentEntityInformation.class); JdbcPersistentEntityInformation<DummyEntity, Long> entityInformation = mock(JdbcPersistentEntityInformation.class);
ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class); ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class);
@Test // DATAJDBC-99 @Test // DATAJDBC-99
public void eventGetsPublishedAfterInstantiation() throws SQLException { public void eventGetsPublishedAfterInstantiation() throws SQLException {
when(rowMapperDelegate.mapRow(any(ResultSet.class), anyInt())).thenReturn(new DummyEntity(1L));
when(entityInformation.getId(any())).thenReturn(1L); when(entityInformation.getId(any())).thenReturn(1L);
EventPublishingEntityRowMapper<DummyEntity, Long> rowMapper = new EventPublishingEntityRowMapper<>( // EventPublishingEntityRowMapper rowMapper = new EventPublishingEntityRowMapper<>( //
rowMapperDelegate, // rowMapperDelegate, //
entityInformation, // entityInformation, //
publisher); publisher);
@ -41,7 +42,6 @@ public class EventPublishingEntityRowMapperTest {
@Data @Data
static class DummyEntity { static class DummyEntity {
@Id private final Long Id; @Id private final Long Id;
} }
} }

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

@ -21,44 +21,45 @@ import static org.mockito.Mockito.*;
import lombok.Data; import lombok.Data;
import javax.sql.DataSource;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.repository.JdbcRepositoryIdGenerationIntegrationTests.TestConfiguration; import org.springframework.data.jdbc.repository.JdbcRepositoryIdGenerationIntegrationTests.TestConfiguration;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
/** /**
* Testing special cases for id generation with {@link SimpleJdbcRepository}. * Testing special cases for id generation with {@link SimpleJdbcRepository}.
* *
* @author Jens Schauder * @author Jens Schauder
*/ */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfiguration.class) @ContextConfiguration(classes = TestConfiguration.class)
public class JdbcRepositoryIdGenerationIntegrationTests { public class JdbcRepositoryIdGenerationIntegrationTests {
@Autowired NamedParameterJdbcTemplate template; @ClassRule public static final SpringClassRule classRule = new SpringClassRule();
@Rule public SpringMethodRule methodRule = new SpringMethodRule();
@Autowired NamedParameterJdbcTemplate template;
@Autowired ReadOnlyIdEntityRepository readOnlyIdrepository; @Autowired ReadOnlyIdEntityRepository readOnlyIdrepository;
@Autowired PrimitiveIdEntityRepository primitiveIdRepository; @Autowired PrimitiveIdEntityRepository primitiveIdRepository;
@Test // DATAJDBC-98 @Test // DATAJDBC-98
public void idWithoutSetterGetsSet() { public void idWithoutSetterGetsSet() {
ReadOnlyIdEntity entity1 = new ReadOnlyIdEntity(null); ReadOnlyIdEntity entity = new ReadOnlyIdEntity(null);
entity1.setName("Entity Name"); entity.setName("Entity Name");
ReadOnlyIdEntity entity = entity1;
entity = readOnlyIdrepository.save(entity); entity = readOnlyIdrepository.save(entity);
assertThat(entity.getId()).isNotNull(); assertThat(entity.getId()).isNotNull();
@ -77,6 +78,7 @@ public class JdbcRepositoryIdGenerationIntegrationTests {
entity = primitiveIdRepository.save(entity); entity = primitiveIdRepository.save(entity);
assertThat(entity.getId()).isNotNull(); assertThat(entity.getId()).isNotNull();
assertThat(entity.getId()).isNotEqualTo(0L);
PrimitiveIdEntity reloadedEntity = primitiveIdRepository.findOne(entity.getId()); PrimitiveIdEntity reloadedEntity = primitiveIdRepository.findOne(entity.getId());
@ -84,9 +86,9 @@ public class JdbcRepositoryIdGenerationIntegrationTests {
assertEquals(entity.getName(), reloadedEntity.getName()); assertEquals(entity.getName(), reloadedEntity.getName());
} }
private interface ReadOnlyIdEntityRepository extends CrudRepository<ReadOnlyIdEntity, Long> { private interface ReadOnlyIdEntityRepository extends CrudRepository<ReadOnlyIdEntity, Long> {}
} private interface PrimitiveIdEntityRepository extends CrudRepository<PrimitiveIdEntity, Long> {}
@Data @Data
static class ReadOnlyIdEntity { static class ReadOnlyIdEntity {
@ -95,10 +97,6 @@ public class JdbcRepositoryIdGenerationIntegrationTests {
String name; String name;
} }
private interface PrimitiveIdEntityRepository extends CrudRepository<PrimitiveIdEntity, Long> {
}
@Data @Data
static class PrimitiveIdEntity { static class PrimitiveIdEntity {
@ -107,28 +105,21 @@ public class JdbcRepositoryIdGenerationIntegrationTests {
} }
@Configuration @Configuration
@ComponentScan("org.springframework.data.jdbc.testing")
static class TestConfiguration { static class TestConfiguration {
@Bean @Bean
EmbeddedDatabase dataSource() { Class<?> testClass() {
return JdbcRepositoryIdGenerationIntegrationTests.class;
System.out.println(" creating datasource");
return new EmbeddedDatabaseBuilder() //
.generateUniqueName(true) //
.setType(EmbeddedDatabaseType.HSQL) //
.setScriptEncoding("UTF-8") //
.ignoreFailedDrops(true) //
.addScript("org.springframework.data.jdbc.repository/jdbc-repository-id-generation-integration-tests.sql")
.build();
} }
@Bean @Bean
NamedParameterJdbcTemplate template(EmbeddedDatabase db) { NamedParameterJdbcTemplate template(DataSource db) {
return new NamedParameterJdbcTemplate(db); return new NamedParameterJdbcTemplate(db);
} }
@Bean @Bean
ReadOnlyIdEntityRepository readOnlyIdRepository(EmbeddedDatabase db) { ReadOnlyIdEntityRepository readOnlyIdRepository(DataSource db) {
return new JdbcRepositoryFactory(new NamedParameterJdbcTemplate(db), mock(ApplicationEventPublisher.class)) return new JdbcRepositoryFactory(new NamedParameterJdbcTemplate(db), mock(ApplicationEventPublisher.class))
.getRepository(ReadOnlyIdEntityRepository.class); .getRepository(ReadOnlyIdEntityRepository.class);

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

@ -24,24 +24,26 @@ import lombok.Data;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.repository.JdbcRepositoryIntegrationTests.TestConfiguration; import org.springframework.data.jdbc.repository.JdbcRepositoryIntegrationTests.TestConfiguration;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 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.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -50,26 +52,40 @@ import org.springframework.transaction.annotation.Transactional;
* *
* @author Jens Schauder * @author Jens Schauder
*/ */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfiguration.class) @ContextConfiguration(classes = TestConfiguration.class)
@Transactional @Transactional
public class JdbcRepositoryIntegrationTests { public class JdbcRepositoryIntegrationTests {
@Autowired NamedParameterJdbcTemplate template; @ClassRule public static final SpringClassRule classRule = new SpringClassRule();
@Rule public SpringMethodRule methodRule = new SpringMethodRule();
@Autowired NamedParameterJdbcTemplate template;
@Autowired DummyEntityRepository repository; @Autowired DummyEntityRepository repository;
private DummyEntity entity = createDummyEntity(); 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 @Test // DATAJDBC-95
public void savesAnEntity() { public void savesAnEntity() {
entity = repository.save(entity); entity = repository.save(entity);
int count = template.queryForObject( // int count = JdbcTestUtils.countRowsInTableWhere( //
"SELECT count(*) FROM dummyentity WHERE idProp = :id", // (JdbcTemplate) template.getJdbcOperations(), //
new MapSqlParameterSource("id", entity.getIdProp()), // "dummyentity", //
Integer.class // "idProp = " + entity.getIdProp() //
); );
assertEquals(1, count); assertEquals(1, count);
@ -95,9 +111,7 @@ public class JdbcRepositoryIntegrationTests {
assertThat(repository.findAll()) // assertThat(repository.findAll()) //
.extracting(DummyEntity::getIdProp) // .extracting(DummyEntity::getIdProp) //
.containsExactlyInAnyOrder( // .containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp());
entity.getIdProp(), other.getIdProp() //
);
} }
@Test // DATAJDBC-97 @Test // DATAJDBC-97
@ -125,9 +139,9 @@ public class JdbcRepositoryIntegrationTests {
@Test // DATAJDBC-97 @Test // DATAJDBC-97
public void findAllFindsAllSpecifiedEntities() { public void findAllFindsAllSpecifiedEntities() {
entity = repository.save(entity);
DummyEntity two = repository.save(createDummyEntity()); DummyEntity two = repository.save(createDummyEntity());
DummyEntity three = repository.save(createDummyEntity()); DummyEntity three = repository.save(createDummyEntity());
entity = repository.save(entity);
Iterable<DummyEntity> all = repository.findAll(asList(entity.getIdProp(), three.getIdProp())); Iterable<DummyEntity> all = repository.findAll(asList(entity.getIdProp(), three.getIdProp()));
@ -155,9 +169,7 @@ public class JdbcRepositoryIntegrationTests {
assertThat(repository.findAll()) // assertThat(repository.findAll()) //
.extracting(DummyEntity::getIdProp) // .extracting(DummyEntity::getIdProp) //
.containsExactlyInAnyOrder( // .containsExactlyInAnyOrder(entity.getIdProp(), three.getIdProp());
entity.getIdProp(), three.getIdProp() //
);
} }
@Test // DATAJDBC-97 @Test // DATAJDBC-97
@ -171,9 +183,7 @@ public class JdbcRepositoryIntegrationTests {
assertThat(repository.findAll()) // assertThat(repository.findAll()) //
.extracting(DummyEntity::getIdProp) // .extracting(DummyEntity::getIdProp) //
.containsExactlyInAnyOrder( // .containsExactlyInAnyOrder(two.getIdProp(), three.getIdProp());
two.getIdProp(), three.getIdProp() //
);
} }
@Test // DATAJDBC-97 @Test // DATAJDBC-97
@ -185,7 +195,9 @@ public class JdbcRepositoryIntegrationTests {
repository.delete(asList(entity, three)); repository.delete(asList(entity, three));
assertThat(repository.findAll()).extracting(DummyEntity::getIdProp).containsExactlyInAnyOrder(two.getIdProp()); assertThat(repository.findAll()) //
.extracting(DummyEntity::getIdProp) //
.containsExactlyInAnyOrder(two.getIdProp());
} }
@Test // DATAJDBC-97 @Test // DATAJDBC-97
@ -225,52 +237,31 @@ public class JdbcRepositoryIntegrationTests {
repository.save(asList(entity, other)); repository.save(asList(entity, other));
assertThat(repository.findAll()).extracting(DummyEntity::getName).containsExactlyInAnyOrder(entity.getName(), assertThat(repository.findAll()) //
other.getName()); .extracting(DummyEntity::getName) //
} .containsExactlyInAnyOrder(entity.getName(), other.getName());
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;
} }
private interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> { private interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {}
}
@Data @Data
static class DummyEntity { static class DummyEntity {
@Id private Long idProp;
String name; String name;
@Id private Long idProp;
} }
@Configuration @Configuration
@ComponentScan("org.springframework.data.jdbc.testing")
static class TestConfiguration { static class TestConfiguration {
@Bean @Bean
EmbeddedDatabase dataSource() { Class<?> testClass() {
return JdbcRepositoryIntegrationTests.class;
System.out.println(" creating datasource");
return new EmbeddedDatabaseBuilder() //
.generateUniqueName(true) //
.setType(EmbeddedDatabaseType.HSQL) //
.setScriptEncoding("UTF-8") //
.ignoreFailedDrops(true) //
.addScript("org.springframework.data.jdbc.repository/jdbc-repository-integration-tests.sql") //
.build();
} }
@Bean @Bean
NamedParameterJdbcTemplate template(EmbeddedDatabase db) { NamedParameterJdbcTemplate template(DataSource db) {
return new NamedParameterJdbcTemplate(db); return new NamedParameterJdbcTemplate(db);
} }
@ -285,6 +276,5 @@ public class JdbcRepositoryIntegrationTests {
PlatformTransactionManager transactionManager(DataSource db) { PlatformTransactionManager transactionManager(DataSource db) {
return new DataSourceTransactionManager(db); return new DataSourceTransactionManager(db);
} }
} }
} }

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

@ -1,17 +1,18 @@
package org.springframework.data.jdbc.repository; package org.springframework.data.jdbc.repository;
import static java.util.Arrays.*; import static java.util.Arrays.*;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
import static org.springframework.util.Assert.*; import static org.springframework.util.Assert.*;
import lombok.Data;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
@ -29,8 +30,6 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.KeyHolder; import org.springframework.jdbc.support.KeyHolder;
import lombok.Data;
/** /**
* @author Jens Schauder * @author Jens Schauder
*/ */
@ -42,22 +41,27 @@ public class SimpleJdbcRepositoryEventsUnitTests {
@Before @Before
public void before() { public void before() {
NamedParameterJdbcOperations operations = createIdGeneratingOperations(); NamedParameterJdbcOperations operations = createIdGeneratingOperations();
JdbcRepositoryFactory factory = new JdbcRepositoryFactory(operations, publisher); JdbcRepositoryFactory factory = new JdbcRepositoryFactory(operations, publisher);
repository = factory.getRepository(DummyEntityRepository.class); repository = factory.getRepository(DummyEntityRepository.class);
} }
private NamedParameterJdbcOperations createIdGeneratingOperations() { private NamedParameterJdbcOperations createIdGeneratingOperations() {
Answer<Integer> setIdInKeyHolder = invocation -> {
HashMap<String, Object> keys = new HashMap<>();
keys.put("id", 4711L);
KeyHolder keyHolder = invocation.getArgumentAt(2, KeyHolder.class);
keyHolder.getKeyList().add(keys);
return 1;
};
NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class); NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class);
when(operations.update(anyString(), any(SqlParameterSource.class), any(KeyHolder.class))).thenAnswer(new Answer<Integer>() { when(operations.update(anyString(), any(SqlParameterSource.class), any(KeyHolder.class)))
@Override .thenAnswer(setIdInKeyHolder);
public Integer answer(InvocationOnMock invocation) throws Throwable {
HashMap<String, Object> keys = new HashMap<>();
keys.put("id", 4711L);
invocation.getArgumentAt(2, KeyHolder.class).getKeyList().add(keys);
return 1;
}
});
return operations; return operations;
} }
@ -75,8 +79,6 @@ public class SimpleJdbcRepositoryEventsUnitTests {
@Test // DATAJDBC-99 @Test // DATAJDBC-99
public void publishesEventsOnSaveMany() { public void publishesEventsOnSaveMany() {
DummyEntity entity1 = new DummyEntity(null); DummyEntity entity1 = new DummyEntity(null);
DummyEntity entity2 = new DummyEntity(23L); DummyEntity entity2 = new DummyEntity(23L);
@ -88,7 +90,6 @@ public class SimpleJdbcRepositoryEventsUnitTests {
isInstanceOf(AfterUpdate.class, publisher.events.get(3)); isInstanceOf(AfterUpdate.class, publisher.events.get(3));
} }
@Test // DATAJDBC-99 @Test // DATAJDBC-99
public void publishesEventsOnDelete() { public void publishesEventsOnDelete() {
@ -115,16 +116,13 @@ public class SimpleJdbcRepositoryEventsUnitTests {
isInstanceOf(AfterDelete.class, publisher.events.get(1)); isInstanceOf(AfterDelete.class, publisher.events.get(1));
} }
private interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {}
@Data @Data
private static class DummyEntity { private static class DummyEntity {
@Id private final Long id; @Id private final Long id;
} }
private interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
}
static class FakePublisher implements ApplicationEventPublisher { static class FakePublisher implements ApplicationEventPublisher {
List<JdbcEvent> events = new ArrayList<>(); List<JdbcEvent> events = new ArrayList<>();
@ -134,4 +132,4 @@ public class SimpleJdbcRepositoryEventsUnitTests {
events.add((JdbcEvent) o); events.add((JdbcEvent) o);
} }
} }
} }

59
src/test/java/org/springframework/data/jdbc/testing/DataSourceFactoryBean.java

@ -0,0 +1,59 @@
/*
* 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;
}
}

52
src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceFactoryBean.java

@ -0,0 +1,52 @@
/*
* 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.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.stereotype.Component;
/**
* @author Jens Schauder
*/
@Component
@Profile({ "hsql", "default" })
class HsqlDataSourceFactoryBean extends DataSourceFactoryBean {
HsqlDataSourceFactoryBean(Class<?> testClass) {
super(testClass);
}
@Override
String scriptSuffix() {
return "hsql";
}
@Override
DataSource create(String scriptName) {
return new EmbeddedDatabaseBuilder() //
.generateUniqueName(true) //
.setType(EmbeddedDatabaseType.HSQL) //
.setScriptEncoding("UTF-8") //
.ignoreFailedDrops(true) //
.addScript(scriptName) //
.build();
}
}

84
src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceFactoryBean.java

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

69
src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceFactoryBean.java

@ -0,0 +1,69 @@
/*
* 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;
}
}

2
src/test/resources/create-mysql.sql

@ -0,0 +1,2 @@
DROP DATABASE test;
CREATE DATABASE test;

1
src/test/resources/org.springframework.data.jdbc.repository/jdbc-repository-id-generation-integration-tests.sql → src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-hsql.sql

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

2
src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-mysql.sql

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

5
src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-postgres.sql

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

1
src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql

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

1
src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql

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

2
src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql

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

1
src/test/resources/org.springframework.data.jdbc.repository/jdbc-repository-integration-tests.sql

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

7
start-all-dbs.sh

@ -0,0 +1,7 @@
#!/bin/sh
# postgres
pg_ctl -D /usr/local/var/postgres start
# mysql
mysql.server start

7
stop-all-dbs.sh

@ -0,0 +1,7 @@
#!/bin/sh
# postgres
pg_ctl -D /usr/local/var/postgres stop
# mysql
mysql.server stop
Loading…
Cancel
Save