DATAJDBC-112 - Allow aggregate roots to refer to other entities.
Aggregate roots that reference other entities get correctly stored, updated and loaded. In order to keep this a simple as possible, referenced entities get deleted and recreated on each update. While this is inefficient it gets the job done and makes sense if we assume referenced entities are owned by that aggregate root. References to other aggregate root could be implemented by using just the id. With the event mechanism one could even inject repositories into the entities, so they could provide getters to the actual referenced aggregate root. The process of storing an entity now works as follows: The request to store an entity is converted into a DbChange object containing a list of DbActions. Each DbAction represents a single change made to the data base, I.e. an insert, update or delete statement. This DbChange then gets interpreted by the interpreter to actually create sql statements and execute them. This should allow various customizations in the future and for users: - We could read the DbActions from the aggregate root itself, if it implements a given interface - A user can modify the way DbActions get created for a requested change. Either by replacing the Jdbc[Delete]EntityWriter that performs this conversion or by modifying the DbChange in an event handler. - By changing the interpreter actually performing the actions one could change completely the sql used for the actions. Since we currently only do one-to-one relationships selects are simply joins. In order to encapsulate the construction of select statements from entities the SelectBuilder got introduced. Also SqlGenerators and the generated sql is properly cached, if it does not depend on additional information. This commit also contains necessary changes due to DATACMNS-1101. Related issues: DATACMNS-1101 DATAJDBC-115
This commit is contained in:
committed by
Greg Turnquist
parent
dda4223341
commit
fec4f99257
@@ -13,31 +13,30 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository;
|
||||
package org.springframework.data.jdbc.core;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.convert.ClassGeneratingEntityInstantiator;
|
||||
import org.springframework.data.convert.EntityInstantiator;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PreferredConstructor.Parameter;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.mapping.model.ParameterValueProvider;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
/**
|
||||
* Maps a ResultSet to an entity of type {@code T}
|
||||
* Maps a ResultSet to an entity of type {@code T}, including entities referenced.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Oliver Gierke
|
||||
@@ -49,6 +48,7 @@ class EntityRowMapper<T> implements RowMapper<T> {
|
||||
private final JdbcPersistentEntity<T> entity;
|
||||
private final EntityInstantiator instantiator = new ClassGeneratingEntityInstantiator();
|
||||
private final ConversionService conversions;
|
||||
private final JdbcMappingContext context;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -58,36 +58,59 @@ class EntityRowMapper<T> implements RowMapper<T> {
|
||||
public T mapRow(ResultSet resultSet, int rowNumber) throws SQLException {
|
||||
|
||||
T result = createInstance(resultSet);
|
||||
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(result);
|
||||
ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor(accessor, conversions);
|
||||
|
||||
entity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) property -> {
|
||||
|
||||
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(result);
|
||||
ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor(accessor, conversions);
|
||||
|
||||
propertyAccessor.setProperty(property, readFrom(resultSet, property));
|
||||
});
|
||||
for (JdbcPersistentProperty property : entity) {
|
||||
propertyAccessor.setProperty(property, readFrom(resultSet, property, ""));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private T createInstance(ResultSet rs) {
|
||||
return instantiator.createInstance(entity, ResultSetParameterValueProvider.of(rs, conversions));
|
||||
return instantiator.createInstance(entity, ResultSetParameterValueProvider.of(rs, conversions, ""));
|
||||
}
|
||||
|
||||
private static Object readFrom(ResultSet resultSet, PersistentProperty<?> property) {
|
||||
private Object readFrom(ResultSet resultSet, JdbcPersistentProperty property, String prefix) {
|
||||
|
||||
try {
|
||||
return resultSet.getObject(property.getName());
|
||||
if (property.isEntity())
|
||||
return readEntityFrom(resultSet, property);
|
||||
return resultSet.getObject(prefix + property.getColumnName());
|
||||
} catch (SQLException o_O) {
|
||||
throw new MappingException(String.format("Could not read property %s from result set!", property), o_O);
|
||||
}
|
||||
}
|
||||
|
||||
private <S> S readEntityFrom(ResultSet rs, PersistentProperty<?> property) {
|
||||
|
||||
String prefix = property.getName() + "_";
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
JdbcPersistentEntity<S> entity = (JdbcPersistentEntity<S>) context.getRequiredPersistentEntity(property.getType());
|
||||
|
||||
if (readFrom(rs, entity.getRequiredIdProperty(), prefix) == null)
|
||||
return null;
|
||||
|
||||
S instance = instantiator.createInstance(entity, ResultSetParameterValueProvider.of(rs, conversions, prefix));
|
||||
|
||||
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(instance);
|
||||
ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor(accessor, conversions);
|
||||
|
||||
for (JdbcPersistentProperty p : entity) {
|
||||
propertyAccessor.setProperty(p, readFrom(rs, p, prefix));
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor(staticName = "of")
|
||||
private static class ResultSetParameterValueProvider implements ParameterValueProvider<JdbcPersistentProperty> {
|
||||
|
||||
private final ResultSet resultSet;
|
||||
private final ConversionService conversionService;
|
||||
@NonNull private final ResultSet resultSet;
|
||||
@NonNull private final ConversionService conversionService;
|
||||
@NonNull private final String prefix;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -97,13 +120,14 @@ class EntityRowMapper<T> implements RowMapper<T> {
|
||||
public <T> T getParameterValue(Parameter<T, JdbcPersistentProperty> parameter) {
|
||||
|
||||
String name = parameter.getName();
|
||||
if (name == null ) return null;
|
||||
if (name == null)
|
||||
return null;
|
||||
|
||||
try {
|
||||
return conversionService.convert(resultSet.getObject(name), parameter.getType().getType());
|
||||
} catch (SQLException o_O) {
|
||||
throw new MappingException(String.format("Couldn't read column %s from ResultSet.", name), o_O);
|
||||
}
|
||||
try {
|
||||
return conversionService.convert(resultSet.getObject(prefix + name), parameter.getType().getType());
|
||||
} catch (SQLException o_O) {
|
||||
throw new MappingException(String.format("Couldn't read column %s from ResultSet.", name), o_O);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository;
|
||||
package org.springframework.data.jdbc.core;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -24,7 +24,7 @@ import java.sql.SQLException;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.jdbc.mapping.event.AfterCreation;
|
||||
import org.springframework.data.jdbc.mapping.event.Identifier;
|
||||
import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityInformation;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
/**
|
||||
@@ -13,7 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository;
|
||||
package org.springframework.data.jdbc.core;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Specifies a operations one can perform on a database, based on an <em>Domain Type</em>.
|
||||
@@ -22,7 +24,7 @@ package org.springframework.data.jdbc.repository;
|
||||
*/
|
||||
public interface JdbcEntityOperations {
|
||||
|
||||
<T> void insert(T instance, Class<T> domainType);
|
||||
<T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameter);
|
||||
|
||||
<T> void update(T instance, Class<T> domainType);
|
||||
|
||||
@@ -40,7 +42,7 @@ public interface JdbcEntityOperations {
|
||||
|
||||
<T> boolean existsById(Object id, Class<T> domainType);
|
||||
|
||||
<T> void deleteAll(Iterable<? extends T> entities, Class<T> domainType);
|
||||
|
||||
void deleteAll(Class<?> domainType);
|
||||
|
||||
<T> void save(T instance, Class<T> domainType);
|
||||
}
|
||||
@@ -13,14 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository;
|
||||
package org.springframework.data.jdbc.core;
|
||||
|
||||
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;
|
||||
@@ -28,13 +23,16 @@ 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.core.conversion.DbChange;
|
||||
import org.springframework.data.jdbc.core.conversion.DbChange.Kind;
|
||||
import org.springframework.data.jdbc.core.conversion.Interpreter;
|
||||
import org.springframework.data.jdbc.core.conversion.JdbcEntityWriter;
|
||||
import org.springframework.data.jdbc.core.conversion.JdbcEntityDeleteWriter;
|
||||
import org.springframework.data.jdbc.mapping.event.AfterDelete;
|
||||
import org.springframework.data.jdbc.mapping.event.AfterInsert;
|
||||
import org.springframework.data.jdbc.mapping.event.AfterUpdate;
|
||||
@@ -43,22 +41,25 @@ 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.BasicJdbcPersistentEntityInformation;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityInformation;
|
||||
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.mapping.PropertyPath;
|
||||
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;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link JdbcEntityOperations} implementation, storing complete entities including references in a JDBC data store.
|
||||
*
|
||||
* @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"
|
||||
@@ -69,6 +70,24 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
|
||||
private final NamedParameterJdbcOperations operations;
|
||||
private final JdbcMappingContext context;
|
||||
private final ConversionService conversions = getDefaultConversionService();
|
||||
private final Interpreter interpreter;
|
||||
private final SqlGeneratorSource sqlGeneratorSource;
|
||||
|
||||
private final JdbcEntityWriter jdbcConverter;
|
||||
private final JdbcEntityDeleteWriter jdbcEntityDeleteConverter;
|
||||
|
||||
public JdbcEntityTemplate(ApplicationEventPublisher publisher, NamedParameterJdbcOperations operations,
|
||||
JdbcMappingContext context) {
|
||||
|
||||
this.publisher = publisher;
|
||||
this.operations = operations;
|
||||
this.context = context;
|
||||
|
||||
jdbcConverter = new JdbcEntityWriter(this.context);
|
||||
jdbcEntityDeleteConverter = new JdbcEntityDeleteWriter(this.context);
|
||||
sqlGeneratorSource = new SqlGeneratorSource(this.context);
|
||||
interpreter = new JdbcInterpreter(this.context, this);
|
||||
}
|
||||
|
||||
private static GenericConversionService getDefaultConversionService() {
|
||||
|
||||
@@ -79,13 +98,18 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> void insert(S instance, Class<S> domainType) {
|
||||
public <T> void save(T instance, Class<T> domainType) {
|
||||
createDbChange(instance).executeWith(interpreter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameter) {
|
||||
|
||||
publisher.publishEvent(new BeforeInsert(instance));
|
||||
|
||||
KeyHolder holder = new GeneratedKeyHolder();
|
||||
JdbcPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType);
|
||||
JdbcPersistentEntityInformation<S, ?> entityInformation = context
|
||||
JdbcPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
|
||||
JdbcPersistentEntityInformation<T, ?> entityInformation = context
|
||||
.getRequiredPersistentEntityInformation(domainType);
|
||||
|
||||
Map<String, Object> propertyMap = getPropertyMap(instance, persistentEntity);
|
||||
@@ -94,7 +118,10 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
|
||||
JdbcPersistentProperty idProperty = persistentEntity.getRequiredIdProperty();
|
||||
propertyMap.put(idProperty.getColumnName(), convert(idValue, idProperty.getColumnType()));
|
||||
|
||||
operations.update(sql(domainType).getInsert(idValue == null), new MapSqlParameterSource(propertyMap), holder);
|
||||
propertyMap.putAll(additionalParameter);
|
||||
|
||||
operations.update(sql(domainType).getInsert(idValue == null, additionalParameter.keySet()),
|
||||
new MapSqlParameterSource(propertyMap), holder);
|
||||
|
||||
setIdFromJdbc(instance, holder, persistentEntity);
|
||||
|
||||
@@ -119,19 +146,6 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
|
||||
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);
|
||||
@@ -170,27 +184,43 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void deleteAll(Iterable<? extends T> entities, Class<T> domainType) {
|
||||
public <S> void delete(S entity, Class<S> domainType) {
|
||||
|
||||
JdbcPersistentEntityInformation<T, ?> entityInformation = context
|
||||
JdbcPersistentEntityInformation<S, ?> entityInformation = context
|
||||
.getRequiredPersistentEntityInformation(domainType);
|
||||
deleteTree(entityInformation.getRequiredId(entity), entity, 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 <S> void deleteById(Object id, Class<S> domainType) {
|
||||
deleteTree(id, null, domainType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAll(Class<?> domainType) {
|
||||
operations.getJdbcOperations().update(sql(domainType).getDeleteAll());
|
||||
|
||||
DbChange change = createDeletingDbChange(domainType);
|
||||
change.executeWith(interpreter);
|
||||
}
|
||||
|
||||
private void delete(Specified specifiedId, Optional<Object> optionalEntity, Class<?> domainType) {
|
||||
void doDelete(Object rootId, PropertyPath propertyPath) {
|
||||
|
||||
JdbcPersistentEntity<?> entityToDelete = context.getRequiredPersistentEntity(propertyPath.getTypeInformation());
|
||||
|
||||
JdbcPersistentEntity<?> rootEntity = context.getRequiredPersistentEntity(propertyPath.getOwningType());
|
||||
|
||||
JdbcPersistentProperty referencingProperty = rootEntity.getRequiredPersistentProperty(propertyPath.getSegment());
|
||||
Assert.notNull(referencingProperty, "No property found matching the PropertyPath " + propertyPath);
|
||||
|
||||
String format = sql(rootEntity.getType()).createDeleteByPath(propertyPath);
|
||||
|
||||
HashMap<String, Object> parameters = new HashMap<>();
|
||||
parameters.put("rootId", rootId);
|
||||
operations.update(format, parameters);
|
||||
|
||||
}
|
||||
|
||||
void doDelete(Specified specifiedId, Optional<Object> optionalEntity, Class<?> domainType) {
|
||||
|
||||
publisher.publishEvent(new BeforeDelete(specifiedId, optionalEntity));
|
||||
|
||||
@@ -203,6 +233,34 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
|
||||
publisher.publishEvent(new AfterDelete(specifiedId, optionalEntity));
|
||||
}
|
||||
|
||||
private void deleteTree(Object id, Object entity, Class<?> domainType) {
|
||||
|
||||
DbChange change = createDeletingDbChange(id, entity, domainType);
|
||||
|
||||
change.executeWith(interpreter);
|
||||
}
|
||||
|
||||
private <T> DbChange createDbChange(T instance) {
|
||||
|
||||
DbChange dbChange = new DbChange(Kind.SAVE, instance.getClass(), instance);
|
||||
jdbcConverter.write(instance, dbChange);
|
||||
return dbChange;
|
||||
}
|
||||
|
||||
private DbChange createDeletingDbChange(Object id, Object entity, Class<?> domainType) {
|
||||
|
||||
DbChange dbChange = new DbChange(Kind.DELETE, domainType, entity);
|
||||
jdbcEntityDeleteConverter.write(id, dbChange);
|
||||
return dbChange;
|
||||
}
|
||||
|
||||
private DbChange createDeletingDbChange(Class<?> domainType) {
|
||||
|
||||
DbChange dbChange = new DbChange(Kind.DELETE, domainType, null);
|
||||
jdbcEntityDeleteConverter.write(null, dbChange);
|
||||
return dbChange;
|
||||
}
|
||||
|
||||
private <T> MapSqlParameterSource createIdParameterSource(Object id, Class<T> domainType) {
|
||||
return new MapSqlParameterSource("id",
|
||||
convert(id, getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType()));
|
||||
@@ -213,11 +271,12 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
|
||||
persistentEntity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) property -> {
|
||||
if (!property.isEntity()) {
|
||||
Object value = persistentEntity.getPropertyAccessor(instance).getProperty(property);
|
||||
|
||||
Optional<Object> value = persistentEntity.getPropertyAccessor(instance).getProperty(property);
|
||||
|
||||
Object convertedValue = convert(value.orElse(null), property.getColumnType());
|
||||
parameters.put(property.getColumnName(), convertedValue);
|
||||
Object convertedValue = convert(value, property.getColumnType());
|
||||
parameters.put(property.getColumnName(), convertedValue);
|
||||
}
|
||||
});
|
||||
|
||||
return parameters;
|
||||
@@ -227,10 +286,9 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
|
||||
|
||||
EntityInformation<S, ID> entityInformation = new BasicJdbcPersistentEntityInformation<>(persistentEntity);
|
||||
|
||||
Optional<ID> idValue = entityInformation.getId(instance);
|
||||
ID idValue = entityInformation.getId(instance);
|
||||
|
||||
return isIdPropertySimpleTypeAndValueZero(idValue, persistentEntity) ? null
|
||||
: idValue.orElseThrow(() -> new IllegalStateException("idValue must have a value at this point."));
|
||||
return isIdPropertySimpleTypeAndValueZero(idValue, persistentEntity) ? null : idValue;
|
||||
}
|
||||
|
||||
private <S> void setIdFromJdbc(S instance, KeyHolder holder, JdbcPersistentEntity<S> persistentEntity) {
|
||||
@@ -244,7 +302,7 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
|
||||
|
||||
Class<?> targetType = persistentEntity.getRequiredIdProperty().getType();
|
||||
Object converted = convert(it, targetType);
|
||||
entityInformation.setId(instance, Optional.of(converted));
|
||||
entityInformation.setId(instance, converted);
|
||||
});
|
||||
|
||||
} catch (NonTransientDataAccessException e) {
|
||||
@@ -264,17 +322,25 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
|
||||
}
|
||||
|
||||
private <V> V convert(Object from, Class<V> to) {
|
||||
return conversions.convert(from, to);
|
||||
|
||||
if (from == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JdbcPersistentEntity<?> persistentEntity = context.getPersistentEntity(from.getClass());
|
||||
|
||||
Object id = persistentEntity == null ? null : persistentEntity.getIdentifierAccessor(from).getIdentifier();
|
||||
|
||||
return conversions.convert(id == null ? from : id, to);
|
||||
}
|
||||
|
||||
private <S, ID> boolean isIdPropertySimpleTypeAndValueZero(Optional<ID> idValue,
|
||||
JdbcPersistentEntity<S> persistentEntity) {
|
||||
private <S, ID> boolean isIdPropertySimpleTypeAndValueZero(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));
|
||||
JdbcPersistentProperty idProperty = persistentEntity.getIdProperty();
|
||||
return idValue == null //
|
||||
|| idProperty == null //
|
||||
|| (idProperty.getType() == int.class && idValue.equals(0)) //
|
||||
|| (idProperty.getType() == long.class && idValue.equals(0L));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -283,10 +349,17 @@ public class JdbcEntityTemplate implements JdbcEntityOperations {
|
||||
}
|
||||
|
||||
private SqlGenerator sql(Class<?> domainType) {
|
||||
return new SqlGenerator(context.getRequiredPersistentEntity(domainType));
|
||||
return sqlGeneratorSource.getSqlGenerator(domainType);
|
||||
}
|
||||
|
||||
private <T> EntityRowMapper<T> getEntityRowMapper(Class<T> domainType) {
|
||||
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), conversions);
|
||||
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), conversions, context);
|
||||
}
|
||||
|
||||
<T> void doDeleteAll(Class<T> domainType, PropertyPath propertyPath) {
|
||||
|
||||
operations.getJdbcOperations()
|
||||
.update(sql(propertyPath == null ? domainType : propertyPath.getOwningType().getType())
|
||||
.createDeleteAllSql(propertyPath));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.jdbc.core.conversion.DbAction;
|
||||
import org.springframework.data.jdbc.core.conversion.DbAction.Delete;
|
||||
import org.springframework.data.jdbc.core.conversion.DbAction.DeleteAll;
|
||||
import org.springframework.data.jdbc.core.conversion.DbAction.Insert;
|
||||
import org.springframework.data.jdbc.core.conversion.DbAction.Update;
|
||||
import org.springframework.data.jdbc.core.conversion.Interpreter;
|
||||
import org.springframework.data.jdbc.mapping.event.Identifier;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
|
||||
|
||||
/**
|
||||
* {@link Interpreter} for {@link DbAction}s using a {@link JdbcEntityTemplate} for performing actual database
|
||||
* interactions.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
class JdbcInterpreter implements Interpreter {
|
||||
|
||||
private final JdbcMappingContext context;
|
||||
private final JdbcEntityTemplate template;
|
||||
|
||||
JdbcInterpreter(JdbcMappingContext context, JdbcEntityTemplate template) {
|
||||
this.context = context;
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void interpret(Insert<T> insert) {
|
||||
|
||||
Map<String, Object> additionalColumnValues = new HashMap<>();
|
||||
DbAction dependingOn = insert.getDependingOn();
|
||||
|
||||
if (dependingOn != null) {
|
||||
|
||||
JdbcPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(dependingOn.getEntityType());
|
||||
String columnName = persistentEntity.getTableName();
|
||||
Object entity = dependingOn.getEntity();
|
||||
Object identifier = persistentEntity.getIdentifierAccessor(entity).getIdentifier();
|
||||
|
||||
additionalColumnValues.put(columnName, identifier);
|
||||
}
|
||||
|
||||
template.insert(insert.getEntity(), insert.getEntityType(), additionalColumnValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void interpret(Update<T> update) {
|
||||
template.update(update.getEntity(), update.getEntityType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void interpret(Delete<T> delete) {
|
||||
|
||||
if (delete.getPropertyPath() == null) {
|
||||
template.doDelete(Identifier.of(delete.getRootId()), Optional.ofNullable(delete.getEntity()),
|
||||
delete.getEntityType());
|
||||
} else {
|
||||
template.doDelete(delete.getRootId(), delete.getPropertyPath());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void interpret(DeleteAll<T> delete) {
|
||||
template.doDeleteAll(delete.getEntityType(), delete.getPropertyPath());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import lombok.Builder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Builder for creating Select-statements. Not intended for general purpose, but only for the needs of the
|
||||
* {@link JdbcEntityTemplate}.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
class SelectBuilder {
|
||||
|
||||
private final List<Column> columns = new ArrayList<>();
|
||||
private final String tableName;
|
||||
private final List<Join> joins = new ArrayList<>();
|
||||
private final List<WhereCondition> conditions = new ArrayList<>();
|
||||
|
||||
SelectBuilder(String tableName) {
|
||||
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
SelectBuilder column(Function<Column.ColumnBuilder, Column.ColumnBuilder> columnSpec) {
|
||||
|
||||
columns.add(columnSpec.apply(Column.builder()).build());
|
||||
return this;
|
||||
}
|
||||
|
||||
SelectBuilder where(Function<WhereConditionBuilder, WhereConditionBuilder> whereSpec) {
|
||||
conditions.add(whereSpec.apply(new WhereConditionBuilder(this)).build());
|
||||
return this;
|
||||
}
|
||||
|
||||
SelectBuilder join(Function<Join.JoinBuilder, Join.JoinBuilder> joinSpec) {
|
||||
joins.add(joinSpec.apply(Join.builder()).build());
|
||||
return this;
|
||||
}
|
||||
|
||||
String build() {
|
||||
|
||||
return selectFrom() + joinClause() + whereClause();
|
||||
}
|
||||
|
||||
private String whereClause() {
|
||||
if (conditions.isEmpty())
|
||||
return "";
|
||||
return conditions.stream().map(wc -> wc.toSql()).collect(Collectors.joining("AND", " WHERE ", ""));
|
||||
}
|
||||
|
||||
private String joinClause() {
|
||||
return joins.stream().map(j -> joinTable(j) + joinConditions(j)).collect(Collectors.joining(" "));
|
||||
}
|
||||
|
||||
private String joinTable(Join j) {
|
||||
return String.format("%s JOIN %s AS %s", j.outerJoinModifier(), j.table, j.as);
|
||||
}
|
||||
|
||||
private String joinConditions(Join j) {
|
||||
return j.conditions.stream().map(w -> String.format("%s %s %s", w.fromExpression, w.operation, w.toExpression))
|
||||
.collect(Collectors.joining(" AND ", " ON ", ""));
|
||||
}
|
||||
|
||||
private String selectFrom() {
|
||||
return columns.stream() //
|
||||
.map(c -> c.columnDefinition()) //
|
||||
.collect(Collectors.joining(", ", "SELECT ", " FROM " + tableName));
|
||||
}
|
||||
|
||||
static class WhereConditionBuilder {
|
||||
|
||||
private String fromTable;
|
||||
private String fromColumn;
|
||||
private final SelectBuilder selectBuilder;
|
||||
|
||||
private String operation = "=";
|
||||
private String expression;
|
||||
|
||||
WhereConditionBuilder(SelectBuilder selectBuilder) {
|
||||
this.selectBuilder = selectBuilder;
|
||||
}
|
||||
|
||||
WhereConditionBuilder eq() {
|
||||
|
||||
operation = "=";
|
||||
return this;
|
||||
}
|
||||
|
||||
public WhereConditionBuilder in() {
|
||||
|
||||
operation = "in";
|
||||
return this;
|
||||
}
|
||||
|
||||
WhereConditionBuilder tableAlias(String fromTable) {
|
||||
|
||||
this.fromTable = fromTable;
|
||||
return this;
|
||||
}
|
||||
|
||||
WhereConditionBuilder column(String fromColumn) {
|
||||
|
||||
this.fromColumn = fromColumn;
|
||||
return this;
|
||||
}
|
||||
|
||||
WhereConditionBuilder variable(String var) {
|
||||
|
||||
expression = ":" + var;
|
||||
return this;
|
||||
}
|
||||
|
||||
WhereCondition build() {
|
||||
return new WhereCondition(fromTable + "." + fromColumn, operation, expression);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class Join {
|
||||
|
||||
private final String table;
|
||||
private final String as;
|
||||
private final Outer outer;
|
||||
private final List<WhereCondition> conditions = new ArrayList<>();
|
||||
|
||||
Join(String table, String as, List<WhereCondition> conditions, Outer outer) {
|
||||
|
||||
this.table = table;
|
||||
this.as = as;
|
||||
this.outer = outer;
|
||||
this.conditions.addAll(conditions);
|
||||
}
|
||||
|
||||
static JoinBuilder builder() {
|
||||
return new JoinBuilder();
|
||||
}
|
||||
|
||||
private String outerJoinModifier() {
|
||||
|
||||
switch (outer) {
|
||||
case NONE:
|
||||
return "";
|
||||
default:
|
||||
return String.format(" %s OUTER", outer.name());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static class JoinBuilder {
|
||||
|
||||
private String table;
|
||||
private String as;
|
||||
private List<WhereCondition> conditions = new ArrayList<>();
|
||||
private Outer outer = Outer.NONE;
|
||||
|
||||
JoinBuilder() {}
|
||||
|
||||
public Join.JoinBuilder table(String table) {
|
||||
|
||||
this.table = table;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Join.JoinBuilder as(String as) {
|
||||
|
||||
this.as = as;
|
||||
return this;
|
||||
}
|
||||
|
||||
WhereConditionBuilder where(String column) {
|
||||
return new WhereConditionBuilder(this, column);
|
||||
}
|
||||
|
||||
private JoinBuilder where(WhereCondition condition) {
|
||||
|
||||
conditions.add(condition);
|
||||
return this;
|
||||
}
|
||||
|
||||
Join build() {
|
||||
return new Join(table, as, conditions, outer);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "org.springframework.data.jdbc.core.SelectBuilder.Join.JoinBuilder(table=" + this.table + ", as="
|
||||
+ this.as + ")";
|
||||
}
|
||||
|
||||
JoinBuilder rightOuter() {
|
||||
|
||||
outer = Outer.RIGHT;
|
||||
return this;
|
||||
}
|
||||
|
||||
JoinBuilder leftOuter() {
|
||||
outer = Outer.LEFT;
|
||||
return this;
|
||||
}
|
||||
|
||||
static class WhereConditionBuilder {
|
||||
|
||||
private final JoinBuilder joinBuilder;
|
||||
private final String fromColumn;
|
||||
|
||||
private String operation = "=";
|
||||
|
||||
WhereConditionBuilder(JoinBuilder joinBuilder, String column) {
|
||||
|
||||
this.joinBuilder = joinBuilder;
|
||||
this.fromColumn = column;
|
||||
}
|
||||
|
||||
WhereConditionBuilder eq() {
|
||||
operation = "=";
|
||||
return this;
|
||||
}
|
||||
|
||||
JoinBuilder column(String table, String column) {
|
||||
|
||||
return joinBuilder.where(new WhereCondition( //
|
||||
joinBuilder.as + "." + fromColumn, //
|
||||
operation, //
|
||||
table + "." + column //
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private enum Outer {
|
||||
NONE, RIGHT, LEFT
|
||||
}
|
||||
}
|
||||
|
||||
static class WhereCondition {
|
||||
|
||||
private final String operation;
|
||||
private final String fromExpression;
|
||||
private final String toExpression;
|
||||
|
||||
WhereCondition(String fromExpression, String operation, String toExpression) {
|
||||
|
||||
this.fromExpression = fromExpression;
|
||||
this.toExpression = toExpression;
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
String toSql() {
|
||||
|
||||
if (operation.equals("in")) {
|
||||
return String.format("%s %s(%s)", fromExpression, operation, toExpression);
|
||||
}
|
||||
|
||||
return String.format("%s %s %s", fromExpression, operation, toExpression);
|
||||
}
|
||||
}
|
||||
|
||||
@Builder
|
||||
static class Column {
|
||||
|
||||
private final String tableAlias;
|
||||
private final String column;
|
||||
private final String as;
|
||||
|
||||
String columnDefinition() {
|
||||
StringBuilder b = new StringBuilder();
|
||||
if (tableAlias != null)
|
||||
b.append(tableAlias).append('.');
|
||||
b.append(column);
|
||||
if (as != null)
|
||||
b.append(" AS ").append(as);
|
||||
return b.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
|
||||
import org.springframework.data.jdbc.mapping.model.PropertyPaths;
|
||||
import org.springframework.data.jdbc.repository.SimpleJdbcRepository;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Generates SQL statements to be used by {@link SimpleJdbcRepository}
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @since 2.0
|
||||
*/
|
||||
class SqlGenerator {
|
||||
|
||||
private final JdbcPersistentEntity<?> entity;
|
||||
private final JdbcMappingContext context;
|
||||
private final List<String> propertyNames = new ArrayList<>();
|
||||
private final List<String> nonIdPropertyNames = new ArrayList<>();
|
||||
|
||||
private final Lazy<String> findOneSql = Lazy.of(this::createFindOneSelectSql);
|
||||
private final Lazy<String> findAllSql = Lazy.of(this::createFindAllSql);
|
||||
private final Lazy<String> findAllInListSql = Lazy.of(this::createFindAllInListSql);
|
||||
|
||||
private final Lazy<String> existsSql = Lazy.of(this::createExistsSql);
|
||||
private final Lazy<String> countSql = Lazy.of(this::createCountSql);
|
||||
|
||||
private final Lazy<String> updateSql = Lazy.of(this::createUpdateSql);
|
||||
|
||||
private final Lazy<String> deleteByIdSql = Lazy.of(this::createDeleteSql);
|
||||
private final Lazy<String> deleteByListSql = Lazy.of(this::createDeleteByListSql);
|
||||
private final SqlGeneratorSource sqlGeneratorSource;
|
||||
|
||||
SqlGenerator(JdbcMappingContext context, JdbcPersistentEntity<?> entity,
|
||||
SqlGeneratorSource sqlGeneratorSource) {
|
||||
|
||||
this.context = context;
|
||||
this.entity = entity;
|
||||
this.sqlGeneratorSource = sqlGeneratorSource;
|
||||
initPropertyNames();
|
||||
}
|
||||
|
||||
private void initPropertyNames() {
|
||||
|
||||
entity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) p -> {
|
||||
// the referencing column of referenced entity is expected to be on the other side of the relation
|
||||
if (!p.isEntity()) {
|
||||
propertyNames.add(p.getName());
|
||||
if (!entity.isIdProperty(p)) {
|
||||
nonIdPropertyNames.add(p.getName());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
String getFindAllInList() {
|
||||
return findAllInListSql.get();
|
||||
}
|
||||
|
||||
String getFindAll() {
|
||||
return findAllSql.get();
|
||||
}
|
||||
|
||||
String getExists() {
|
||||
return existsSql.get();
|
||||
}
|
||||
|
||||
String getFindOne() {
|
||||
return findOneSql.get();
|
||||
}
|
||||
|
||||
String getInsert(boolean excludeId, Set additionalColumns) {
|
||||
return createInsertSql(excludeId, additionalColumns);
|
||||
}
|
||||
|
||||
String getUpdate() {
|
||||
return updateSql.get();
|
||||
}
|
||||
|
||||
String getCount() {
|
||||
return countSql.get();
|
||||
}
|
||||
|
||||
String getDeleteById() {
|
||||
return deleteByIdSql.get();
|
||||
}
|
||||
|
||||
String getDeleteByList() {
|
||||
return deleteByListSql.get();
|
||||
}
|
||||
|
||||
private String createFindOneSelectSql() {
|
||||
|
||||
return createSelectBuilder() //
|
||||
.where(wb -> wb.tableAlias(entity.getTableName()).column(entity.getIdColumn()).eq().variable("id")) //
|
||||
.build();
|
||||
}
|
||||
|
||||
private SelectBuilder createSelectBuilder() {
|
||||
SelectBuilder builder = new SelectBuilder(entity.getTableName());
|
||||
|
||||
for (JdbcPersistentProperty property : entity) {
|
||||
if (!property.isEntity()) {
|
||||
|
||||
builder.column(cb -> cb //
|
||||
.tableAlias(entity.getTableName()) //
|
||||
.column(property.getColumnName()) //
|
||||
.as(property.getColumnName()));
|
||||
}
|
||||
}
|
||||
|
||||
for (JdbcPersistentProperty property : entity) {
|
||||
if (property.isEntity()) {
|
||||
|
||||
JdbcPersistentEntity<?> refEntity = context.getRequiredPersistentEntity(property.getType());
|
||||
String joinAlias = property.getName();
|
||||
builder.join(jb -> jb.leftOuter().table(refEntity.getTableName()).as(joinAlias) //
|
||||
.where(entity.getTableName()).eq().column(entity.getTableName(), entity.getIdColumn()));
|
||||
|
||||
for (JdbcPersistentProperty refProperty : refEntity) {
|
||||
builder.column( //
|
||||
cb -> cb.tableAlias(joinAlias) //
|
||||
.column(refProperty.getColumnName()) //
|
||||
.as(joinAlias + "_" + refProperty.getColumnName()) //
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private Stream<String> getColumnNameStream(String prefix) {
|
||||
return StreamUtils.createStreamFromIterator(entity.iterator()).flatMap(p -> getColumnNameStream(p, prefix));
|
||||
}
|
||||
|
||||
private Stream<String> getColumnNameStream(JdbcPersistentProperty p, String prefix) {
|
||||
|
||||
if (p.isEntity()) {
|
||||
return sqlGeneratorSource.getSqlGenerator(p.getType()).getColumnNameStream(prefix + p.getColumnName() + "_");
|
||||
} else {
|
||||
return Stream.of(prefix + p.getColumnName());
|
||||
}
|
||||
}
|
||||
|
||||
private String createFindAllSql() {
|
||||
return createSelectBuilder().build();
|
||||
}
|
||||
|
||||
private String createFindAllInListSql() {
|
||||
|
||||
return createSelectBuilder() //
|
||||
.where(wb -> wb.tableAlias(entity.getTableName()).column(entity.getIdColumn()).in().variable("ids")) //
|
||||
.build();
|
||||
}
|
||||
|
||||
private String createExistsSql() {
|
||||
return String.format("select count(*) from %s where %s = :id", entity.getTableName(), entity.getIdColumn());
|
||||
}
|
||||
|
||||
private String createCountSql() {
|
||||
return String.format("select count(*) from %s", entity.getTableName());
|
||||
}
|
||||
|
||||
private String createInsertSql(boolean excludeId, Set additionalColumns) {
|
||||
|
||||
String insertTemplate = "insert into %s (%s) values (%s)";
|
||||
List<String> propertyNamesForInsert = new ArrayList<>(excludeId ? nonIdPropertyNames : propertyNames);
|
||||
propertyNamesForInsert.addAll(additionalColumns);
|
||||
String tableColumns = String.join(", ", propertyNamesForInsert);
|
||||
String parameterNames = propertyNamesForInsert.stream().collect(Collectors.joining(", :", ":", ""));
|
||||
|
||||
return String.format(insertTemplate, entity.getTableName(), tableColumns, parameterNames);
|
||||
}
|
||||
|
||||
private String createUpdateSql() {
|
||||
|
||||
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() {
|
||||
return String.format("DELETE from %s where %s = :id", entity.getTableName(), entity.getIdColumn());
|
||||
}
|
||||
|
||||
String createDeleteAllSql(PropertyPath path) {
|
||||
if (path == null) {
|
||||
return String.format("DELETE FROM %s", entity.getTableName());
|
||||
}
|
||||
|
||||
JdbcPersistentEntity<?> entityToDelete = context.getRequiredPersistentEntity(PropertyPaths.getLeafType(path));
|
||||
|
||||
String innerMostCondition = String.format("%s IS NOT NULL", entity.getTableName(), entity.getTableName(),
|
||||
entity.getIdColumn());
|
||||
|
||||
String condition = cascadeConditions(innerMostCondition, path.next());
|
||||
|
||||
return String.format("DELETE FROM %s WHERE %s", entityToDelete.getTableName(), condition, condition);
|
||||
|
||||
}
|
||||
|
||||
private String createDeleteByListSql() {
|
||||
return String.format("doDelete from %s where %s in (:ids)", entity.getTableName(), entity.getIdColumn());
|
||||
}
|
||||
|
||||
String createDeleteByPath(PropertyPath path) {
|
||||
|
||||
JdbcPersistentEntity<?> entityToDelete = context.getRequiredPersistentEntity(PropertyPaths.getLeafType(path));
|
||||
|
||||
String innerMostCondition = String.format("%s = :rootId", entity.getTableName());
|
||||
|
||||
String condition = cascadeConditions(innerMostCondition, path.next());
|
||||
|
||||
return String.format("DELETE FROM %s WHERE %s", entityToDelete.getTableName(), condition);
|
||||
|
||||
}
|
||||
|
||||
private String cascadeConditions(String innerCondition, PropertyPath path) {
|
||||
|
||||
if (path == null) {
|
||||
return innerCondition;
|
||||
}
|
||||
|
||||
JdbcPersistentEntity<?> entity = context.getRequiredPersistentEntity(path.getOwningType());
|
||||
JdbcPersistentProperty property = entity.getPersistentProperty(path.getSegment());
|
||||
|
||||
Assert.notNull(property, "could not find property for path " + path.getSegment() + " in " + entity);
|
||||
|
||||
String tableName = entity.getTableName();
|
||||
String idColumn = entity.getIdColumn();
|
||||
|
||||
return String.format("%s IN (SELECT %s FROM %s WHERE %s)", tableName, idColumn, tableName, innerCondition);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
|
||||
|
||||
/**
|
||||
* Provides {@link SqlGenerator}s per domain type. Instances get cched, so when asked multiple times for the same domain
|
||||
* type, the same generator will get returned.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class SqlGeneratorSource {
|
||||
|
||||
private final Map<Class, SqlGenerator> sqlGeneratorCache = new HashMap<>();
|
||||
private final JdbcMappingContext context;
|
||||
|
||||
SqlGenerator getSqlGenerator(Class<?> domainType) {
|
||||
|
||||
return sqlGeneratorCache.computeIfAbsent(domainType,
|
||||
t -> new SqlGenerator(context, context.getRequiredPersistentEntity(t), this));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository;
|
||||
package org.springframework.data.jdbc.core;
|
||||
|
||||
import org.springframework.dao.NonTransientDataAccessException;
|
||||
|
||||
@@ -27,7 +27,7 @@ public class UnableToSetId extends NonTransientDataAccessException {
|
||||
|
||||
private static final long serialVersionUID = 3285001352389420376L;
|
||||
|
||||
public UnableToSetId(String message, Throwable cause) {
|
||||
UnableToSetId(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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.core.conversion;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstracts over a single interaction with a database.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@ToString
|
||||
@Getter
|
||||
public abstract class DbAction<T> {
|
||||
|
||||
/**
|
||||
* {@link Class} of the entity of which the database representation is affected by this action.
|
||||
*/
|
||||
private final Class<T> entityType;
|
||||
|
||||
/**
|
||||
* The entity of which the database representation is affected by this action. Might be {@literal null}.
|
||||
*/
|
||||
private final T entity;
|
||||
|
||||
/**
|
||||
* Another action, this action depends on. For example the insert for one entity might need the id of another entity,
|
||||
* which gets insert before this one. That action would be referenced by this property, so that the id becomes
|
||||
* available at execution time. Might be {@literal null}.
|
||||
*/
|
||||
private final DbAction dependingOn;
|
||||
|
||||
private DbAction(Class<T> entityType, T entity, DbAction dependingOn) {
|
||||
|
||||
this.entityType = entityType;
|
||||
this.entity = entity;
|
||||
this.dependingOn = dependingOn;
|
||||
}
|
||||
|
||||
public static <T> Insert<T> insert(T entity, DbAction dependingOn) {
|
||||
return new Insert<>(entity, dependingOn);
|
||||
}
|
||||
|
||||
public static <T> Update<T> update(T entity, DbAction dependingOn) {
|
||||
return new Update<>(entity, dependingOn);
|
||||
}
|
||||
|
||||
public static <T> Delete<T> delete(Object id, Class<T> type, T entity, PropertyPath propertyPath,
|
||||
DbAction dependingOn) {
|
||||
return new Delete<>(id, type, entity, propertyPath, dependingOn);
|
||||
}
|
||||
|
||||
public static <T> DeleteAll<T> deleteAll(Class<T> type, PropertyPath propertyPath, DbAction dependingOn) {
|
||||
return new DeleteAll<>(type, propertyPath, dependingOn);
|
||||
}
|
||||
|
||||
abstract void executeWith(Interpreter interpreter);
|
||||
|
||||
/**
|
||||
* {@link InsertOrUpdate} must reference an entity.
|
||||
*
|
||||
* @param <T> type o the entity for which this represents a database interaction
|
||||
*/
|
||||
abstract static class InsertOrUpdate<T> extends DbAction<T> {
|
||||
|
||||
InsertOrUpdate(T entity, DbAction dependingOn) {
|
||||
super((Class<T>) entity.getClass(), entity, dependingOn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an insert statement.
|
||||
*
|
||||
* @param <T> type o the entity for which this represents a database interaction
|
||||
*/
|
||||
public static class Insert<T> extends InsertOrUpdate<T> {
|
||||
|
||||
private Insert(T entity, DbAction dependingOn) {
|
||||
super(entity, dependingOn);
|
||||
}
|
||||
|
||||
@Override
|
||||
void executeWith(Interpreter interpreter) {
|
||||
interpreter.interpret(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an update statement.
|
||||
*
|
||||
* @param <T> type o the entity for which this represents a database interaction
|
||||
*/
|
||||
public static class Update<T> extends InsertOrUpdate<T> {
|
||||
|
||||
private Update(T entity, DbAction dependingOn) {
|
||||
super(entity, dependingOn);
|
||||
}
|
||||
|
||||
@Override
|
||||
void executeWith(Interpreter interpreter) {
|
||||
interpreter.interpret(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an delete statement, possibly a cascading delete statement, i.e. the delete necessary because one
|
||||
* aggregate root gets deleted.
|
||||
*
|
||||
* @param <T> type o the entity for which this represents a database interaction
|
||||
*/
|
||||
@Getter
|
||||
public static class Delete<T> extends DbAction<T> {
|
||||
|
||||
/**
|
||||
* Id of the root for which all via {@link #propertyPath} referenced entities shall get deleted
|
||||
*/
|
||||
private final Object rootId;
|
||||
|
||||
/**
|
||||
* {@link PropertyPath} which connects the aggregate root with the entities to be deleted. If this is the action to
|
||||
* delete the root enity itself, this is {@literal null}.
|
||||
*/
|
||||
private final PropertyPath propertyPath;
|
||||
|
||||
private Delete(Object rootId, Class<T> type, T entity, PropertyPath propertyPath, DbAction dependingOn) {
|
||||
|
||||
super(type, entity, dependingOn);
|
||||
|
||||
Assert.notNull(rootId, "rootId must not be null.");
|
||||
|
||||
this.rootId = rootId;
|
||||
this.propertyPath = propertyPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
void executeWith(Interpreter interpreter) {
|
||||
interpreter.interpret(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an delete statement.
|
||||
*
|
||||
* @param <T> type o the entity for which this represents a database interaction
|
||||
*/
|
||||
@Getter
|
||||
public static class DeleteAll<T> extends DbAction<T> {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private final PropertyPath propertyPath;
|
||||
|
||||
private DeleteAll(Class<T> entityType, PropertyPath propertyPath, DbAction dependingOn) {
|
||||
|
||||
super(entityType, null, dependingOn);
|
||||
|
||||
this.propertyPath = propertyPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
void executeWith(Interpreter interpreter) {
|
||||
interpreter.interpret(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.core.conversion;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public class DbChange<T> {
|
||||
|
||||
private final Kind kind;
|
||||
|
||||
/** Type of the aggregate root to be changed */
|
||||
private final Class<T> entityType;
|
||||
|
||||
/** Aggregate root, to which the change applies, if available */
|
||||
private final T entity;
|
||||
|
||||
private final List<DbAction> actions = new ArrayList<>();
|
||||
|
||||
public void executeWith(Interpreter interpreter) {
|
||||
actions.forEach(a -> a.executeWith(interpreter));
|
||||
}
|
||||
|
||||
public void addAction(DbAction action) {
|
||||
actions.add(action);
|
||||
}
|
||||
|
||||
public enum Kind {
|
||||
SAVE, DELETE
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core.conversion;
|
||||
|
||||
import org.springframework.data.jdbc.core.conversion.DbAction.Delete;
|
||||
import org.springframework.data.jdbc.core.conversion.DbAction.DeleteAll;
|
||||
import org.springframework.data.jdbc.core.conversion.DbAction.Insert;
|
||||
import org.springframework.data.jdbc.core.conversion.DbAction.Update;
|
||||
|
||||
/**
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public interface Interpreter {
|
||||
|
||||
<T>void interpret(Update<T> update);
|
||||
<T>void interpret(Insert<T> insert);
|
||||
|
||||
<T> void interpret(Delete<T> delete);
|
||||
<T> void interpret(DeleteAll<T> delete);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.core.conversion;
|
||||
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.mapping.model.PropertyPaths;
|
||||
|
||||
/**
|
||||
* Converts an entity that is about to be deleted into {@link DbAction}s inside a {@link DbChange} that need to be
|
||||
* executed against the database to recreate the appropriate state in the database.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class JdbcEntityDeleteWriter extends JdbcEntityWriterSupport {
|
||||
|
||||
public JdbcEntityDeleteWriter(JdbcMappingContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object id, DbChange dbChange) {
|
||||
|
||||
if (id == null) {
|
||||
deleteAll(dbChange);
|
||||
} else {
|
||||
deleteById(id, dbChange);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteAll(DbChange dbChange) {
|
||||
|
||||
context.referencedEntities(dbChange.getEntityType(), null)
|
||||
.forEach(p -> dbChange.addAction(DbAction.deleteAll(PropertyPaths.getLeafType(p), p, null)));
|
||||
|
||||
dbChange.addAction(DbAction.deleteAll(dbChange.getEntityType(), null, null));
|
||||
}
|
||||
|
||||
private void deleteById(Object id, DbChange dbChange) {
|
||||
|
||||
deleteReferencedEntities(id, dbChange);
|
||||
|
||||
dbChange.addAction(DbAction.delete(id, dbChange.getEntityType(), dbChange.getEntity(), null, null));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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.core.conversion;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.jdbc.core.conversion.DbAction.Insert;
|
||||
import org.springframework.data.jdbc.core.conversion.DbAction.Update;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityInformation;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
|
||||
/**
|
||||
* Converts an entity that is about to be saved into {@link DbAction}s inside a {@link DbChange} that need to be
|
||||
* executed against the database to recreate the appropriate state in the database.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class JdbcEntityWriter extends JdbcEntityWriterSupport {
|
||||
|
||||
public JdbcEntityWriter(JdbcMappingContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object o, DbChange dbChange) {
|
||||
write(o, dbChange, null);
|
||||
}
|
||||
|
||||
private void write(Object o, DbChange dbChange, DbAction dependingOn) {
|
||||
|
||||
JdbcPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(dbChange.getEntityType());
|
||||
|
||||
JdbcPersistentEntityInformation<Object, ?> entityInformation = context
|
||||
.getRequiredPersistentEntityInformation((Class<Object>) o.getClass());
|
||||
|
||||
if (entityInformation.isNew(o)) {
|
||||
Insert<Object> insert = DbAction.insert(o, dependingOn);
|
||||
dbChange.addAction(insert);
|
||||
|
||||
referencedEntities(o).forEach(e -> saveReferencedEntities(e, dbChange, insert));
|
||||
} else {
|
||||
|
||||
deleteReferencedEntities(entityInformation.getRequiredId(o), dbChange);
|
||||
|
||||
Update<Object> update = DbAction.update(o, dependingOn);
|
||||
dbChange.addAction(update);
|
||||
|
||||
referencedEntities(o).forEach(e -> insertReferencedEntities(e, dbChange, update));
|
||||
}
|
||||
}
|
||||
|
||||
private void saveReferencedEntities(Object o, DbChange dbChange, DbAction dependingOn) {
|
||||
|
||||
DbAction action = saveAction(o, dependingOn);
|
||||
dbChange.addAction(action);
|
||||
|
||||
referencedEntities(o).forEach(e -> saveReferencedEntities(e, dbChange, action));
|
||||
}
|
||||
|
||||
private <T> DbAction saveAction(T t, DbAction dependingOn) {
|
||||
|
||||
JdbcPersistentEntityInformation<T, ?> entityInformation = context
|
||||
.getRequiredPersistentEntityInformation((Class<T>) t.getClass());
|
||||
|
||||
if (entityInformation.isNew(t))
|
||||
return DbAction.insert(t, dependingOn);
|
||||
else
|
||||
return DbAction.update(t, dependingOn);
|
||||
}
|
||||
|
||||
private void insertReferencedEntities(Object o, DbChange dbChange, DbAction dependingOn) {
|
||||
|
||||
System.out.println("adding an insert");
|
||||
dbChange.addAction(DbAction.insert(o, dependingOn));
|
||||
referencedEntities(o).forEach(e -> insertReferencedEntities(e, dbChange, dependingOn));
|
||||
}
|
||||
|
||||
private Stream<Object> referencedEntities(Object o) {
|
||||
|
||||
JdbcPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(o.getClass());
|
||||
|
||||
return StreamUtils.createStreamFromIterator(persistentEntity.iterator()) //
|
||||
.filter(PersistentProperty::isEntity)
|
||||
.flatMap(p -> referencedEntity(p, persistentEntity.getPropertyAccessor(o)));
|
||||
}
|
||||
|
||||
private Stream<?> referencedEntity(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) {
|
||||
|
||||
Class<?> type = p.getActualType();
|
||||
JdbcPersistentEntity<?> persistentEntity = context //
|
||||
.getPersistentEntity(type);
|
||||
|
||||
if (persistentEntity == null) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
Object property = propertyAccessor.getProperty(p);
|
||||
if (property == null) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
return Stream.of(property);
|
||||
}
|
||||
}
|
||||
@@ -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.core.conversion;
|
||||
|
||||
import org.springframework.data.convert.EntityWriter;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.mapping.model.PropertyPaths;
|
||||
|
||||
/**
|
||||
* Common infrastructure needed by different implementations of {@link EntityWriter}<Object, DbChange>.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
abstract class JdbcEntityWriterSupport implements EntityWriter<Object, DbChange> {
|
||||
protected final JdbcMappingContext context;
|
||||
|
||||
JdbcEntityWriterSupport(JdbcMappingContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* add {@link org.springframework.data.jdbc.core.conversion.DbAction.Delete} actions to the {@link DbChange} for
|
||||
* deleting all referenced entities.
|
||||
*
|
||||
* @param id id of the aggregate root, of which the referenced entities get deleted.
|
||||
* @param dbChange the change object to which the actions should get added. Must not be {@literal null}
|
||||
*/
|
||||
void deleteReferencedEntities(Object id, DbChange dbChange) {
|
||||
|
||||
context.referencedEntities(dbChange.getEntityType(), null)
|
||||
.forEach(p -> dbChange.addAction(DbAction.delete(id, PropertyPaths.getLeafType(p), null, p, null)));
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository.support;
|
||||
package org.springframework.data.jdbc.mapping.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
|
||||
import org.springframework.data.repository.core.support.PersistentEntityInformation;
|
||||
|
||||
/**
|
||||
@@ -39,7 +35,7 @@ public class BasicJdbcPersistentEntityInformation<T, ID> extends PersistentEntit
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation#setId(java.lang.Object, java.util.Optional)
|
||||
* @see org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityInformation#setId(java.lang.Object, java.util.Optional)
|
||||
*/
|
||||
@Override
|
||||
public void setId(T instance, Object value) {
|
||||
@@ -20,8 +20,6 @@ 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;
|
||||
@@ -40,6 +38,7 @@ public class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProper
|
||||
implements JdbcPersistentProperty {
|
||||
|
||||
private static final Map<Class<?>, Class<?>> javaToDbType = new LinkedHashMap<>();
|
||||
private final JdbcMappingContext context;
|
||||
|
||||
static {
|
||||
javaToDbType.put(Enum.class, String.class);
|
||||
@@ -49,14 +48,16 @@ public class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProper
|
||||
|
||||
/**
|
||||
* Creates a new {@link AnnotationBasedPersistentProperty}.
|
||||
*
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @param owner must not be {@literal null}.
|
||||
* @param simpleTypeHolder must not be {@literal null}.
|
||||
* @param context
|
||||
*/
|
||||
public BasicJdbcPersistentProperty(Property property, PersistentEntity<?, JdbcPersistentProperty> owner,
|
||||
SimpleTypeHolder simpleTypeHolder) {
|
||||
SimpleTypeHolder simpleTypeHolder, JdbcMappingContext context) {
|
||||
super(property, owner, simpleTypeHolder);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -85,10 +86,32 @@ public class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProper
|
||||
@Override
|
||||
public Class getColumnType() {
|
||||
|
||||
Class type = getType();
|
||||
Class columnType = columnTypeIfEntity(getType());
|
||||
|
||||
return columnType == null ? columnTypeForNonEntity(getType()) : columnType;
|
||||
}
|
||||
|
||||
private Class columnTypeIfEntity(Class type) {
|
||||
|
||||
JdbcPersistentEntity<?> persistentEntity = context.getPersistentEntity(type);
|
||||
|
||||
if (persistentEntity == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JdbcPersistentProperty idProperty = persistentEntity.getIdProperty();
|
||||
|
||||
if (idProperty == null) {
|
||||
return null;
|
||||
}
|
||||
return idProperty.getColumnType();
|
||||
}
|
||||
|
||||
private Class columnTypeForNonEntity(Class type) {
|
||||
|
||||
return javaToDbType.entrySet().stream() //
|
||||
.filter(e -> e.getKey().isAssignableFrom(type)) //
|
||||
.map(e -> (Class)e.getValue()) //
|
||||
.map(e -> (Class) e.getValue()) //
|
||||
.findFirst() //
|
||||
.orElseGet(() -> ClassUtils.resolvePrimitiveIfNecessary(type));
|
||||
}
|
||||
|
||||
@@ -13,14 +13,19 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.mapping.context;
|
||||
package org.springframework.data.jdbc.mapping.model;
|
||||
|
||||
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 static java.util.Arrays.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.time.temporal.Temporal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.context.AbstractMappingContext;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.Property;
|
||||
@@ -35,6 +40,40 @@ import org.springframework.data.util.TypeInformation;
|
||||
*/
|
||||
public class JdbcMappingContext extends AbstractMappingContext<JdbcPersistentEntity<?>, JdbcPersistentProperty> {
|
||||
|
||||
private static final HashSet<Class<?>> CUSTOM_SIMPLE_TYPES = new HashSet<>(asList( //
|
||||
BigDecimal.class, //
|
||||
BigInteger.class, //
|
||||
Temporal.class //
|
||||
));
|
||||
|
||||
public JdbcMappingContext() {
|
||||
setSimpleTypeHolder(new SimpleTypeHolder(CUSTOM_SIMPLE_TYPES, true));
|
||||
}
|
||||
|
||||
public List<PropertyPath> referencedEntities(Class<?> rootType, PropertyPath path) {
|
||||
|
||||
List<PropertyPath> paths = new ArrayList<>();
|
||||
|
||||
Class<?> currentType = path == null ? rootType : PropertyPaths.getLeafType(path);
|
||||
JdbcPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(currentType);
|
||||
|
||||
String rootPrefix = path == null ? "" : path.toDotPath() + ".";
|
||||
|
||||
for (JdbcPersistentProperty property : persistentEntity) {
|
||||
if (property.isEntity()) {
|
||||
|
||||
PropertyPath nextPath = path == null ? PropertyPath.from(property.getName(), rootType)
|
||||
: PropertyPaths.extendBy(path, property.getColumnName());
|
||||
paths.add(nextPath);
|
||||
paths.addAll(referencedEntities(rootType, nextPath));
|
||||
}
|
||||
}
|
||||
|
||||
Collections.reverse(paths);
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation)
|
||||
@@ -51,11 +90,12 @@ public class JdbcMappingContext extends AbstractMappingContext<JdbcPersistentEnt
|
||||
@Override
|
||||
protected JdbcPersistentProperty createPersistentProperty(Property property, JdbcPersistentEntity<?> owner,
|
||||
SimpleTypeHolder simpleTypeHolder) {
|
||||
return new BasicJdbcPersistentProperty(property, owner, simpleTypeHolder);
|
||||
return new BasicJdbcPersistentProperty(property, owner, simpleTypeHolder, this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> JdbcPersistentEntityInformation<T, ?> getRequiredPersistentEntityInformation(Class<T> type) {
|
||||
return new BasicJdbcPersistentEntityInformation<>((JdbcPersistentEntity<T>) getRequiredPersistentEntity(type));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import org.springframework.data.util.TypeInformation;
|
||||
* @author Jens Schauder
|
||||
* @since 2.0
|
||||
*/
|
||||
public class JdbcPersistentEntityImpl<T> extends BasicPersistentEntity<T, JdbcPersistentProperty>
|
||||
class JdbcPersistentEntityImpl<T> extends BasicPersistentEntity<T, JdbcPersistentProperty>
|
||||
implements JdbcPersistentEntity<T> {
|
||||
|
||||
private final @Getter String tableName;
|
||||
@@ -36,7 +36,7 @@ public class JdbcPersistentEntityImpl<T> extends BasicPersistentEntity<T, JdbcPe
|
||||
*
|
||||
* @param information must not be {@literal null}.
|
||||
*/
|
||||
public JdbcPersistentEntityImpl(TypeInformation<T> information) {
|
||||
JdbcPersistentEntityImpl(TypeInformation<T> information) {
|
||||
|
||||
super(information);
|
||||
|
||||
@@ -51,4 +51,9 @@ public class JdbcPersistentEntityImpl<T> extends BasicPersistentEntity<T, JdbcPe
|
||||
public String getIdColumn() {
|
||||
return getRequiredIdProperty().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("JdbcpersistentEntityImpl<%s>", getType());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository.support;
|
||||
|
||||
import java.util.Optional;
|
||||
package org.springframework.data.jdbc.mapping.model;
|
||||
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
|
||||
@@ -35,8 +33,11 @@ public interface JdbcPersistentEntityInformation<T, ID> extends EntityInformatio
|
||||
* @throws IllegalArgumentException in case no identifier can be obtained for the given entity.
|
||||
*/
|
||||
default ID getRequiredId(T entity) {
|
||||
|
||||
ID id = getId(entity);
|
||||
if (id == null) throw new IllegalStateException(String.format("Could not obtain required identifier from entity %s!", entity));
|
||||
if (id == null)
|
||||
throw new IllegalStateException(String.format("Could not obtain required identifier from entity %s!", entity));
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.mapping.model;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utilities for working with {@link PropertyPath}s.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@UtilityClass
|
||||
public class PropertyPaths {
|
||||
|
||||
public static Class<?> getLeafType(PropertyPath path) {
|
||||
if (path.hasNext())
|
||||
return getLeafType(path.next());
|
||||
return path.getType();
|
||||
}
|
||||
|
||||
public static PropertyPath extendBy(PropertyPath path, String name) {
|
||||
|
||||
Assert.notNull(path, "Path must not be null.");
|
||||
Assert.hasText(name, "Name must not be empty");
|
||||
|
||||
return PropertyPath.from(path.toDotPath() + "." + name, path.getOwningType());
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityImpl;
|
||||
import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation;
|
||||
import org.springframework.data.jdbc.core.JdbcEntityOperations;
|
||||
import org.springframework.data.jdbc.core.JdbcEntityTemplate;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityInformation;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
@@ -35,7 +36,7 @@ public class SimpleJdbcRepository<T, ID> implements CrudRepository<T, ID> {
|
||||
private final JdbcEntityOperations entityOperations;
|
||||
|
||||
/**
|
||||
* Creates a new {@link SimpleJdbcRepository} for the given {@link JdbcPersistentEntityImpl}
|
||||
* Creates a new {@link SimpleJdbcRepository}.
|
||||
*/
|
||||
public SimpleJdbcRepository(JdbcEntityTemplate entityOperations,
|
||||
JdbcPersistentEntityInformation<T, ID> entityInformation) {
|
||||
@@ -51,11 +52,7 @@ public class SimpleJdbcRepository<T, ID> implements CrudRepository<T, ID> {
|
||||
@Override
|
||||
public <S extends T> S save(S instance) {
|
||||
|
||||
if (entityInformation.isNew(instance)) {
|
||||
entityOperations.insert(instance, entityInformation.getJavaType());
|
||||
} else {
|
||||
entityOperations.update(instance, entityInformation.getJavaType());
|
||||
}
|
||||
entityOperations.save(instance, entityInformation.getJavaType());
|
||||
|
||||
return instance;
|
||||
}
|
||||
@@ -142,7 +139,11 @@ public class SimpleJdbcRepository<T, ID> implements CrudRepository<T, ID> {
|
||||
*/
|
||||
@Override
|
||||
public void deleteAll(Iterable<? extends T> entities) {
|
||||
entityOperations.deleteAll(entities, entityInformation.getJavaType());
|
||||
|
||||
for (T entity : entities) {
|
||||
entityOperations.delete(entity, (Class<T>) entity.getClass());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.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.jdbc.mapping.model.JdbcPersistentProperty;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
|
||||
/**
|
||||
* Generates SQL statements to be used by {@link SimpleJdbcRepository}
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @since 2.0
|
||||
*/
|
||||
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 updateSql;
|
||||
|
||||
private final String deleteByIdSql;
|
||||
private final String deleteAllSql;
|
||||
private final String deleteByListSql;
|
||||
|
||||
private final JdbcPersistentEntity<?> entity;
|
||||
private final List<String> propertyNames = new ArrayList<>();
|
||||
private final List<String> nonIdPropertyNames = new ArrayList<>();
|
||||
|
||||
SqlGenerator(JdbcPersistentEntity<?> entity) {
|
||||
|
||||
this.entity = entity;
|
||||
|
||||
initPropertyNames();
|
||||
|
||||
findOneSql = createFindOneSelectSql();
|
||||
findAllSql = createFindAllSql();
|
||||
findAllInListSql = createFindAllInListSql();
|
||||
|
||||
existsSql = createExistsSql();
|
||||
countSql = createCountSql();
|
||||
|
||||
updateSql = createUpdateSql();
|
||||
|
||||
deleteByIdSql = createDeleteSql();
|
||||
deleteAllSql = createDeleteAllSql();
|
||||
deleteByListSql = createDeleteByListSql();
|
||||
}
|
||||
|
||||
private void initPropertyNames() {
|
||||
|
||||
entity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) p -> {
|
||||
propertyNames.add(p.getName());
|
||||
if (!entity.isIdProperty(p)) {
|
||||
nonIdPropertyNames.add(p.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
String getFindAllInList() {
|
||||
return findAllInListSql;
|
||||
}
|
||||
|
||||
String getFindAll() {
|
||||
return findAllSql;
|
||||
}
|
||||
|
||||
String getExists() {
|
||||
return existsSql;
|
||||
}
|
||||
|
||||
String getFindOne() {
|
||||
return findOneSql;
|
||||
}
|
||||
|
||||
String getInsert(boolean excludeId) {
|
||||
return createInsertSql(excludeId);
|
||||
}
|
||||
|
||||
String getUpdate() {
|
||||
return updateSql;
|
||||
}
|
||||
|
||||
String getCount() {
|
||||
return countSql;
|
||||
}
|
||||
|
||||
String getDeleteById() {
|
||||
return deleteByIdSql;
|
||||
}
|
||||
|
||||
String getDeleteAll() {
|
||||
return deleteAllSql;
|
||||
}
|
||||
|
||||
String getDeleteByList() {
|
||||
return deleteByListSql;
|
||||
}
|
||||
|
||||
private String createFindOneSelectSql() {
|
||||
return String.format("select * from %s where %s = :id", entity.getTableName(), entity.getIdColumn());
|
||||
}
|
||||
|
||||
private String createFindAllSql() {
|
||||
return String.format("select * from %s", entity.getTableName());
|
||||
}
|
||||
|
||||
private String createFindAllInListSql() {
|
||||
return String.format("select * from %s where %s in (:ids)", entity.getTableName(), entity.getIdColumn());
|
||||
}
|
||||
|
||||
private String createExistsSql() {
|
||||
return String.format("select count(*) from %s where %s = :id", entity.getTableName(), entity.getIdColumn());
|
||||
}
|
||||
|
||||
private String createCountSql() {
|
||||
return String.format("select count(*) from %s", entity.getTableName());
|
||||
}
|
||||
|
||||
private String createInsertSql(boolean excludeId) {
|
||||
|
||||
String insertTemplate = "insert into %s (%s) values (%s)";
|
||||
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 String createUpdateSql() {
|
||||
|
||||
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() {
|
||||
return String.format("delete from %s where %s = :id", entity.getTableName(), entity.getIdColumn());
|
||||
}
|
||||
|
||||
private String createDeleteAllSql() {
|
||||
return String.format("delete from %s", entity.getTableName());
|
||||
}
|
||||
|
||||
private String createDeleteByListSql() {
|
||||
return String.format("delete from %s where %s in (:ids)", entity.getTableName(), entity.getIdColumn());
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,11 @@ package org.springframework.data.jdbc.repository.support;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.jdbc.mapping.context.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.core.JdbcEntityTemplate;
|
||||
import org.springframework.data.jdbc.mapping.model.BasicJdbcPersistentEntityInformation;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
|
||||
import org.springframework.data.jdbc.repository.JdbcEntityTemplate;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityInformation;
|
||||
import org.springframework.data.jdbc.repository.SimpleJdbcRepository;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
@@ -43,21 +45,18 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport {
|
||||
@Override
|
||||
public <T, ID> EntityInformation<T, ID> getEntityInformation(Class<T> aClass) {
|
||||
|
||||
JdbcPersistentEntityImpl<?> persistentEntity = context.getPersistentEntity(aClass);
|
||||
JdbcPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(aClass);
|
||||
if (persistentEntity == null)
|
||||
return null;
|
||||
return new BasicJdbcPersistentEntityInformation<T, ID>((JdbcPersistentEntity<T>) persistentEntity);
|
||||
return new BasicJdbcPersistentEntityInformation<>((JdbcPersistentEntity<T>) persistentEntity);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected Object getTargetRepository(RepositoryInformation repositoryInformation) {
|
||||
|
||||
JdbcPersistentEntity<?> persistentEntity = context //
|
||||
.getPersistentEntity(repositoryInformation.getDomainType()) //
|
||||
.orElseThrow(() -> new IllegalArgumentException("%s does not represent a persistent entity")); //
|
||||
JdbcPersistentEntityInformation persistentEntityInformation = context
|
||||
.getRequiredPersistentEntityInformation(persistentEntity.getType());
|
||||
.getRequiredPersistentEntityInformation(repositoryInformation.getDomainType());
|
||||
JdbcEntityTemplate template = new JdbcEntityTemplate(publisher, jdbcOperations, context);
|
||||
|
||||
return new SimpleJdbcRepository<>(template, persistentEntityInformation);
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import static java.util.Collections.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.testing.TestConfiguration;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.rules.SpringClassRule;
|
||||
import org.springframework.test.context.junit4.rules.SpringMethodRule;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link JdbcEntityTemplate}.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@Transactional
|
||||
public class JdbcEntityTemplateIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import(TestConfiguration.class)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
Class<?> testClass() {
|
||||
return JdbcEntityTemplateIntegrationTests.class;
|
||||
}
|
||||
|
||||
@Bean
|
||||
JdbcEntityOperations operations(ApplicationEventPublisher publisher,
|
||||
NamedParameterJdbcOperations namedParameterJdbcOperations) {
|
||||
|
||||
return new JdbcEntityTemplate(publisher, namedParameterJdbcOperations, new JdbcMappingContext());
|
||||
}
|
||||
}
|
||||
|
||||
@ClassRule public static final SpringClassRule classRule = new SpringClassRule();
|
||||
@Rule public SpringMethodRule methodRule = new SpringMethodRule();
|
||||
|
||||
@Autowired JdbcEntityOperations template;
|
||||
|
||||
LegoSet legoSet = createLegoSet();
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void saveAndLoadAnEntityWithReferencedEntityById() {
|
||||
|
||||
template.save(legoSet, LegoSet.class);
|
||||
|
||||
assertThat(legoSet.manual.id).describedAs("id of stored manual").isNotNull();
|
||||
|
||||
LegoSet reloadedLegoSet = template.findById(legoSet.getId(), LegoSet.class);
|
||||
|
||||
assertThat(reloadedLegoSet.manual).isNotNull();
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
|
||||
softly.assertThat(reloadedLegoSet.manual.getId()) //
|
||||
.isEqualTo(legoSet.getManual().getId()) //
|
||||
.isNotNull();
|
||||
softly.assertThat(reloadedLegoSet.manual.getContent()).isEqualTo(legoSet.getManual().getContent());
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void saveAndLoadManyEntitiesWithReferencedEntity() {
|
||||
|
||||
template.save(legoSet, LegoSet.class);
|
||||
|
||||
Iterable<LegoSet> reloadedLegoSets = template.findAll(LegoSet.class);
|
||||
|
||||
assertThat(reloadedLegoSets).hasSize(1).extracting("id", "manual.id", "manual.content")
|
||||
.contains(tuple(legoSet.getId(), legoSet.getManual().getId(), legoSet.getManual().getContent()));
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void saveAndLoadManyEntitiesByIdWithReferencedEntity() {
|
||||
|
||||
template.save(legoSet, LegoSet.class);
|
||||
|
||||
Iterable<LegoSet> reloadedLegoSets = template.findAllById(singletonList(legoSet.getId()), LegoSet.class);
|
||||
|
||||
assertThat(reloadedLegoSets).hasSize(1).extracting("id", "manual.id", "manual.content")
|
||||
.contains(tuple(legoSet.getId(), legoSet.getManual().getId(), legoSet.getManual().getContent()));
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void saveAndLoadAnEntityWithReferencedNullEntity() {
|
||||
|
||||
legoSet.setManual(null);
|
||||
|
||||
template.save(legoSet, LegoSet.class);
|
||||
|
||||
LegoSet reloadedLegoSet = template.findById(legoSet.getId(), LegoSet.class);
|
||||
|
||||
assertThat(reloadedLegoSet.manual).isNull();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void saveAndDeleteAnEntityWithReferencedEntity() {
|
||||
|
||||
template.save(legoSet, LegoSet.class);
|
||||
|
||||
template.delete(legoSet, LegoSet.class);
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
|
||||
softly.assertThat(template.findAll(LegoSet.class)).isEmpty();
|
||||
softly.assertThat(template.findAll(Manual.class)).isEmpty();
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void saveAndDeleteAllWithReferencedEntity() {
|
||||
|
||||
template.save(legoSet, LegoSet.class);
|
||||
|
||||
template.deleteAll(LegoSet.class);
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
|
||||
assertThat(template.findAll(LegoSet.class)).isEmpty();
|
||||
assertThat(template.findAll(Manual.class)).isEmpty();
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void updateReferencedEntityFromNull() {
|
||||
|
||||
legoSet.setManual(null);
|
||||
template.save(legoSet, LegoSet.class);
|
||||
|
||||
Manual manual = new Manual(23L);
|
||||
manual.setContent("Some content");
|
||||
legoSet.setManual(manual);
|
||||
|
||||
template.save(legoSet, LegoSet.class);
|
||||
|
||||
LegoSet reloadedLegoSet = template.findById(legoSet.getId(), LegoSet.class);
|
||||
|
||||
assertThat(reloadedLegoSet.manual.content).isEqualTo("Some content");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void updateReferencedEntityToNull() {
|
||||
|
||||
template.save(legoSet, LegoSet.class);
|
||||
|
||||
legoSet.setManual(null);
|
||||
|
||||
template.save(legoSet, LegoSet.class);
|
||||
|
||||
LegoSet reloadedLegoSet = template.findById(legoSet.getId(), LegoSet.class);
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
|
||||
softly.assertThat(reloadedLegoSet.manual).isNull();
|
||||
softly.assertThat(template.findAll(Manual.class)).describedAs("Manuals failed to delete").isEmpty();
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void replaceReferencedEntity() {
|
||||
|
||||
template.save(legoSet, LegoSet.class);
|
||||
|
||||
Manual manual = new Manual(null);
|
||||
manual.setContent("other content");
|
||||
legoSet.setManual(manual);
|
||||
|
||||
template.save(legoSet, LegoSet.class);
|
||||
|
||||
LegoSet reloadedLegoSet = template.findById(legoSet.getId(), LegoSet.class);
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
|
||||
softly.assertThat(reloadedLegoSet.manual.content).isEqualTo("other content");
|
||||
softly.assertThat(template.findAll(Manual.class)).describedAs("The should be only one manual").hasSize(1);
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void changeReferencedEntity() {
|
||||
|
||||
template.save(legoSet, LegoSet.class);
|
||||
|
||||
legoSet.manual.setContent("new content");
|
||||
|
||||
template.save(legoSet, LegoSet.class);
|
||||
|
||||
LegoSet reloadedLegoSet = template.findById(legoSet.getId(), LegoSet.class);
|
||||
|
||||
assertThat(reloadedLegoSet.manual.content).isEqualTo("new content");
|
||||
}
|
||||
|
||||
private static LegoSet createLegoSet() {
|
||||
|
||||
LegoSet entity = new LegoSet();
|
||||
entity.setName("Star Destroyer");
|
||||
|
||||
Manual manual = new Manual(null);
|
||||
manual.setContent("Accelerates to 99% of light speed. Destroys almost everything. See https://what-if.xkcd.com/1/");
|
||||
entity.setManual(manual);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class LegoSet {
|
||||
|
||||
@Id private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
private Manual manual;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class Manual {
|
||||
@Id private final Long id;
|
||||
|
||||
private String content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link SelectBuilder}.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class SelectBuilderUnitTests {
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void simplestSelect() {
|
||||
|
||||
String sql = new SelectBuilder("mytable") //
|
||||
.column(cb -> cb.tableAlias("mytable").column("mycolumn").as("myalias")) //
|
||||
.build();
|
||||
|
||||
assertThat(sql).isEqualTo("SELECT mytable.mycolumn AS myalias FROM mytable");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void columnWithoutTableAlias() {
|
||||
|
||||
String sql = new SelectBuilder("mytable") //
|
||||
.column(cb -> cb.column("mycolumn").as("myalias")) //
|
||||
.build();
|
||||
|
||||
assertThat(sql).isEqualTo("SELECT mycolumn AS myalias FROM mytable");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void whereClause() {
|
||||
|
||||
String sql = new SelectBuilder("mytable") //
|
||||
.column(cb -> cb.tableAlias("mytable").column("mycolumn").as("myalias")) //
|
||||
.where(cb -> cb.tableAlias("mytable").column("mycolumn").eq().variable("var")).build();
|
||||
|
||||
assertThat(sql).isEqualTo("SELECT mytable.mycolumn AS myalias FROM mytable WHERE mytable.mycolumn = :var");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void multipleColumnsSelect() {
|
||||
|
||||
String sql = new SelectBuilder("mytable") //
|
||||
.column(cb -> cb.tableAlias("mytable").column("one").as("oneAlias")) //
|
||||
.column(cb -> cb.tableAlias("mytable").column("two").as("twoAlias")) //
|
||||
.build();
|
||||
|
||||
assertThat(sql).isEqualTo("SELECT mytable.one AS oneAlias, mytable.two AS twoAlias FROM mytable");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void join() {
|
||||
String sql = new SelectBuilder("mytable") //
|
||||
.column(cb -> cb.tableAlias("mytable").column("mycolumn").as("myalias")) //
|
||||
.join(jb -> jb.table("other").as("o").where("oid").eq().column("mytable", "id")).build();
|
||||
|
||||
assertThat(sql).isEqualTo("SELECT mytable.mycolumn AS myalias FROM mytable JOIN other AS o ON o.oid = mytable.id");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void outerJoin() {
|
||||
String sql = new SelectBuilder("mytable") //
|
||||
.column(cb -> cb.tableAlias("mytable").column("mycolumn").as("myalias")) //
|
||||
.join(jb -> jb.rightOuter().table("other").as("o").where("oid").eq().column("mytable", "id")).build();
|
||||
|
||||
assertThat(sql)
|
||||
.isEqualTo("SELECT mytable.mycolumn AS myalias FROM mytable RIGHT OUTER JOIN other AS o ON o.oid = mytable.id");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link SqlGenerator}.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class SqlGeneratorUnitTests {
|
||||
|
||||
JdbcMappingContext context = new JdbcMappingContext();
|
||||
JdbcPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class);
|
||||
SqlGenerator sqlGenerator = new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context));
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void findOne() {
|
||||
|
||||
String sql = sqlGenerator.getFindOne();
|
||||
|
||||
new SoftAssertions().assertThat(sql) //
|
||||
.startsWith("SELECT") //
|
||||
.contains("DummyEntity.id as id,") //
|
||||
.contains("DummyEntity.name as name,") //
|
||||
.contains("ref.id AS ref_id") //
|
||||
.contains("ref.content AS ref_content").contains(" FROM DummyEntity");
|
||||
new SoftAssertions().assertAll();
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void cascadingDeleteFirstLevel() {
|
||||
|
||||
String sql = sqlGenerator.createDeleteByPath(PropertyPath.from("ref", DummyEntity.class));
|
||||
|
||||
assertThat(sql).isEqualTo("DELETE FROM ReferencedEntity WHERE DummyEntity = :rootId");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void cascadingDeleteAllSecondLevel() {
|
||||
|
||||
String sql = sqlGenerator.createDeleteByPath(PropertyPath.from("ref.further", DummyEntity.class));
|
||||
|
||||
assertThat(sql).isEqualTo(
|
||||
"DELETE FROM SecondLevelReferencedEntity WHERE ReferencedEntity IN (SELECT l1id FROM ReferencedEntity WHERE DummyEntity = :rootId)");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void deleteAll() {
|
||||
|
||||
String sql = sqlGenerator.createDeleteAllSql(null);
|
||||
|
||||
assertThat(sql).isEqualTo("DELETE FROM DummyEntity");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void cascadingDeleteAllFirstLevel() {
|
||||
|
||||
String sql = sqlGenerator.createDeleteAllSql(PropertyPath.from("ref", DummyEntity.class));
|
||||
|
||||
assertThat(sql).isEqualTo("DELETE FROM ReferencedEntity WHERE DummyEntity IS NOT NULL");
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void cascadingDeleteSecondLevel() {
|
||||
|
||||
String sql = sqlGenerator.createDeleteAllSql(PropertyPath.from("ref.further", DummyEntity.class));
|
||||
|
||||
assertThat(sql).isEqualTo(
|
||||
"DELETE FROM SecondLevelReferencedEntity WHERE ReferencedEntity IN (SELECT l1id FROM ReferencedEntity WHERE DummyEntity IS NOT NULL)");
|
||||
}
|
||||
|
||||
static class DummyEntity {
|
||||
|
||||
@Id Long id;
|
||||
String name;
|
||||
ReferencedEntity ref;
|
||||
}
|
||||
|
||||
static class ReferencedEntity {
|
||||
|
||||
@Id Long l1id;
|
||||
String content;
|
||||
SecondLevelReferencedEntity further;
|
||||
}
|
||||
|
||||
static class SecondLevelReferencedEntity {
|
||||
|
||||
@Id Long l2id;
|
||||
String something;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.core.conversion;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.assertj.core.groups.Tuple;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.core.conversion.DbAction.Delete;
|
||||
import org.springframework.data.jdbc.core.conversion.DbChange.Kind;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link JdbcEntityDeleteWriter}
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class JdbcEntityDeleteWriterUnitTests {
|
||||
|
||||
JdbcEntityDeleteWriter converter = new JdbcEntityDeleteWriter(new JdbcMappingContext());
|
||||
|
||||
@Test
|
||||
public void deleteDeletesTheEntityAndReferencedEntities() {
|
||||
|
||||
SomeEntity entity = new SomeEntity(23L);
|
||||
|
||||
DbChange<SomeEntity> dbChange = new DbChange(Kind.DELETE, SomeEntity.class, entity);
|
||||
|
||||
converter.write(entity, dbChange);
|
||||
|
||||
Assertions.assertThat(dbChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType)
|
||||
.containsExactly( //
|
||||
Tuple.tuple(Delete.class, YetAnother.class), //
|
||||
Tuple.tuple(Delete.class, OtherEntity.class), //
|
||||
Tuple.tuple(Delete.class, SomeEntity.class) //
|
||||
);
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class SomeEntity {
|
||||
|
||||
@Id final Long id;
|
||||
OtherEntity other;
|
||||
// should not trigger own Dbaction
|
||||
String name;
|
||||
}
|
||||
|
||||
@Data
|
||||
private class OtherEntity {
|
||||
|
||||
@Id final Long id;
|
||||
YetAnother yetAnother;
|
||||
}
|
||||
|
||||
@Data
|
||||
private class YetAnother {
|
||||
@Id final Long id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.core.conversion;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.core.conversion.DbAction.Delete;
|
||||
import org.springframework.data.jdbc.core.conversion.DbAction.Insert;
|
||||
import org.springframework.data.jdbc.core.conversion.DbAction.Update;
|
||||
import org.springframework.data.jdbc.core.conversion.DbChange.Kind;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link JdbcEntityWriter}
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class JdbcEntityWriterUnitTests {
|
||||
|
||||
JdbcEntityWriter converter = new JdbcEntityWriter(new JdbcMappingContext());
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void newEntityGetsConvertedToOneInsert() {
|
||||
|
||||
SomeEntity entity = new SomeEntity(null);
|
||||
DbChange<SomeEntity> dbChange = new DbChange(Kind.SAVE, SomeEntity.class, entity);
|
||||
converter.write(entity, dbChange);
|
||||
|
||||
assertThat(dbChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType) //
|
||||
.containsExactly( //
|
||||
tuple(Insert.class, SomeEntity.class) //
|
||||
);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void existingEntityGetsConvertedToUpdate() {
|
||||
|
||||
SomeEntity entity = new SomeEntity(23L);
|
||||
|
||||
DbChange<SomeEntity> dbChange = new DbChange(Kind.SAVE, SomeEntity.class, entity);
|
||||
|
||||
converter.write(entity, dbChange);
|
||||
|
||||
assertThat(dbChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType) //
|
||||
.containsExactly( //
|
||||
tuple(Delete.class, OtherEntity.class), //
|
||||
tuple(Update.class, SomeEntity.class) //
|
||||
);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void referenceTriggersDeletePlusInsert() {
|
||||
|
||||
SomeEntity entity = new SomeEntity(23L);
|
||||
entity.setOther(new OtherEntity(null));
|
||||
|
||||
DbChange<SomeEntity> dbChange = new DbChange(Kind.SAVE, SomeEntity.class, entity);
|
||||
|
||||
converter.write(entity, dbChange);
|
||||
|
||||
assertThat(dbChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType) //
|
||||
.containsExactly( //
|
||||
tuple(Delete.class, OtherEntity.class), //
|
||||
tuple(Update.class, SomeEntity.class), //
|
||||
tuple(Insert.class, OtherEntity.class) //
|
||||
);
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class SomeEntity {
|
||||
|
||||
@Id final Long id;
|
||||
OtherEntity other;
|
||||
// should not trigger own Dbaction
|
||||
String name;
|
||||
}
|
||||
|
||||
@Data
|
||||
private class OtherEntity {
|
||||
@Id final Long id;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,35 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.mapping.model;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.jdbc.mapping.context.JdbcMappingContext;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
|
||||
/**
|
||||
* Unti tests for the {@link BasicJdbcPersistentProperty}.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class BasicJdbcPersistentPropertyUnitTests {
|
||||
@@ -42,6 +58,7 @@ public class BasicJdbcPersistentPropertyUnitTests {
|
||||
|
||||
@Data
|
||||
private static class DummyEntity {
|
||||
|
||||
private final SomeEnum someEnum;
|
||||
private final LocalDateTime localDateTime;
|
||||
private final ZonedDateTime zonedDateTime;
|
||||
|
||||
@@ -15,14 +15,15 @@
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -30,8 +31,9 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.core.EventPublishingEntityRowMapper;
|
||||
import org.springframework.data.jdbc.mapping.event.AfterCreation;
|
||||
import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation;
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityInformation;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
/**
|
||||
@@ -41,7 +43,7 @@ import org.springframework.jdbc.core.RowMapper;
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class EventPublishingEntityRowMapperTest {
|
||||
public class EventPublishingEntityRowMapperUnitTests {
|
||||
|
||||
@Mock RowMapper<DummyEntity> rowMapperDelegate;
|
||||
@Mock JdbcPersistentEntityInformation<DummyEntity, Long> entityInformation;
|
||||
@@ -51,7 +53,7 @@ public class EventPublishingEntityRowMapperTest {
|
||||
public void eventGetsPublishedAfterInstantiation() throws SQLException {
|
||||
|
||||
when(rowMapperDelegate.mapRow(any(ResultSet.class), anyInt())).thenReturn(new DummyEntity(1L));
|
||||
when(entityInformation.getId(any())).thenReturn(1L);
|
||||
when(entityInformation.getRequiredId(any())).thenReturn(1L);
|
||||
|
||||
EventPublishingEntityRowMapper<?> rowMapper = new EventPublishingEntityRowMapper<>(rowMapperDelegate,
|
||||
entityInformation, publisher);
|
||||
@@ -67,7 +67,7 @@ public class EnableJdbcRepositoriesIntegrationTests {
|
||||
|
||||
@Bean
|
||||
Class<?> testClass() {
|
||||
return JdbcRepositoryIntegrationTests.class;
|
||||
return EnableJdbcRepositoriesIntegrationTests.class;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
CREATE TABLE dummyentity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY)
|
||||
@@ -7,9 +7,11 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.springframework.data" level="error" />
|
||||
<logger name="org.springframework.data" level="info" />
|
||||
|
||||
<root level="error">
|
||||
<logger name="org.springframework.jdbc.core.JdbcTemplate" level="debug" />
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="console" />
|
||||
</root>
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE LEGOSET ( id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(30));
|
||||
CREATE TABLE MANUAL ( id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, LEGOSET BIGINT, CONTENT VARCHAR(2000));
|
||||
|
||||
ALTER TABLE MANUAL ADD FOREIGN KEY (LEGOSET)
|
||||
REFERENCES LEGOSET(id);
|
||||
@@ -0,0 +1 @@
|
||||
CREATE TABLE DummyEntity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY)
|
||||
Reference in New Issue
Block a user