diff --git a/pom.xml b/pom.xml
index 8f9de8fe20..8fc40b10df 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.
@@ -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
new file mode 100644
index 0000000000..4bde019f07
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java
@@ -0,0 +1,78 @@
+/*
+ * 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.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 JdbcPersistentEntity entity;
+
+ private final EntityInstantiator instantiator = new ClassGeneratingEntityInstantiator();
+
+ EntityRowMapper(JdbcPersistentEntity entity) {
+ this.entity = entity;
+ }
+
+ @Override
+ public T mapRow(ResultSet rs, int rowNum) throws SQLException {
+
+ T t = createInstance(rs);
+
+ entity.doWithProperties((PropertyHandler) property -> {
+ setProperty(rs, t, property);
+ });
+
+ return t;
+ }
+
+ 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 {
+ 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);
+ }
+ }
+}
\ 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..556e2a1eaf 100644
--- a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java
+++ b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java
@@ -18,9 +18,14 @@
import java.io.Serializable;
import java.util.HashMap;
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.jdbc.mapping.model.JdbcPersistentProperty;
+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,74 +34,121 @@
*/
public class SimpleJdbcRepository implements CrudRepository {
- private final EntityInformation entityInformation;
+ private final JdbcPersistentEntity entity;
private final NamedParameterJdbcOperations template;
+ private final SqlGenerator sql;
- public SimpleJdbcRepository(EntityInformation entityInformation, DataSource dataSource) {
- this.entityInformation = entityInformation;
+ private final EntityRowMapper entityRowMapper;
+
+ public SimpleJdbcRepository(JdbcPersistentEntity entity, DataSource dataSource) {
+
+ this.entity = entity;
this.template = new NamedParameterJdbcTemplate(dataSource);
+
+ entityRowMapper = new EntityRowMapper(entity);
+ sql = new SqlGenerator(entity);
}
@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(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 null;
+
+ return template.queryForObject(
+ sql.getFindOne(),
+ new MapSqlParameterSource("id", id),
+ 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 extends T> entities) {
+ template.update(
+ sql.getDeleteByList(),
+ new MapSqlParameterSource("ids",
+ StreamSupport
+ .stream(entities.spliterator(), false)
+ .map(entity::getIdValue)
+ .collect(Collectors.toList())
+ )
+ );
}
@Override
public void deleteAll() {
+ template.getJdbcOperations().update(sql.getDeleteAll());
+ }
+
+ 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));
+ }
+ });
+ return parameters;
}
}
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/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..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;
@@ -48,19 +51,18 @@ public class JdbcRepositoryIntegrationTests {
private final NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(db);
+ private final DummyEntityRepository repository = createRepository(db);
+
+ private DummyEntity entity = createDummyEntity(23L);
+
@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");
+ public void canSaveAnEntity() {
entity = repository.save(entity);
@@ -74,17 +76,146 @@ 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() {
+
+ entity = repository.save(entity);
+
+ DummyEntity reloadedEntity = repository.findOne(entity.getId());
+
+ assertEquals(
+ entity.getId(),
+ reloadedEntity.getId());
+ assertEquals(
+ entity.getName(),
+ 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(long id) {
+
+ DummyEntity entity = new DummyEntity();
+ entity.setId(id);
+ entity.setName("Entity Name");
+ return entity;
}
private interface DummyEntityRepository extends CrudRepository {
}
+ // needs to be public in order for the Hamcrest property matcher to work.
@Data
- private static class DummyEntity {
+ public static class DummyEntity {
@Id
Long id;