DATAJDBC-104 - Introducing conversions for storing properties.

All properties get passed through a conversion step to convert them to types that can be passed to JDBC drivers.

Conversions happen in the newly introduced JdbcEnitityTemplate. It contains most of the code formerly found in the SimpleJdbcRepository.
This commit is contained in:
Jens Schauder
2017-06-07 14:49:44 +02:00
committed by Greg Turnquist
parent 323a56c156
commit dda4223341
13 changed files with 678 additions and 191 deletions

View File

@@ -16,8 +16,11 @@
package org.springframework.data.jdbc.mapping.context;
import org.springframework.data.jdbc.mapping.model.BasicJdbcPersistentProperty;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityImpl;
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.context.AbstractMappingContext;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.Property;
@@ -30,14 +33,14 @@ import org.springframework.data.util.TypeInformation;
* @author Jens Schauder
* @since 2.0
*/
public class JdbcMappingContext extends AbstractMappingContext<JdbcPersistentEntityImpl<?>, JdbcPersistentProperty> {
public class JdbcMappingContext extends AbstractMappingContext<JdbcPersistentEntity<?>, JdbcPersistentProperty> {
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation)
*/
@Override
protected <T> JdbcPersistentEntityImpl<?> createPersistentEntity(TypeInformation<T> typeInformation) {
protected <T> JdbcPersistentEntity<T> createPersistentEntity(TypeInformation<T> typeInformation) {
return new JdbcPersistentEntityImpl<>(typeInformation);
}
@@ -46,8 +49,13 @@ public class JdbcMappingContext extends AbstractMappingContext<JdbcPersistentEnt
* @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentProperty(org.springframework.data.mapping.model.Property, org.springframework.data.mapping.model.MutablePersistentEntity, org.springframework.data.mapping.model.SimpleTypeHolder)
*/
@Override
protected JdbcPersistentProperty createPersistentProperty(Property property, JdbcPersistentEntityImpl<?> owner,
protected JdbcPersistentProperty createPersistentProperty(Property property, JdbcPersistentEntity<?> owner,
SimpleTypeHolder simpleTypeHolder) {
return new BasicJdbcPersistentProperty(property, owner, simpleTypeHolder);
}
@SuppressWarnings("unchecked")
public <T> JdbcPersistentEntityInformation<T, ?> getRequiredPersistentEntityInformation(Class<T> type) {
return new BasicJdbcPersistentEntityInformation<>((JdbcPersistentEntity<T>) getRequiredPersistentEntity(type));
}
}

View File

@@ -15,11 +15,20 @@
*/
package org.springframework.data.jdbc.mapping.model;
import java.time.ZonedDateTime;
import java.time.temporal.Temporal;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
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.Property;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.util.ClassUtils;
/**
* Meta data about a property to be used by repository implementations.
@@ -30,6 +39,14 @@ import org.springframework.data.mapping.model.SimpleTypeHolder;
public class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProperty<JdbcPersistentProperty>
implements JdbcPersistentProperty {
private static final Map<Class<?>, Class<?>> javaToDbType = new LinkedHashMap<>();
static {
javaToDbType.put(Enum.class, String.class);
javaToDbType.put(ZonedDateTime.class, String.class);
javaToDbType.put(Temporal.class, Date.class);
}
/**
* Creates a new {@link AnnotationBasedPersistentProperty}.
*
@@ -58,4 +75,21 @@ public class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProper
public String getColumnName() {
return getName();
}
/**
* The type to be used to store this property in the database.
*
* @return a {@link Class} that is suitable for usage with JDBC drivers
*/
@SuppressWarnings("unchecked")
@Override
public Class getColumnType() {
Class type = getType();
return javaToDbType.entrySet().stream() //
.filter(e -> e.getKey().isAssignableFrom(type)) //
.map(e -> (Class)e.getValue()) //
.findFirst() //
.orElseGet(() -> ClassUtils.resolvePrimitiveIfNecessary(type));
}
}

View File

@@ -16,18 +16,19 @@
package org.springframework.data.jdbc.mapping.model;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.model.MutablePersistentEntity;
/**
* @author Jens Schauder
* @author Oliver Gierke
* @since 2.0
*/
public interface JdbcPersistentEntity<T> extends PersistentEntity<T, JdbcPersistentProperty> {
public interface JdbcPersistentEntity<T> extends MutablePersistentEntity<T, JdbcPersistentProperty> {
/**
* Returns the name of the table backing the given entity.
*
* @return
* @return the table name.
*/
String getTableName();

View File

@@ -27,9 +27,16 @@ import org.springframework.data.mapping.PersistentProperty;
public interface JdbcPersistentProperty extends PersistentProperty<JdbcPersistentProperty> {
/**
* Returns the name of the column backing that property.
* Returns the name of the column backing this property.
*
* @return
* @return the name of the column backing this property.
*/
String getColumnName();
/**
* The type to be used to store this property in the database.
*
* @return a {@link Class} that is suitable for usage with JDBC drivers
*/
Class<?> getColumnType();
}

View File

@@ -48,7 +48,7 @@ class EntityRowMapper<T> implements RowMapper<T> {
private final JdbcPersistentEntity<T> entity;
private final EntityInstantiator instantiator = new ClassGeneratingEntityInstantiator();
private final ConversionService conversions = new DefaultConversionService();
private final ConversionService conversions;
/*
* (non-Javadoc)

View File

@@ -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.repository;
/**
* Specifies a operations one can perform on a database, based on an <em>Domain Type</em>.
*
* @author Jens Schauder
*/
public interface JdbcEntityOperations {
<T> void insert(T instance, Class<T> domainType);
<T> void update(T instance, Class<T> domainType);
<T> void deleteById(Object id, Class<T> domainType);
<T> void delete(T entity, Class<T> domainType);
long count(Class<?> domainType);
<T> T findById(Object id, Class<T> domainType);
<T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType);
<T> Iterable<T> findAll(Class<T> domainType);
<T> boolean existsById(Object id, Class<T> domainType);
<T> void deleteAll(Iterable<? extends T> entities, Class<T> domainType);
void deleteAll(Class<?> domainType);
}

View File

@@ -0,0 +1,292 @@
/*
* 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 lombok.RequiredArgsConstructor;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
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.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.data.convert.Jsr310Converters;
import org.springframework.data.jdbc.mapping.context.JdbcMappingContext;
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;
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.core.EntityInformation;
import org.springframework.data.util.Streamable;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
/**
* @author Jens Schauder
*/
@RequiredArgsConstructor
public class JdbcEntityTemplate implements JdbcEntityOperations {
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 ApplicationEventPublisher publisher;
private final NamedParameterJdbcOperations operations;
private final JdbcMappingContext context;
private final ConversionService conversions = getDefaultConversionService();
private static GenericConversionService getDefaultConversionService() {
DefaultConversionService conversionService = new DefaultConversionService();
Jsr310Converters.getConvertersToRegister().forEach(conversionService::addConverter);
return conversionService;
}
@Override
public <S> void insert(S instance, Class<S> domainType) {
publisher.publishEvent(new BeforeInsert(instance));
KeyHolder holder = new GeneratedKeyHolder();
JdbcPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType);
JdbcPersistentEntityInformation<S, ?> entityInformation = context
.getRequiredPersistentEntityInformation(domainType);
Map<String, Object> propertyMap = getPropertyMap(instance, persistentEntity);
Object idValue = getIdValueOrNull(instance, persistentEntity);
JdbcPersistentProperty idProperty = persistentEntity.getRequiredIdProperty();
propertyMap.put(idProperty.getColumnName(), convert(idValue, idProperty.getColumnType()));
operations.update(sql(domainType).getInsert(idValue == null), new MapSqlParameterSource(propertyMap), holder);
setIdFromJdbc(instance, holder, persistentEntity);
if (entityInformation.isNew(instance)) {
throw new IllegalStateException(String.format(ENTITY_NEW_AFTER_INSERT, persistentEntity));
}
publisher.publishEvent(new AfterInsert(Identifier.of(entityInformation.getRequiredId(instance)), instance));
}
@Override
public <S> void update(S instance, Class<S> domainType) {
JdbcPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType);
JdbcPersistentEntityInformation<S, ?> entityInformation = context
.getRequiredPersistentEntityInformation(domainType);
Specified specifiedId = Identifier.of(entityInformation.getRequiredId(instance));
publisher.publishEvent(new BeforeUpdate(specifiedId, instance));
operations.update(sql(domainType).getUpdate(), getPropertyMap(instance, persistentEntity));
publisher.publishEvent(new AfterUpdate(specifiedId, instance));
}
@Override
public <S> void delete(S entity, Class<S> domainType) {
JdbcPersistentEntityInformation<S, ?> entityInformation = context
.getRequiredPersistentEntityInformation(domainType);
delete(Identifier.of(entityInformation.getRequiredId(entity)), Optional.of(entity), domainType);
}
@Override
public <S> void deleteById(Object id, Class<S> domainType) {
delete(Identifier.of(id), Optional.empty(), domainType);
}
@Override
public long count(Class<?> domainType) {
return operations.getJdbcOperations().queryForObject(sql(domainType).getCount(), Long.class);
}
@Override
public <T> T findById(Object id, Class<T> domainType) {
String findOneSql = sql(domainType).getFindOne();
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
return operations.queryForObject(findOneSql, parameter, getEntityRowMapper(domainType));
}
@Override
public <T> boolean existsById(Object id, Class<T> domainType) {
String existsSql = sql(domainType).getExists();
MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
return operations.queryForObject(existsSql, parameter, Boolean.class);
}
@Override
public <T> Iterable<T> findAll(Class<T> domainType) {
return operations.query(sql(domainType).getFindAll(), getEntityRowMapper(domainType));
}
@Override
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
String findAllInListSql = sql(domainType).getFindAllInList();
Class<?> targetType = getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType();
MapSqlParameterSource parameter = new MapSqlParameterSource("ids",
StreamSupport.stream(ids.spliterator(), false).map(id -> convert(id, targetType)).collect(Collectors.toList()));
return operations.query(findAllInListSql, parameter, getEntityRowMapper(domainType));
}
@Override
public <T> void deleteAll(Iterable<? extends T> entities, Class<T> domainType) {
JdbcPersistentEntityInformation<T, ?> entityInformation = context
.getRequiredPersistentEntityInformation(domainType);
Class<?> targetType = context.getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType();
List<?> idList = Streamable.of(entities).stream() //
.map(entityInformation::getRequiredId) //
.map(id -> convert(id, targetType))
.collect(Collectors.toList());
MapSqlParameterSource sqlParameterSource = new MapSqlParameterSource("ids", idList);
operations.update(sql(domainType).getDeleteByList(), sqlParameterSource);
}
@Override
public void deleteAll(Class<?> domainType) {
operations.getJdbcOperations().update(sql(domainType).getDeleteAll());
}
private void delete(Specified specifiedId, Optional<Object> optionalEntity, Class<?> domainType) {
publisher.publishEvent(new BeforeDelete(specifiedId, optionalEntity));
String deleteByIdSql = sql(domainType).getDeleteById();
MapSqlParameterSource parameter = createIdParameterSource(specifiedId.getValue(), domainType);
operations.update(deleteByIdSql, parameter);
publisher.publishEvent(new AfterDelete(specifiedId, optionalEntity));
}
private <T> MapSqlParameterSource createIdParameterSource(Object id, Class<T> domainType) {
return new MapSqlParameterSource("id",
convert(id, getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType()));
}
private <S> Map<String, Object> getPropertyMap(final S instance, JdbcPersistentEntity<S> persistentEntity) {
Map<String, Object> parameters = new HashMap<>();
persistentEntity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) property -> {
Optional<Object> value = persistentEntity.getPropertyAccessor(instance).getProperty(property);
Object convertedValue = convert(value.orElse(null), property.getColumnType());
parameters.put(property.getColumnName(), convertedValue);
});
return parameters;
}
private <S, ID> ID getIdValueOrNull(S instance, JdbcPersistentEntity<S> persistentEntity) {
EntityInformation<S, ID> entityInformation = new BasicJdbcPersistentEntityInformation<>(persistentEntity);
Optional<ID> idValue = entityInformation.getId(instance);
return isIdPropertySimpleTypeAndValueZero(idValue, persistentEntity) ? null
: idValue.orElseThrow(() -> new IllegalStateException("idValue must have a value at this point."));
}
private <S> void setIdFromJdbc(S instance, KeyHolder holder, JdbcPersistentEntity<S> persistentEntity) {
JdbcPersistentEntityInformation<S, ?> entityInformation = new BasicJdbcPersistentEntityInformation<>(
persistentEntity);
try {
getIdFromHolder(holder, persistentEntity).ifPresent(it -> {
Class<?> targetType = persistentEntity.getRequiredIdProperty().getType();
Object converted = convert(it, targetType);
entityInformation.setId(instance, Optional.of(converted));
});
} catch (NonTransientDataAccessException e) {
throw new UnableToSetId("Unable to set id of " + instance, e);
}
}
private <S> Optional<Object> getIdFromHolder(KeyHolder holder, JdbcPersistentEntity<S> persistentEntity) {
try {
// MySQL just returns one value with a special name
return Optional.ofNullable(holder.getKey());
} catch (InvalidDataAccessApiUsageException e) {
// Postgres returns a value for each column
return Optional.ofNullable(holder.getKeys().get(persistentEntity.getIdColumn()));
}
}
private <V> V convert(Object from, Class<V> to) {
return conversions.convert(from, to);
}
private <S, ID> boolean isIdPropertySimpleTypeAndValueZero(Optional<ID> idValue,
JdbcPersistentEntity<S> persistentEntity) {
Optional<JdbcPersistentProperty> idProperty = persistentEntity.getIdProperty();
return !idValue.isPresent() //
|| !idProperty.isPresent() //
|| (idProperty.get().getType() == int.class && idValue.get().equals(0)) //
|| (idProperty.get().getType() == long.class && idValue.get().equals(0L));
}
@SuppressWarnings("unchecked")
private <S> JdbcPersistentEntity<S> getRequiredPersistentEntity(Class<S> domainType) {
return (JdbcPersistentEntity<S>) context.getRequiredPersistentEntity(domainType);
}
private SqlGenerator sql(Class<?> domainType) {
return new SqlGenerator(context.getRequiredPersistentEntity(domainType));
}
private <T> EntityRowMapper<T> getEntityRowMapper(Class<T> domainType) {
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), conversions);
}
}

