Browse Source
This introduces the JdbcRepository interface which offers two additional methods beyond the CrudRepository: `insert` and `update`. Both methods skip the test if the aggregate is new and perform the respective operation. Especially `insert` is useful for saving new aggregates which are new but have an ID set by the client and not generated by the database. Original pull request: #107.pull/108/head
11 changed files with 1074 additions and 584 deletions
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
package org.springframework.data.jdbc.core; |
||||
|
||||
import org.springframework.data.repository.Repository; |
||||
|
||||
/** |
||||
* Jdbc repository for dedicated insert(), update() and upsert() sql functions. |
||||
* Other than {@link org.springframework.data.jdbc.repository.support.SimpleJdbcRepository} |
||||
* there should be bypassing of the isNew check. |
||||
* |
||||
* @author Thomas Lang |
||||
* @see <a href="https://jira.spring.io/browse/DATAJDBC-282">DATAJDBC-282</a> |
||||
*/ |
||||
public interface JdbcRepository<T, ID> extends Repository<T, ID> { |
||||
|
||||
<S extends T> S insert(S var1); |
||||
|
||||
<S extends T> S update(S var1); |
||||
} |
||||
@ -0,0 +1,274 @@
@@ -0,0 +1,274 @@
|
||||
package org.springframework.data.relational.core.conversion; |
||||
|
||||
import lombok.Value; |
||||
import org.springframework.data.convert.EntityWriter; |
||||
import org.springframework.data.mapping.PersistentProperty; |
||||
import org.springframework.data.mapping.PersistentPropertyPath; |
||||
import org.springframework.data.mapping.PersistentPropertyPaths; |
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext; |
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; |
||||
import org.springframework.data.util.Pair; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.Assert; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* Used as an abstract class to derive relational writer actions (save, insert, update). |
||||
* Implementations see {@link RelationalEntityWriter} and {@link RelationalEntityInsertWriter} |
||||
* |
||||
* @author Thomas Lang |
||||
*/ |
||||
abstract class AbstractRelationalEntityWriter implements EntityWriter<Object, AggregateChange<?>> { |
||||
|
||||
protected final RelationalMappingContext context; |
||||
|
||||
AbstractRelationalEntityWriter(RelationalMappingContext context) { |
||||
this.context = context; |
||||
} |
||||
|
||||
/** |
||||
* Holds context information for the current save operation. |
||||
*/ |
||||
class WritingContext { |
||||
|
||||
private final Object root; |
||||
private final Object entity; |
||||
private final Class<?> entityType; |
||||
private final PersistentPropertyPaths<?, RelationalPersistentProperty> paths; |
||||
private final Map<PathNode, DbAction> previousActions = new HashMap<>(); |
||||
private Map<PersistentPropertyPath<RelationalPersistentProperty>, List<PathNode>> nodesCache = new HashMap<>(); |
||||
|
||||
WritingContext(Object root, AggregateChange<?> aggregateChange) { |
||||
|
||||
this.root = root; |
||||
this.entity = aggregateChange.getEntity(); |
||||
this.entityType = aggregateChange.getEntityType(); |
||||
this.paths = context.findPersistentPropertyPaths(entityType, PersistentProperty::isEntity); |
||||
} |
||||
|
||||
/** |
||||
* Leaves out the isNew check as defined in #DATAJDBC-282 |
||||
* |
||||
* @return List of {@link DbAction}s |
||||
* @see <a href="https://jira.spring.io/browse/DATAJDBC-282">DAJDBC-282</a> |
||||
*/ |
||||
List<DbAction<?>> insert() { |
||||
|
||||
List<DbAction<?>> actions = new ArrayList<>(); |
||||
actions.add(setRootAction(new DbAction.InsertRoot<>(entity))); |
||||
actions.addAll(insertReferenced()); |
||||
return actions; |
||||
} |
||||
|
||||
/** |
||||
* Leaves out the isNew check as defined in #DATAJDBC-282 |
||||
* |
||||
* @return List of {@link DbAction}s |
||||
* @see <a href="https://jira.spring.io/browse/DATAJDBC-282">DAJDBC-282</a> |
||||
*/ |
||||
List<DbAction<?>> update() { |
||||
|
||||
List<DbAction<?>> actions = new ArrayList<>(deleteReferenced()); |
||||
actions.add(setRootAction(new DbAction.UpdateRoot<>(entity))); |
||||
actions.addAll(insertReferenced()); |
||||
return actions; |
||||
} |
||||
|
||||
List<DbAction<?>> save() { |
||||
|
||||
List<DbAction<?>> actions = new ArrayList<>(); |
||||
if (isNew(root)) { |
||||
|
||||
actions.add(setRootAction(new DbAction.InsertRoot<>(entity))); |
||||
actions.addAll(insertReferenced()); |
||||
} else { |
||||
|
||||
actions.addAll(deleteReferenced()); |
||||
actions.add(setRootAction(new DbAction.UpdateRoot<>(entity))); |
||||
actions.addAll(insertReferenced()); |
||||
} |
||||
|
||||
return actions; |
||||
} |
||||
|
||||
private boolean isNew(Object o) { |
||||
return context.getRequiredPersistentEntity(o.getClass()).isNew(o); |
||||
} |
||||
|
||||
//// Operations on all paths
|
||||
|
||||
private List<DbAction<?>> insertReferenced() { |
||||
|
||||
List<DbAction<?>> actions = new ArrayList<>(); |
||||
|
||||
paths.forEach(path -> actions.addAll(insertAll(path))); |
||||
|
||||
return actions; |
||||
} |
||||
|
||||
private List<DbAction<?>> insertAll(PersistentPropertyPath<RelationalPersistentProperty> path) { |
||||
|
||||
List<DbAction<?>> actions = new ArrayList<>(); |
||||
|
||||
from(path).forEach(node -> { |
||||
|
||||
DbAction.Insert<Object> insert; |
||||
if (node.path.getRequiredLeafProperty().isQualified()) { |
||||
|
||||
Pair<Object, Object> value = (Pair) node.getValue(); |
||||
insert = new DbAction.Insert<>(value.getSecond(), path, getAction(node.parent)); |
||||
insert.getAdditionalValues().put(node.path.getRequiredLeafProperty().getKeyColumn(), value.getFirst()); |
||||
|
||||
} else { |
||||
insert = new DbAction.Insert<>(node.getValue(), path, getAction(node.parent)); |
||||
} |
||||
|
||||
previousActions.put(node, insert); |
||||
actions.add(insert); |
||||
}); |
||||
|
||||
return actions; |
||||
} |
||||
|
||||
private List<DbAction<?>> deleteReferenced() { |
||||
|
||||
List<DbAction<?>> deletes = new ArrayList<>(); |
||||
paths.forEach(path -> deletes.add(0, deleteReferenced(path))); |
||||
|
||||
return deletes; |
||||
} |
||||
|
||||
/// Operations on a single path
|
||||
|
||||
private DbAction.Delete<?> deleteReferenced(PersistentPropertyPath<RelationalPersistentProperty> path) { |
||||
|
||||
Object id = context.getRequiredPersistentEntity(entityType).getIdentifierAccessor(entity).getIdentifier(); |
||||
|
||||
return new DbAction.Delete<>(id, path); |
||||
} |
||||
|
||||
//// methods not directly related to the creation of DbActions
|
||||
|
||||
private DbAction<?> setRootAction(DbAction<?> dbAction) { |
||||
|
||||
previousActions.put(null, dbAction); |
||||
return dbAction; |
||||
} |
||||
|
||||
@Nullable |
||||
private DbAction.WithEntity<?> getAction(@Nullable RelationalEntityInsertWriter.PathNode parent) { |
||||
|
||||
DbAction action = previousActions.get(parent); |
||||
|
||||
if (action != null) { |
||||
|
||||
Assert.isInstanceOf( //
|
||||
DbAction.WithEntity.class, //
|
||||
action, //
|
||||
"dependsOn action is not a WithEntity, but " + action.getClass().getSimpleName() //
|
||||
); |
||||
|
||||
return (DbAction.WithEntity<?>) action; |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
// commented as of #DATAJDBC-282
|
||||
// private boolean isNew(Object o) {
|
||||
// return context.getRequiredPersistentEntity(o.getClass()).isNew(o);
|
||||
// }
|
||||
|
||||
private List<RelationalEntityInsertWriter.PathNode> from( |
||||
PersistentPropertyPath<RelationalPersistentProperty> path) { |
||||
|
||||
List<RelationalEntityInsertWriter.PathNode> nodes = new ArrayList<>(); |
||||
|
||||
if (path.getLength() == 1) { |
||||
|
||||
Object value = context //
|
||||
.getRequiredPersistentEntity(entityType) //
|
||||
.getPropertyAccessor(entity) //
|
||||
.getProperty(path.getRequiredLeafProperty()); |
||||
|
||||
nodes.addAll(createNodes(path, null, value)); |
||||
|
||||
} else { |
||||
|
||||
List<RelationalEntityInsertWriter.PathNode> pathNodes = nodesCache.get(path.getParentPath()); |
||||
pathNodes.forEach(parentNode -> { |
||||
|
||||
Object value = path.getRequiredLeafProperty().getOwner().getPropertyAccessor(parentNode.getValue()) |
||||
.getProperty(path.getRequiredLeafProperty()); |
||||
|
||||
nodes.addAll(createNodes(path, parentNode, value)); |
||||
}); |
||||
} |
||||
|
||||
nodesCache.put(path, nodes); |
||||
|
||||
return nodes; |
||||
} |
||||
|
||||
private List<RelationalEntityInsertWriter.PathNode> createNodes( |
||||
PersistentPropertyPath<RelationalPersistentProperty> path, |
||||
@Nullable RelationalEntityInsertWriter.PathNode parentNode, @Nullable Object value) { |
||||
|
||||
if (value == null) { |
||||
return Collections.emptyList(); |
||||
} |
||||
|
||||
List<RelationalEntityInsertWriter.PathNode> nodes = new ArrayList<>(); |
||||
|
||||
if (path.getRequiredLeafProperty().isQualified()) { |
||||
|
||||
if (path.getRequiredLeafProperty().isMap()) { |
||||
((Map<?, ?>) value) |
||||
.forEach((k, v) -> nodes.add(new RelationalEntityInsertWriter.PathNode(path, parentNode, Pair.of(k, v)))); |
||||
} else { |
||||
|
||||
List listValue = (List) value; |
||||
for (int k = 0; k < listValue.size(); k++) { |
||||
nodes.add(new RelationalEntityInsertWriter.PathNode(path, parentNode, Pair.of(k, listValue.get(k)))); |
||||
} |
||||
} |
||||
} else if (path.getRequiredLeafProperty().isCollectionLike()) { // collection value
|
||||
((Collection<?>) value).forEach(v -> nodes.add(new RelationalEntityInsertWriter.PathNode(path, parentNode, v))); |
||||
} else { // single entity value
|
||||
nodes.add(new RelationalEntityInsertWriter.PathNode(path, parentNode, value)); |
||||
} |
||||
|
||||
return nodes; |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Represents a single entity in an aggregate along with its property path from the root entity and the chain of |
||||
* objects to traverse a long this path. |
||||
*/ |
||||
@Value |
||||
static class PathNode { |
||||
|
||||
/** |
||||
* The path to this entity |
||||
*/ |
||||
PersistentPropertyPath<RelationalPersistentProperty> path; |
||||
|
||||
/** |
||||
* The parent {@link PathNode}. This is {@code null} if this is the root entity. |
||||
*/ |
||||
@Nullable |
||||
PathNode parent; |
||||
|
||||
/** |
||||
* The value of the entity. |
||||
*/ |
||||
Object value; |
||||
} |
||||
} |
||||
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
/* |
||||
* 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.relational.core.conversion; |
||||
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* Converts an aggregate represented by its root into an {@link AggregateChange}. |
||||
* Does not perform any isNew check. |
||||
* |
||||
* @author Jens Schauder |
||||
* @author Mark Paluch |
||||
* @author Thomas Lang |
||||
*/ |
||||
public class RelationalEntityInsertWriter extends AbstractRelationalEntityWriter { |
||||
|
||||
public RelationalEntityInsertWriter(RelationalMappingContext context) { |
||||
super(context); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.convert.EntityWriter#save(java.lang.Object, java.lang.Object) |
||||
*/ |
||||
@Override public void write(Object root, AggregateChange<?> aggregateChange) { |
||||
List<DbAction<?>> actions = new WritingContext(root, aggregateChange).insert(); |
||||
actions.forEach(aggregateChange::addAction); |
||||
} |
||||
} |
||||
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
/* |
||||
* 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.relational.core.conversion; |
||||
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* Converts an aggregate represented by its root into an {@link AggregateChange}. |
||||
* Does not perform any isNew check. |
||||
* |
||||
* @author Jens Schauder |
||||
* @author Mark Paluch |
||||
* @author Thomas Lang |
||||
*/ |
||||
public class RelationalEntityUpdateWriter extends AbstractRelationalEntityWriter { |
||||
|
||||
public RelationalEntityUpdateWriter(RelationalMappingContext context) { |
||||
super(context); |
||||
} |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.convert.EntityWriter#save(java.lang.Object, java.lang.Object) |
||||
*/ |
||||
@Override public void write(Object root, AggregateChange<?> aggregateChange) { |
||||
List<DbAction<?>> actions = new WritingContext(root, aggregateChange).update(); |
||||
actions.forEach(aggregateChange::addAction); |
||||
} |
||||
} |
||||
@ -0,0 +1,111 @@
@@ -0,0 +1,111 @@
|
||||
/* |
||||
* 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.relational.core.conversion; |
||||
|
||||
import lombok.RequiredArgsConstructor; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.junit.MockitoJUnitRunner; |
||||
import org.springframework.data.annotation.Id; |
||||
import org.springframework.data.relational.core.conversion.AggregateChange.Kind; |
||||
import org.springframework.data.relational.core.conversion.DbAction.InsertRoot; |
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
|
||||
/** |
||||
* Unit tests for the {@link RelationalEntityInsertWriter} |
||||
* |
||||
* @author Jens Schauder |
||||
* @author Thomas Lang |
||||
*/ |
||||
@RunWith(MockitoJUnitRunner.class) public class RelationalEntityInsertWriterUnitTests { |
||||
|
||||
public static final long SOME_ENTITY_ID = 23L; |
||||
RelationalEntityInsertWriter converter = new RelationalEntityInsertWriter(new RelationalMappingContext()); |
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void newEntityGetsConvertedToOneInsert() { |
||||
|
||||
SingleReferenceEntity entity = new SingleReferenceEntity(null); |
||||
AggregateChange<SingleReferenceEntity> aggregateChange = //
|
||||
new AggregateChange(Kind.SAVE, SingleReferenceEntity.class, entity); |
||||
|
||||
converter.write(entity, aggregateChange); |
||||
|
||||
assertThat(aggregateChange.getActions()) //
|
||||
.extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath, this::actualEntityType, |
||||
this::isWithDependsOn) //
|
||||
.containsExactly( //
|
||||
tuple(InsertRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false) //
|
||||
); |
||||
} |
||||
|
||||
@Test // DATAJDBC-282
|
||||
public void existingEntityGetsNotConvertedToDeletePlusUpdate() { |
||||
|
||||
SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID); |
||||
|
||||
AggregateChange<SingleReferenceEntity> aggregateChange = //
|
||||
new AggregateChange(Kind.SAVE, SingleReferenceEntity.class, entity); |
||||
|
||||
converter.write(entity, aggregateChange); |
||||
|
||||
assertThat(aggregateChange.getActions()) //
|
||||
.extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath, this::actualEntityType, |
||||
this::isWithDependsOn) //
|
||||
.containsExactly( //
|
||||
tuple(InsertRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false) //
|
||||
); |
||||
|
||||
assertThat(aggregateChange.getEntity()).isNotNull(); |
||||
// the new id should not be the same as the origin one - should do insert, not update
|
||||
// assertThat(aggregateChange.getEntity().id).isNotEqualTo(SOME_ENTITY_ID);
|
||||
} |
||||
|
||||
private String extractPath(DbAction action) { |
||||
|
||||
if (action instanceof DbAction.WithPropertyPath) { |
||||
return ((DbAction.WithPropertyPath<?>) action).getPropertyPath().toDotPath(); |
||||
} |
||||
|
||||
return ""; |
||||
} |
||||
|
||||
private boolean isWithDependsOn(DbAction dbAction) { |
||||
return dbAction instanceof DbAction.WithDependingOn; |
||||
} |
||||
|
||||
private Class<?> actualEntityType(DbAction a) { |
||||
|
||||
if (a instanceof DbAction.WithEntity) { |
||||
return ((DbAction.WithEntity) a).getEntity().getClass(); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
@RequiredArgsConstructor static class SingleReferenceEntity { |
||||
|
||||
@Id final Long id; |
||||
Element other; |
||||
// should not trigger own Dbaction
|
||||
String name; |
||||
} |
||||
|
||||
@RequiredArgsConstructor private static class Element { |
||||
@Id final Long id; |
||||
} |
||||
} |
||||
@ -0,0 +1,96 @@
@@ -0,0 +1,96 @@
|
||||
/* |
||||
* 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.relational.core.conversion; |
||||
|
||||
import lombok.RequiredArgsConstructor; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.junit.MockitoJUnitRunner; |
||||
import org.springframework.data.annotation.Id; |
||||
import org.springframework.data.relational.core.conversion.AggregateChange.Kind; |
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
|
||||
/** |
||||
* Unit tests for the {@link RelationalEntityUpdateWriter} |
||||
* |
||||
* @author Jens Schauder |
||||
* @author Thomas Lang |
||||
*/ |
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class RelationalEntityUpdateWriterUnitTests { |
||||
|
||||
public static final long SOME_ENTITY_ID = 23L; |
||||
RelationalEntityUpdateWriter converter = new RelationalEntityUpdateWriter(new RelationalMappingContext()); |
||||
|
||||
@Test // DATAJDBC-112
|
||||
public void existingEntityGetsConvertedToDeletePlusUpdate() { |
||||
|
||||
SingleReferenceEntity entity = new SingleReferenceEntity(SOME_ENTITY_ID); |
||||
|
||||
AggregateChange<RelationalEntityWriterUnitTests.SingleReferenceEntity> aggregateChange = //
|
||||
new AggregateChange(Kind.SAVE, SingleReferenceEntity.class, entity); |
||||
|
||||
converter.write(entity, aggregateChange); |
||||
|
||||
assertThat(aggregateChange.getActions()) //
|
||||
.extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath, this::actualEntityType, |
||||
this::isWithDependsOn) //
|
||||
.containsExactly( //
|
||||
tuple(DbAction.Delete.class, Element.class, "other", null, false), //
|
||||
tuple(DbAction.UpdateRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false) //
|
||||
); |
||||
} |
||||
|
||||
private String extractPath(DbAction action) { |
||||
|
||||
if (action instanceof DbAction.WithPropertyPath) { |
||||
return ((DbAction.WithPropertyPath<?>) action).getPropertyPath().toDotPath(); |
||||
} |
||||
|
||||
return ""; |
||||
} |
||||
|
||||
private boolean isWithDependsOn(DbAction dbAction) { |
||||
return dbAction instanceof DbAction.WithDependingOn; |
||||
} |
||||
|
||||
private Class<?> actualEntityType(DbAction a) { |
||||
|
||||
if (a instanceof DbAction.WithEntity) { |
||||
return ((DbAction.WithEntity) a).getEntity().getClass(); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
@RequiredArgsConstructor |
||||
static class SingleReferenceEntity { |
||||
|
||||
@Id |
||||
final Long id; |
||||
Element other; |
||||
// should not trigger own Dbaction
|
||||
String name; |
||||
} |
||||
|
||||
@RequiredArgsConstructor |
||||
private static class Element { |
||||
@Id |
||||
final Long id; |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue