diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java index 3dbadfccd0..57c81d85a4 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/SqlGenerator.java @@ -15,15 +15,7 @@ */ package org.springframework.data.jdbc.core; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - +import org.springframework.data.annotation.ReadOnlyProperty; import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PropertyHandler; @@ -35,6 +27,16 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + /** * Generates SQL statements to be used by {@link SimpleJdbcRepository} * @@ -48,6 +50,7 @@ class SqlGenerator { private final RelationalMappingContext context; private final List columnNames = new ArrayList<>(); private final List nonIdColumnNames = new ArrayList<>(); + private final Set readOnlyColumnNames = new HashSet<>(); private final Lazy findOneSql = Lazy.of(this::createFindOneSelectSql); private final Lazy findAllSql = Lazy.of(this::createFindAllSql); @@ -93,6 +96,9 @@ private void initSimpleColumnName(RelationalPersistentProperty property, String if (!entity.isIdProperty(property)) { nonIdColumnNames.add(columnName); } + if (property.isAnnotationPresent(ReadOnlyProperty.class)) { + readOnlyColumnNames.add(columnName); + } } private void initEmbeddedColumnNames(RelationalPersistentProperty property, String prefix) { @@ -328,6 +334,7 @@ private String createInsertSql(Set additionalColumns) { LinkedHashSet columnNamesForInsert = new LinkedHashSet<>(nonIdColumnNames); columnNamesForInsert.addAll(additionalColumns); + columnNamesForInsert.removeIf(readOnlyColumnNames::contains); String tableColumns = String.join(", ", columnNamesForInsert); @@ -344,6 +351,7 @@ private String createUpdateSql() { String setClause = columnNames.stream() // .filter(s -> !s.equals(entity.getIdColumn())) // + .filter(s -> !readOnlyColumnNames.contains(s)) // .map(n -> String.format("%s = :%s", n, n)) // .collect(Collectors.joining(", ")); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java index 57517c86d5..6f2cb370d9 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java @@ -15,16 +15,11 @@ */ package org.springframework.data.jdbc.core; -import static java.util.Collections.*; -import static org.assertj.core.api.Assertions.*; - -import java.util.Map; -import java.util.Set; - import org.assertj.core.api.SoftAssertions; import org.junit.Before; import org.junit.Test; import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.ReadOnlyProperty; import org.springframework.data.jdbc.core.mapping.AggregateReference; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils; @@ -35,6 +30,12 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import java.util.Map; +import java.util.Set; + +import static java.util.Collections.emptySet; +import static org.assertj.core.api.Assertions.assertThat; + /** * Unit tests for the {@link SqlGenerator}. * @@ -226,6 +227,78 @@ public void update() { "id1 = :id"); } + @Test // DATAJDBC-324 + public void readOnlyPropertyExcludedFromQuery_when_generateUpdateSql() { + final SqlGenerator sqlGenerator = createSqlGenerator(EntityWithReadOnlyProperty.class); + assertThat(sqlGenerator.getUpdate()).isEqualToIgnoringCase( + "UPDATE entity_with_read_only_property " + + "SET x_name = :x_name " + + "WHERE x_id = :x_id" + ); + } + + @Test // DATAJDBC-324 + public void readOnlyPropertyExcludedFromQuery_when_generateInsertSql() { + final SqlGenerator sqlGenerator = createSqlGenerator(EntityWithReadOnlyProperty.class); + assertThat(sqlGenerator.getInsert(emptySet())).isEqualToIgnoringCase( + "INSERT INTO entity_with_read_only_property (x_name) " + + "VALUES (:x_name)" + ); + } + + @Test // DATAJDBC-324 + public void readOnlyPropertyIncludedIntoQuery_when_generateFindAllSql() { + final SqlGenerator sqlGenerator = createSqlGenerator(EntityWithReadOnlyProperty.class); + assertThat(sqlGenerator.getFindAll()).isEqualToIgnoringCase( + "SELECT " + + "entity_with_read_only_property.x_id AS x_id, " + + "entity_with_read_only_property.x_name AS x_name, " + + "entity_with_read_only_property.x_read_only_value AS x_read_only_value " + + "FROM entity_with_read_only_property" + ); + } + + @Test // DATAJDBC-324 + public void readOnlyPropertyIncludedIntoQuery_when_generateFindAllByPropertySql() { + final SqlGenerator sqlGenerator = createSqlGenerator(EntityWithReadOnlyProperty.class); + assertThat(sqlGenerator.getFindAllByProperty("back-ref", "key-column", true)).isEqualToIgnoringCase( + "SELECT " + + "entity_with_read_only_property.x_id AS x_id, " + + "entity_with_read_only_property.x_name AS x_name, " + + "entity_with_read_only_property.x_read_only_value AS x_read_only_value, " + + "entity_with_read_only_property.key-column AS key-column " + + "FROM entity_with_read_only_property " + + "WHERE back-ref = :back-ref " + + "ORDER BY key-column" + ); + } + + @Test // DATAJDBC-324 + public void readOnlyPropertyIncludedIntoQuery_when_generateFindAllInListSql() { + final SqlGenerator sqlGenerator = createSqlGenerator(EntityWithReadOnlyProperty.class); + assertThat(sqlGenerator.getFindAllInList()).isEqualToIgnoringCase( + "SELECT " + + "entity_with_read_only_property.x_id AS x_id, " + + "entity_with_read_only_property.x_name AS x_name, " + + "entity_with_read_only_property.x_read_only_value AS x_read_only_value " + + "FROM entity_with_read_only_property " + + "WHERE entity_with_read_only_property.x_id in(:ids)" + ); + } + + @Test // DATAJDBC-324 + public void readOnlyPropertyIncludedIntoQuery_when_generateFindOneSql() { + final SqlGenerator sqlGenerator = createSqlGenerator(EntityWithReadOnlyProperty.class); + assertThat(sqlGenerator.getFindOne()).isEqualToIgnoringCase( + "SELECT " + + "entity_with_read_only_property.x_id AS x_id, " + + "entity_with_read_only_property.x_name AS x_name, " + + "entity_with_read_only_property.x_read_only_value AS x_read_only_value " + + "FROM entity_with_read_only_property " + + "WHERE entity_with_read_only_property.x_id = :id" + ); + } + private PersistentPropertyPath getPath(String path, Class base) { return PersistentPropertyPathTestUtils.getPath(context, path, base); } @@ -289,4 +362,9 @@ static class IdOnlyEntity { @Id Long id; } + static class EntityWithReadOnlyProperty { + @Id Long id; + String name; + @ReadOnlyProperty String readOnlyValue; + } }