View File

@@ -17,78 +17,31 @@ 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 org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.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;
import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityImpl;
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;
import org.springframework.data.util.Streamable;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.util.Assert;
/**
* @author Jens Schauder
* @since 2.0
*/
public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRepository<T, ID> {
public class SimpleJdbcRepository<T, ID> implements CrudRepository<T, ID> {
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<T> persistentEntity;
private final JdbcPersistentEntityInformation<T, ID> entityInformation;
private final NamedParameterJdbcOperations operations;
private final SqlGenerator sql;
private final EntityRowMapper<T> entityRowMapper;
private final ApplicationEventPublisher publisher;
private final ConversionService conversions = new DefaultConversionService();
private final JdbcEntityOperations entityOperations;
/**
* Creates a new {@link SimpleJdbcRepository} for the given {@link JdbcPersistentEntityImpl}
*
* @param persistentEntity
* @param jdbcOperations
* @param publisher
*/
public SimpleJdbcRepository(JdbcPersistentEntity<T> persistentEntity, NamedParameterJdbcOperations jdbcOperations,
ApplicationEventPublisher publisher) {
public SimpleJdbcRepository(JdbcEntityTemplate entityOperations,
JdbcPersistentEntityInformation<T, ID> entityInformation) {
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.persistentEntity = persistentEntity;
this.entityInformation = new BasicJdbcPersistentEntityInformation<>(persistentEntity);
this.operations = jdbcOperations;
this.publisher = publisher;
this.entityRowMapper = new EntityRowMapper<>(persistentEntity);
this.sql = new SqlGenerator(persistentEntity);
this.entityOperations = entityOperations;
this.entityInformation = entityInformation;
}
/*
@@ -99,9 +52,9 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
public <S extends T> S save(S instance) {
if (entityInformation.isNew(instance)) {
doInsert(instance);
entityOperations.insert(instance, entityInformation.getJavaType());
} else {
doUpdate(instance);
entityOperations.update(instance, entityInformation.getJavaType());
}
return instance;
@@ -126,8 +79,7 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
@Override
public Optional<T> findById(ID id) {
return Optional
.ofNullable(operations.queryForObject(sql.getFindOne(), new MapSqlParameterSource("id", id), entityRowMapper));
return Optional.ofNullable(entityOperations.findById(id, entityInformation.getJavaType()));
}
/*
@@ -136,7 +88,7 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
*/
@Override
public boolean existsById(ID id) {
return operations.queryForObject(sql.getExists(), new MapSqlParameterSource("id", id), Boolean.class);
return entityOperations.existsById(id, entityInformation.getJavaType());
}
/*
@@ -145,7 +97,7 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
*/
@Override
public Iterable<T> findAll() {
return operations.query(sql.getFindAll(), entityRowMapper);
return entityOperations.findAll(entityInformation.getJavaType());
}
/*
@@ -154,7 +106,7 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
*/
@Override
public Iterable<T> findAllById(Iterable<ID> ids) {
return operations.query(sql.getFindAllInList(), new MapSqlParameterSource("ids", ids), entityRowMapper);
return entityOperations.findAllById(ids, entityInformation.getJavaType());
}
/*
@@ -163,7 +115,7 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
*/
@Override
public long count() {
return operations.getJdbcOperations().queryForObject(sql.getCount(), Long.class);
return entityOperations.count(entityInformation.getJavaType());
}
/*
@@ -172,7 +124,7 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
*/
@Override
public void deleteById(ID id) {
doDelete(Identifier.of(id), Optional.empty());
entityOperations.deleteById(id, entityInformation.getJavaType());
}
/*
@@ -181,7 +133,7 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
*/
@Override
public void delete(T instance) {
doDelete(Identifier.of(entityInformation.getRequiredId(instance)), Optional.of(instance));
entityOperations.delete(instance, entityInformation.getJavaType());
}
/*
@@ -190,111 +142,11 @@ public class SimpleJdbcRepository<T, ID extends Serializable> implements CrudRep
*/
@Override
public void deleteAll(Iterable<? extends T> entities) {
List<ID> idList = Streamable.of(entities).stream() //
.map(e -> entityInformation.getRequiredId(e)) //
.collect(Collectors.toList());
MapSqlParameterSource sqlParameterSource = new MapSqlParameterSource("ids", idList);
operations.update(sql.getDeleteByList(), sqlParameterSource);
entityOperations.deleteAll(entities, entityInformation.getJavaType());
}
@Override
public void deleteAll() {
operations.getJdbcOperations().update(sql.getDeleteAll());
}
private <S extends T> Map<String, Object> getPropertyMap(final S instance) {
Map<String, Object> parameters = new HashMap<>();
this.persistentEntity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) property -> {
Object value = persistentEntity.getPropertyAccessor(instance).getProperty(property);
parameters.put(property.getColumnName(), value);
});
return parameters;
}
private <S extends T> void doInsert(S instance) {
publisher.publishEvent(new BeforeInsert(instance));
KeyHolder holder = new GeneratedKeyHolder();
Map<String, Object> propertyMap = getPropertyMap(instance);
propertyMap.put(persistentEntity.getRequiredIdProperty().getColumnName(), getIdValueOrNull(instance));
operations.update(sql.getInsert(), new MapSqlParameterSource(propertyMap), holder);
setIdFromJdbc(instance, holder);
if (entityInformation.isNew(instance)) {
throw new IllegalStateException(String.format(ENTITY_NEW_AFTER_INSERT, persistentEntity));
}
publisher.publishEvent(new AfterInsert(Identifier.of(entityInformation.getRequiredId(instance)), instance));
}
private <S extends T> ID getIdValueOrNull(S instance) {
ID idValue = entityInformation.getId(instance);
return isIdPropertySimpleTypeAndValueZero(idValue) ? null : idValue;
}
private boolean isIdPropertySimpleTypeAndValueZero(ID idValue) {
JdbcPersistentProperty idProperty = persistentEntity.getIdProperty();
return idValue == null //
|| idProperty == null //
|| (idProperty.getType() == int.class && idValue.equals(0)) //
|| (idProperty.getType() == long.class && idValue.equals(0L));
}
private <S extends T> void setIdFromJdbc(S instance, KeyHolder holder) {
try {
getIdFromHolder(holder).ifPresent(it -> {
Class<?> targetType = persistentEntity.getRequiredIdProperty().getType();
Object converted = convert(it, targetType);
entityInformation.setId(instance, converted);
});
} catch (NonTransientDataAccessException e) {
throw new UnableToSetId("Unable to set id of " + instance, e);
}
}
private Optional<Object> getIdFromHolder(KeyHolder holder) {
try {
// MySQL just returns one value with a special name
return Optional.ofNullable(holder.getKey());
} catch (InvalidDataAccessApiUsageException e) {
// Postgres returns a value for each column
return Optional.ofNullable(holder.getKeys().get(persistentEntity.getIdColumn()));
}
}
private <V> V convert(Object idValueFromJdbc, Class<V> targetType) {
return conversions.convert(idValueFromJdbc, targetType);
}
private void doDelete(Specified specifiedId, Optional<Object> optionalEntity) {
publisher.publishEvent(new BeforeDelete(specifiedId, optionalEntity));
operations.update(sql.getDeleteById(), new MapSqlParameterSource("id", specifiedId.getValue()));
publisher.publishEvent(new AfterDelete(specifiedId, optionalEntity));
}
private <S extends T> void doUpdate(S instance) {
Specified specifiedId = Identifier.of(entityInformation.getRequiredId(instance));
publisher.publishEvent(new BeforeUpdate(specifiedId, instance));
operations.update(sql.getUpdate(), getPropertyMap(instance));
publisher.publishEvent(new AfterUpdate(specifiedId, instance));
entityOperations.deleteAll(entityInformation.getJavaType());
}
}

