diff --git a/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java b/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java index 82b690fa..5e9ed7fb 100644 --- a/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java @@ -200,11 +200,11 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { Class actualType = property.getActualType(); String findAllByProperty = sql(actualType).getFindAllByProperty(property.getReverseColumnName(), - property.getKeyColumn()); + property.getKeyColumn(), property.isOrdered()); MapSqlParameterSource parameter = new MapSqlParameterSource(property.getReverseColumnName(), rootId); - return (Iterable) operations.query(findAllByProperty, parameter, property.isQualified() // + return (Iterable) operations.query(findAllByProperty, parameter, property.isMap() // ? getMapEntityRowMapper(property) // : getEntityRowMapper(actualType)); } diff --git a/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java index 24f60df4..a6b76f30 100644 --- a/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java +++ b/src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java @@ -19,10 +19,8 @@ import lombok.NonNull; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.Map; -import java.util.Set; - import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.ClassGeneratingEntityInstantiator; import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; @@ -45,6 +43,8 @@ import org.springframework.jdbc.core.RowMapper; */ public class EntityRowMapper implements RowMapper { + private static final Converter ITERABLE_OF_ENTRY_TO_MAP_CONVERTER = new IterableOfEntryToMapConverter(); + private final JdbcPersistentEntity entity; private final EntityInstantiator instantiator = new ClassGeneratingEntityInstantiator(); private final ConversionService conversions; @@ -79,13 +79,12 @@ public class EntityRowMapper implements RowMapper { for (JdbcPersistentProperty property : entity) { - if (Set.class.isAssignableFrom(property.getType())) { + if (property.isCollectionLike()) { propertyAccessor.setProperty(property, accessStrategy.findAllByProperty(id, property)); - } else if (Map.class.isAssignableFrom(property.getType())) { + } else if (property.isMap()) { Iterable allByProperty = accessStrategy.findAllByProperty(id, property); - IterableOfEntryToMapConverter converter = new IterableOfEntryToMapConverter(); - propertyAccessor.setProperty(property, converter.convert(allByProperty)); + propertyAccessor.setProperty(property, ITERABLE_OF_ENTRY_TO_MAP_CONVERTER.convert(allByProperty)); } else { propertyAccessor.setProperty(property, readFrom(resultSet, property, "")); } diff --git a/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java b/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java index ebd60468..a6a003ff 100644 --- a/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java +++ b/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java @@ -95,17 +95,22 @@ class SqlGenerator { * a referencing entity. * * @param columnName name of the column of the FK back to the referencing entity. - * @param keyColumn if the property is of type {@link Map} this column contains the map key. + * @param keyColumn if the property is of type {@link Map} this column contains the map key. + * @param ordered whether the SQL statement should include an ORDER BY for the keyColumn. If this is {@literal true}, the keyColumn must not be {@literal null}. * @return a SQL String. */ - String getFindAllByProperty(String columnName, String keyColumn) { + String getFindAllByProperty(String columnName, String keyColumn, boolean ordered) { + + Assert.isTrue(keyColumn != null || !ordered, "If the SQL statement should be ordered a keyColumn to order by must be provided."); String baseSelect = (keyColumn != null) // ? createSelectBuilder().column(cb -> cb.tableAlias(entity.getTableName()).column(keyColumn).as(keyColumn)) - .build() + .build() : getFindAll(); - return String.format("%s WHERE %s = :%s", baseSelect, columnName, columnName); + String orderBy = ordered ? " ORDER BY " + keyColumn : ""; + + return String.format("%s WHERE %s = :%s%s", baseSelect, columnName, columnName, orderBy); } String getExists() { diff --git a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriter.java b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriter.java index 4f5e2345..bd19212d 100644 --- a/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriter.java +++ b/src/main/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriter.java @@ -16,12 +16,6 @@ package org.springframework.data.jdbc.core.conversion; import lombok.RequiredArgsConstructor; - -import java.util.Collection; -import java.util.Map; -import java.util.Map.Entry; -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; @@ -33,6 +27,13 @@ import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.util.StreamUtils; import org.springframework.util.ClassUtils; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Stream; + /** * Converts an entity that is about to be saved into {@link DbAction}s inside a {@link AggregateChange} that need to be * executed against the database to recreate the appropriate state in the database. @@ -157,6 +158,10 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport { Class type = p.getType(); + if (List.class.isAssignableFrom(type)) { + return listPropertyAsStream(p, propertyAccessor); + } + if (Collection.class.isAssignableFrom(type)) { return collectionPropertyAsStream(p, propertyAccessor); } @@ -178,6 +183,21 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport { : ((Collection) property).stream(); } + private Stream listPropertyAsStream(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) { + + Object property = propertyAccessor.getProperty(p); + + if (property == null) return Stream.empty(); + + List listProperty = (List) property; + HashMap map = new HashMap<>(); + for (int i = 0; i < listProperty.size(); i++) { + map.put(i, listProperty.get(i)); + } + + return map.entrySet().stream().map(e -> (Object) e); + } + private Stream mapPropertyAsStream(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) { Object property = propertyAccessor.getProperty(p); diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java b/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java index b7895b77..a276d23f 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java @@ -15,12 +15,6 @@ */ package org.springframework.data.jdbc.mapping.model; -import java.time.ZonedDateTime; -import java.time.temporal.Temporal; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.Map; - import org.springframework.data.mapping.Association; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; @@ -29,6 +23,13 @@ import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import java.time.ZonedDateTime; +import java.time.temporal.Temporal; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + /** * Meta data about a property to be used by repository implementations. * @@ -114,7 +115,16 @@ public class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProper @Override public boolean isQualified() { - return isMap(); + return isMap() || isListLike(); + } + + private boolean isListLike() { + return isCollectionLike() && !Set.class.isAssignableFrom(this.getType()); + } + + @Override + public boolean isOrdered() { + return isListLike(); } private Class columnTypeIfEntity(Class type) { diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java index 87d7e5fc..b35edeb8 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java @@ -51,4 +51,9 @@ public interface JdbcPersistentProperty extends PersistentProperty e.id, e -> e.name, e -> e.children.size()) // + .containsExactly(ID_FOR_ENTITY_REFERENCING_LIST, "alpha", 2); + } + private EntityRowMapper createRowMapper(Class type) { JdbcMappingContext context = new JdbcMappingContext(mock(NamedParameterJdbcOperations.class)); @@ -130,6 +146,11 @@ public class EntityRowMapperUnitTests { new SimpleEntry("two", new Trivial()) // ))).when(accessStrategy).findAllByProperty(eq(ID_FOR_ENTITY_REFERENCING_MAP), any(JdbcPersistentProperty.class)); + doReturn(new HashSet<>(asList( // + new SimpleEntry(1, new Trivial()), // + new SimpleEntry(2, new Trivial()) // + ))).when(accessStrategy).findAllByProperty(eq(ID_FOR_ENTITY_REFERENCING_LIST), any(JdbcPersistentProperty.class)); + GenericConversionService conversionService = new GenericConversionService(); conversionService.addConverter(new IterableOfEntryToMapConverter()); DefaultConversionService.addDefaultConverters(conversionService); @@ -261,4 +282,13 @@ public class EntityRowMapperUnitTests { String name; Map children; } + + @RequiredArgsConstructor + static class OneToList { + + @Id + Long id; + String name; + List children; + } } diff --git a/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java index 41db7987..3ec255a5 100644 --- a/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java @@ -115,7 +115,7 @@ public class SqlGeneratorUnitTests { public void findAllByProperty() { // this would get called when DummyEntity is the element type of a Set - String sql = sqlGenerator.getFindAllByProperty("back-ref", null); + String sql = sqlGenerator.getFindAllByProperty("back-ref", null, false); assertThat(sql).isEqualTo("SELECT DummyEntity.x_id AS x_id, DummyEntity.x_name AS x_name, " + "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, ref.x_further AS ref_x_further " @@ -127,7 +127,7 @@ public class SqlGeneratorUnitTests { public void findAllByPropertyWithKey() { // this would get called when DummyEntity is th element type of a Map - String sql = sqlGenerator.getFindAllByProperty("back-ref", "key-column"); + String sql = sqlGenerator.getFindAllByProperty("back-ref", "key-column", false); assertThat(sql).isEqualTo("SELECT DummyEntity.x_id AS x_id, DummyEntity.x_name AS x_name, " + "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, ref.x_further AS ref_x_further, " @@ -136,6 +136,26 @@ public class SqlGeneratorUnitTests { + "WHERE back-ref = :back-ref"); } + @Test (expected = IllegalArgumentException.class) // DATAJDBC-130 + public void findAllByPropertyOrderedWithoutKey() { + String sql = sqlGenerator.getFindAllByProperty("back-ref", null, true); + } + + @Test // DATAJDBC-131 + public void findAllByPropertyWithKeyOrdered() { + + // this would get called when DummyEntity is th element type of a Map + String sql = sqlGenerator.getFindAllByProperty("back-ref", "key-column", true); + + assertThat(sql).isEqualTo("SELECT DummyEntity.x_id AS x_id, DummyEntity.x_name AS x_name, " + + "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, ref.x_further AS ref_x_further, " + + "DummyEntity.key-column AS key-column " + + "FROM DummyEntity LEFT OUTER JOIN ReferencedEntity AS ref ON ref.DummyEntity = DummyEntity.x_id " + + "WHERE back-ref = :back-ref " + + "ORDER BY key-column" + ); + } + @SuppressWarnings("unused") static class DummyEntity { diff --git a/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java index 0b45c71d..f37fb2f3 100644 --- a/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java @@ -20,8 +20,10 @@ import static org.mockito.Mockito.*; import lombok.RequiredArgsConstructor; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -167,7 +169,7 @@ public class JdbcEntityWriterUnitTests { public void newEntityWithEmptyMapResultsInSingleInsert() { MapContainer entity = new MapContainer(null); - AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, MapContainer.class, entity); + AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, MapContainer.class, entity); converter.write(entity, aggregateChange); @@ -183,7 +185,7 @@ public class JdbcEntityWriterUnitTests { entity.elements.put("one", new Element(null)); entity.elements.put("two", new Element(null)); - AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, SetContainer.class, entity); + AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, MapContainer.class, entity); converter.write(entity, aggregateChange); assertThat(aggregateChange.getActions()) @@ -193,15 +195,53 @@ public class JdbcEntityWriterUnitTests { tuple(Insert.class, Element.class, "one", "elements"), // tuple(Insert.class, Element.class, "two", "elements") // ).containsSubsequence( // container comes before the elements - tuple(Insert.class, MapContainer.class, null, ""), // - tuple(Insert.class, Element.class, "two", "elements") // - ).containsSubsequence( // container comes before the elements - tuple(Insert.class, MapContainer.class, null, ""), // - tuple(Insert.class, Element.class, "one", "elements") // + tuple(Insert.class, MapContainer.class, null, ""), // + tuple(Insert.class, Element.class, "two", "elements") // + ).containsSubsequence( // container comes before the elements + tuple(Insert.class, MapContainer.class, null, ""), // + tuple(Insert.class, Element.class, "one", "elements") // ); } - @Test // DATAJDBC-112 + @Test // DATAJDBC-130 + public void newEntityWithEmptyListResultsInSingleInsert() { + + ListContainer entity = new ListContainer(null); + AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, ListContainer.class, entity); + + converter.write(entity, aggregateChange); + + assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath) // + .containsExactly( // + tuple(Insert.class, ListContainer.class, "")); + } + + @Test // DATAJDBC-130 + public void newEntityWithListResultsInAdditionalInsertPerElement() { + + ListContainer entity = new ListContainer(null); + entity.elements.add( new Element(null)); + entity.elements.add( new Element(null)); + + AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, ListContainer.class, entity); + converter.write(entity, aggregateChange); + + assertThat(aggregateChange.getActions()) + .extracting(DbAction::getClass, DbAction::getEntityType, this::getListKey, this::extractPath) // + .containsExactlyInAnyOrder( // + tuple(Insert.class, ListContainer.class, null, ""), // + tuple(Insert.class, Element.class, 0, "elements"), // + tuple(Insert.class, Element.class, 1, "elements") // + ).containsSubsequence( // container comes before the elements + tuple(Insert.class, ListContainer.class, null, ""), // + tuple(Insert.class, Element.class, 1, "elements") // + ).containsSubsequence( // container comes before the elements + tuple(Insert.class, ListContainer.class, null, ""), // + tuple(Insert.class, Element.class, 0, "elements") // + ); + } + + @Test // DATAJDBC-131 public void mapTriggersDeletePlusInsert() { MapContainer entity = new MapContainer(SOME_ENTITY_ID); @@ -217,7 +257,26 @@ public class JdbcEntityWriterUnitTests { tuple(Delete.class, Element.class, null, "elements"), // tuple(Update.class, MapContainer.class, null, ""), // tuple(Insert.class, Element.class, "one", "elements") // - ); + ); + } + + @Test // DATAJDBC-130 + public void listTriggersDeletePlusInsert() { + + ListContainer entity = new ListContainer(SOME_ENTITY_ID); + entity.elements.add( new Element(null)); + + AggregateChange aggregateChange = new AggregateChange(Kind.SAVE, ListContainer.class, entity); + + converter.write(entity, aggregateChange); + + assertThat(aggregateChange.getActions()) // + .extracting(DbAction::getClass, DbAction::getEntityType, this::getListKey, this::extractPath) // + .containsExactly( // + tuple(Delete.class, Element.class, null, "elements"), // + tuple(Update.class, ListContainer.class, null, ""), // + tuple(Insert.class, Element.class, 0, "elements") // + ); } private CascadingReferenceMiddleElement createMiddleElement(Element first, Element second) { @@ -232,6 +291,10 @@ public class JdbcEntityWriterUnitTests { return a.getAdditionalValues().get("MapContainer_key"); } + private Object getListKey(DbAction a) { + return a.getAdditionalValues().get("ListContainer_key"); + } + private String extractPath(DbAction action) { return action.getPropertyPath().toDotPath(); } @@ -273,6 +336,13 @@ public class JdbcEntityWriterUnitTests { Map elements = new HashMap<>(); } + @RequiredArgsConstructor + private static class ListContainer { + + @Id final Long id; + List< Element> elements = new ArrayList<>(); + } + @RequiredArgsConstructor private static class Element { @Id final Long id; diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithListsIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithListsIntegrationTests.java new file mode 100644 index 00000000..23607d0f --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithListsIntegrationTests.java @@ -0,0 +1,236 @@ +/* + * Copyright 2017-2018 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 junit.framework.AssertionFailedError; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +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.repository.support.JdbcRepositoryFactory; +import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.repository.CrudRepository; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +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; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +/** + * Very simple use cases for creation and usage of JdbcRepositories for Entities that contain {@link List}s. + * + * @author Jens Schauder + */ +@ContextConfiguration +@Transactional +public class JdbcRepositoryWithListsIntegrationTests { + + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired + JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryWithListsIntegrationTests.class; + } + + @Bean + DummyEntityRepository dummyEntityRepository() { + return factory.getRepository(DummyEntityRepository.class); + } + } + + @ClassRule + public static final SpringClassRule classRule = new SpringClassRule(); + @Rule + public SpringMethodRule methodRule = new SpringMethodRule(); + + @Autowired + NamedParameterJdbcTemplate template; + @Autowired + DummyEntityRepository repository; + + @Test // DATAJDBC-130 + public void saveAndLoadEmptyList() { + + DummyEntity entity = repository.save(createDummyEntity()); + + assertThat(entity.id).isNotNull(); + + DummyEntity reloaded = repository.findById(entity.id).orElseThrow(AssertionFailedError::new); + + assertThat(reloaded.content) // + .isNotNull() // + .isEmpty(); + } + + @Test // DATAJDBC-130 + public void saveAndLoadNonEmptyList() { + + Element element1 = new Element(); + Element element2 = new Element(); + + DummyEntity entity = createDummyEntity(); + entity.content.add(element1); + entity.content.add(element2); + + entity = repository.save(entity); + + assertThat(entity.id).isNotNull(); + assertThat(entity.content).allMatch(v -> v.id != null); + + DummyEntity reloaded = repository.findById(entity.id).orElseThrow(AssertionFailedError::new); + + assertThat(reloaded.content) // + .isNotNull() // + .extracting(e -> e.id) // + .containsExactlyInAnyOrder(element1.id, element2.id); + } + + @Test // DATAJDBC-130 + public void findAllLoadsList() { + + Element element1 = new Element(); + Element element2 = new Element(); + + DummyEntity entity = createDummyEntity(); + entity.content.add(element1); + entity.content.add(element2); + + entity = repository.save(entity); + + assertThat(entity.id).isNotNull(); + assertThat(entity.content).allMatch(v -> v.id != null); + + Iterable reloaded = repository.findAll(); + + reloaded.forEach(de -> System.out.println("id " + de.id + " content " + de.content.iterator().next().content)); + + assertThat(reloaded) // + .extracting(e -> e.id, e -> e.content.size()) // + .containsExactly(tuple(entity.id, entity.content.size())); + } + + @Test // DATAJDBC-130 + public void updateList() { + + Element element1 = createElement("one"); + Element element2 = createElement("two"); + Element element3 = createElement("three"); + + DummyEntity entity = createDummyEntity(); + entity.content.add(element1); + entity.content.add(element2); + + entity = repository.save(entity); + + entity.content.remove(element1); + element2.content = "two changed"; + entity.content.add(element3); + + entity = repository.save(entity); + + assertThat(entity.id).isNotNull(); + assertThat(entity.content).allMatch(v -> v.id != null); + + DummyEntity reloaded = repository.findById(entity.id).orElseThrow(AssertionFailedError::new); + + // the elements got properly updated and reloaded + assertThat(reloaded.content) // + .isNotNull(); + + assertThat(reloaded.content) // + .extracting(e -> e.id, e -> e.content) // + .containsExactly( // + tuple(element2.id, "two changed"), // + tuple(element3.id, "three") // + ); + + Long count = template.queryForObject("SELECT count(1) FROM Element", new HashMap<>(), Long.class); + assertThat(count).isEqualTo(2); + } + + @Test // DATAJDBC-130 + public void deletingWithList() { + + Element element1 = createElement("one"); + Element element2 = createElement("two"); + + DummyEntity entity = createDummyEntity(); + entity.content.add(element1); + entity.content.add(element2); + + entity = repository.save(entity); + + repository.deleteById(entity.id); + + assertThat(repository.findById(entity.id)).isEmpty(); + + Long count = template.queryForObject("SELECT count(1) FROM Element", new HashMap<>(), Long.class); + assertThat(count).isEqualTo(0); + } + + private Element createElement(String content) { + + Element element = new Element(); + element.content = content; + return element; + } + + private static DummyEntity createDummyEntity() { + + DummyEntity entity = new DummyEntity(); + entity.setName("Entity Name"); + return entity; + } + + interface DummyEntityRepository extends CrudRepository { + } + + @Data + static class DummyEntity { + + @Id + private Long id; + String name; + List content = new ArrayList<>(); + + } + + @RequiredArgsConstructor + static class Element { + + @Id + private Long id; + String content; + } + +} diff --git a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithListsIntegrationTests-hsql.sql b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithListsIntegrationTests-hsql.sql new file mode 100644 index 00000000..8ef56e10 --- /dev/null +++ b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithListsIntegrationTests-hsql.sql @@ -0,0 +1,2 @@ +CREATE TABLE dummyentity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100)); +CREATE TABLE element (id BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, content VARCHAR(100), DummyEntity_key BIGINT, dummyentity BIGINT); diff --git a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithListsIntegrationTests-mysql.sql b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithListsIntegrationTests-mysql.sql new file mode 100644 index 00000000..7dad33bd --- /dev/null +++ b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithListsIntegrationTests-mysql.sql @@ -0,0 +1,2 @@ +CREATE TABLE dummyentity ( id BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100)); +CREATE TABLE element (id BIGINT AUTO_INCREMENT PRIMARY KEY, content VARCHAR(100), DummyEntity_key BIGINT,dummyentity BIGINT); diff --git a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithListsIntegrationTests-postgres.sql b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithListsIntegrationTests-postgres.sql new file mode 100644 index 00000000..80fd52e3 --- /dev/null +++ b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithListsIntegrationTests-postgres.sql @@ -0,0 +1,4 @@ +DROP TABLE element; +DROP TABLE dummyentity; +CREATE TABLE dummyentity ( id SERIAL PRIMARY KEY, NAME VARCHAR(100)); +CREATE TABLE element (id SERIAL PRIMARY KEY, content VARCHAR(100),dummyentity_key BIGINT, dummyentity BIGINT);