From 06343865b87c1afe4025bdab5fa2daae1e7bafcc Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 21 Feb 2017 13:57:25 +0100 Subject: [PATCH 1/7] 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 6cbd7793d6596173be2aff67ea13cf8d86cc53cd Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Wed, 22 Feb 2017 08:56:26 +0100 Subject: [PATCH 2/7] DATAJDBC-97 - Prepare feature branch. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8f9de8fe20..38eeae5ea9 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-97-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. From d7fadf90a8d3353591b321b32bf2d53dbdb8de4f Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Wed, 22 Feb 2017 07:02:47 +0100 Subject: [PATCH 3/7] 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 38eeae5ea9..8fc40b10df 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 10:38:14 +0100 Subject: [PATCH 4/7] DATAJDBC-96 - Prepare feature branch. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8fc40b10df..9826f390a2 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-jdbc - 1.0.0.DATAJDBC-97-SNAPSHOT + 1.0.0.DATAJDBC-96-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. From a390cf7dd86fc6636087a7bff869e042781c519f Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 27 Feb 2017 08:22:19 +0100 Subject: [PATCH 5/7] 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 13b74bdbd4f5cb51e3f523da64b1c36b409e599e Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 27 Feb 2017 11:02:21 +0100 Subject: [PATCH 6/7] DATAJDBC-98 - Prepare feature branch. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9826f390a2..762448b140 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-jdbc - 1.0.0.DATAJDBC-96-SNAPSHOT + 1.0.0.DATAJDBC-98-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. From c07e70410f1f6a4a5833318290666312ef229ccb Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 27 Feb 2017 12:38:39 +0100 Subject: [PATCH 7/7] 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