Skip to content

Commit 59e5d4c

Browse files
authored
Skip insert/update for @GeneratedValue annotated fields in @Embedded (#3344)
* Investigating option to not insert/update embedded @GeneratedValue fields * Fix update with generated fields in embedded * Remove temp project * Remove branches from build triggers * Trigger build
1 parent c9aa2a6 commit 59e5d4c

File tree

7 files changed

+139
-6
lines changed

7 files changed

+139
-6
lines changed

data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/embeddedAssociation/EmbeddedAssociationJoinSpec.groovy

+94
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.micronaut.data.jdbc.h2.embeddedAssociation
33
import io.micronaut.context.ApplicationContext
44
import io.micronaut.data.annotation.*
55
import io.micronaut.data.annotation.repeatable.JoinSpecifications
6+
import io.micronaut.data.connection.jdbc.advice.DelegatingDataSource
67
import io.micronaut.data.jdbc.annotation.JdbcRepository
78
import io.micronaut.data.jdbc.h2.H2DBProperties
89
import io.micronaut.data.jdbc.h2.H2TestPropertyProvider
@@ -11,6 +12,7 @@ import io.micronaut.data.model.Pageable
1112
import io.micronaut.data.model.Sort
1213
import io.micronaut.data.model.query.builder.sql.Dialect
1314
import io.micronaut.data.repository.CrudRepository
15+
import io.micronaut.data.repository.GenericRepository
1416
import io.micronaut.data.repository.jpa.JpaSpecificationExecutor
1517
import io.micronaut.data.repository.jpa.criteria.PredicateSpecification
1618
import io.micronaut.data.tck.entities.Order
@@ -21,6 +23,8 @@ import spock.lang.Specification
2123

2224
import jakarta.inject.Inject
2325

26+
import javax.sql.DataSource
27+
2428
@MicronautTest
2529
@H2DBProperties
2630
class EmbeddedAssociationJoinSpec extends Specification implements H2TestPropertyProvider {
@@ -40,6 +44,29 @@ class EmbeddedAssociationJoinSpec extends Specification implements H2TestPropert
4044
@Inject
4145
OneMainEntityEmRepository oneMainEntityEmRepository = applicationContext.getBean(OneMainEntityEmRepository)
4246

47+
@Shared
48+
@Inject
49+
MyMainEntityRepository myMainEntityRepository = applicationContext.getBean(MyMainEntityRepository)
50+
51+
void setup() {
52+
def dataSource = DelegatingDataSource.unwrapDataSource(applicationContext.getBean(DataSource))
53+
def connection = dataSource.connection
54+
connection.prepareStatement("DROP TABLE IF EXISTS `my_main_entity`").execute()
55+
connection.prepareStatement("""
56+
CREATE TABLE `my_main_entity` (
57+
`id` bigint primary key not null,
58+
`value` text,
59+
`example` text,
60+
`part_text` text);
61+
""").execute()
62+
}
63+
64+
void cleanup() {
65+
def dataSource = DelegatingDataSource.unwrapDataSource(applicationContext.getBean(DataSource))
66+
def connection = dataSource.connection
67+
connection.prepareStatement("DROP TABLE IF EXISTS `my_main_entity`")
68+
}
69+
4370
void 'test one-to-one update'() {
4471
given:
4572
ChildEntity child = new ChildEntity(name: "child")
@@ -122,6 +149,41 @@ class EmbeddedAssociationJoinSpec extends Specification implements H2TestPropert
122149
oem.id.one.em.assoc[0].name == "C"
123150
oem.id.one.em.assoc[1].name == "D"
124151
}
152+
153+
void 'test save/update embedded with @GeneratedValue'() {
154+
when:"should not update field 'example'"
155+
myMainEntityRepository.save(new MyMainEntity(id: 1L, example: "Test", value: "Val"))
156+
def persistedEntity = myMainEntityRepository.findById(1L).orElse(null)
157+
then:
158+
persistedEntity
159+
persistedEntity.value == "Val"
160+
!persistedEntity.example
161+
when:
162+
myMainEntityRepository.update(new MyMainEntity(id: 1L, example: "Changed", value: "Val-Changed"))
163+
def updatedEntity = myMainEntityRepository.findById(1L).orElse(null)
164+
then:
165+
updatedEntity
166+
updatedEntity.value == "Val-Changed"
167+
!updatedEntity.example
168+
169+
when:"should not update field 'part_text'"
170+
myMainEntityRepository.save(new MyMainEntity(id: 2L, value: "Val1", part: new MyPart(text: "Test")))
171+
persistedEntity = myMainEntityRepository.findById(2L).orElse(null)
172+
then:
173+
persistedEntity
174+
persistedEntity.value == "Val1"
175+
!persistedEntity.part.text
176+
when:
177+
myMainEntityRepository.update(new MyMainEntity(id: 2L, value: "Val2", part: new MyPart(text: "Changed")))
178+
updatedEntity = myMainEntityRepository.findById(2L).orElse(null)
179+
then:
180+
updatedEntity
181+
updatedEntity.value == "Val2"
182+
!updatedEntity.part.text
183+
184+
cleanup:
185+
myMainEntityRepository.deleteAll()
186+
}
125187
}
126188

127189
@JdbcRepository(dialect = Dialect.H2)
@@ -218,3 +280,35 @@ class MainEntityAssociation {
218280
Long id
219281
String name
220282
}
283+
284+
@MappedEntity("my_main_entity")
285+
class MyMainEntity {
286+
287+
@Id
288+
Long id
289+
290+
@GeneratedValue
291+
String example
292+
293+
String value
294+
295+
@Relation(value = Relation.Kind.EMBEDDED)
296+
MyPart part = new MyPart()
297+
}
298+
299+
@Embeddable
300+
class MyPart {
301+
@GeneratedValue
302+
String text
303+
}
304+
305+
@JdbcRepository(dialect = Dialect.H2)
306+
interface MyMainEntityRepository extends GenericRepository<MyMainEntity, Long> {
307+
Optional<MyMainEntity> findById(Long id)
308+
309+
MyMainEntity save(MyMainEntity entity)
310+
311+
MyMainEntity update(MyMainEntity entity)
312+
313+
void deleteAll()
314+
}

data-model/src/main/java/io/micronaut/data/model/PersistentEntityUtils.java

+22
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,28 @@ public static Optional<String> getPersistentPropertyPath(PersistentEntity entity
236236
return entity.getPath(path);
237237
}
238238

239+
/**
240+
* Checks whether a given property is considered generated based on its annotations and relationship with its owner property.
241+
*
242+
* @param entity the persistent entity that owns the property
243+
* @param ownerProperty the property that owns the given property. This means
244+
* when we are doing traversal in case it is an association. If not
245+
* an association then ownerProperty will be the same as property.
246+
* @param property the property to check
247+
* @return true if the property is considered generated, false otherwise
248+
*/
249+
public static boolean isPropertyGenerated(PersistentEntity entity, PersistentProperty ownerProperty, PersistentProperty property) {
250+
boolean generated = property.isGenerated();
251+
if (generated) {
252+
if (ownerProperty instanceof Association association) {
253+
generated = association.isEmbedded();
254+
} else if (!entity.equals(property.getOwner())) {
255+
generated = false;
256+
}
257+
}
258+
return generated;
259+
}
260+
239261
private static PersistentProperty getJoinColumnAssocIdentity(PersistentProperty property, PersistentEntity associatedEntity) {
240262
AnnotationMetadata propertyAnnotationMetadata = property.getAnnotationMetadata();
241263
AnnotationValue<JoinColumns> joinColumnsAnnotationValue = propertyAnnotationMetadata.getAnnotation(JoinColumns.class);

data-model/src/main/java/io/micronaut/data/model/query/builder/AbstractSqlLikeQueryBuilder.java

+7
Original file line numberDiff line numberDiff line change
@@ -1631,6 +1631,13 @@ public int getParameterIndex() {
16311631
QueryPropertyPath propertyPath = entry.getKey();
16321632
if (entry.getValue() instanceof BindingParameter bindingParameter) {
16331633
traversePersistentProperties(propertyPath.getAssociations(), propertyPath.getProperty(), (associations, property) -> {
1634+
1635+
boolean generated = PersistentEntityUtils.isPropertyGenerated(entity,
1636+
propertyPath.getProperty(), property);
1637+
if (generated) {
1638+
return;
1639+
}
1640+
16341641
String tableAlias = propertyPath.getTableAlias();
16351642
if (tableAlias != null) {
16361643
queryString.append(tableAlias).append(DOT);

data-model/src/main/java/io/micronaut/data/model/query/builder/sql/AbstractSqlLikeQueryBuilder2.java

+7
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,13 @@ public JsonDataType getJsonDataType() {
791791
QueryPropertyPath propertyPath = entry.getKey();
792792
if (entry.getValue() instanceof BindingParameter bindingParameter) {
793793
PersistentEntityUtils.traversePersistentProperties(propertyPath.getPropertyPath(), traverseEmbedded(), (associations, property) -> {
794+
795+
boolean generated = PersistentEntityUtils.isPropertyGenerated(entity,
796+
propertyPath.getProperty(), property);
797+
if (generated) {
798+
return;
799+
}
800+
794801
String tableAlias = propertyPath.getTableAlias();
795802
if (tableAlias != null) {
796803
queryString.append(tableAlias).append(DOT);

data-model/src/main/java/io/micronaut/data/model/query/builder/sql/SqlQueryBuilder.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import io.micronaut.data.model.Pageable;
4646
import io.micronaut.data.model.Pageable.Mode;
4747
import io.micronaut.data.model.PersistentEntity;
48+
import io.micronaut.data.model.PersistentEntityUtils;
4849
import io.micronaut.data.model.PersistentProperty;
4950
import io.micronaut.data.model.PersistentPropertyPath;
5051
import io.micronaut.data.model.naming.NamingStrategy;
@@ -1026,7 +1027,8 @@ public int getParameterIndex() {
10261027

10271028
for (PersistentProperty prop : persistentProperties) {
10281029
traversePersistentProperties(prop, (associations, property) -> {
1029-
if (prop.isGenerated()) {
1030+
boolean generated = PersistentEntityUtils.isPropertyGenerated(entity, prop, property);
1031+
if (generated) {
10301032
String columnName = getMappedName(namingStrategy, associations, property);
10311033
if (escape) {
10321034
columnName = quote(columnName);
@@ -1035,7 +1037,7 @@ public int getParameterIndex() {
10351037
return;
10361038
}
10371039

1038-
addWriteExpression(values, prop);
1040+
addWriteExpression(values, property);
10391041

10401042
String key = String.valueOf(values.size());
10411043
String[] path = asStringPath(associations, property);

data-model/src/main/java/io/micronaut/data/model/query/builder/sql/SqlQueryBuilder2.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -861,7 +861,8 @@ public JsonDataType getJsonDataType() {
861861

862862
for (PersistentProperty prop : persistentProperties) {
863863
PersistentEntityUtils.traversePersistentProperties(Collections.emptyList(), prop, (associations, property) -> {
864-
if (prop.isGenerated()) {
864+
boolean generated = PersistentEntityUtils.isPropertyGenerated(entity, prop, property);
865+
if (generated) {
865866
String columnName = getMappedName(namingStrategy, associations, property);
866867
if (escape) {
867868
columnName = quote(columnName);
@@ -870,7 +871,7 @@ public JsonDataType getJsonDataType() {
870871
return;
871872
}
872873

873-
addWriteExpression(values, prop);
874+
addWriteExpression(values, property);
874875

875876
String key = String.valueOf(values.size());
876877
String[] path = asStringPath(associations, property);

data-tck/src/main/java/io/micronaut/data/tck/entities/UuidChildEntity.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515
*/
1616
package io.micronaut.data.tck.entities;
1717

18-
import io.micronaut.data.annotation.GeneratedValue;
18+
import io.micronaut.data.annotation.AutoPopulated;
1919
import io.micronaut.data.annotation.Id;
2020
import io.micronaut.data.annotation.MappedEntity;
2121

2222
import java.util.UUID;
2323

2424
@MappedEntity
2525
public class UuidChildEntity {
26-
@GeneratedValue
26+
@AutoPopulated
2727
@Id
2828
private UUID uuid;
2929

0 commit comments

Comments
 (0)