View File

@@ -24,7 +24,7 @@ import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
import org.springframework.data.mapping.PropertyHandler;
/**
* Generates SQL statements to be used by {@Link SimpleJdbcRepository}
* Generates SQL statements to be used by {@link SimpleJdbcRepository}
*
* @author Jens Schauder
* @since 2.0
@@ -38,8 +38,6 @@ class SqlGenerator {
private final String existsSql;
private final String countSql;
private final String insertSql;
private final String updateSql;
private final String deleteByIdSql;
@@ -63,8 +61,6 @@ class SqlGenerator {
existsSql = createExistsSql();
countSql = createCountSql();
insertSql = createInsertSql();
updateSql = createUpdateSql();
deleteByIdSql = createDeleteSql();
@@ -72,7 +68,7 @@ class SqlGenerator {
deleteByListSql = createDeleteByListSql();
}
private <T> void initPropertyNames() {
private void initPropertyNames() {
entity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) p -> {
propertyNames.add(p.getName());
@@ -98,8 +94,8 @@ class SqlGenerator {
return findOneSql;
}
String getInsert() {
return insertSql;
String getInsert(boolean excludeId) {
return createInsertSql(excludeId);
}
String getUpdate() {
@@ -138,20 +134,21 @@ class SqlGenerator {
return String.format("select count(*) from %s where %s = :id", entity.getTableName(), entity.getIdColumn());
}
private <T> String createCountSql() {
private String createCountSql() {
return String.format("select count(*) from %s", entity.getTableName());
}
private String createInsertSql() {
private String createInsertSql(boolean excludeId) {
String insertTemplate = "insert into %s (%s) values (%s)";
String tableColumns = String.join(", ", nonIdPropertyNames);
String parameterNames = nonIdPropertyNames.stream().collect(Collectors.joining(", :", ":", ""));
List<String> propertyNamesForInsert = excludeId ? nonIdPropertyNames : propertyNames;
String tableColumns = String.join(", ", propertyNamesForInsert);
String parameterNames = propertyNamesForInsert.stream().collect(Collectors.joining(", :", ":", ""));
return String.format(insertTemplate, entity.getTableName(), tableColumns, parameterNames);
}
private <T> String createUpdateSql() {
private String createUpdateSql() {
String updateTemplate = "update %s set %s where %s = :%s";

View File

@@ -20,7 +20,7 @@ import lombok.RequiredArgsConstructor;
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.mapping.model.JdbcPersistentEntityImpl;
import org.springframework.data.jdbc.repository.JdbcEntityTemplate;
import org.springframework.data.jdbc.repository.SimpleJdbcRepository;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.RepositoryInformation;
@@ -49,13 +49,18 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
return new BasicJdbcPersistentEntityInformation<T, ID>((JdbcPersistentEntity<T>) persistentEntity);
}
@SuppressWarnings("unchecked")
@Override
protected Object getTargetRepository(RepositoryInformation repositoryInformation) {
JdbcPersistentEntity<?> persistentEntity = context
.getRequiredPersistentEntity(repositoryInformation.getDomainType());
JdbcPersistentEntity<?> persistentEntity = context //
.getPersistentEntity(repositoryInformation.getDomainType()) //
.orElseThrow(() -> new IllegalArgumentException("%s does not represent a persistent entity")); //
JdbcPersistentEntityInformation persistentEntityInformation = context
.getRequiredPersistentEntityInformation(persistentEntity.getType());
JdbcEntityTemplate template = new JdbcEntityTemplate(publisher, jdbcOperations, context);
return new SimpleJdbcRepository<>(persistentEntity, jdbcOperations, publisher);
return new SimpleJdbcRepository<>(template, persistentEntityInformation);
}
@Override