From b2d4de4e99b2c380623e55af5641a2a4f9dee80e Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 17 Feb 2017 11:31:18 +0100 Subject: [PATCH 01/12] noissue - initial setup of infrastructure. Created a maven project based on spring-data-jpa. Created JdbcRepositoryFactory, which actually creates Repositories. Wired the construction of meta data. Implemented a dummy version of saving an entity to demonstrate availability of meta data. Included a simple test for exercising the JdbcRepositoryFactory and the resulting JdbcRepository. --- pom.xml | 208 ++++++++++++++++++ .../mapping/context/JdbcMappingContext.java | 41 ++++ .../mapping/model/JdbcPersistentEntity.java | 29 +++ .../mapping/model/JdbcPersistentProperty.java | 46 ++++ .../jdbc/repository/SimpleJdbcRepository.java | 102 +++++++++ .../JdbcPersistentEntityInformation.java | 30 +++ .../support/JdbcRepositoryFactory.java | 55 +++++ .../JdbcRepositoryIntegrationTests.java | 93 ++++++++ .../createTable.sql | 1 + 9 files changed, 605 insertions(+) create mode 100644 pom.xml create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/context/JdbcMappingContext.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java create mode 100644 src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java create mode 100644 src/main/java/org/springframework/data/jdbc/repository/support/JdbcPersistentEntityInformation.java create mode 100644 src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java create mode 100644 src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java create mode 100644 src/test/resources/org.springframework.data.jdbc.repository/createTable.sql diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000..8f9de8fe20 --- /dev/null +++ b/pom.xml @@ -0,0 +1,208 @@ + + + + 4.0.0 + + org.springframework.data + spring-data-jdbc + 1.0.0.BUILD-SNAPSHOT + + Spring Data JDBC + Spring Data module for JDBC repositories. + http://projects.spring.io/spring-data-jdbc + + + org.springframework.data.build + spring-data-parent + 2.0.0.BUILD-SNAPSHOT + + + + + DATAJDBC + + 2.0.0.BUILD-SNAPSHOT + + reuseReports + 1.8.0.10 + + + + + + release + + + + org.jfrog.buildinfo + artifactory-maven-plugin + false + + + + + + + + + + ${project.groupId} + spring-data-commons + ${springdata.commons} + + + + org.springframework + spring-tx + + + + org.springframework + spring-context + + + + org.springframework + spring-beans + + + + org.springframework + spring-core + + + commons-logging + commons-logging + + + + + + org.hsqldb + hsqldb + 2.2.8 + test + + + org.springframework + spring-jdbc + + + + + + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco} + + ${jacoco.destfile} + + + + jacoco-initialize + + prepare-agent + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + org.springframework + spring-instrument + ${spring} + runtime + + + org.hsqldb + hsqldb + ${hsqldb1} + runtime + + + + + default-test + + + **/* + + + + + unit-tests + + test + + test + + + **/*UnitTests.java + + + + + integration-tests + + test + + test + + + **/*IntegrationTests.java + **/*Tests.java + + + **/*UnitTests.java +\ + + -javaagent:${settings.localRepository}/org/jacoco/org.jacoco.agent/${jacoco}/org.jacoco.agent-${jacoco}-runtime.jar=destfile=${jacoco.destfile} + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + org.codehaus.mojo + wagon-maven-plugin + + + org.asciidoctor + asciidoctor-maven-plugin + + + + + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + + + + + spring-plugins-snapshot + https://repo.spring.io/plugins-snapshot + + + + \ No newline at end of file diff --git a/src/main/java/org/springframework/data/jdbc/mapping/context/JdbcMappingContext.java b/src/main/java/org/springframework/data/jdbc/mapping/context/JdbcMappingContext.java new file mode 100644 index 0000000000..e9fd47d319 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/context/JdbcMappingContext.java @@ -0,0 +1,41 @@ +/* + * 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.context; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; +import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; +import org.springframework.data.mapping.context.AbstractMappingContext; +import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.util.TypeInformation; + +/** + * @author Jens Schauder + */ +public class JdbcMappingContext extends AbstractMappingContext, JdbcPersistentProperty> { + + + @Override + protected JdbcPersistentEntity createPersistentEntity(TypeInformation typeInformation) { + return new JdbcPersistentEntity(typeInformation); + } + + @Override + protected JdbcPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, JdbcPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { + return new JdbcPersistentProperty(field, descriptor, owner, simpleTypeHolder); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java new file mode 100644 index 0000000000..7e91a166c9 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java @@ -0,0 +1,29 @@ +/* + * 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 org.springframework.data.mapping.model.BasicPersistentEntity; +import org.springframework.data.util.TypeInformation; + +/** + * @author Jens Schauder + */ +public class JdbcPersistentEntity extends BasicPersistentEntity { + + public JdbcPersistentEntity(TypeInformation information) { + super(information); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java new file mode 100644 index 0000000000..1bcc1be849 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java @@ -0,0 +1,46 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.model; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import org.springframework.data.mapping.Association; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; +import org.springframework.data.mapping.model.SimpleTypeHolder; + +/** + * @author Jens Schauder + */ +public class JdbcPersistentProperty extends AnnotationBasedPersistentProperty { + + /** + * Creates a new {@link AnnotationBasedPersistentProperty}. + * + * @param field must not be {@literal null}. + * @param propertyDescriptor can be {@literal null}. + * @param owner must not be {@literal null}. + * @param simpleTypeHolder + */ + public JdbcPersistentProperty(Field field, PropertyDescriptor propertyDescriptor, PersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { + super(field, propertyDescriptor, owner, simpleTypeHolder); + } + + @Override + protected Association createAssociation() { + return null; + } +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java new file mode 100644 index 0000000000..45bb7ad979 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java @@ -0,0 +1,102 @@ +/* + * 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 java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import javax.sql.DataSource; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.core.EntityInformation; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; + +/** + * @author Jens Schauder + */ +public class SimpleJdbcRepository implements CrudRepository { + + private final EntityInformation entityInformation; + private final NamedParameterJdbcOperations template; + + public SimpleJdbcRepository(EntityInformation entityInformation, DataSource dataSource) { + this.entityInformation = entityInformation; + this.template = new NamedParameterJdbcTemplate(dataSource); + } + + @Override + public S save(S entity) { + Map parameters = new HashMap<>(); + parameters.put("id", entityInformation.getId(entity)); + parameters.put("name", "blah blah"); + + template.update( + "insert into dummyentity (id, name) values (:id, :name)", + parameters); + + return entity; + } + + @Override + public Iterable save(Iterable entities) { + return null; + } + + @Override + public T findOne(ID id) { + return null; + } + + @Override + public boolean exists(ID id) { + return false; + } + + @Override + public Iterable findAll() { + return null; + } + + @Override + public Iterable findAll(Iterable ids) { + return null; + } + + @Override + public long count() { + return 0; + } + + @Override + public void delete(ID id) { + + } + + @Override + public void delete(T entity) { + + } + + @Override + public void delete(Iterable entities) { + + } + + @Override + public void deleteAll() { + + } +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcPersistentEntityInformation.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcPersistentEntityInformation.java new file mode 100644 index 0000000000..97664acf0e --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcPersistentEntityInformation.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.repository.support; + +import java.io.Serializable; +import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; +import org.springframework.data.repository.core.support.PersistentEntityInformation; + +/** + * @author Jens Schauder + */ +public class JdbcPersistentEntityInformation extends PersistentEntityInformation { + + public JdbcPersistentEntityInformation(JdbcPersistentEntity persistentEntity) { + super(persistentEntity); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java new file mode 100644 index 0000000000..a92d74d6cb --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java @@ -0,0 +1,55 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.repository.support; + +import java.io.Serializable; +import javax.sql.DataSource; +import org.springframework.data.jdbc.mapping.context.JdbcMappingContext; +import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; +import org.springframework.data.jdbc.repository.SimpleJdbcRepository; +import org.springframework.data.repository.core.EntityInformation; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; + +/** + * @author Jens Schauder + */ +public class JdbcRepositoryFactory extends RepositoryFactorySupport { + + private final DataSource dataSource; + private final JdbcMappingContext context = new JdbcMappingContext(); + + public JdbcRepositoryFactory(DataSource dataSource) { + this.dataSource = dataSource; + } + + @Override + public EntityInformation getEntityInformation(Class aClass) { + JdbcPersistentEntity persistentEntity = (JdbcPersistentEntity) context.getPersistentEntity(aClass); + return new JdbcPersistentEntityInformation(persistentEntity); + } + + @Override + protected Object getTargetRepository(RepositoryInformation repositoryInformation) { + return new SimpleJdbcRepository(getEntityInformation(repositoryInformation.getDomainType()), dataSource); + } + + @Override + protected Class getRepositoryBaseClass(RepositoryMetadata repositoryMetadata) { + return SimpleJdbcRepository.class; + } +} diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java new file mode 100644 index 0000000000..b938925f62 --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -0,0 +1,93 @@ +/* + * 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.junit.Assert.*; + +import java.sql.SQLException; +import org.junit.After; +import org.junit.Test; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.repository.CrudRepository; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; + +import lombok.Data; + +/** + * very simple use cases for creation and usage of JdbcRepositories. + * + * @author Jens Schauder + */ +public class JdbcRepositoryIntegrationTests { + + private final EmbeddedDatabase db = new EmbeddedDatabaseBuilder() + .generateUniqueName(true) + .setType(EmbeddedDatabaseType.HSQL) + .setScriptEncoding("UTF-8") + .ignoreFailedDrops(true) + .addScript("org.springframework.data.jdbc.repository/createTable.sql") + .build(); + + private final NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(db); + + @After + public void afeter() { + db.shutdown(); + } + + + @Test + public void canSaveAnEntity() throws SQLException { + DummyEntityRepository repository = createRepository(); + + DummyEntity entity = new DummyEntity(); + entity.setId(23L); + entity.setName("Entity Name"); + + entity = repository.save(entity); + + int count = template.queryForObject( + "SELECT count(*) FROM dummyentity WHERE id = :id", + new MapSqlParameterSource("id", entity.getId()), + Integer.class); + + assertEquals( + 1, + count); + } + + private DummyEntityRepository createRepository() throws SQLException { + JdbcRepositoryFactory jdbcRepositoryFactory = new JdbcRepositoryFactory(db); + return jdbcRepositoryFactory.getRepository(DummyEntityRepository.class); + } + + private interface DummyEntityRepository extends CrudRepository { + + } + + @Data + private static class DummyEntity { + + @Id + Long id; + String name; + } +} diff --git a/src/test/resources/org.springframework.data.jdbc.repository/createTable.sql b/src/test/resources/org.springframework.data.jdbc.repository/createTable.sql new file mode 100644 index 0000000000..6bfdd47170 --- /dev/null +++ b/src/test/resources/org.springframework.data.jdbc.repository/createTable.sql @@ -0,0 +1 @@ +CREATE TABLE dummyentity (ID BIGINT, NAME VARCHAR(100), PRIMARY KEY (ID)) \ No newline at end of file From 11df79b914ca9699c81915990acadcc21c0ce546 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 28 Feb 2017 08:56:06 +0100 Subject: [PATCH 02/12] DATAJDBC-99 - Prepare feature branch. --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8f9de8fe20..113ebdf725 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-jdbc - 1.0.0.BUILD-SNAPSHOT + 1.0.0.DATAJDBC-99-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -205,4 +205,4 @@ - \ No newline at end of file + From 720a0a3bd30c05523abdf96565f40a8f526e4047 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 21 Feb 2017 13:57:25 +0100 Subject: [PATCH 03/12] DATAJDBC-95 - Saving and loading a trivial entity. Creation of the necessary sql statements is now dynamic and driven in a trivial way by the entity. Known issues with the solution that need to get fixed later: Sql generating code is in the repository and should go somewhere else. Mapping logic is very trivial and should go in a separate place. --- .../data/jdbc/repository/EntityRowMapper.java | 65 ++++++++++++++++ .../jdbc/repository/SimpleJdbcRepository.java | 76 ++++++++++++++++--- .../support/JdbcRepositoryFactory.java | 5 +- .../JdbcRepositoryIntegrationTests.java | 39 +++++++--- 4 files changed, 162 insertions(+), 23 deletions(-) create mode 100644 src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java diff --git a/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java new file mode 100644 index 0000000000..7045067075 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java @@ -0,0 +1,65 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.repository; + +import java.lang.reflect.InvocationTargetException; +import java.sql.ResultSet; +import java.sql.SQLException; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.PropertyHandler; + +/** + * @author Jens Schauder + */ +class EntityRowMapper implements org.springframework.jdbc.core.RowMapper { + + private final PersistentEntity entity; + + EntityRowMapper(PersistentEntity entity) { + this.entity = entity; + } + + @Override + public T mapRow(ResultSet rs, int rowNum) throws SQLException { + + try { + + T t = createInstance(); + + entity.doWithProperties((PropertyHandler) property -> { + setProperty(rs, t, property); + }); + + return t; + } catch (Exception e) { + throw new RuntimeException(String.format("Could not instantiate %s", entity.getType())); + } + } + + private T createInstance() throws InstantiationException, IllegalAccessException, InvocationTargetException { + return (T) entity.getPersistenceConstructor().getConstructor().newInstance(); + } + + private void setProperty(ResultSet rs, T t, PersistentProperty property) { + + try { + property.getSetter().invoke(t, rs.getObject(property.getName())); + } catch (Exception e) { + throw new RuntimeException(String.format("Couldn't set property %s.", property.getName()), e); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java index 45bb7ad979..8ae8d4b723 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java +++ b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java @@ -16,11 +16,17 @@ package org.springframework.data.jdbc.repository; import java.io.Serializable; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.sql.DataSource; +import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.core.EntityInformation; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; @@ -29,23 +35,25 @@ */ public class SimpleJdbcRepository implements CrudRepository { - private final EntityInformation entityInformation; + private final JdbcPersistentEntity entity; private final NamedParameterJdbcOperations template; - public SimpleJdbcRepository(EntityInformation entityInformation, DataSource dataSource) { - this.entityInformation = entityInformation; + private final String findOneSql; + private final String insertSql; + + public SimpleJdbcRepository(JdbcPersistentEntity entity, DataSource dataSource) { + + this.entity = entity; this.template = new NamedParameterJdbcTemplate(dataSource); + + findOneSql = createFindOneSelectSql(); + insertSql = createInsertSql(); } @Override public S save(S entity) { - Map parameters = new HashMap<>(); - parameters.put("id", entityInformation.getId(entity)); - parameters.put("name", "blah blah"); - template.update( - "insert into dummyentity (id, name) values (:id, :name)", - parameters); + template.update(insertSql, getPropertyMap(entity)); return entity; } @@ -57,7 +65,12 @@ public Iterable save(Iterable entities) { @Override public T findOne(ID id) { - return null; + + return template.queryForObject( + findOneSql, + new MapSqlParameterSource("id", id), + new EntityRowMapper(entity) + ); } @Override @@ -99,4 +112,45 @@ public void delete(Iterable entities) { public void deleteAll() { } + + private String createFindOneSelectSql() { + + String tableName = entity.getType().getSimpleName(); + String idColumn = entity.getIdProperty().getName(); + + return String.format("select * from %s where %s = :id", tableName, idColumn); + } + + private String createInsertSql() { + + List propertyNames = new ArrayList<>(); + entity.doWithProperties((PropertyHandler) persistentProperty -> propertyNames.add(persistentProperty.getName())); + + String insertTemplate = "insert into %s (%s) values (%s)"; + + String tableName = entity.getType().getSimpleName(); + + String tableColumns = propertyNames.stream().collect(Collectors.joining(", ")); + String parameterNames = propertyNames.stream().collect(Collectors.joining(", :", ":", "")); + + return String.format(insertTemplate, tableName, tableColumns, parameterNames); + } + + private Map getPropertyMap(final S entity) { + + Map parameters = new HashMap<>(); + + this.entity.doWithProperties(new PropertyHandler() { + @Override + public void doWithPersistentProperty(PersistentProperty persistentProperty) { + try { + parameters.put(persistentProperty.getName(), persistentProperty.getGetter().invoke(entity)); + } catch (Exception e) { + throw new RuntimeException(String.format("Couldn't get value of property %s", persistentProperty.getName())); + } + } + }); + + return parameters; + } } diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java index a92d74d6cb..d24598250d 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java @@ -39,13 +39,12 @@ public JdbcRepositoryFactory(DataSource dataSource) { @Override public EntityInformation getEntityInformation(Class aClass) { - JdbcPersistentEntity persistentEntity = (JdbcPersistentEntity) context.getPersistentEntity(aClass); - return new JdbcPersistentEntityInformation(persistentEntity); + return new JdbcPersistentEntityInformation((JdbcPersistentEntity) context.getPersistentEntity(aClass)); } @Override protected Object getTargetRepository(RepositoryInformation repositoryInformation) { - return new SimpleJdbcRepository(getEntityInformation(repositoryInformation.getDomainType()), dataSource); + return new SimpleJdbcRepository(context.getPersistentEntity(repositoryInformation.getDomainType()), dataSource); } @Override diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java index b938925f62..b1fd9045cd 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -48,19 +48,18 @@ public class JdbcRepositoryIntegrationTests { private final NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(db); + private final DummyEntityRepository repository = createRepository(db); + + private DummyEntity entity = createDummyEntity(); + @After - public void afeter() { + public void after() { db.shutdown(); } @Test public void canSaveAnEntity() throws SQLException { - DummyEntityRepository repository = createRepository(); - - DummyEntity entity = new DummyEntity(); - entity.setId(23L); - entity.setName("Entity Name"); entity = repository.save(entity); @@ -74,9 +73,31 @@ public void canSaveAnEntity() throws SQLException { count); } - private DummyEntityRepository createRepository() throws SQLException { - JdbcRepositoryFactory jdbcRepositoryFactory = new JdbcRepositoryFactory(db); - return jdbcRepositoryFactory.getRepository(DummyEntityRepository.class); + @Test + public void canSaveAndLoadAnEntity() throws SQLException { + + entity = repository.save(entity); + + DummyEntity reloadedEntity = repository.findOne(entity.getId()); + + assertEquals( + entity.getId(), + reloadedEntity.getId()); + assertEquals( + entity.getName(), + reloadedEntity.getName()); + } + + private static DummyEntityRepository createRepository(EmbeddedDatabase db) { + return new JdbcRepositoryFactory(db).getRepository(DummyEntityRepository.class); + } + + + private static DummyEntity createDummyEntity() { + DummyEntity entity = new DummyEntity(); + entity.setId(23L); + entity.setName("Entity Name"); + return entity; } private interface DummyEntityRepository extends CrudRepository { From 82f73a307305302df7a319d3c7764c31becead8e Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Wed, 22 Feb 2017 07:02:47 +0100 Subject: [PATCH 04/12] DATAJDBC-97 - Basic implementation of all CRUD methods. --- pom.xml | 12 +- .../mapping/model/JdbcPersistentEntity.java | 24 +++ .../mapping/model/JdbcPersistentProperty.java | 6 + .../data/jdbc/repository/EntityRowMapper.java | 47 +++--- .../jdbc/repository/SimpleJdbcRepository.java | 96 ++++++------ .../data/jdbc/repository/SqlGenerator.java | 138 ++++++++++++++++++ .../JdbcRepositoryIntegrationTests.java | 122 +++++++++++++++- 7 files changed, 371 insertions(+), 74 deletions(-) create mode 100644 src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java diff --git a/pom.xml b/pom.xml index 113ebdf725..ab7a0f4451 100644 --- a/pom.xml +++ b/pom.xml @@ -67,6 +67,11 @@ spring-beans + + org.springframework + spring-jdbc + + org.springframework spring-core @@ -84,9 +89,12 @@ 2.2.8 test + - org.springframework - spring-jdbc + org.assertj + assertj-core + 3.6.2 + test diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java index 7e91a166c9..217d8d701f 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java @@ -19,11 +19,35 @@ 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 */ public class JdbcPersistentEntity extends BasicPersistentEntity { + private String tableName; + private String idColumn; + public JdbcPersistentEntity(TypeInformation information) { super(information); } + + public String getTableName() { + + if (tableName == null) + tableName = getType().getSimpleName(); + + return tableName; + } + + public String getIdColumn() { + + if (idColumn == null) + idColumn = getIdProperty().getName(); + + return idColumn; + } + + public Object getIdValue(T instance) { + return getPropertyAccessor(instance).getProperty(getIdProperty()); + } } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java index 1bcc1be849..841d64c650 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java @@ -23,6 +23,8 @@ import org.springframework.data.mapping.model.SimpleTypeHolder; /** + * meta data about a property to be used by repository implementations. + * * @author Jens Schauder */ public class JdbcPersistentProperty extends AnnotationBasedPersistentProperty { @@ -43,4 +45,8 @@ public JdbcPersistentProperty(Field field, PropertyDescriptor propertyDescriptor protected Association createAssociation() { return null; } + + public String getColumnName() { + return getName(); + } } diff --git a/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java index 7045067075..4bde019f07 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java +++ b/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java @@ -15,49 +15,62 @@ */ package org.springframework.data.jdbc.repository; -import java.lang.reflect.InvocationTargetException; import java.sql.ResultSet; import java.sql.SQLException; -import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.convert.ClassGeneratingEntityInstantiator; +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.PreferredConstructor; import org.springframework.data.mapping.PropertyHandler; +import org.springframework.data.mapping.model.MappingException; +import org.springframework.data.mapping.model.ParameterValueProvider; /** + * maps a ResultSet to an entity of type {@code T} + * * @author Jens Schauder */ class EntityRowMapper implements org.springframework.jdbc.core.RowMapper { - private final PersistentEntity entity; + private final JdbcPersistentEntity entity; + + private final EntityInstantiator instantiator = new ClassGeneratingEntityInstantiator(); - EntityRowMapper(PersistentEntity entity) { + EntityRowMapper(JdbcPersistentEntity entity) { this.entity = entity; } @Override public T mapRow(ResultSet rs, int rowNum) throws SQLException { - try { - - T t = createInstance(); + T t = createInstance(rs); - entity.doWithProperties((PropertyHandler) property -> { - setProperty(rs, t, property); - }); + entity.doWithProperties((PropertyHandler) property -> { + setProperty(rs, t, property); + }); - return t; - } catch (Exception e) { - throw new RuntimeException(String.format("Could not instantiate %s", entity.getType())); - } + return t; } - private T createInstance() throws InstantiationException, IllegalAccessException, InvocationTargetException { - return (T) entity.getPersistenceConstructor().getConstructor().newInstance(); + private T createInstance(ResultSet rs) { + return instantiator.createInstance(entity, new ParameterValueProvider() { + @Override + public T getParameterValue(PreferredConstructor.Parameter parameter) { + try { + return (T) rs.getObject(parameter.getName()); + } catch (SQLException e) { + throw new MappingException(String.format("Couldn't read column %s from ResultSet.", parameter.getName())); + } + } + }); } private void setProperty(ResultSet rs, T t, PersistentProperty property) { try { - property.getSetter().invoke(t, rs.getObject(property.getName())); + entity.getPropertyAccessor(t).setProperty(property, rs.getObject(property.getName())); } catch (Exception e) { throw new RuntimeException(String.format("Couldn't set property %s.", property.getName()), e); } diff --git a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java index 8ae8d4b723..556e2a1eaf 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java +++ b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java @@ -16,14 +16,13 @@ package org.springframework.data.jdbc.repository; import java.io.Serializable; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import javax.sql.DataSource; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; -import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -37,117 +36,116 @@ public class SimpleJdbcRepository implements CrudRep private final JdbcPersistentEntity entity; private final NamedParameterJdbcOperations template; + private final SqlGenerator sql; - private final String findOneSql; - private final String insertSql; + private final EntityRowMapper entityRowMapper; public SimpleJdbcRepository(JdbcPersistentEntity entity, DataSource dataSource) { this.entity = entity; this.template = new NamedParameterJdbcTemplate(dataSource); - findOneSql = createFindOneSelectSql(); - insertSql = createInsertSql(); + entityRowMapper = new EntityRowMapper(entity); + sql = new SqlGenerator(entity); } @Override public S save(S entity) { - template.update(insertSql, getPropertyMap(entity)); + template.update(sql.getInsert(), getPropertyMap(entity)); return entity; } @Override public Iterable save(Iterable entities) { - return null; + + Map[] batchValues = StreamSupport + .stream(entities.spliterator(), false) + .map(i -> getPropertyMap(i)) + .toArray(size -> new Map[size]); + + template.batchUpdate(sql.getInsert(), batchValues); + + return entities; } @Override public T findOne(ID id) { return template.queryForObject( - findOneSql, + sql.getFindOne(), new MapSqlParameterSource("id", id), - new EntityRowMapper(entity) + entityRowMapper ); } @Override public boolean exists(ID id) { - return false; + + return template.queryForObject( + sql.getExists(), + new MapSqlParameterSource("id", id), + Boolean.class + ); } @Override public Iterable findAll() { - return null; + return template.query(sql.getFindAll(), entityRowMapper); } @Override public Iterable findAll(Iterable ids) { - return null; + return template.query(sql.getFindAllInList(), new MapSqlParameterSource("ids", ids), entityRowMapper); } @Override public long count() { - return 0; + return template.getJdbcOperations().queryForObject(sql.getCount(), Long.class); } @Override public void delete(ID id) { - + template.update(sql.getDeleteById(), new MapSqlParameterSource("id", id)); } @Override - public void delete(T entity) { + public void delete(T instance) { + template.update( + sql.getDeleteById(), + new MapSqlParameterSource("id", + entity.getIdValue(instance))); } @Override public void delete(Iterable entities) { + template.update( + sql.getDeleteByList(), + new MapSqlParameterSource("ids", + StreamSupport + .stream(entities.spliterator(), false) + .map(entity::getIdValue) + .collect(Collectors.toList()) + ) + ); } @Override public void deleteAll() { - - } - - private String createFindOneSelectSql() { - - String tableName = entity.getType().getSimpleName(); - String idColumn = entity.getIdProperty().getName(); - - return String.format("select * from %s where %s = :id", tableName, idColumn); - } - - private String createInsertSql() { - - List propertyNames = new ArrayList<>(); - entity.doWithProperties((PropertyHandler) persistentProperty -> propertyNames.add(persistentProperty.getName())); - - String insertTemplate = "insert into %s (%s) values (%s)"; - - String tableName = entity.getType().getSimpleName(); - - String tableColumns = propertyNames.stream().collect(Collectors.joining(", ")); - String parameterNames = propertyNames.stream().collect(Collectors.joining(", :", ":", "")); - - return String.format(insertTemplate, tableName, tableColumns, parameterNames); + template.getJdbcOperations().update(sql.getDeleteAll()); } - private Map getPropertyMap(final S entity) { + private Map getPropertyMap(final S instance) { Map parameters = new HashMap<>(); - this.entity.doWithProperties(new PropertyHandler() { + this.entity.doWithProperties(new PropertyHandler() { @Override - public void doWithPersistentProperty(PersistentProperty persistentProperty) { - try { - parameters.put(persistentProperty.getName(), persistentProperty.getGetter().invoke(entity)); - } catch (Exception e) { - throw new RuntimeException(String.format("Couldn't get value of property %s", persistentProperty.getName())); - } + public void doWithPersistentProperty(JdbcPersistentProperty persistentProperty) { + parameters.put(persistentProperty.getColumnName(), entity.getPropertyAccessor(instance).getProperty(persistentProperty)); } }); diff --git a/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java b/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java new file mode 100644 index 0000000000..b561029bb5 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java @@ -0,0 +1,138 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; +import org.springframework.data.mapping.PropertyHandler; + +/** + * @author Jens Schauder + */ +class SqlGenerator { + + private final String findOneSql; + private final String findAllSql; + private final String findAllInListSql; + + private final String existsSql; + private final String countSql; + + private final String insertSql; + private final String deleteByIdSql; + private final String deleteAllSql; + private final String deleteByListSql; + + SqlGenerator(JdbcPersistentEntity entity) { + + findOneSql = createFindOneSelectSql(entity); + findAllSql = createFindAllSql(entity); + findAllInListSql = createFindAllInListSql(entity); + + existsSql = createExistsSql(entity); + countSql = createCountSql(entity); + + insertSql = createInsertSql(entity); + + deleteByIdSql = createDeleteSql(entity); + deleteAllSql = createDeleteAllSql(entity); + deleteByListSql = createDeleteByListSql(entity); + } + + String getFindAllInList() { + return findAllInListSql; + } + + String getFindAll() { + return findAllSql; + } + + String getExists() { + return existsSql; + } + + String getFindOne() { + return findOneSql; + } + + String getInsert() { + return insertSql; + } + + String getCount() { + return countSql; + } + + String getDeleteById() { + return deleteByIdSql; + } + + String getDeleteAll() { + return deleteAllSql; + } + + String getDeleteByList() { + return deleteByListSql; + } + private String createFindOneSelectSql(JdbcPersistentEntity entity) { + return String.format("select * from %s where %s = :id", entity.getTableName(), entity.getIdColumn()); + } + + private String createFindAllSql(JdbcPersistentEntity entity) { + return String.format("select * from %s", entity.getTableName()); + } + + private String createFindAllInListSql(JdbcPersistentEntity entity) { + return String.format(String.format("select * from %s where %s in (:ids)", entity.getTableName(), entity.getIdColumn()), entity.getTableName()); + } + + private String createExistsSql(JdbcPersistentEntity entity) { + return String.format("select count(*) from %s where %s = :id", entity.getTableName(), entity.getIdColumn()); + } + + private String createCountSql(JdbcPersistentEntity entity) { + return String.format("select count(*) from %s", entity.getTableName(), entity.getIdColumn()); + } + + private String createInsertSql(JdbcPersistentEntity entity) { + + List propertyNames = new ArrayList<>(); + entity.doWithProperties((PropertyHandler) persistentProperty -> propertyNames.add(persistentProperty.getName())); + + String insertTemplate = "insert into %s (%s) values (%s)"; + + String tableName = entity.getType().getSimpleName(); + + String tableColumns = propertyNames.stream().collect(Collectors.joining(", ")); + String parameterNames = propertyNames.stream().collect(Collectors.joining(", :", ":", "")); + + return String.format(insertTemplate, tableName, tableColumns, parameterNames); + } + + private String createDeleteSql(JdbcPersistentEntity entity) { + return String.format("delete from %s where %s = :id", entity.getTableName(), entity.getIdColumn()); + } + + private String createDeleteAllSql(JdbcPersistentEntity entity) { + return String.format("delete from %s", entity.getTableName()); + } + + private String createDeleteByListSql(JdbcPersistentEntity entity) { + return String.format("delete from %s where id in (:ids)", entity.getTableName()); + } +} diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java index b1fd9045cd..8af0faf152 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -15,10 +15,13 @@ */ 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 java.sql.SQLException; import org.junit.After; +import org.junit.Assert; import org.junit.Test; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; @@ -50,7 +53,7 @@ public class JdbcRepositoryIntegrationTests { private final DummyEntityRepository repository = createRepository(db); - private DummyEntity entity = createDummyEntity(); + private DummyEntity entity = createDummyEntity(23L); @After public void after() { @@ -59,7 +62,7 @@ public void after() { @Test - public void canSaveAnEntity() throws SQLException { + public void canSaveAnEntity() { entity = repository.save(entity); @@ -74,7 +77,7 @@ public void canSaveAnEntity() throws SQLException { } @Test - public void canSaveAndLoadAnEntity() throws SQLException { + public void canSaveAndLoadAnEntity() { entity = repository.save(entity); @@ -88,14 +91,120 @@ public void canSaveAndLoadAnEntity() throws SQLException { reloadedEntity.getName()); } + @Test + public void saveMany() { + + DummyEntity other = createDummyEntity(24L); + + repository.save(asList(entity, other)); + + assertThat(repository.findAll()).extracting(DummyEntity::getId).containsExactlyInAnyOrder(23L, 24L); + } + + @Test + public void existsReturnsTrueIffEntityExists() { + + entity = repository.save(entity); + + assertTrue(repository.exists(entity.getId())); + assertFalse(repository.exists(entity.getId() + 1)); + } + + @Test + public void findAllFindsAllEntities() { + + DummyEntity other = createDummyEntity(24L); + + other = repository.save(other); + entity = repository.save(entity); + + Iterable all = repository.findAll(); + + assertThat(all).extracting("id").containsExactlyInAnyOrder(entity.getId(), other.getId()); + } + + @Test + public void findAllFindsAllSpecifiedEntities() { + + repository.save(createDummyEntity(24L)); + DummyEntity other = repository.save(createDummyEntity(25L)); + entity = repository.save(entity); + + Iterable all = repository.findAll(asList(entity.getId(), other.getId())); + + assertThat(all).extracting("id").containsExactlyInAnyOrder(entity.getId(), other.getId()); + } + + @Test + public void count() { + + repository.save(createDummyEntity(24L)); + repository.save(createDummyEntity(25L)); + repository.save(entity); + + assertThat(repository.count()).isEqualTo(3L); + } + + @Test + public void deleteById() { + + repository.save(createDummyEntity(24L)); + repository.save(createDummyEntity(25L)); + repository.save(entity); + + repository.delete(24L); + + assertThat(repository.findAll()).extracting(DummyEntity::getId).containsExactlyInAnyOrder(23L, 25L); + } + + @Test + public void deleteByEntity() { + + repository.save(createDummyEntity(24L)); + repository.save(createDummyEntity(25L)); + repository.save(entity); + + repository.delete(entity); + + assertThat(repository.findAll()).extracting(DummyEntity::getId).containsExactlyInAnyOrder(24L, 25L); + } + + + @Test + public void deleteByList() { + + repository.save(entity); + repository.save(createDummyEntity(24L)); + DummyEntity other = repository.save(createDummyEntity(25L)); + + repository.delete(asList(entity, other)); + + assertThat(repository.findAll()).extracting(DummyEntity::getId).containsExactlyInAnyOrder(24L); + } + + @Test + public void deleteAll() { + + repository.save(entity); + repository.save(createDummyEntity(24L)); + repository.save(createDummyEntity(25L)); + + repository.deleteAll(); + + assertThat(repository.findAll()).isEmpty(); + } + + + private static DummyEntityRepository createRepository(EmbeddedDatabase db) { return new JdbcRepositoryFactory(db).getRepository(DummyEntityRepository.class); } - private static DummyEntity createDummyEntity() { + private static DummyEntity createDummyEntity(long id) { + DummyEntity entity = new DummyEntity(); - entity.setId(23L); + entity.setId(id); entity.setName("Entity Name"); return entity; } @@ -104,8 +213,9 @@ private interface DummyEntityRepository extends CrudRepository Date: Mon, 27 Feb 2017 08:22:19 +0100 Subject: [PATCH 05/12] DATAJDBC-96 - Database based Id generation. No longer using batch inserts, which won't (easily) hold up for long anyway, since we need to decide between update and insert anyway. If one wants to support batch insert one also has to check if any autoid columns are present, because those seem not to work with batch inserts with many or at least some drivers. Related ticket: SPR-1836. --- .../mapping/model/JdbcPersistentEntity.java | 4 ++ .../jdbc/repository/SimpleJdbcRepository.java | 22 +++++----- .../JdbcRepositoryIntegrationTests.java | 40 +++++++++++++++++-- .../createTable.sql | 2 +- 4 files changed, 54 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java index 217d8d701f..bbe2d69cfc 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java @@ -50,4 +50,8 @@ public String getIdColumn() { public Object getIdValue(T instance) { return getPropertyAccessor(instance).getProperty(getIdProperty()); } + + public void setId(T instance, Object value) { + getPropertyAccessor(instance).setProperty(getIdProperty(),value); + } } diff --git a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java index 556e2a1eaf..e66502ac8c 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java +++ b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java @@ -28,6 +28,8 @@ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; /** * @author Jens Schauder @@ -50,22 +52,24 @@ public SimpleJdbcRepository(JdbcPersistentEntity entity, DataSource dataSourc } @Override - public S save(S entity) { + public S save(S instance) { - template.update(sql.getInsert(), getPropertyMap(entity)); + KeyHolder holder = new GeneratedKeyHolder(); - return entity; + template.update( + sql.getInsert(), + new MapSqlParameterSource(getPropertyMap(instance)), + holder); + + entity.setId(instance, holder.getKey()); + + return instance; } @Override public Iterable save(Iterable entities) { - Map[] batchValues = StreamSupport - .stream(entities.spliterator(), false) - .map(i -> getPropertyMap(i)) - .toArray(size -> new Map[size]); - - template.batchUpdate(sql.getInsert(), batchValues); + entities.forEach(this::save); return entities; } diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java index 8af0faf152..0251b976b6 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -19,9 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.*; -import java.sql.SQLException; import org.junit.After; -import org.junit.Assert; import org.junit.Test; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; @@ -91,6 +89,26 @@ public void canSaveAndLoadAnEntity() { reloadedEntity.getName()); } + @Test + public void canSaveAndLoadAnEntityWithDatabaseBasedIdGeneration() { + + entity = createDummyEntity(null); + + entity = repository.save(entity); + + assertThat(entity).isNotNull(); + + DummyEntity reloadedEntity = repository.findOne(entity.getId()); + + assertEquals( + entity.getId(), + reloadedEntity.getId()); + assertEquals( + entity.getName(), + reloadedEntity.getName()); + } + + @Test public void saveMany() { @@ -101,6 +119,21 @@ public void saveMany() { assertThat(repository.findAll()).extracting(DummyEntity::getId).containsExactlyInAnyOrder(23L, 24L); } + @Test + public void saveManyWithIdGeneration() { + + DummyEntity one = createDummyEntity(null); + DummyEntity two = createDummyEntity(null); + + Iterable entities = repository.save(asList(one, two)); + + assertThat(entities).allMatch(e -> e.getId() != null); + + assertThat(repository.findAll()) + .extracting(DummyEntity::getId) + .containsExactlyInAnyOrder(new Long[]{one.getId(), two.getId()}); + } + @Test public void existsReturnsTrueIffEntityExists() { @@ -195,13 +228,12 @@ public void deleteAll() { } - private static DummyEntityRepository createRepository(EmbeddedDatabase db) { return new JdbcRepositoryFactory(db).getRepository(DummyEntityRepository.class); } - private static DummyEntity createDummyEntity(long id) { + private static DummyEntity createDummyEntity(Long id) { DummyEntity entity = new DummyEntity(); entity.setId(id); diff --git a/src/test/resources/org.springframework.data.jdbc.repository/createTable.sql b/src/test/resources/org.springframework.data.jdbc.repository/createTable.sql index 6bfdd47170..c818bad883 100644 --- a/src/test/resources/org.springframework.data.jdbc.repository/createTable.sql +++ b/src/test/resources/org.springframework.data.jdbc.repository/createTable.sql @@ -1 +1 @@ -CREATE TABLE dummyentity (ID BIGINT, NAME VARCHAR(100), PRIMARY KEY (ID)) \ No newline at end of file +CREATE TABLE dummyentity (ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 4711) PRIMARY KEY, NAME VARCHAR(100)) \ No newline at end of file From 177f05259eef22796d4c2d425ccf222c6396105b Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 27 Feb 2017 12:38:39 +0100 Subject: [PATCH 06/12] DATAJDBC-98 - Update implemented. New instances get saved with an insert statement. Existing instances get updated. Also added some test to find certain corner cases that I feared may cause problems: - Id properties being not editable (no setter and final). - Id properties being primitive. - Id properties not being named "Id" and fixed the issues resulting from those. --- .../jdbc/repository/SimpleJdbcRepository.java | 20 ++- .../data/jdbc/repository/SqlGenerator.java | 31 +++- ...epositoryIdGenerationIntegrationTests.java | 129 ++++++++++++++++ .../JdbcRepositoryIntegrationTests.java | 143 +++++++++--------- .../createTable.sql | 1 - ...sitory-id-generation-integration-tests.sql | 3 + .../jdbc-repository-integration-tests.sql | 1 + 7 files changed, 239 insertions(+), 89 deletions(-) create mode 100644 src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java delete mode 100644 src/test/resources/org.springframework.data.jdbc.repository/createTable.sql create mode 100644 src/test/resources/org.springframework.data.jdbc.repository/jdbc-repository-id-generation-integration-tests.sql create mode 100644 src/test/resources/org.springframework.data.jdbc.repository/jdbc-repository-integration-tests.sql diff --git a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java index e66502ac8c..8ec48d2d51 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java +++ b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java @@ -23,6 +23,7 @@ import javax.sql.DataSource; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; +import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -37,6 +38,7 @@ public class SimpleJdbcRepository implements CrudRepository { private final JdbcPersistentEntity entity; + private final JdbcPersistentEntityInformation entityInformation; private final NamedParameterJdbcOperations template; private final SqlGenerator sql; @@ -45,6 +47,7 @@ public class SimpleJdbcRepository implements CrudRep public SimpleJdbcRepository(JdbcPersistentEntity entity, DataSource dataSource) { this.entity = entity; + this.entityInformation = new JdbcPersistentEntityInformation(entity); this.template = new NamedParameterJdbcTemplate(dataSource); entityRowMapper = new EntityRowMapper(entity); @@ -54,14 +57,19 @@ public SimpleJdbcRepository(JdbcPersistentEntity entity, DataSource dataSourc @Override public S save(S instance) { - KeyHolder holder = new GeneratedKeyHolder(); + if (entityInformation.isNew(instance)) { - template.update( - sql.getInsert(), - new MapSqlParameterSource(getPropertyMap(instance)), - holder); + KeyHolder holder = new GeneratedKeyHolder(); + + template.update( + sql.getInsert(), + new MapSqlParameterSource(getPropertyMap(instance)), + holder); - entity.setId(instance, holder.getKey()); + entity.setId(instance, holder.getKey()); + } else { + template.update(sql.getUpdate(), getPropertyMap(instance)); + } return instance; } diff --git a/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java b/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java index b561029bb5..2d613d78a6 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java +++ b/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Collector; import java.util.stream.Collectors; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; import org.springframework.data.mapping.PropertyHandler; @@ -37,9 +38,13 @@ class SqlGenerator { private final String deleteByIdSql; private final String deleteAllSql; private final String deleteByListSql; + private final String updateSql; + private final List propertyNames = new ArrayList<>(); SqlGenerator(JdbcPersistentEntity entity) { + entity.doWithProperties((PropertyHandler) persistentProperty -> propertyNames.add(persistentProperty.getName())); + findOneSql = createFindOneSelectSql(entity); findAllSql = createFindAllSql(entity); findAllInListSql = createFindAllInListSql(entity); @@ -48,6 +53,7 @@ SqlGenerator(JdbcPersistentEntity entity) { countSql = createCountSql(entity); insertSql = createInsertSql(entity); + updateSql = createUpdateSql(entity); deleteByIdSql = createDeleteSql(entity); deleteAllSql = createDeleteAllSql(entity); @@ -74,6 +80,10 @@ String getInsert() { return insertSql; } + String getUpdate() { + return updateSql; + } + String getCount() { return countSql; } @@ -106,22 +116,26 @@ private String createExistsSql(JdbcPersistentEntity entity) { } private String createCountSql(JdbcPersistentEntity entity) { - return String.format("select count(*) from %s", entity.getTableName(), entity.getIdColumn()); + return String.format("select count(*) from %s", entity.getTableName()); } private String createInsertSql(JdbcPersistentEntity entity) { - List propertyNames = new ArrayList<>(); - entity.doWithProperties((PropertyHandler) persistentProperty -> propertyNames.add(persistentProperty.getName())); - String insertTemplate = "insert into %s (%s) values (%s)"; - String tableName = entity.getType().getSimpleName(); - String tableColumns = propertyNames.stream().collect(Collectors.joining(", ")); String parameterNames = propertyNames.stream().collect(Collectors.joining(", :", ":", "")); - return String.format(insertTemplate, tableName, tableColumns, parameterNames); + return String.format(insertTemplate, entity.getTableName(), tableColumns, parameterNames); + } + + private String createUpdateSql(JdbcPersistentEntity entity) { + + String updateTemplate = "update %s set %s where %s = :%s"; + + 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()); } private String createDeleteSql(JdbcPersistentEntity entity) { @@ -133,6 +147,7 @@ private String createDeleteAllSql(JdbcPersistentEntity entity) { } private String createDeleteByListSql(JdbcPersistentEntity entity) { - return String.format("delete from %s where id in (:ids)", entity.getTableName()); + return String.format("delete from %s where %s in (:ids)", entity.getTableName(), entity.getIdColumn()); } + } diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java new file mode 100644 index 0000000000..2f2b5396a8 --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java @@ -0,0 +1,129 @@ +/* + * 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.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.*; + +import org.junit.After; +import org.junit.Test; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.repository.CrudRepository; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; + +import lombok.Data; + +/** + * testing special cases for Id generation with JdbcRepositories. + * + * @author Jens Schauder + */ +public class JdbcRepositoryIdGenerationIntegrationTests { + + private final EmbeddedDatabase db = new EmbeddedDatabaseBuilder() + .generateUniqueName(true) + .setType(EmbeddedDatabaseType.HSQL) + .setScriptEncoding("UTF-8") + .ignoreFailedDrops(true) + .addScript("org.springframework.data.jdbc.repository/jdbc-repository-id-generation-integration-tests.sql") + .build(); + + private final NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(db); + + private final ReadOnlyIdEntityRepository repository = createRepository(db); + + private ReadOnlyIdEntity entity = createDummyEntity(); + + @After + public void after() { + db.shutdown(); + } + + @Test + public void idWithoutSetterGetsSet() { + + entity = repository.save(entity); + + assertThat(entity.getId()).isNotNull(); + + ReadOnlyIdEntity reloadedEntity = repository.findOne(entity.getId()); + + assertEquals( + entity.getId(), + reloadedEntity.getId()); + assertEquals( + entity.getName(), + reloadedEntity.getName()); + } + + @Test + public void primitiveIdGetsSet() { + + entity = repository.save(entity); + + assertThat(entity.getId()).isNotNull(); + + ReadOnlyIdEntity reloadedEntity = repository.findOne(entity.getId()); + + assertEquals( + entity.getId(), + reloadedEntity.getId()); + assertEquals( + entity.getName(), + reloadedEntity.getName()); + } + + + private static ReadOnlyIdEntityRepository createRepository(EmbeddedDatabase db) { + return new JdbcRepositoryFactory(db).getRepository(ReadOnlyIdEntityRepository.class); + } + + + private static ReadOnlyIdEntity createDummyEntity() { + + ReadOnlyIdEntity entity = new ReadOnlyIdEntity(null); + entity.setName("Entity Name"); + return entity; + } + + private interface ReadOnlyIdEntityRepository extends CrudRepository { + + } + + @Data + static class ReadOnlyIdEntity { + + @Id + private final Long id; + String name; + } + + private interface PrimitiveIdEntityRepository extends CrudRepository { + + } + + @Data + static class PrimitiveIdEntity { + + @Id + private final Long id; + String name; + } +} diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java index 0251b976b6..410b92fbd6 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -44,14 +44,14 @@ public class JdbcRepositoryIntegrationTests { .setType(EmbeddedDatabaseType.HSQL) .setScriptEncoding("UTF-8") .ignoreFailedDrops(true) - .addScript("org.springframework.data.jdbc.repository/createTable.sql") + .addScript("org.springframework.data.jdbc.repository/jdbc-repository-integration-tests.sql") .build(); private final NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(db); private final DummyEntityRepository repository = createRepository(db); - private DummyEntity entity = createDummyEntity(23L); + private DummyEntity entity = createDummyEntity(); @After public void after() { @@ -65,8 +65,8 @@ public void canSaveAnEntity() { entity = repository.save(entity); int count = template.queryForObject( - "SELECT count(*) FROM dummyentity WHERE id = :id", - new MapSqlParameterSource("id", entity.getId()), + "SELECT count(*) FROM dummyentity WHERE idProp = :id", + new MapSqlParameterSource("id", entity.getIdProp()), Integer.class); assertEquals( @@ -79,59 +79,24 @@ public void canSaveAndLoadAnEntity() { entity = repository.save(entity); - DummyEntity reloadedEntity = repository.findOne(entity.getId()); + DummyEntity reloadedEntity = repository.findOne(entity.getIdProp()); assertEquals( - entity.getId(), - reloadedEntity.getId()); + entity.getIdProp(), + reloadedEntity.getIdProp()); assertEquals( entity.getName(), reloadedEntity.getName()); } - @Test - public void canSaveAndLoadAnEntityWithDatabaseBasedIdGeneration() { - - entity = createDummyEntity(null); - - entity = repository.save(entity); - - assertThat(entity).isNotNull(); - - DummyEntity reloadedEntity = repository.findOne(entity.getId()); - - assertEquals( - entity.getId(), - reloadedEntity.getId()); - assertEquals( - entity.getName(), - reloadedEntity.getName()); - } - - @Test public void saveMany() { - DummyEntity other = createDummyEntity(24L); + DummyEntity other = createDummyEntity(); repository.save(asList(entity, other)); - assertThat(repository.findAll()).extracting(DummyEntity::getId).containsExactlyInAnyOrder(23L, 24L); - } - - @Test - public void saveManyWithIdGeneration() { - - DummyEntity one = createDummyEntity(null); - DummyEntity two = createDummyEntity(null); - - Iterable entities = repository.save(asList(one, two)); - - assertThat(entities).allMatch(e -> e.getId() != null); - - assertThat(repository.findAll()) - .extracting(DummyEntity::getId) - .containsExactlyInAnyOrder(new Long[]{one.getId(), two.getId()}); + assertThat(repository.findAll()).extracting(DummyEntity::getIdProp).containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); } @Test @@ -139,40 +104,40 @@ public void existsReturnsTrueIffEntityExists() { entity = repository.save(entity); - assertTrue(repository.exists(entity.getId())); - assertFalse(repository.exists(entity.getId() + 1)); + assertTrue(repository.exists(entity.getIdProp())); + assertFalse(repository.exists(entity.getIdProp() + 1)); } @Test public void findAllFindsAllEntities() { - DummyEntity other = createDummyEntity(24L); + DummyEntity other = createDummyEntity(); other = repository.save(other); entity = repository.save(entity); Iterable all = repository.findAll(); - assertThat(all).extracting("id").containsExactlyInAnyOrder(entity.getId(), other.getId()); + assertThat(all).extracting("idProp").containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); } @Test public void findAllFindsAllSpecifiedEntities() { - repository.save(createDummyEntity(24L)); - DummyEntity other = repository.save(createDummyEntity(25L)); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); entity = repository.save(entity); - Iterable all = repository.findAll(asList(entity.getId(), other.getId())); + Iterable all = repository.findAll(asList(entity.getIdProp(), three.getIdProp())); - assertThat(all).extracting("id").containsExactlyInAnyOrder(entity.getId(), other.getId()); + assertThat(all).extracting("idProp").containsExactlyInAnyOrder(entity.getIdProp(), three.getIdProp()); } @Test public void count() { - repository.save(createDummyEntity(24L)); - repository.save(createDummyEntity(25L)); + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); repository.save(entity); assertThat(repository.count()).isEqualTo(3L); @@ -181,25 +146,27 @@ public void count() { @Test public void deleteById() { - repository.save(createDummyEntity(24L)); - repository.save(createDummyEntity(25L)); - repository.save(entity); + entity = repository.save(entity); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); - repository.delete(24L); + repository.delete(two.getIdProp()); - assertThat(repository.findAll()).extracting(DummyEntity::getId).containsExactlyInAnyOrder(23L, 25L); + assertThat(repository.findAll()) + .extracting(DummyEntity::getIdProp) + .containsExactlyInAnyOrder(entity.getIdProp(), three.getIdProp()); } @Test public void deleteByEntity() { - repository.save(createDummyEntity(24L)); - repository.save(createDummyEntity(25L)); - repository.save(entity); + entity = repository.save(entity); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); repository.delete(entity); - assertThat(repository.findAll()).extracting(DummyEntity::getId).containsExactlyInAnyOrder(24L, 25L); + assertThat(repository.findAll()).extracting(DummyEntity::getIdProp).containsExactlyInAnyOrder(two.getIdProp(), three.getIdProp()); } @@ -207,20 +174,20 @@ public void deleteByEntity() { public void deleteByList() { repository.save(entity); - repository.save(createDummyEntity(24L)); - DummyEntity other = repository.save(createDummyEntity(25L)); + DummyEntity two = repository.save(createDummyEntity()); + DummyEntity three = repository.save(createDummyEntity()); - repository.delete(asList(entity, other)); + repository.delete(asList(entity, three)); - assertThat(repository.findAll()).extracting(DummyEntity::getId).containsExactlyInAnyOrder(24L); + assertThat(repository.findAll()).extracting(DummyEntity::getIdProp).containsExactlyInAnyOrder(two.getIdProp()); } @Test public void deleteAll() { repository.save(entity); - repository.save(createDummyEntity(24L)); - repository.save(createDummyEntity(25L)); + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); repository.deleteAll(); @@ -228,15 +195,44 @@ public void deleteAll() { } + @Test + public void update() { + + entity = repository.save(entity); + + entity.setName("something else"); + + entity = repository.save(entity); + + DummyEntity reloaded = repository.findOne(entity.getIdProp()); + + assertThat(reloaded.getName()).isEqualTo(entity.getName()); + } + + @Test + public void updateMany() { + + entity = repository.save(entity); + DummyEntity other = repository.save(createDummyEntity()); + + entity.setName("something else"); + other.setName("others Name"); + + repository.save(asList(entity, other)); + + assertThat(repository.findAll()) + .extracting(DummyEntity::getName) + .containsExactlyInAnyOrder(entity.getName(), other.getName()); + } + private static DummyEntityRepository createRepository(EmbeddedDatabase db) { return new JdbcRepositoryFactory(db).getRepository(DummyEntityRepository.class); } - private static DummyEntity createDummyEntity(Long id) { + private static DummyEntity createDummyEntity() { DummyEntity entity = new DummyEntity(); - entity.setId(id); entity.setName("Entity Name"); return entity; } @@ -245,12 +241,11 @@ private interface DummyEntityRepository extends CrudRepository Date: Fri, 3 Mar 2017 11:34:17 +0100 Subject: [PATCH 07/12] DATAJDBC-99 - Eventsupport. The repository publishes events before and after inserting, updating and deleting entities, as well as after instantiation of entities. JdbcEvent ist the common super class of all events and makes the id and the entity available (if possible). Added issue id comments to tests from previous issues. --- .../mapping/event/AfterCreationEvent.java | 36 ++++++ .../jdbc/mapping/event/AfterDeleteEvent.java | 36 ++++++ .../jdbc/mapping/event/AfterInsertEvent.java | 35 ++++++ .../jdbc/mapping/event/AfterSaveEvent.java | 34 +++++ .../jdbc/mapping/event/AfterUpdateEvent.java | 35 ++++++ .../jdbc/mapping/event/BeforeDeleteEvent.java | 38 ++++++ .../jdbc/mapping/event/BeforeInsertEvent.java | 37 ++++++ .../jdbc/mapping/event/BeforeSaveEvent.java | 37 ++++++ .../jdbc/mapping/event/BeforeUpdateEvent.java | 35 ++++++ .../data/jdbc/mapping/event/JdbcEvent.java | 62 ++++++++++ .../data/jdbc/repository/EntityRowMapper.java | 3 +- .../EventPublishingEntityRowMapper.java | 59 +++++++++ .../jdbc/repository/SimpleJdbcRepository.java | 105 +++++++++++----- .../support/JdbcRepositoryFactory.java | 24 +++- .../EventPublishingEntityRowMapperTest.java | 47 +++++++ ...epositoryIdGenerationIntegrationTests.java | 12 +- .../JdbcRepositoryIntegrationTests.java | 53 +++++--- .../SimpleJdbcRepositoryEventsUnitTests.java | 116 ++++++++++++++++++ 18 files changed, 742 insertions(+), 62 deletions(-) create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreationEvent.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsertEvent.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/AfterSaveEvent.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdateEvent.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsertEvent.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdateEvent.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java create mode 100644 src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java create mode 100644 src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java create mode 100644 src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreationEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreationEvent.java new file mode 100644 index 0000000000..1e96c44060 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreationEvent.java @@ -0,0 +1,36 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +/** + * gets published after instantiation and setting of all the properties of an entity. This allows to do some + * postprocessing of entities. + * + * @author Jens Schauder + */ +public class AfterCreationEvent extends JdbcEvent{ + + /** + * @param instance the newly instantiated entity. + * @param idProvider a function providing the id, for the instance. + * @param type of the entity and the argument of the {@code idProvider} + */ + public AfterCreationEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java new file mode 100644 index 0000000000..982ebbc2a2 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java @@ -0,0 +1,36 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +/** + * get published after deletion of an entity. The source might contain the Id or the actual entity, depending on the + * {@code delete(...)} method used. + * + * @author Jens Schauder + */ +public class AfterDeleteEvent extends JdbcEvent{ + + /** + * @param instance the deleted entity. + * @param idProvider a function providing the id, for the instance. + * @param type of the entity and the argument of the {@code idProvider} + */ + public AfterDeleteEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsertEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsertEvent.java new file mode 100644 index 0000000000..aa733f9104 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsertEvent.java @@ -0,0 +1,35 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +/** + * gets published after an entity got inserted into the database. + * + * @author Jens Schauder + */ +public class AfterInsertEvent extends AfterSaveEvent { + + /** + * @param instance the newly inserted entity. + * @param idProvider a function providing the id, for the instance. + * @param type of the entity and the argument of the {@code idProvider} + */ + public AfterInsertEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSaveEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSaveEvent.java new file mode 100644 index 0000000000..bf25700f6d --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSaveEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +/** + * subclasses of this get published after a new instance or a changed instance was saved in the database + * @author Jens Schauder + */ +public class AfterSaveEvent extends JdbcEvent{ + + /** + * @param instance the newly saved entity. + * @param idProvider a function providing the id, for the instance. + * @param type of the entity and the argument of the {@code idProvider} + */ + AfterSaveEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdateEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdateEvent.java new file mode 100644 index 0000000000..075fde2bed --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdateEvent.java @@ -0,0 +1,35 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +/** + * gets published after an entity was updated in the database. + * + * @author Jens Schauder + */ +public class AfterUpdateEvent extends AfterSaveEvent { + + /** + * @param instance the updated entity. + * @param idProvider a function providing the id, for the instance. + * @param type of the entity and the argument of the {@code idProvider} + */ + public AfterUpdateEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java new file mode 100644 index 0000000000..1e9a789970 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java @@ -0,0 +1,38 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +import org.springframework.context.ApplicationEvent; + +/** + * gets published when an entity is about to get deleted. {@link ApplicationEvent#getSource()} might contain either the + * entity or the id of the entity, depending on which delete method was used. + * + * @author Jens Schauder + */ +public class BeforeDeleteEvent extends JdbcEvent { + + /** + * @param instance the entity about to get deleted. Might be {@literal NULL} + * @param idProvider a function providing the id, for the instance. Must provide a not {@literal NULL} id, when called with {@link #instance} + * @param type of the entity and the argument of the {@code idProvider} + */ + public BeforeDeleteEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsertEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsertEvent.java new file mode 100644 index 0000000000..45ecba9841 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsertEvent.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +/** + * 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. + * + * @author Jens Schauder + */ +public class BeforeInsertEvent extends BeforeSaveEvent { + + /** + * @param instance the entity about to get inserted. + * @param idProvider a function providing the id, for the instance. + * @param type of the entity and the argument of the {@code idProvider} + */ + public BeforeInsertEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java new file mode 100644 index 0000000000..626c6f0387 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +import org.springframework.context.ApplicationEvent; + +/** + * subclasses of this get published before an entity gets saved to the database. + * + * @author Jens Schauder + */ +public class BeforeSaveEvent extends JdbcEvent { + + /** + * @param instance the entity about to get saved. + * @param idProvider a function providing the id, for the instance. + * @param type of the entity and the argument of the {@code idProvider} + */ + BeforeSaveEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdateEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdateEvent.java new file mode 100644 index 0000000000..543cbcd786 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdateEvent.java @@ -0,0 +1,35 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +/** + * gets published before an entity gets updated in the database. + * + * @author Jens Schauder + */ +public class BeforeUpdateEvent extends BeforeSaveEvent { + + /** + * @param instance the entity about to get saved. + * @param idProvider a function providing the id, for the instance. + * @param type of the entity and the argument of the {@code idProvider} + */ + public BeforeUpdateEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java new file mode 100644 index 0000000000..ad0df42f2a --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java @@ -0,0 +1,62 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +import org.springframework.context.ApplicationEvent; + +/** + * is the common superclass for all events published by JDBC repositories. + * + * It is recommendet not to use the {@link #getSource()} since it may contain the entity if it was available, when the + * event was published, or in case of delete events only the Id. + * + * Use the dedicated methods {@link #getId()} or {@link #getInstance()} instead. Note that the later might be + * {@literal NULL} in the cases mentioned above. + * + * @author Jens Schauder + */ +public class JdbcEvent extends ApplicationEvent { + + private final Object id; + private final Object instance; + + JdbcEvent(T instance, Function idProvider) { + + super(instance == null ? idProvider.apply(instance) : instance); + this.instance = instance; + this.id = idProvider.apply(instance); + } + + /** + * the entity for which this event was publish. Might be {@literal NULL} in cases of delete events where only the id + * was provided to the delete method. + * + * @return instance of the entity triggering this event. + */ + public Object getInstance() { + return instance; + } + + /** + * the id of the entity, triggering this event. Guaranteed not to be {@literal NULL}. + * @return + */ + public Object getId() { + return id; + } +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java index 4bde019f07..b604352150 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java +++ b/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java @@ -26,13 +26,14 @@ import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mapping.model.ParameterValueProvider; +import org.springframework.jdbc.core.RowMapper; /** * maps a ResultSet to an entity of type {@code T} * * @author Jens Schauder */ -class EntityRowMapper implements org.springframework.jdbc.core.RowMapper { +class EntityRowMapper implements RowMapper { private final JdbcPersistentEntity entity; diff --git a/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java new file mode 100644 index 0000000000..b499296c83 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.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.repository; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.jdbc.mapping.event.AfterCreationEvent; +import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; +import org.springframework.jdbc.core.RowMapper; + +/** + * a RowMapper that publishes events after a delegate, did the actual work of mapping a {@link ResultSet} to an entity. + * + * @author Jens Schauder + */ +public class EventPublishingEntityRowMapper implements RowMapper { + + private final RowMapper delegate; + private final JdbcPersistentEntity entity; + private final ApplicationEventPublisher publisher; + + /** + * + * @param delegate does the actuall mapping. + * @param entity provides functionality to create ids from entities + * @param publisher used for event publishing after the mapping. + */ + EventPublishingEntityRowMapper(RowMapper delegate,JdbcPersistentEntity entity, ApplicationEventPublisher publisher) { + + this.delegate = delegate; + this.entity = entity; + this.publisher = publisher; + } + + @Override + public T mapRow(ResultSet resultSet, int i) throws SQLException { + + T instance = delegate.mapRow(resultSet, i); + + publisher.publishEvent(new AfterCreationEvent(instance, entity::getIdValue)); + + return instance; + } +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java index 8ec48d2d51..04aaf4672d 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java +++ b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java @@ -20,7 +20,15 @@ import java.util.Map; import java.util.stream.Collectors; import java.util.stream.StreamSupport; -import javax.sql.DataSource; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.data.jdbc.mapping.event.AfterDeleteEvent; +import org.springframework.data.jdbc.mapping.event.AfterInsertEvent; +import org.springframework.data.jdbc.mapping.event.AfterUpdateEvent; +import org.springframework.data.jdbc.mapping.event.BeforeDeleteEvent; +import org.springframework.data.jdbc.mapping.event.BeforeInsertEvent; +import org.springframework.data.jdbc.mapping.event.BeforeUpdateEvent; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation; @@ -28,47 +36,46 @@ import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; +import org.springframework.util.Assert; /** * @author Jens Schauder */ -public class SimpleJdbcRepository implements CrudRepository { +public class SimpleJdbcRepository + implements CrudRepository, ApplicationEventPublisherAware { private final JdbcPersistentEntity entity; - private final JdbcPersistentEntityInformation entityInformation; - private final NamedParameterJdbcOperations template; + private final JdbcPersistentEntityInformation entityInformation; + private final NamedParameterJdbcOperations operations; private final SqlGenerator sql; private final EntityRowMapper entityRowMapper; + private final ApplicationEventPublisher publisher; + + public SimpleJdbcRepository(JdbcPersistentEntity persistentEntity, NamedParameterJdbcOperations jdbcOperations, ApplicationEventPublisher publisher) { - public SimpleJdbcRepository(JdbcPersistentEntity entity, DataSource dataSource) { + Assert.notNull(persistentEntity, "PersistentEntity must not be null."); + Assert.notNull(jdbcOperations, "JdbcOperations must not be null."); + Assert.notNull(publisher, "Publisher must not be null."); - this.entity = entity; - this.entityInformation = new JdbcPersistentEntityInformation(entity); - this.template = new NamedParameterJdbcTemplate(dataSource); + this.entity = persistentEntity; + this.entityInformation = new JdbcPersistentEntityInformation(persistentEntity); + this.operations = jdbcOperations; + this.publisher = publisher; - entityRowMapper = new EntityRowMapper(entity); - sql = new SqlGenerator(entity); + entityRowMapper = new EntityRowMapper(persistentEntity); + sql = new SqlGenerator(persistentEntity); } @Override public S save(S instance) { if (entityInformation.isNew(instance)) { - - KeyHolder holder = new GeneratedKeyHolder(); - - template.update( - sql.getInsert(), - new MapSqlParameterSource(getPropertyMap(instance)), - holder); - - entity.setId(instance, holder.getKey()); + doInsert(instance); } else { - template.update(sql.getUpdate(), getPropertyMap(instance)); + doUpdate(instance); } return instance; @@ -85,7 +92,7 @@ public Iterable save(Iterable entities) { @Override public T findOne(ID id) { - return template.queryForObject( + return operations.queryForObject( sql.getFindOne(), new MapSqlParameterSource("id", id), entityRowMapper @@ -95,7 +102,7 @@ public T findOne(ID id) { @Override public boolean exists(ID id) { - return template.queryForObject( + return operations.queryForObject( sql.getExists(), new MapSqlParameterSource("id", id), Boolean.class @@ -104,37 +111,34 @@ public boolean exists(ID id) { @Override public Iterable findAll() { - return template.query(sql.getFindAll(), entityRowMapper); + return operations.query(sql.getFindAll(), entityRowMapper); } @Override public Iterable findAll(Iterable ids) { - return template.query(sql.getFindAllInList(), new MapSqlParameterSource("ids", ids), entityRowMapper); + return operations.query(sql.getFindAllInList(), new MapSqlParameterSource("ids", ids), entityRowMapper); } @Override public long count() { - return template.getJdbcOperations().queryForObject(sql.getCount(), Long.class); + return operations.getJdbcOperations().queryForObject(sql.getCount(), Long.class); } @Override public void delete(ID id) { - template.update(sql.getDeleteById(), new MapSqlParameterSource("id", id)); + doDelete(id, null); } @Override public void delete(T instance) { - template.update( - sql.getDeleteById(), - new MapSqlParameterSource("id", - entity.getIdValue(instance))); + doDelete((ID) entity.getIdValue(instance), instance); } @Override public void delete(Iterable entities) { - template.update( + operations.update( sql.getDeleteByList(), new MapSqlParameterSource("ids", StreamSupport @@ -147,7 +151,12 @@ public void delete(Iterable entities) { @Override public void deleteAll() { - template.getJdbcOperations().update(sql.getDeleteAll()); + operations.getJdbcOperations().update(sql.getDeleteAll()); + } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + } private Map getPropertyMap(final S instance) { @@ -163,4 +172,34 @@ public void doWithPersistentProperty(JdbcPersistentProperty persistentProperty) return parameters; } + + private void doInsert(S instance) { + publisher.publishEvent(new BeforeInsertEvent(instance, entity::getIdValue)); + + KeyHolder holder = new GeneratedKeyHolder(); + + operations.update( + sql.getInsert(), + new MapSqlParameterSource(getPropertyMap(instance)), + holder); + + entity.setId(instance, holder.getKey()); + + publisher.publishEvent(new AfterInsertEvent(instance, entity::getIdValue)); + } + + private void doDelete(ID id, Object instance) { + + publisher.publishEvent(new BeforeDeleteEvent(instance, o -> id)); + operations.update(sql.getDeleteById(), new MapSqlParameterSource("id", id)); + publisher.publishEvent(new AfterDeleteEvent(instance, o -> id)); + } + + private void doUpdate(S instance) { + publisher.publishEvent(new BeforeUpdateEvent(instance, entity::getIdValue)); + + operations.update(sql.getUpdate(), getPropertyMap(instance)); + + publisher.publishEvent(new AfterUpdateEvent(instance, entity::getIdValue)); + } } diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java index d24598250d..f2e9b8952b 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java @@ -16,7 +16,8 @@ package org.springframework.data.jdbc.repository.support; import java.io.Serializable; -import javax.sql.DataSource; + +import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.jdbc.mapping.context.JdbcMappingContext; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; import org.springframework.data.jdbc.repository.SimpleJdbcRepository; @@ -24,27 +25,38 @@ import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; /** * @author Jens Schauder */ public class JdbcRepositoryFactory extends RepositoryFactorySupport { - private final DataSource dataSource; private final JdbcMappingContext context = new JdbcMappingContext(); + private final ApplicationEventPublisher publisher; + private final NamedParameterJdbcOperations jdbcOperations; + + public JdbcRepositoryFactory( + ApplicationEventPublisher publisher, + NamedParameterJdbcOperations jdbcOperations + ) { - public JdbcRepositoryFactory(DataSource dataSource) { - this.dataSource = dataSource; + this.publisher = publisher; + this.jdbcOperations = jdbcOperations; } @Override public EntityInformation getEntityInformation(Class aClass) { - return new JdbcPersistentEntityInformation((JdbcPersistentEntity) context.getPersistentEntity(aClass)); + return new JdbcPersistentEntityInformation<>((JdbcPersistentEntity) context.getPersistentEntity(aClass)); } @Override protected Object getTargetRepository(RepositoryInformation repositoryInformation) { - return new SimpleJdbcRepository(context.getPersistentEntity(repositoryInformation.getDomainType()), dataSource); + + return new SimpleJdbcRepository( + context.getPersistentEntity(repositoryInformation.getDomainType()), + jdbcOperations, + publisher); } @Override diff --git a/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java b/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java new file mode 100644 index 0000000000..44113c48c9 --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java @@ -0,0 +1,47 @@ +package org.springframework.data.jdbc.repository; + +import static org.mockito.Mockito.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.junit.Test; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.mapping.event.AfterCreationEvent; +import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; +import org.springframework.jdbc.core.RowMapper; + +import lombok.Data; + +/** + * @author Jens Schauder + */ +public class EventPublishingEntityRowMapperTest { + + private RowMapper rowMapperDelegate = mock(RowMapper.class); + private JdbcPersistentEntity entity = mock(JdbcPersistentEntity.class); + private ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class); + + @Test // DATAJDBC-99 + public void eventGetsPublishedAfterInstantiation() throws SQLException { + + when(entity.getIdValue(any())).thenReturn(1L); + + EventPublishingEntityRowMapper rowMapper = new EventPublishingEntityRowMapper<>( + rowMapperDelegate, + entity, + publisher); + + ResultSet resultSet = mock(ResultSet.class); + rowMapper.mapRow(resultSet, 1); + + verify(publisher).publishEvent(isA(AfterCreationEvent.class)); + } + + @Data + private static class DummyEntity { + + @Id private final Long Id; + } +} \ No newline at end of file diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java index 2f2b5396a8..f537f6d006 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java @@ -17,9 +17,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; import org.junit.After; import org.junit.Test; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.repository.CrudRepository; @@ -56,7 +58,7 @@ public void after() { db.shutdown(); } - @Test + @Test // DATAJDBC-98 public void idWithoutSetterGetsSet() { entity = repository.save(entity); @@ -73,7 +75,7 @@ public void idWithoutSetterGetsSet() { reloadedEntity.getName()); } - @Test + @Test // DATAJDBC-98 public void primitiveIdGetsSet() { entity = repository.save(entity); @@ -92,7 +94,11 @@ public void primitiveIdGetsSet() { private static ReadOnlyIdEntityRepository createRepository(EmbeddedDatabase db) { - return new JdbcRepositoryFactory(db).getRepository(ReadOnlyIdEntityRepository.class); + + return new JdbcRepositoryFactory( + mock(ApplicationEventPublisher.class), + new NamedParameterJdbcTemplate(db) + ).getRepository(ReadOnlyIdEntityRepository.class); } diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java index 410b92fbd6..8a90a9c547 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -18,9 +18,11 @@ import static java.util.Arrays.*; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; import org.junit.After; import org.junit.Test; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.repository.CrudRepository; @@ -58,8 +60,7 @@ public void after() { db.shutdown(); } - - @Test + @Test // DATAJDBC-95 public void canSaveAnEntity() { entity = repository.save(entity); @@ -74,7 +75,7 @@ public void canSaveAnEntity() { count); } - @Test + @Test // DATAJDBC-95 public void canSaveAndLoadAnEntity() { entity = repository.save(entity); @@ -89,17 +90,22 @@ public void canSaveAndLoadAnEntity() { reloadedEntity.getName()); } - @Test + @Test // DATAJDBC-97 public void saveMany() { DummyEntity other = createDummyEntity(); repository.save(asList(entity, other)); - assertThat(repository.findAll()).extracting(DummyEntity::getIdProp).containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); + assertThat(repository.findAll()) + .extracting(DummyEntity::getIdProp) + .containsExactlyInAnyOrder( + entity.getIdProp(), + other.getIdProp() + ); } - @Test + @Test // DATAJDBC-97 public void existsReturnsTrueIffEntityExists() { entity = repository.save(entity); @@ -108,7 +114,7 @@ public void existsReturnsTrueIffEntityExists() { assertFalse(repository.exists(entity.getIdProp() + 1)); } - @Test + @Test // DATAJDBC-97 public void findAllFindsAllEntities() { DummyEntity other = createDummyEntity(); @@ -118,10 +124,11 @@ public void findAllFindsAllEntities() { Iterable all = repository.findAll(); - assertThat(all).extracting("idProp").containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); + assertThat(all).extracting("idProp") + .containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); } - @Test + @Test // DATAJDBC-97 public void findAllFindsAllSpecifiedEntities() { DummyEntity two = repository.save(createDummyEntity()); @@ -130,10 +137,11 @@ public void findAllFindsAllSpecifiedEntities() { Iterable all = repository.findAll(asList(entity.getIdProp(), three.getIdProp())); - assertThat(all).extracting("idProp").containsExactlyInAnyOrder(entity.getIdProp(), three.getIdProp()); + assertThat(all).extracting("idProp") + .containsExactlyInAnyOrder(entity.getIdProp(), three.getIdProp()); } - @Test + @Test // DATAJDBC-97 public void count() { repository.save(createDummyEntity()); @@ -143,7 +151,7 @@ public void count() { assertThat(repository.count()).isEqualTo(3L); } - @Test + @Test // DATAJDBC-97 public void deleteById() { entity = repository.save(entity); @@ -157,7 +165,7 @@ public void deleteById() { .containsExactlyInAnyOrder(entity.getIdProp(), three.getIdProp()); } - @Test + @Test // DATAJDBC-97 public void deleteByEntity() { entity = repository.save(entity); @@ -166,11 +174,16 @@ public void deleteByEntity() { repository.delete(entity); - assertThat(repository.findAll()).extracting(DummyEntity::getIdProp).containsExactlyInAnyOrder(two.getIdProp(), three.getIdProp()); + assertThat(repository.findAll()) + .extracting(DummyEntity::getIdProp) + .containsExactlyInAnyOrder( + two.getIdProp(), + three.getIdProp() + ); } - @Test + @Test // DATAJDBC-97 public void deleteByList() { repository.save(entity); @@ -182,7 +195,7 @@ public void deleteByList() { assertThat(repository.findAll()).extracting(DummyEntity::getIdProp).containsExactlyInAnyOrder(two.getIdProp()); } - @Test + @Test // DATAJDBC-97 public void deleteAll() { repository.save(entity); @@ -195,7 +208,7 @@ public void deleteAll() { } - @Test + @Test // DATAJDBC-98 public void update() { entity = repository.save(entity); @@ -209,7 +222,7 @@ public void update() { assertThat(reloaded.getName()).isEqualTo(entity.getName()); } - @Test + @Test // DATAJDBC-98 public void updateMany() { entity = repository.save(entity); @@ -226,7 +239,9 @@ public void updateMany() { } private static DummyEntityRepository createRepository(EmbeddedDatabase db) { - return new JdbcRepositoryFactory(db).getRepository(DummyEntityRepository.class); + + return new JdbcRepositoryFactory(mock(ApplicationEventPublisher.class), new NamedParameterJdbcTemplate(db)) + .getRepository(DummyEntityRepository.class); } diff --git a/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java b/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java new file mode 100644 index 0000000000..36c06e17c6 --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java @@ -0,0 +1,116 @@ +package org.springframework.data.jdbc.repository; + +import static java.util.Arrays.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +import static org.springframework.util.Assert.*; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.mapping.event.AfterDeleteEvent; +import org.springframework.data.jdbc.mapping.event.AfterInsertEvent; +import org.springframework.data.jdbc.mapping.event.AfterUpdateEvent; +import org.springframework.data.jdbc.mapping.event.BeforeDeleteEvent; +import org.springframework.data.jdbc.mapping.event.BeforeInsertEvent; +import org.springframework.data.jdbc.mapping.event.BeforeUpdateEvent; +import org.springframework.data.jdbc.mapping.event.JdbcEvent; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.repository.CrudRepository; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; + +import lombok.Data; + +/** + * @author Jens Schauder + */ +public class SimpleJdbcRepositoryEventsUnitTests { + + private FakePublisher publisher = new FakePublisher(); + + private DummyEntityRepository repository; + + @Before + public void before() { + JdbcRepositoryFactory factory = new JdbcRepositoryFactory(publisher, mock(NamedParameterJdbcOperations.class)); + repository = factory.getRepository(DummyEntityRepository.class); + } + + @Test // DATAJDBC-99 + public void publishesEventsOnSave() { + + DummyEntity entity = new DummyEntity(23L); + + repository.save(entity); + + isInstanceOf(BeforeUpdateEvent.class, publisher.events.get(0)); + isInstanceOf(AfterUpdateEvent.class, publisher.events.get(1)); + } + + @Test // DATAJDBC-99 + public void publishesEventsOnSaveMany() { + + DummyEntity entity1 = new DummyEntity(null); + DummyEntity entity2 = new DummyEntity(23L); + + repository.save(asList(entity1, entity2)); + + isInstanceOf(BeforeInsertEvent.class, publisher.events.get(0)); + isInstanceOf(AfterInsertEvent.class, publisher.events.get(1)); + isInstanceOf(BeforeUpdateEvent.class, publisher.events.get(2)); + isInstanceOf(AfterUpdateEvent.class, publisher.events.get(3)); + } + + + @Test // DATAJDBC-99 + public void publishesEventsOnDelete() { + + DummyEntity entity = new DummyEntity(23L); + + repository.delete(entity); + + isInstanceOf(BeforeDeleteEvent.class, publisher.events.get(0)); + isInstanceOf(AfterDeleteEvent.class, publisher.events.get(1)); + + assertEquals(entity, publisher.events.get(0).getInstance()); + assertEquals(entity, publisher.events.get(1).getInstance()); + + assertEquals(23L, publisher.events.get(0).getId()); + assertEquals(23L, publisher.events.get(1).getId()); + } + + + @Test // DATAJDBC-99 + public void publishesEventsOnDeleteById() { + + repository.delete(23L); + + isInstanceOf(BeforeDeleteEvent.class, publisher.events.get(0)); + isInstanceOf(AfterDeleteEvent.class, publisher.events.get(1)); + } + + + @Data + private static class DummyEntity { + + @Id private final Long id; + } + + private interface DummyEntityRepository extends CrudRepository { + + } + + static class FakePublisher implements ApplicationEventPublisher { + + List events = new ArrayList<>(); + + @Override + public void publishEvent(Object o) { + events.add((JdbcEvent) o); + } + } +} \ No newline at end of file From 84a8e0bca1cb682a42696a1b5477d2b360cc3cb2 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 10 Mar 2017 13:39:20 +0100 Subject: [PATCH 08/12] DATAJDBC-99 - Polishing. Events now use the id as source. The id is encapsulated in a value object to support null ids in BeforeInsert events. Entity references in events are now Optionals. Dropped redundant Event suffix from events. Added since 2.0 to classes Replaced constructors and getters with Lombok annotations. Simplified pom.xml. Extracted interface JdbcPersistentEntity and JdbcPersistentProperty. Integration tests use the Spring Test framework. Fixed test for entities with primitive id type. --- lombok.config | 2 + pom.xml | 55 ------ .../mapping/context/JdbcMappingContext.java | 13 +- .../jdbc/mapping/event/AfterCreation.java | 35 ++++ .../mapping/event/AfterCreationEvent.java | 36 ---- .../data/jdbc/mapping/event/AfterDelete.java | 39 +++++ .../jdbc/mapping/event/AfterDeleteEvent.java | 36 ---- ...AfterInsertEvent.java => AfterInsert.java} | 14 +- .../{AfterSaveEvent.java => AfterSave.java} | 17 +- ...AfterUpdateEvent.java => AfterUpdate.java} | 14 +- .../data/jdbc/mapping/event/BeforeDelete.java | 37 ++++ .../jdbc/mapping/event/BeforeDeleteEvent.java | 38 ----- ...foreInsertEvent.java => BeforeInsert.java} | 16 +- ...BeforeUpdateEvent.java => BeforeSave.java} | 14 +- .../jdbc/mapping/event/BeforeSaveEvent.java | 37 ---- .../data/jdbc/mapping/event/BeforeUpdate.java | 35 ++++ .../data/jdbc/mapping/event/Identifier.java | 65 +++++++ .../data/jdbc/mapping/event/JdbcEvent.java | 45 +++-- .../mapping/event/JdbcEventWithEntity.java | 31 ++++ .../jdbc/mapping/event/JdbcEventWithId.java | 33 ++++ .../event/JdbcEventWithIdAndEntity.java | 36 ++++ .../data/jdbc/mapping/event/WithEntity.java | 30 ++++ .../data/jdbc/mapping/event/WithId.java | 33 ++++ .../model/BasicJdbcPersistentProperty.java | 60 +++++++ .../mapping/model/JdbcPersistentEntity.java | 26 +-- .../mapping/model/JdbcPersistentProperty.java | 33 +--- .../data/jdbc/repository/EntityRowMapper.java | 11 +- .../EventPublishingEntityRowMapper.java | 31 ++-- .../jdbc/repository/SimpleJdbcRepository.java | 143 +++++++++------- .../data/jdbc/repository/SqlGenerator.java | 1 + .../repository/UnableToSetIdException.java | 31 ++++ .../BasicJdbcPersistentEntityInformation.java | 42 +++++ .../JdbcPersistentEntityInformation.java | 11 +- .../support/JdbcRepositoryFactory.java | 24 +-- .../EventPublishingEntityRowMapperTest.java | 28 ++-- ...epositoryIdGenerationIntegrationTests.java | 125 +++++++------- .../JdbcRepositoryIntegrationTests.java | 158 ++++++++++-------- .../SimpleJdbcRepositoryEventsUnitTests.java | 69 +++++--- ...sitory-id-generation-integration-tests.sql | 4 +- 39 files changed, 924 insertions(+), 584 deletions(-) create mode 100644 lombok.config create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java delete mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreationEvent.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java delete mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java rename src/main/java/org/springframework/data/jdbc/mapping/event/{AfterInsertEvent.java => AfterInsert.java} (65%) rename src/main/java/org/springframework/data/jdbc/mapping/event/{AfterSaveEvent.java => AfterSave.java} (63%) rename src/main/java/org/springframework/data/jdbc/mapping/event/{AfterUpdateEvent.java => AfterUpdate.java} (65%) create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDelete.java delete mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java rename src/main/java/org/springframework/data/jdbc/mapping/event/{BeforeInsertEvent.java => BeforeInsert.java} (60%) rename src/main/java/org/springframework/data/jdbc/mapping/event/{BeforeUpdateEvent.java => BeforeSave.java} (65%) delete mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdate.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithEntity.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithIdAndEntity.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/WithEntity.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/event/WithId.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java create mode 100644 src/main/java/org/springframework/data/jdbc/repository/UnableToSetIdException.java create mode 100644 src/main/java/org/springframework/data/jdbc/repository/support/BasicJdbcPersistentEntityInformation.java diff --git a/lombok.config b/lombok.config new file mode 100644 index 0000000000..e50c7ea439 --- /dev/null +++ b/lombok.config @@ -0,0 +1,2 @@ +lombok.nonNull.exceptionType = IllegalArgumentException +lombok.log.fieldName = LOG diff --git a/pom.xml b/pom.xml index ab7a0f4451..e61a5fca05 100644 --- a/pom.xml +++ b/pom.xml @@ -128,61 +128,6 @@ org.apache.maven.plugins maven-surefire-plugin 2.12 - - - org.springframework - spring-instrument - ${spring} - runtime - - - org.hsqldb - hsqldb - ${hsqldb1} - runtime - - - - - default-test - - - **/* - - - - - unit-tests - - test - - test - - - **/*UnitTests.java - - - - - integration-tests - - test - - test - - - **/*IntegrationTests.java - **/*Tests.java - - - **/*UnitTests.java -\ - - -javaagent:${settings.localRepository}/org/jacoco/org.jacoco.agent/${jacoco}/org.jacoco.agent-${jacoco}-runtime.jar=destfile=${jacoco.destfile} - - - - org.apache.maven.plugins diff --git a/src/main/java/org/springframework/data/jdbc/mapping/context/JdbcMappingContext.java b/src/main/java/org/springframework/data/jdbc/mapping/context/JdbcMappingContext.java index e9fd47d319..a2cb020782 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/context/JdbcMappingContext.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/context/JdbcMappingContext.java @@ -17,6 +17,8 @@ import java.beans.PropertyDescriptor; import java.lang.reflect.Field; + +import org.springframework.data.jdbc.mapping.model.BasicJdbcPersistentProperty; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; import org.springframework.data.mapping.context.AbstractMappingContext; @@ -25,17 +27,22 @@ /** * @author Jens Schauder + * @since 2.0 */ public class JdbcMappingContext extends AbstractMappingContext, JdbcPersistentProperty> { - @Override protected JdbcPersistentEntity createPersistentEntity(TypeInformation typeInformation) { return new JdbcPersistentEntity(typeInformation); } @Override - protected JdbcPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, JdbcPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { - return new JdbcPersistentProperty(field, descriptor, owner, simpleTypeHolder); + protected JdbcPersistentProperty createPersistentProperty( // + Field field, // + PropertyDescriptor descriptor, // + JdbcPersistentEntity owner, // + SimpleTypeHolder simpleTypeHolder // + ) { + return new BasicJdbcPersistentProperty(field, descriptor, owner, simpleTypeHolder); } } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java new file mode 100644 index 0000000000..9515ff80b1 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java @@ -0,0 +1,35 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import org.springframework.data.jdbc.mapping.event.Identifier.Specified; + +/** + * Gets published after instantiation and setting of all the properties of an entity. This allows to do some postprocessing of entities. + * + * @author Jens Schauder + * @since 2.0 + */ +public class AfterCreation extends JdbcEventWithIdAndEntity { + + /** + * @param id of the entity + * @param entity the newly instantiated entity. + */ + public AfterCreation(Specified id, Object entity) { + super(id, entity); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreationEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreationEvent.java deleted file mode 100644 index 1e96c44060..0000000000 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreationEvent.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.jdbc.mapping.event; - -import java.util.function.Function; - -/** - * gets published after instantiation and setting of all the properties of an entity. This allows to do some - * postprocessing of entities. - * - * @author Jens Schauder - */ -public class AfterCreationEvent extends JdbcEvent{ - - /** - * @param instance the newly instantiated entity. - * @param idProvider a function providing the id, for the instance. - * @param type of the entity and the argument of the {@code idProvider} - */ - public AfterCreationEvent(T instance, Function idProvider) { - super(instance, idProvider); - } -} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java new file mode 100644 index 0000000000..73c889b075 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import java.util.Optional; + +import org.springframework.data.jdbc.mapping.event.Identifier.Specified; + +/** + * Gets published after deletion of an entity. It will have a {@link Specified} identifier. + * + * If the entity is empty or not depends on the delete method used. + * + * @author Jens Schauder + * @since 2.0 + */ +public class AfterDelete extends JdbcEventWithId{ + + /** + * @param id of the entity. + * @param instance the deleted entity if it is available. + */ + public AfterDelete(Specified id, Optional instance) { + super(id, instance); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java deleted file mode 100644 index 982ebbc2a2..0000000000 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.jdbc.mapping.event; - -import java.util.function.Function; - -/** - * get published after deletion of an entity. The source might contain the Id or the actual entity, depending on the - * {@code delete(...)} method used. - * - * @author Jens Schauder - */ -public class AfterDeleteEvent extends JdbcEvent{ - - /** - * @param instance the deleted entity. - * @param idProvider a function providing the id, for the instance. - * @param type of the entity and the argument of the {@code idProvider} - */ - public AfterDeleteEvent(T instance, Function idProvider) { - super(instance, idProvider); - } -} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsertEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsert.java similarity index 65% rename from src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsertEvent.java rename to src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsert.java index aa733f9104..9a6f4145de 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsertEvent.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsert.java @@ -15,21 +15,21 @@ */ package org.springframework.data.jdbc.mapping.event; -import java.util.function.Function; +import org.springframework.data.jdbc.mapping.event.Identifier.Specified; /** - * gets published after an entity got inserted into the database. + * Gets published after an entity got inserted into the database. * * @author Jens Schauder + * @since 2.0 */ -public class AfterInsertEvent extends AfterSaveEvent { +public class AfterInsert extends AfterSave { /** + * @param id identifier of the entity triggering the event. * @param instance the newly inserted entity. - * @param idProvider a function providing the id, for the instance. - * @param type of the entity and the argument of the {@code idProvider} */ - public AfterInsertEvent(T instance, Function idProvider) { - super(instance, idProvider); + public AfterInsert(Specified id, Object instance) { + super(id, instance); } } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSaveEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java similarity index 63% rename from src/main/java/org/springframework/data/jdbc/mapping/event/AfterSaveEvent.java rename to src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java index bf25700f6d..e029e1aa57 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSaveEvent.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java @@ -15,20 +15,23 @@ */ package org.springframework.data.jdbc.mapping.event; -import java.util.function.Function; +import java.util.Optional; + +import org.springframework.data.jdbc.mapping.event.Identifier.Specified; /** - * subclasses of this get published after a new instance or a changed instance was saved in the database + * Subclasses of this get published after a new instance or a changed instance was saved in the database. + * * @author Jens Schauder + * @since 2.0 */ -public class AfterSaveEvent extends JdbcEvent{ +public class AfterSave extends JdbcEventWithIdAndEntity { /** + * @param id identifier of * @param instance the newly saved entity. - * @param idProvider a function providing the id, for the instance. - * @param type of the entity and the argument of the {@code idProvider} */ - AfterSaveEvent(T instance, Function idProvider) { - super(instance, idProvider); + AfterSave(Specified id, Object instance) { + super(id, instance); } } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdateEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdate.java similarity index 65% rename from src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdateEvent.java rename to src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdate.java index 075fde2bed..b95cd09306 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdateEvent.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdate.java @@ -15,21 +15,21 @@ */ package org.springframework.data.jdbc.mapping.event; -import java.util.function.Function; +import org.springframework.data.jdbc.mapping.event.Identifier.Specified; /** - * gets published after an entity was updated in the database. + * Gets published after an entity was updated in the database. * * @author Jens Schauder + * @since 2.0 */ -public class AfterUpdateEvent extends AfterSaveEvent { +public class AfterUpdate extends AfterSave { /** + * @param id of the entity * @param instance the updated entity. - * @param idProvider a function providing the id, for the instance. - * @param type of the entity and the argument of the {@code idProvider} */ - public AfterUpdateEvent(T instance, Function idProvider) { - super(instance, idProvider); + public AfterUpdate(Specified id, Object instance) { + super(id, instance); } } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDelete.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDelete.java new file mode 100644 index 0000000000..4d221b2ec6 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDelete.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import java.util.Optional; + +import org.springframework.data.jdbc.mapping.event.Identifier.Specified; + +/** + * Gets published when an entity is about to get deleted. + * + * @author Jens Schauder + * @since 2.0 + */ +public class BeforeDelete extends JdbcEventWithId { + + /** + * @param id the id of the entity + * @param entity the entity about to get deleted. Might be empty. + */ + public BeforeDelete(Specified id, Optional entity) { + super(id, entity); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java deleted file mode 100644 index 1e9a789970..0000000000 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.jdbc.mapping.event; - -import java.util.function.Function; - -import org.springframework.context.ApplicationEvent; - -/** - * gets published when an entity is about to get deleted. {@link ApplicationEvent#getSource()} might contain either the - * entity or the id of the entity, depending on which delete method was used. - * - * @author Jens Schauder - */ -public class BeforeDeleteEvent extends JdbcEvent { - - /** - * @param instance the entity about to get deleted. Might be {@literal NULL} - * @param idProvider a function providing the id, for the instance. Must provide a not {@literal NULL} id, when called with {@link #instance} - * @param type of the entity and the argument of the {@code idProvider} - */ - public BeforeDeleteEvent(T instance, Function idProvider) { - super(instance, idProvider); - } -} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsertEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java similarity index 60% rename from src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsertEvent.java rename to src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java index 45ecba9841..c290cd442c 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsertEvent.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java @@ -15,23 +15,23 @@ */ package org.springframework.data.jdbc.mapping.event; -import java.util.function.Function; +import org.springframework.data.jdbc.mapping.event.Identifier.Unset; /** - * gets published before an entity gets inserted into the database. + * Gets published before an entity gets inserted into the database. When the id-property of the entity must get set + * manually, an event listener for this event may do so. * - * When the id-property of the entity must get set manually, an event listener for this event may do so. + * The {@link Identifier} is {@link org.springframework.data.jdbc.mapping.event.Identifier.Unset#UNSET} * * @author Jens Schauder + * @since 2.0 */ -public class BeforeInsertEvent extends BeforeSaveEvent { +public class BeforeInsert extends BeforeSave { /** * @param instance the entity about to get inserted. - * @param idProvider a function providing the id, for the instance. - * @param type of the entity and the argument of the {@code idProvider} */ - public BeforeInsertEvent(T instance, Function idProvider) { - super(instance, idProvider); + public BeforeInsert(Object instance) { + super(Unset.UNSET, instance); } } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdateEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java similarity index 65% rename from src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdateEvent.java rename to src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java index 543cbcd786..bf20430db7 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdateEvent.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java @@ -15,21 +15,21 @@ */ package org.springframework.data.jdbc.mapping.event; -import java.util.function.Function; +import java.util.Optional; /** - * gets published before an entity gets updated in the database. + * Subclasses of this get published before an entity gets saved to the database. * * @author Jens Schauder + * @since 2.0 */ -public class BeforeUpdateEvent extends BeforeSaveEvent { +public class BeforeSave extends JdbcEventWithEntity { /** + * @param id of the entity to be saved. * @param instance the entity about to get saved. - * @param idProvider a function providing the id, for the instance. - * @param type of the entity and the argument of the {@code idProvider} */ - public BeforeUpdateEvent(T instance, Function idProvider) { - super(instance, idProvider); + BeforeSave(Identifier id, Object instance) { + super(id, instance); } } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java deleted file mode 100644 index 626c6f0387..0000000000 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.jdbc.mapping.event; - -import java.util.function.Function; - -import org.springframework.context.ApplicationEvent; - -/** - * subclasses of this get published before an entity gets saved to the database. - * - * @author Jens Schauder - */ -public class BeforeSaveEvent extends JdbcEvent { - - /** - * @param instance the entity about to get saved. - * @param idProvider a function providing the id, for the instance. - * @param type of the entity and the argument of the {@code idProvider} - */ - BeforeSaveEvent(T instance, Function idProvider) { - super(instance, idProvider); - } -} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdate.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdate.java new file mode 100644 index 0000000000..72ad695d53 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdate.java @@ -0,0 +1,35 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import org.springframework.data.jdbc.mapping.event.Identifier.Specified; + +/** + * Gets published before an entity gets updated in the database. + * + * @author Jens Schauder + * @since 2.0 + */ +public class BeforeUpdate extends BeforeSave implements WithId { + + /** + * @param id of the entity about to get updated + * @param instance the entity about to get updated. + */ + public BeforeUpdate(Specified id, Object instance) { + super(id, instance); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java b/src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java new file mode 100644 index 0000000000..492cc1edc6 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java @@ -0,0 +1,65 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import lombok.Data; +import lombok.NonNull; + +import java.util.Optional; + +/** + * Wrapper for an identifier of an entity. Might either be a {@link Specified} or {@link Unset#UNSET} + * + * @author Jens Schauder + * @since 2.0 + */ +public interface Identifier { + + Optional getOptionalValue(); + + static Identifier fromNullable(Object value) { + return (value != null) ? new Specified(value) : Unset.UNSET; + } + + /** + * An unset identifier. Always returns {@link Optional#empty()} as value. + */ + enum Unset implements Identifier { + UNSET { + @Override + public Optional getOptionalValue() { + return Optional.empty(); + } + } + } + + /** + * An {@link Identifier} guaranteed to have a non empty value. + * + * Since it is guaranteed to exist the value can get access directly. + */ + @Data + class Specified implements Identifier { + + @NonNull + private final Object value; + + @Override + public Optional getOptionalValue() { + return Optional.of(value); + } + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java index ad0df42f2a..59a1911d39 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java @@ -15,48 +15,43 @@ */ package org.springframework.data.jdbc.mapping.event; -import java.util.function.Function; +import java.util.Optional; import org.springframework.context.ApplicationEvent; +import lombok.Getter; + /** - * is the common superclass for all events published by JDBC repositories. - * - * It is recommendet not to use the {@link #getSource()} since it may contain the entity if it was available, when the - * event was published, or in case of delete events only the Id. - * - * Use the dedicated methods {@link #getId()} or {@link #getInstance()} instead. Note that the later might be - * {@literal NULL} in the cases mentioned above. + * The common superclass for all events published by JDBC repositories. + * {@link #getSource} contains the {@link Identifier} of the entity triggering the event. * * @author Jens Schauder + * @since 2.0 */ +@Getter public class JdbcEvent extends ApplicationEvent { - private final Object id; - private final Object instance; - - JdbcEvent(T instance, Function idProvider) { - - super(instance == null ? idProvider.apply(instance) : instance); - this.instance = instance; - this.id = idProvider.apply(instance); - } - /** - * the entity for which this event was publish. Might be {@literal NULL} in cases of delete events where only the id + * The optional entity for which this event was published. Might be empty in cases of delete events where only the identifier * was provided to the delete method. * - * @return instance of the entity triggering this event. + * @return The entity triggering this event or empty. */ - public Object getInstance() { - return instance; + private final Optional optionalEntity; + + public JdbcEvent(Identifier id, Optional optionalEntity) { + super(id); + this.optionalEntity = optionalEntity; } /** - * the id of the entity, triggering this event. Guaranteed not to be {@literal NULL}. + * The identifier of the entity, triggering this event. Also available via + * {@link #getSource()}. + * * @return */ - public Object getId() { - return id; + public Identifier getId() { + return (Identifier) getSource(); } + } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithEntity.java b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithEntity.java new file mode 100644 index 0000000000..c103dd6e01 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithEntity.java @@ -0,0 +1,31 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import java.util.Optional; + +/** + * A {@link JdbcEvent} which is guaranteed to have an entity. + * + * @author Jens Schauder + * @since 2.0 + */ +public class JdbcEventWithEntity extends JdbcEvent implements WithEntity { + + public JdbcEventWithEntity(Identifier id, Object entity) { + super(id, Optional.of(entity)); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java new file mode 100644 index 0000000000..fa0c61d650 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java @@ -0,0 +1,33 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import java.util.Optional; + +import org.springframework.data.jdbc.mapping.event.Identifier.Specified; + +/** + * A {@link JdbcEvent} guaranteed to have an identifier. + * + * @author Jens Schauder + * @since 2.0 + */ +public class JdbcEventWithId extends JdbcEvent implements WithId{ + + public JdbcEventWithId(Specified id, Optional entity) { + super(id, entity); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithIdAndEntity.java b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithIdAndEntity.java new file mode 100644 index 0000000000..819ef8962e --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithIdAndEntity.java @@ -0,0 +1,36 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import lombok.Getter; + +import java.util.Optional; + +import org.springframework.data.jdbc.mapping.event.Identifier.Specified; + +/** + * A {@link JdbcEvent} which is guaranteed to have an identifier and an entity. + * + * @author Jens Schauder + * @since 2.0 + */ +@Getter +public class JdbcEventWithIdAndEntity extends JdbcEvent implements WithId, WithEntity { + + public JdbcEventWithIdAndEntity(Specified id, Object entity) { + super(id, Optional.of(entity)); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/WithEntity.java b/src/main/java/org/springframework/data/jdbc/mapping/event/WithEntity.java new file mode 100644 index 0000000000..a465fd54b4 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/WithEntity.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +/** + * Interface for {@link JdbcEvent}s which are guaranteed to have an entity. Allows direct access to that entity, without going through an {@link java.util.Optional} + * + * @author Jens Schauder + * @since 2.0 + */ +public interface WithEntity { + + default Object getEntity() { + return ((JdbcEvent) this).getOptionalEntity() + .orElseThrow(() -> new IllegalStateException("Entity must not be NULL")); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/WithId.java b/src/main/java/org/springframework/data/jdbc/mapping/event/WithId.java new file mode 100644 index 0000000000..108a419ac7 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/WithId.java @@ -0,0 +1,33 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.event; + +import org.springframework.data.jdbc.mapping.event.Identifier.Specified; + +/** + * Interface for {@link JdbcEvent}s which are guaranteed to have a {@link Specified} identifier. + * + * Offers direct access to the {@link Specified} identifier. + * + * @author Jens Schauder + * @since 2.0 + */ +public interface WithId { + + default Specified getSpecifiedId(){ + return (Specified) ((JdbcEvent)this).getId(); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java b/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java new file mode 100644 index 0000000000..a5edb0d006 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java @@ -0,0 +1,60 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.mapping.model; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; + +import org.springframework.data.mapping.Association; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; +import org.springframework.data.mapping.model.SimpleTypeHolder; + +/** + * Meta data about a property to be used by repository implementations. + * + * @author Jens Schauder + * @since 2.0 + */ +public class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProperty + implements JdbcPersistentProperty { + + /** + * Creates a new {@link AnnotationBasedPersistentProperty}. + * + * @param field must not be {@literal null}. + * @param propertyDescriptor can be {@literal null}. + * @param owner must not be {@literal null}. + * @param simpleTypeHolder + */ + public BasicJdbcPersistentProperty( // + Field field, // + PropertyDescriptor propertyDescriptor, // + PersistentEntity owner, // + SimpleTypeHolder simpleTypeHolder // + ) { + super(field, propertyDescriptor, owner, simpleTypeHolder); + } + + @Override + protected Association createAssociation() { + return null; + } + + public String getColumnName() { + return getName(); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java index bbe2d69cfc..1c3a342f3a 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java @@ -20,38 +20,26 @@ /** * meta data a repository might need for implementing persistence operations for instances of type {@code T} + * * @author Jens Schauder + * @since 2.0 */ public class JdbcPersistentEntity extends BasicPersistentEntity { - private String tableName; - private String idColumn; + private final String tableName; public JdbcPersistentEntity(TypeInformation information) { + super(information); + + tableName = getType().getSimpleName(); } public String getTableName() { - - if (tableName == null) - tableName = getType().getSimpleName(); - return tableName; } public String getIdColumn() { - - if (idColumn == null) - idColumn = getIdProperty().getName(); - - return idColumn; - } - - public Object getIdValue(T instance) { - return getPropertyAccessor(instance).getProperty(getIdProperty()); - } - - public void setId(T instance, Object value) { - getPropertyAccessor(instance).setProperty(getIdProperty(),value); + return getIdProperty().getName(); } } diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java index 841d64c650..e63cd4faf3 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java @@ -15,38 +15,13 @@ */ package org.springframework.data.jdbc.mapping.model; -import java.beans.PropertyDescriptor; -import java.lang.reflect.Field; -import org.springframework.data.mapping.Association; -import org.springframework.data.mapping.PersistentEntity; -import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; -import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.mapping.PersistentProperty; /** - * meta data about a property to be used by repository implementations. - * * @author Jens Schauder + * @since 2.0 */ -public class JdbcPersistentProperty extends AnnotationBasedPersistentProperty { - - /** - * Creates a new {@link AnnotationBasedPersistentProperty}. - * - * @param field must not be {@literal null}. - * @param propertyDescriptor can be {@literal null}. - * @param owner must not be {@literal null}. - * @param simpleTypeHolder - */ - public JdbcPersistentProperty(Field field, PropertyDescriptor propertyDescriptor, PersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { - super(field, propertyDescriptor, owner, simpleTypeHolder); - } - - @Override - protected Association createAssociation() { - return null; - } +public interface JdbcPersistentProperty extends PersistentProperty { - public String getColumnName() { - return getName(); - } + String getColumnName(); } diff --git a/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java index b604352150..7d6e190445 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java +++ b/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java @@ -17,6 +17,7 @@ import java.sql.ResultSet; import java.sql.SQLException; + import org.springframework.data.convert.ClassGeneratingEntityInstantiator; import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; @@ -32,6 +33,7 @@ * maps a ResultSet to an entity of type {@code T} * * @author Jens Schauder + * @since 2.0 */ class EntityRowMapper implements RowMapper { @@ -48,7 +50,7 @@ public T mapRow(ResultSet rs, int rowNum) throws SQLException { T t = createInstance(rs); - entity.doWithProperties((PropertyHandler) property -> { + entity.doWithProperties((PropertyHandler) property -> { setProperty(rs, t, property); }); @@ -57,12 +59,15 @@ public T mapRow(ResultSet rs, int rowNum) throws SQLException { private T createInstance(ResultSet rs) { return instantiator.createInstance(entity, new ParameterValueProvider() { + @SuppressWarnings("unchecked") @Override public T getParameterValue(PreferredConstructor.Parameter parameter) { try { return (T) rs.getObject(parameter.getName()); } catch (SQLException e) { - throw new MappingException(String.format("Couldn't read column %s from ResultSet.", parameter.getName())); + throw new MappingException( // + String.format("Couldn't read column %s from ResultSet.", parameter.getName()) // + ); } } }); @@ -76,4 +81,4 @@ private void setProperty(ResultSet rs, T t, PersistentProperty property) { throw new RuntimeException(String.format("Couldn't set property %s.", property.getName()), e); } } -} \ No newline at end of file +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java index b499296c83..1721c7873d 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java +++ b/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java @@ -15,44 +15,37 @@ */ package org.springframework.data.jdbc.repository; +import java.io.Serializable; import java.sql.ResultSet; import java.sql.SQLException; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.data.jdbc.mapping.event.AfterCreationEvent; -import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; +import org.springframework.data.jdbc.mapping.event.AfterCreation; +import org.springframework.data.jdbc.mapping.event.Identifier.Specified; +import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation; import org.springframework.jdbc.core.RowMapper; +import lombok.RequiredArgsConstructor; + /** - * a RowMapper that publishes events after a delegate, did the actual work of mapping a {@link ResultSet} to an entity. + * a RowMapper that publishes events after a delegate, did the actual work of mapping a {@link ResultSet} to an entityInformation. * * @author Jens Schauder + * @since 2.0 */ -public class EventPublishingEntityRowMapper implements RowMapper { +@RequiredArgsConstructor +public class EventPublishingEntityRowMapper implements RowMapper { private final RowMapper delegate; - private final JdbcPersistentEntity entity; + private final JdbcPersistentEntityInformation entityInformation; private final ApplicationEventPublisher publisher; - /** - * - * @param delegate does the actuall mapping. - * @param entity provides functionality to create ids from entities - * @param publisher used for event publishing after the mapping. - */ - EventPublishingEntityRowMapper(RowMapper delegate,JdbcPersistentEntity entity, ApplicationEventPublisher publisher) { - - this.delegate = delegate; - this.entity = entity; - this.publisher = publisher; - } - @Override public T mapRow(ResultSet resultSet, int i) throws SQLException { T instance = delegate.mapRow(resultSet, i); - publisher.publishEvent(new AfterCreationEvent(instance, entity::getIdValue)); + publisher.publishEvent(new AfterCreation(new Specified(entityInformation.getId(instance)), instance)); return instance; } diff --git a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java index 04aaf4672d..ebff6a34d5 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java +++ b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java @@ -16,21 +16,26 @@ package org.springframework.data.jdbc.repository; import java.io.Serializable; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.data.jdbc.mapping.event.AfterDeleteEvent; -import org.springframework.data.jdbc.mapping.event.AfterInsertEvent; -import org.springframework.data.jdbc.mapping.event.AfterUpdateEvent; -import org.springframework.data.jdbc.mapping.event.BeforeDeleteEvent; -import org.springframework.data.jdbc.mapping.event.BeforeInsertEvent; -import org.springframework.data.jdbc.mapping.event.BeforeUpdateEvent; +import org.springframework.dao.NonTransientDataAccessException; +import org.springframework.data.jdbc.mapping.event.AfterDelete; +import org.springframework.data.jdbc.mapping.event.AfterInsert; +import org.springframework.data.jdbc.mapping.event.AfterUpdate; +import org.springframework.data.jdbc.mapping.event.BeforeDelete; +import org.springframework.data.jdbc.mapping.event.BeforeInsert; +import org.springframework.data.jdbc.mapping.event.BeforeUpdate; +import org.springframework.data.jdbc.mapping.event.Identifier.Specified; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; +import org.springframework.data.jdbc.repository.support.BasicJdbcPersistentEntityInformation; import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.repository.CrudRepository; @@ -42,11 +47,15 @@ /** * @author Jens Schauder + * @since 2.0 */ -public class SimpleJdbcRepository - implements CrudRepository, ApplicationEventPublisherAware { +public class SimpleJdbcRepository implements CrudRepository { - private final JdbcPersistentEntity entity; + private static final String ENTITY_NEW_AFTER_INSERT = "Entity [%s] still 'new' after insert. Please set either" + + " the id property in a before insert event handler, or ensure the database creates a value and your " + + "JDBC driver returns it."; + + private final JdbcPersistentEntity persistentEntity; private final JdbcPersistentEntityInformation entityInformation; private final NamedParameterJdbcOperations operations; private final SqlGenerator sql; @@ -54,18 +63,19 @@ public class SimpleJdbcRepository private final EntityRowMapper entityRowMapper; private final ApplicationEventPublisher publisher; - public SimpleJdbcRepository(JdbcPersistentEntity persistentEntity, NamedParameterJdbcOperations jdbcOperations, ApplicationEventPublisher publisher) { + public SimpleJdbcRepository(JdbcPersistentEntity persistentEntity, NamedParameterJdbcOperations jdbcOperations, + ApplicationEventPublisher publisher) { Assert.notNull(persistentEntity, "PersistentEntity must not be null."); Assert.notNull(jdbcOperations, "JdbcOperations must not be null."); Assert.notNull(publisher, "Publisher must not be null."); - this.entity = persistentEntity; - this.entityInformation = new JdbcPersistentEntityInformation(persistentEntity); + this.persistentEntity = persistentEntity; + this.entityInformation = new BasicJdbcPersistentEntityInformation<>(persistentEntity); this.operations = jdbcOperations; this.publisher = publisher; - entityRowMapper = new EntityRowMapper(persistentEntity); + entityRowMapper = new EntityRowMapper<>(persistentEntity); sql = new SqlGenerator(persistentEntity); } @@ -84,29 +94,23 @@ public S save(S instance) { @Override public Iterable save(Iterable entities) { - entities.forEach(this::save); + List savedEntities = new ArrayList<>(); + + entities.forEach(e -> savedEntities.add(save(e))); - return entities; + return savedEntities; } @Override public T findOne(ID id) { - return operations.queryForObject( - sql.getFindOne(), - new MapSqlParameterSource("id", id), - entityRowMapper - ); + return operations.queryForObject(sql.getFindOne(), new MapSqlParameterSource("id", id), entityRowMapper); } @Override public boolean exists(ID id) { - return operations.queryForObject( - sql.getExists(), - new MapSqlParameterSource("id", id), - Boolean.class - ); + return operations.queryForObject(sql.getExists(), new MapSqlParameterSource("id", id), Boolean.class); } @Override @@ -126,27 +130,19 @@ public long count() { @Override public void delete(ID id) { - doDelete(id, null); + doDelete(new Specified(id), Optional.empty()); } @Override public void delete(T instance) { - - doDelete((ID) entity.getIdValue(instance), instance); + doDelete(new Specified(entityInformation.getId(instance)), Optional.of(instance)); } @Override public void delete(Iterable entities) { - operations.update( - sql.getDeleteByList(), - new MapSqlParameterSource("ids", - StreamSupport - .stream(entities.spliterator(), false) - .map(entity::getIdValue) - .collect(Collectors.toList()) - ) - ); + operations.update(sql.getDeleteByList(), new MapSqlParameterSource("ids", StreamSupport + .stream(entities.spliterator(), false).map(entityInformation::getId).collect(Collectors.toList()))); } @Override @@ -154,52 +150,73 @@ public void deleteAll() { operations.getJdbcOperations().update(sql.getDeleteAll()); } - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - - } - private Map getPropertyMap(final S instance) { Map parameters = new HashMap<>(); - this.entity.doWithProperties(new PropertyHandler() { - @Override - public void doWithPersistentProperty(JdbcPersistentProperty persistentProperty) { - parameters.put(persistentProperty.getColumnName(), entity.getPropertyAccessor(instance).getProperty(persistentProperty)); - } - }); + this.persistentEntity.doWithProperties((PropertyHandler) // + property -> parameters.put( // + property.getColumnName(), // + persistentEntity.getPropertyAccessor(instance).getProperty(property)) // + ); return parameters; } private void doInsert(S instance) { - publisher.publishEvent(new BeforeInsertEvent(instance, entity::getIdValue)); + + publisher.publishEvent(new BeforeInsert(instance)); KeyHolder holder = new GeneratedKeyHolder(); - operations.update( - sql.getInsert(), - new MapSqlParameterSource(getPropertyMap(instance)), - holder); + Map propertyMap = getPropertyMap(instance); + propertyMap.put(persistentEntity.getIdColumn(), getIdValueOrNull(instance)); - entity.setId(instance, holder.getKey()); + operations.update(sql.getInsert(), new MapSqlParameterSource(propertyMap), holder); + setIdFromJdbc(instance, holder); - publisher.publishEvent(new AfterInsertEvent(instance, entity::getIdValue)); + if (entityInformation.isNew(instance)) { + throw new IllegalStateException(String.format(ENTITY_NEW_AFTER_INSERT, persistentEntity)); + } + + publisher.publishEvent(new AfterInsert(new Specified(entityInformation.getId(instance)), instance)); } - private void doDelete(ID id, Object instance) { + private ID getIdValueOrNull(S instance) { - publisher.publishEvent(new BeforeDeleteEvent(instance, o -> id)); - operations.update(sql.getDeleteById(), new MapSqlParameterSource("id", id)); - publisher.publishEvent(new AfterDeleteEvent(instance, o -> id)); + ID idValue = entityInformation.getId(instance); + return isIdPropertySimpleTypeAndValueZero(idValue) ? null : idValue; + } + + private boolean isIdPropertySimpleTypeAndValueZero(ID idValue) { + + return (persistentEntity.getIdProperty().getType() == int.class && idValue.equals(0)) + || (persistentEntity.getIdProperty().getType() == long.class && idValue.equals(0L)); + } + + private void setIdFromJdbc(S instance, KeyHolder holder) { + try { + Number idValueFromJdbc = holder.getKey(); + if (idValueFromJdbc != null) { + entityInformation.setId(instance, idValueFromJdbc); + } + } catch (NonTransientDataAccessException e) { + throw new UnableToSetIdException("Unable to set id of " + instance, e); + } + } + + private void doDelete(Specified specifiedId, Optional optionalEntity) { + + publisher.publishEvent(new BeforeDelete(specifiedId, optionalEntity)); + operations.update(sql.getDeleteById(), new MapSqlParameterSource("id", specifiedId.getValue())); + publisher.publishEvent(new AfterDelete(specifiedId, optionalEntity)); } private void doUpdate(S instance) { - publisher.publishEvent(new BeforeUpdateEvent(instance, entity::getIdValue)); + Specified specifiedId = new Specified(entityInformation.getId(instance)); + publisher.publishEvent(new BeforeUpdate(specifiedId, instance)); operations.update(sql.getUpdate(), getPropertyMap(instance)); - - publisher.publishEvent(new AfterUpdateEvent(instance, entity::getIdValue)); + publisher.publishEvent(new AfterUpdate(specifiedId, instance)); } } diff --git a/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java b/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java index 2d613d78a6..5036b979f5 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java +++ b/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java @@ -24,6 +24,7 @@ /** * @author Jens Schauder + * @since 2.0 */ class SqlGenerator { diff --git a/src/main/java/org/springframework/data/jdbc/repository/UnableToSetIdException.java b/src/main/java/org/springframework/data/jdbc/repository/UnableToSetIdException.java new file mode 100644 index 0000000000..1bbb0126ff --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/UnableToSetIdException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.repository; + +import org.springframework.dao.NonTransientDataAccessException; + +/** + * Signals failure to set the id property of an entity. + * + * @author Jens Schauder + * @since 2.0 + */ +public class UnableToSetIdException extends NonTransientDataAccessException { + + public UnableToSetIdException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/BasicJdbcPersistentEntityInformation.java b/src/main/java/org/springframework/data/jdbc/repository/support/BasicJdbcPersistentEntityInformation.java new file mode 100644 index 0000000000..ea22fccaeb --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/support/BasicJdbcPersistentEntityInformation.java @@ -0,0 +1,42 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.repository.support; + +import java.io.Serializable; + +import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; +import org.springframework.data.repository.core.support.PersistentEntityInformation; + +/** + * @author Jens Schauder + * @since 2.0 + */ +public class BasicJdbcPersistentEntityInformation extends PersistentEntityInformation + implements JdbcPersistentEntityInformation { + + private final JdbcPersistentEntity persistentEntity; + + public BasicJdbcPersistentEntityInformation(JdbcPersistentEntity persistentEntity) { + super(persistentEntity); + + this.persistentEntity = persistentEntity; + } + + @Override + public void setId(T instance, Object value) { + persistentEntity.getPropertyAccessor(instance).setProperty(persistentEntity.getIdProperty(), value); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcPersistentEntityInformation.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcPersistentEntityInformation.java index 97664acf0e..53df278c9d 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcPersistentEntityInformation.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcPersistentEntityInformation.java @@ -16,15 +16,14 @@ package org.springframework.data.jdbc.repository.support; import java.io.Serializable; -import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; -import org.springframework.data.repository.core.support.PersistentEntityInformation; + +import org.springframework.data.repository.core.EntityInformation; /** * @author Jens Schauder + * @since 2.0 */ -public class JdbcPersistentEntityInformation extends PersistentEntityInformation { +public interface JdbcPersistentEntityInformation extends EntityInformation { - public JdbcPersistentEntityInformation(JdbcPersistentEntity persistentEntity) { - super(persistentEntity); - } + void setId(T instance, Object value); } diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java index f2e9b8952b..2b92c33171 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java @@ -15,6 +15,8 @@ */ package org.springframework.data.jdbc.repository.support; +import lombok.RequiredArgsConstructor; + import java.io.Serializable; import org.springframework.context.ApplicationEventPublisher; @@ -29,34 +31,26 @@ /** * @author Jens Schauder + * @since 2.0 */ +@RequiredArgsConstructor public class JdbcRepositoryFactory extends RepositoryFactorySupport { private final JdbcMappingContext context = new JdbcMappingContext(); - private final ApplicationEventPublisher publisher; private final NamedParameterJdbcOperations jdbcOperations; + private final ApplicationEventPublisher publisher; - public JdbcRepositoryFactory( - ApplicationEventPublisher publisher, - NamedParameterJdbcOperations jdbcOperations - ) { - - this.publisher = publisher; - this.jdbcOperations = jdbcOperations; - } - + @SuppressWarnings("unchecked") @Override public EntityInformation getEntityInformation(Class aClass) { - return new JdbcPersistentEntityInformation<>((JdbcPersistentEntity) context.getPersistentEntity(aClass)); + return new BasicJdbcPersistentEntityInformation<>((JdbcPersistentEntity) context.getPersistentEntity(aClass)); } @Override protected Object getTargetRepository(RepositoryInformation repositoryInformation) { - return new SimpleJdbcRepository( - context.getPersistentEntity(repositoryInformation.getDomainType()), - jdbcOperations, - publisher); + JdbcPersistentEntity persistentEntity = context.getPersistentEntity(repositoryInformation.getDomainType()); + return new SimpleJdbcRepository<>(persistentEntity, jdbcOperations, publisher); } @Override diff --git a/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java b/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java index 44113c48c9..9fefea380b 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java +++ b/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java @@ -2,46 +2,46 @@ import static org.mockito.Mockito.*; +import lombok.Data; + import java.sql.ResultSet; import java.sql.SQLException; import org.junit.Test; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.annotation.Id; -import org.springframework.data.jdbc.mapping.event.AfterCreationEvent; -import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; +import org.springframework.data.jdbc.mapping.event.AfterCreation; +import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation; import org.springframework.jdbc.core.RowMapper; -import lombok.Data; - /** * @author Jens Schauder */ public class EventPublishingEntityRowMapperTest { - private RowMapper rowMapperDelegate = mock(RowMapper.class); - private JdbcPersistentEntity entity = mock(JdbcPersistentEntity.class); - private ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class); + RowMapper rowMapperDelegate = mock(RowMapper.class); + JdbcPersistentEntityInformation entityInformation = mock(JdbcPersistentEntityInformation.class); + ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class); @Test // DATAJDBC-99 public void eventGetsPublishedAfterInstantiation() throws SQLException { - when(entity.getIdValue(any())).thenReturn(1L); + when(entityInformation.getId(any())).thenReturn(1L); - EventPublishingEntityRowMapper rowMapper = new EventPublishingEntityRowMapper<>( - rowMapperDelegate, - entity, + EventPublishingEntityRowMapper rowMapper = new EventPublishingEntityRowMapper<>( // + rowMapperDelegate, // + entityInformation, // publisher); ResultSet resultSet = mock(ResultSet.class); rowMapper.mapRow(resultSet, 1); - verify(publisher).publishEvent(isA(AfterCreationEvent.class)); + verify(publisher).publishEvent(isA(AfterCreation.class)); } @Data - private static class DummyEntity { + static class DummyEntity { @Id private final Long Id; } -} \ No newline at end of file +} diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java index f537f6d006..9a1cf50d26 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java @@ -17,96 +17,71 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.*; + +import lombok.Data; -import org.junit.After; import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.repository.JdbcRepositoryIdGenerationIntegrationTests.TestConfiguration; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; - -import lombok.Data; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** - * testing special cases for Id generation with JdbcRepositories. + * Testing special cases for id generation with {@link SimpleJdbcRepository}. * * @author Jens Schauder */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = TestConfiguration.class) public class JdbcRepositoryIdGenerationIntegrationTests { - private final EmbeddedDatabase db = new EmbeddedDatabaseBuilder() - .generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .ignoreFailedDrops(true) - .addScript("org.springframework.data.jdbc.repository/jdbc-repository-id-generation-integration-tests.sql") - .build(); + @Autowired NamedParameterJdbcTemplate template; - private final NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(db); + @Autowired ReadOnlyIdEntityRepository readOnlyIdrepository; - private final ReadOnlyIdEntityRepository repository = createRepository(db); - - private ReadOnlyIdEntity entity = createDummyEntity(); - - @After - public void after() { - db.shutdown(); - } + @Autowired PrimitiveIdEntityRepository primitiveIdRepository; @Test // DATAJDBC-98 public void idWithoutSetterGetsSet() { - entity = repository.save(entity); + ReadOnlyIdEntity entity1 = new ReadOnlyIdEntity(null); + entity1.setName("Entity Name"); + ReadOnlyIdEntity entity = entity1; + entity = readOnlyIdrepository.save(entity); assertThat(entity.getId()).isNotNull(); - ReadOnlyIdEntity reloadedEntity = repository.findOne(entity.getId()); + ReadOnlyIdEntity reloadedEntity = readOnlyIdrepository.findOne(entity.getId()); - assertEquals( - entity.getId(), - reloadedEntity.getId()); - assertEquals( - entity.getName(), - reloadedEntity.getName()); + assertEquals(entity.getId(), reloadedEntity.getId()); + assertEquals(entity.getName(), reloadedEntity.getName()); } @Test // DATAJDBC-98 public void primitiveIdGetsSet() { - entity = repository.save(entity); + PrimitiveIdEntity entity = new PrimitiveIdEntity(0); + entity.setName("Entity Name"); + entity = primitiveIdRepository.save(entity); assertThat(entity.getId()).isNotNull(); - ReadOnlyIdEntity reloadedEntity = repository.findOne(entity.getId()); - - assertEquals( - entity.getId(), - reloadedEntity.getId()); - assertEquals( - entity.getName(), - reloadedEntity.getName()); - } - - - private static ReadOnlyIdEntityRepository createRepository(EmbeddedDatabase db) { - - return new JdbcRepositoryFactory( - mock(ApplicationEventPublisher.class), - new NamedParameterJdbcTemplate(db) - ).getRepository(ReadOnlyIdEntityRepository.class); - } - + PrimitiveIdEntity reloadedEntity = primitiveIdRepository.findOne(entity.getId()); - private static ReadOnlyIdEntity createDummyEntity() { - - ReadOnlyIdEntity entity = new ReadOnlyIdEntity(null); - entity.setName("Entity Name"); - return entity; + assertEquals(entity.getId(), reloadedEntity.getId()); + assertEquals(entity.getName(), reloadedEntity.getName()); } private interface ReadOnlyIdEntityRepository extends CrudRepository { @@ -116,8 +91,7 @@ private interface ReadOnlyIdEntityRepository extends CrudRepository all = repository.findAll(); - assertThat(all).extracting("idProp") - .containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); + assertThat(all).extracting("idProp").containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); } @Test // DATAJDBC-97 @@ -137,12 +131,11 @@ public void findAllFindsAllSpecifiedEntities() { Iterable all = repository.findAll(asList(entity.getIdProp(), three.getIdProp())); - assertThat(all).extracting("idProp") - .containsExactlyInAnyOrder(entity.getIdProp(), three.getIdProp()); + assertThat(all).extracting("idProp").containsExactlyInAnyOrder(entity.getIdProp(), three.getIdProp()); } @Test // DATAJDBC-97 - public void count() { + public void countsEntities() { repository.save(createDummyEntity()); repository.save(createDummyEntity()); @@ -151,7 +144,7 @@ public void count() { assertThat(repository.count()).isEqualTo(3L); } - @Test // DATAJDBC-97 + @Test // DATAJDBC-97 public void deleteById() { entity = repository.save(entity); @@ -160,9 +153,11 @@ public void deleteById() { repository.delete(two.getIdProp()); - assertThat(repository.findAll()) - .extracting(DummyEntity::getIdProp) - .containsExactlyInAnyOrder(entity.getIdProp(), three.getIdProp()); + assertThat(repository.findAll()) // + .extracting(DummyEntity::getIdProp) // + .containsExactlyInAnyOrder( // + entity.getIdProp(), three.getIdProp() // + ); } @Test // DATAJDBC-97 @@ -174,15 +169,13 @@ public void deleteByEntity() { repository.delete(entity); - assertThat(repository.findAll()) - .extracting(DummyEntity::getIdProp) - .containsExactlyInAnyOrder( - two.getIdProp(), - three.getIdProp() - ); + assertThat(repository.findAll()) // + .extracting(DummyEntity::getIdProp) // + .containsExactlyInAnyOrder( // + two.getIdProp(), three.getIdProp() // + ); } - @Test // DATAJDBC-97 public void deleteByList() { @@ -195,7 +188,7 @@ public void deleteByList() { assertThat(repository.findAll()).extracting(DummyEntity::getIdProp).containsExactlyInAnyOrder(two.getIdProp()); } - @Test // DATAJDBC-97 + @Test // DATAJDBC-97 public void deleteAll() { repository.save(entity); @@ -207,7 +200,6 @@ public void deleteAll() { assertThat(repository.findAll()).isEmpty(); } - @Test // DATAJDBC-98 public void update() { @@ -233,18 +225,16 @@ public void updateMany() { repository.save(asList(entity, other)); - assertThat(repository.findAll()) - .extracting(DummyEntity::getName) - .containsExactlyInAnyOrder(entity.getName(), other.getName()); + assertThat(repository.findAll()).extracting(DummyEntity::getName).containsExactlyInAnyOrder(entity.getName(), + other.getName()); } private static DummyEntityRepository createRepository(EmbeddedDatabase db) { - return new JdbcRepositoryFactory(mock(ApplicationEventPublisher.class), new NamedParameterJdbcTemplate(db)) + return new JdbcRepositoryFactory(new NamedParameterJdbcTemplate(db), mock(ApplicationEventPublisher.class)) .getRepository(DummyEntityRepository.class); } - private static DummyEntity createDummyEntity() { DummyEntity entity = new DummyEntity(); @@ -259,8 +249,42 @@ private interface DummyEntityRepository extends CrudRepository() { + @Override + public Integer answer(InvocationOnMock invocation) throws Throwable { + HashMap keys = new HashMap<>(); + keys.put("id", 4711L); + invocation.getArgumentAt(2, KeyHolder.class).getKeyList().add(keys); + return 1; + } + }); + return operations; + } + @Test // DATAJDBC-99 public void publishesEventsOnSave() { @@ -47,22 +68,24 @@ public void publishesEventsOnSave() { repository.save(entity); - isInstanceOf(BeforeUpdateEvent.class, publisher.events.get(0)); - isInstanceOf(AfterUpdateEvent.class, publisher.events.get(1)); + isInstanceOf(BeforeUpdate.class, publisher.events.get(0)); + isInstanceOf(AfterUpdate.class, publisher.events.get(1)); } @Test // DATAJDBC-99 public void publishesEventsOnSaveMany() { + + DummyEntity entity1 = new DummyEntity(null); DummyEntity entity2 = new DummyEntity(23L); repository.save(asList(entity1, entity2)); - isInstanceOf(BeforeInsertEvent.class, publisher.events.get(0)); - isInstanceOf(AfterInsertEvent.class, publisher.events.get(1)); - isInstanceOf(BeforeUpdateEvent.class, publisher.events.get(2)); - isInstanceOf(AfterUpdateEvent.class, publisher.events.get(3)); + isInstanceOf(BeforeInsert.class, publisher.events.get(0)); + isInstanceOf(AfterInsert.class, publisher.events.get(1)); + isInstanceOf(BeforeUpdate.class, publisher.events.get(2)); + isInstanceOf(AfterUpdate.class, publisher.events.get(3)); } @@ -73,27 +96,25 @@ public void publishesEventsOnDelete() { repository.delete(entity); - isInstanceOf(BeforeDeleteEvent.class, publisher.events.get(0)); - isInstanceOf(AfterDeleteEvent.class, publisher.events.get(1)); + isInstanceOf(BeforeDelete.class, publisher.events.get(0)); + isInstanceOf(AfterDelete.class, publisher.events.get(1)); - assertEquals(entity, publisher.events.get(0).getInstance()); - assertEquals(entity, publisher.events.get(1).getInstance()); + assertEquals(entity, publisher.events.get(0).getOptionalEntity().get()); + assertEquals(entity, publisher.events.get(1).getOptionalEntity().get()); - assertEquals(23L, publisher.events.get(0).getId()); - assertEquals(23L, publisher.events.get(1).getId()); + assertEquals(new Specified(23L), publisher.events.get(0).getId()); + assertEquals(new Specified(23L), publisher.events.get(1).getId()); } - @Test // DATAJDBC-99 public void publishesEventsOnDeleteById() { repository.delete(23L); - isInstanceOf(BeforeDeleteEvent.class, publisher.events.get(0)); - isInstanceOf(AfterDeleteEvent.class, publisher.events.get(1)); + isInstanceOf(BeforeDelete.class, publisher.events.get(0)); + isInstanceOf(AfterDelete.class, publisher.events.get(1)); } - @Data private static class DummyEntity { diff --git a/src/test/resources/org.springframework.data.jdbc.repository/jdbc-repository-id-generation-integration-tests.sql b/src/test/resources/org.springframework.data.jdbc.repository/jdbc-repository-id-generation-integration-tests.sql index 0e008210e2..c7a764b232 100644 --- a/src/test/resources/org.springframework.data.jdbc.repository/jdbc-repository-id-generation-integration-tests.sql +++ b/src/test/resources/org.springframework.data.jdbc.repository/jdbc-repository-id-generation-integration-tests.sql @@ -1,3 +1,5 @@ -- noinspection SqlNoDataSourceInspectionForFile -CREATE TABLE ReadOnlyIdEntity (ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(100)) \ No newline at end of file +CREATE TABLE ReadOnlyIdEntity (ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(100)) + +CREATE TABLE PrimitiveIdEntity (ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(100)) \ No newline at end of file From d4238f2d3552f9555e745c65a4fa03467ec61007 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 14 Mar 2017 15:47:02 +0100 Subject: [PATCH 09/12] 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 ` 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. --- pom.xml | 78 ++++++++++++++- readme.md | 29 ++++++ run-tests-against-all-dbs.sh | 3 + .../jdbc/mapping/event/AfterCreation.java | 3 +- .../data/jdbc/mapping/event/AfterDelete.java | 7 +- .../data/jdbc/mapping/event/AfterSave.java | 2 - .../data/jdbc/mapping/event/BeforeInsert.java | 5 +- .../data/jdbc/mapping/event/BeforeSave.java | 2 - .../data/jdbc/mapping/event/Identifier.java | 12 +-- .../data/jdbc/mapping/event/JdbcEvent.java | 21 ++-- .../jdbc/mapping/event/JdbcEventWithId.java | 2 +- .../data/jdbc/mapping/event/WithEntity.java | 3 +- .../data/jdbc/mapping/event/WithId.java | 9 +- .../mapping/model/JdbcPersistentEntity.java | 2 +- .../data/jdbc/repository/EntityRowMapper.java | 17 +++- .../EventPublishingEntityRowMapper.java | 7 +- .../jdbc/repository/SimpleJdbcRepository.java | 62 ++++++++---- .../data/jdbc/repository/SqlGenerator.java | 78 +++++++++------ .../EventPublishingEntityRowMapperTest.java | 6 +- ...epositoryIdGenerationIntegrationTests.java | 49 ++++------ .../JdbcRepositoryIntegrationTests.java | 96 +++++++++---------- .../SimpleJdbcRepositoryEventsUnitTests.java | 42 ++++---- .../jdbc/testing/DataSourceFactoryBean.java | 59 ++++++++++++ .../testing/HsqlDataSourceFactoryBean.java | 52 ++++++++++ .../testing/MySqlDataSourceFactoryBean.java | 84 ++++++++++++++++ .../PostgresDataSourceFactoryBean.java | 69 +++++++++++++ src/test/resources/create-mysql.sql | 2 + ...toryIdGenerationIntegrationTests-hsql.sql} | 1 - ...toryIdGenerationIntegrationTests-mysql.sql | 2 + ...yIdGenerationIntegrationTests-postgres.sql | 5 + .../JdbcRepositoryIntegrationTests-hsql.sql | 1 + .../JdbcRepositoryIntegrationTests-mysql.sql | 1 + ...dbcRepositoryIntegrationTests-postgres.sql | 2 + .../jdbc-repository-integration-tests.sql | 1 - start-all-dbs.sh | 7 ++ stop-all-dbs.sh | 7 ++ 36 files changed, 626 insertions(+), 202 deletions(-) create mode 100755 run-tests-against-all-dbs.sh create mode 100644 src/test/java/org/springframework/data/jdbc/testing/DataSourceFactoryBean.java create mode 100644 src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceFactoryBean.java create mode 100644 src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceFactoryBean.java create mode 100644 src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceFactoryBean.java create mode 100644 src/test/resources/create-mysql.sql rename src/test/resources/org.springframework.data.jdbc.repository/{jdbc-repository-id-generation-integration-tests.sql => JdbcRepositoryIdGenerationIntegrationTests-hsql.sql} (99%) create mode 100644 src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-mysql.sql create mode 100644 src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-postgres.sql create mode 100644 src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql create mode 100644 src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql create mode 100644 src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql delete mode 100644 src/test/resources/org.springframework.data.jdbc.repository/jdbc-repository-integration-tests.sql create mode 100755 start-all-dbs.sh create mode 100755 stop-all-dbs.sh diff --git a/pom.xml b/pom.xml index e61a5fca05..a0bb887d5d 100644 --- a/pom.xml +++ b/pom.xml @@ -42,6 +42,50 @@ + + + all-dbs + + + + org.apache.maven.plugins + maven-surefire-plugin + + + mysql-test + test + + test + + + + **/*IntegrationTests.java + + + mysql + + + + + postgres-test + test + + test + + + + **/*IntegrationTests.java + + + postgres + + + + + + + + @@ -97,10 +141,33 @@ test + + mysql + mysql-connector-java + 5.1.41 + test + + + + org.postgresql + postgresql + 42.0.0 + test + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + +