DATAJDBC-97 - Basic implementation of all CRUD methods.

Original pull request: #5.
This commit is contained in:
Jens Schauder
2017-02-22 07:02:47 +01:00
committed by Oliver Gierke
parent 38a91d97a0
commit 2181d8d3c6
7 changed files with 371 additions and 74 deletions

View File

@@ -19,11 +19,35 @@ import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.util.TypeInformation;
/**
* meta data a repository might need for implementing persistence operations for instances of type {@code T}
* @author Jens Schauder
*/
public class JdbcPersistentEntity<T> extends BasicPersistentEntity<T, JdbcPersistentProperty> {
private String tableName;
private String idColumn;
public JdbcPersistentEntity(TypeInformation<T> 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());
}
}

View File

@@ -23,6 +23,8 @@ 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
*/
public class JdbcPersistentProperty extends AnnotationBasedPersistentProperty<JdbcPersistentProperty> {
@@ -43,4 +45,8 @@ public class JdbcPersistentProperty extends AnnotationBasedPersistentProperty<Jd
protected Association<JdbcPersistentProperty> createAssociation() {
return null;
}
public String getColumnName() {
return getName();
}
}

View File

@@ -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<T> implements org.springframework.jdbc.core.RowMapper<T> {
private final PersistentEntity<T, ?> entity;
private final JdbcPersistentEntity<T> entity;
EntityRowMapper(PersistentEntity<T, ?> entity) {
private final EntityInstantiator instantiator = new ClassGeneratingEntityInstantiator();
EntityRowMapper(JdbcPersistentEntity<T> entity) {
this.entity = entity;
}
@Override
public T mapRow(ResultSet rs, int rowNum) throws SQLException {
try {
T t = createInstance(rs);
T t = createInstance();
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<JdbcPersistentProperty>() {
@Override
public <T> T getParameterValue(PreferredConstructor.Parameter<T, JdbcPersistentProperty> 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);
}

View File

@@ -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<T, ID extends Serializable> implements CrudRep
private final JdbcPersistentEntity<T> entity;
private final NamedParameterJdbcOperations template;
private final SqlGenerator sql;
private final String findOneSql;
private final String insertSql;
private final EntityRowMapper<T> entityRowMapper;
public SimpleJdbcRepository(JdbcPersistentEntity<T> entity, DataSource dataSource) {
this.entity = entity;
this.template = new NamedParameterJdbcTemplate(dataSource);
findOneSql = createFindOneSelectSql();
insertSql = createInsertSql();
entityRowMapper = new EntityRowMapper<T>(entity);
sql = new SqlGenerator(entity);
}
@Override
public <S extends T> S save(S entity) {
template.update(insertSql, getPropertyMap(entity));
template.update(sql.getInsert(), getPropertyMap(entity));
return entity;
}
@Override
public <S extends T> Iterable<S> save(Iterable<S> entities) {
return null;
Map<String, ?>[] 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<T>(entity)
entityRowMapper
);
}
@Override
public boolean exists(ID id) {
return false;
return template.queryForObject(
sql.getExists(),
new MapSqlParameterSource("id", id),
Boolean.class
);
}
@Override
public Iterable<T> findAll() {
return null;
return template.query(sql.getFindAll(), entityRowMapper);
}
@Override
public Iterable<T> findAll(Iterable<ID> 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 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<String> 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 <S extends T> Map<String, Object> getPropertyMap(final S entity) {
private <S extends T> Map<String, Object> getPropertyMap(final S instance) {
Map<String, Object> parameters = new HashMap<>();
this.entity.doWithProperties(new PropertyHandler() {
this.entity.doWithProperties(new PropertyHandler<JdbcPersistentProperty>() {
@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));
}
});

View File

@@ -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;
<T> SqlGenerator(JdbcPersistentEntity<T> 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 <T> String createCountSql(JdbcPersistentEntity<T> entity) {
return String.format("select count(*) from %s", entity.getTableName(), entity.getIdColumn());
}
private String createInsertSql(JdbcPersistentEntity<?> entity) {
List<String> 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());
}
}