Browse Source

Introduce `@InsertOnlyProperty`.

You may now annotate properties of the aggregate root with `@InsertOnlyProperty`.
Properties annotated in such way will be written to the database only during insert operations, but they will not be updated afterwards.

Closes #637
Original pull request #1327
pull/1353/head
Jens Schauder 3 years ago
parent
commit
004804aad8
  1. 1894
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java
  2. 2
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlParametersFactory.java
  3. 23
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java
  4. 8
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-db2.sql
  5. 6
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-h2.sql
  6. 6
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql
  7. 6
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mariadb.sql
  8. 8
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mssql.sql
  9. 6
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql
  10. 8
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql
  11. 7
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql
  12. 8
      spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java
  13. 896
      spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplateUnitTests.java
  14. 5
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java
  15. 36
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/InsertOnlyProperty.java
  16. 9
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java
  17. 8
      src/main/asciidoc/jdbc.adoc

1894
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java

File diff suppressed because it is too large Load Diff

2
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlParametersFactory.java

@ -95,7 +95,7 @@ public class SqlParametersFactory { @@ -95,7 +95,7 @@ public class SqlParametersFactory {
*/
<T> SqlIdentifierParameterSource forUpdate(T instance, Class<T> domainType) {
return getParameterSource(instance, getRequiredPersistentEntity(domainType), "", Predicates.includeAll(),
return getParameterSource(instance, getRequiredPersistentEntity(domainType), "", RelationalPersistentProperty::isInsertOnly,
dialect.getIdentifierProcessing());
}

23
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java

@ -64,6 +64,7 @@ import org.springframework.data.jdbc.testing.TestConfiguration; @@ -64,6 +64,7 @@ import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.jdbc.testing.TestDatabaseFeatures;
import org.springframework.data.relational.core.conversion.DbActionExecutionException;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.InsertOnlyProperty;
import org.springframework.data.relational.core.mapping.MappedCollection;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.Table;
@ -1030,6 +1031,20 @@ class JdbcAggregateTemplateIntegrationTests { @@ -1030,6 +1031,20 @@ class JdbcAggregateTemplateIntegrationTests {
template.save(entity);
}
@Test // GH-637
void insertOnlyPropertyDoesNotGetUpdated() {
WithInsertOnly entity = new WithInsertOnly();
entity.insertOnly = "first value";
assertThat(template.save(entity).id).isNotNull();
entity.insertOnly = "second value";
template.save(entity);
assertThat(template.findById(entity.id, WithInsertOnly.class).insertOnly).isEqualTo("first value");
}
private <T extends Number> void saveAndUpdateAggregateWithVersion(VersionedAggregate aggregate,
Function<Number, T> toConcreteNumber) {
saveAndUpdateAggregateWithVersion(aggregate, toConcreteNumber, 0);
@ -1461,10 +1476,16 @@ class JdbcAggregateTemplateIntegrationTests { @@ -1461,10 +1476,16 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Table
class WithIdOnly {
static class WithIdOnly {
@Id Long id;
}
@Table
static class WithInsertOnly {
@Id Long id;
@InsertOnlyProperty
String insertOnly;
}
@Configuration
@Import(TestConfiguration.class)

8
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-db2.sql

@ -39,6 +39,8 @@ DROP TABLE WITH_LOCAL_DATE_TIME; @@ -39,6 +39,8 @@ DROP TABLE WITH_LOCAL_DATE_TIME;
DROP TABLE WITH_ID_ONLY;
DROP TABLE WITH_INSERT_ONLY;
CREATE TABLE LEGO_SET
(
"id1" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
@ -357,4 +359,10 @@ CREATE TABLE WITH_LOCAL_DATE_TIME @@ -357,4 +359,10 @@ CREATE TABLE WITH_LOCAL_DATE_TIME
CREATE TABLE WITH_ID_ONLY
(
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY
);
CREATE TABLE WITH_INSERT_ONLY
(
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
INSERT_ONLY VARCHAR(100)
);

6
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-h2.sql

@ -326,4 +326,10 @@ CREATE TABLE WITH_LOCAL_DATE_TIME @@ -326,4 +326,10 @@ CREATE TABLE WITH_LOCAL_DATE_TIME
CREATE TABLE WITH_ID_ONLY
(
ID SERIAL PRIMARY KEY
);
CREATE TABLE WITH_INSERT_ONLY
(
ID SERIAL PRIMARY KEY,
INSERT_ONLY VARCHAR(100)
);

6
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql

@ -325,6 +325,12 @@ CREATE TABLE WITH_LOCAL_DATE_TIME @@ -325,6 +325,12 @@ CREATE TABLE WITH_LOCAL_DATE_TIME
TEST_TIME TIMESTAMP(9)
);
CREATE TABLE WITH_INSERT_ONLY
(
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
INSERT_ONLY VARCHAR(100)
);
CREATE TABLE WITH_ID_ONLY
(
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY

6
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mariadb.sql

@ -301,4 +301,10 @@ CREATE TABLE WITH_LOCAL_DATE_TIME @@ -301,4 +301,10 @@ CREATE TABLE WITH_LOCAL_DATE_TIME
CREATE TABLE WITH_ID_ONLY
(
ID BIGINT AUTO_INCREMENT PRIMARY KEY
);
CREATE TABLE WITH_INSERT_ONLY
(
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
INSERT_ONLY VARCHAR(100)
);

8
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mssql.sql

@ -331,4 +331,12 @@ DROP TABLE IF EXISTS WITH_ID_ONLY; @@ -331,4 +331,12 @@ DROP TABLE IF EXISTS WITH_ID_ONLY;
CREATE TABLE WITH_ID_ONLY
(
ID BIGINT IDENTITY PRIMARY KEY
);
DROP TABLE IF EXISTS WITH_INSERT_ONLY;
CREATE TABLE WITH_INSERT_ONLY
(
ID BIGINT IDENTITY PRIMARY KEY,
INSERT_ONLY VARCHAR(100)
);

6
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql

@ -306,4 +306,10 @@ CREATE TABLE WITH_LOCAL_DATE_TIME @@ -306,4 +306,10 @@ CREATE TABLE WITH_LOCAL_DATE_TIME
CREATE TABLE WITH_ID_ONLY
(
ID BIGINT AUTO_INCREMENT PRIMARY KEY
);
CREATE TABLE WITH_INSERT_ONLY
(
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
INSERT_ONLY VARCHAR(100)
);

8
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql

@ -29,6 +29,7 @@ DROP TABLE VERSIONED_AGGREGATE CASCADE CONSTRAINTS PURGE; @@ -29,6 +29,7 @@ DROP TABLE VERSIONED_AGGREGATE CASCADE CONSTRAINTS PURGE;
DROP TABLE WITH_READ_ONLY CASCADE CONSTRAINTS PURGE;
DROP TABLE WITH_LOCAL_DATE_TIME CASCADE CONSTRAINTS PURGE;
DROP TABLE WITH_ID_ONLY CASCADE CONSTRAINTS PURGE;
DROP TABLE WITH_INSERT_ONLY CASCADE CONSTRAINTS PURGE;
CREATE TABLE LEGO_SET
(
@ -338,4 +339,11 @@ CREATE TABLE WITH_LOCAL_DATE_TIME @@ -338,4 +339,11 @@ CREATE TABLE WITH_LOCAL_DATE_TIME
CREATE TABLE WITH_ID_ONLY
(
ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY
);
CREATE TABLE WITH_INSERT_ONLY
(
ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY,
INSERT_ONLY VARCHAR(100)
);

7
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql

@ -13,6 +13,7 @@ DROP TABLE CHAIN1; @@ -13,6 +13,7 @@ DROP TABLE CHAIN1;
DROP TABLE CHAIN0;
DROP TABLE WITH_READ_ONLY;
DROP TABLE WITH_ID_ONLY;
DROP TABLE WITH_INSERT_ONLY;
CREATE TABLE LEGO_SET
(
@ -341,4 +342,10 @@ CREATE TABLE WITH_LOCAL_DATE_TIME @@ -341,4 +342,10 @@ CREATE TABLE WITH_LOCAL_DATE_TIME
CREATE TABLE WITH_ID_ONLY
(
ID SERIAL PRIMARY KEY
);
CREATE TABLE WITH_INSERT_ONLY
(
ID SERIAL PRIMARY KEY,
INSERT_ONLY VARCHAR(100)
);

8
spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java

@ -31,7 +31,6 @@ import java.util.function.Function; @@ -31,7 +31,6 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import org.reactivestreams.Publisher;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
@ -594,6 +593,13 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw @@ -594,6 +593,13 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw
SqlIdentifier idColumn = persistentEntity.getRequiredIdProperty().getColumnName();
Parameter id = outboundRow.remove(idColumn);
persistentEntity.forEach(p -> {
if (p.isInsertOnly()) {
outboundRow.remove(p.getColumnName());
}
});
Criteria criteria = Criteria.where(dataAccessStrategy.toSql(idColumn)).is(id);
if (matchingVersionCriteria != null) {

896
spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplateUnitTests.java

File diff suppressed because it is too large Load Diff

5
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java

@ -199,6 +199,11 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent @@ -199,6 +199,11 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent
return findAnnotation != null && OnEmpty.USE_EMPTY.equals(findAnnotation.onEmpty());
}
@Override
public boolean isInsertOnly() {
return findAnnotation(InsertOnlyProperty.class) != null;
}
private boolean isListLike() {
return isCollectionLike() && !Set.class.isAssignableFrom(this.getType());
}

36
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/InsertOnlyProperty.java

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
/*
* Copyright 2022 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
*
* https://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.relational.core.mapping;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* A property with this annotation will only be written to the database during insert operations, not during updates.
*
* @author Jens Schauder
* @since 3.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Documented
public @interface InsertOnlyProperty {
}

9
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java

@ -73,8 +73,13 @@ public interface RelationalPersistentProperty extends PersistentProperty<Relatio @@ -73,8 +73,13 @@ public interface RelationalPersistentProperty extends PersistentProperty<Relatio
/**
* Returns whether an empty embedded object is supposed to be created for this property.
*
* @return
*/
boolean shouldCreateEmptyEmbedded();
/**
* Returns whether this property is only to be used during inserts and read.
*
* @since 3.0
*/
boolean isInsertOnly();
}

8
src/main/asciidoc/jdbc.adoc

@ -435,6 +435,14 @@ Therefore, you have to reload it explicitly if you want to see data that was gen @@ -435,6 +435,14 @@ Therefore, you have to reload it explicitly if you want to see data that was gen
If the annotated attribute is an entity or collection of entities, it is represented by one or more separate rows in separate tables.
Spring Data JDBC will not perform any insert, delete or update for these rows.
[[jdbc.entity-persistence.insert-only-properties]]
=== Insert Only Properties
Attributes annotated with `@InsertOnlyProperty` will only be written to the database by Spring Data JDBC during insert operations.
For updates these properties will be ignored.
`@InsertOnlyProperty` is only supported for the aggregate root.
[[jdbc.entity-persistence.optimistic-locking]]
=== Optimistic Locking

Loading…
Cancel
Save