Skip to content

Improve handling of non-distinct collections and already visited objects. #2355

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Sm0keySa1m0n opened this issue Aug 12, 2021 · 7 comments
Closed
Assignees
Labels
type: bug A general bug

Comments

@Sm0keySa1m0n
Copy link

When saving two nodes with version properties that reference each other, an OptimisticLockingFailureException is thrown upon calling save. This only occurs when saving with two pre-existing nodes, if you create two new nodes with a cyclic relationship or create one new node and use one pre-existing node it works.

The exception is thrown in ReactiveNeo4jTemplate::saveRelatedNode, inside of

switchIfEmpty(Mono.defer(() -> {
    if (targetNodeDescription.hasVersionProperty()) {
        return Mono.error(() -> new OptimisticLockingFailureException(OPTIMISTIC_LOCKING_ERROR_MESSAGE));
    }
    return Mono.empty();
})
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Aug 12, 2021
@michael-simons
Copy link
Collaborator

Hi. Would you please share the domain or the excerpt of it causing the issues? Thank you.

@michael-simons michael-simons self-assigned this Aug 13, 2021
@Sm0keySa1m0n
Copy link
Author

The problem occurs if you have two Users who are both friends with each other, it also occurs if one User has a friend and the other User has that User as their family etc. Essentially if two Users both reference each other in a relationship, it causes this exception.

I've tried with immutable and mutable, I've tried using a Long object and long primitive for the version, I've tried different types for the ID and I've tried using Sets instead of Lists.

@Node
public class User {

  @Id
  private final Long id;

  @Version
  private final Long version;

  @Relationship(direction = Relationship.Direction.OUTGOING, type = "FRIEND")
  private List<User> friends = new ArrayList<>();

  @Relationship(direction = Relationship.Direction.OUTGOING, type = "FAMILY")
  private List<User> family = new ArrayList<>();

  public User(Long id, Long version) {
    this.id = id;
    this.version = version;
  }

  public Long getId() {
    return this.id;
  }

  public Long getVersion() {
    return this.version;
  }

  public List<User> getFriends() {
    return this.friends;
  }

  public List<User> getFamily() {
    return this.family;
  }

  public void addFamily(User user) {
    this.family.add(user);
  }

  public void remveFamily(User user) {
    this.family.remove(user);
  }

  public void addFriend(User user) {
    this.friends.add(user);
  }

  public void removeFriend(User user) {
    this.friends.remove(user);
  }
}

@michael-simons
Copy link
Collaborator

Thanks for sharing. On it.

@michael-simons
Copy link
Collaborator

One workaround is to use save instead of saveAll

User u1 = userRepository.findOneByName("U1");
		User u2 = userRepository.findOneByName("U2");
		u1.addFriend(u2);
		u2.addFriend(u1);

		userRepository.save(u1);

like this, which will correctly update u2 as well…

@Sm0keySa1m0n
Copy link
Author

That doesn't throw an exception but doesn't persist the relationship:

@Sm0keySa1m0n
Copy link
Author

To produce the above I first ran:

this.repo.save(new User(1L, 0L)).block();
this.repo.save(new User(2L, 0L)).block();

Then ran:

var user1 = this.repo.findById(1L).block();
var user2 = this.repo.findById(2L).block();
user1.addFriend(user2);
user2.addFriend(user1);
this.repo.save(user1).block();

@michael-simons michael-simons added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged labels Aug 13, 2021
@michael-simons michael-simons changed the title Nodes with cyclic relationships and version property result in OptimisticLockingFailureException Improve handling of non-distinct collections and already visited objects. Aug 16, 2021
michael-simons added a commit that referenced this issue Aug 17, 2021
…sited objects.

This commit started as a bunch of tests that should show which
self-referential relationships with optimistic locking are supported.

The bug in #2355 could be reproduced with the `saveAll` call: The
underlying issue bieng that one or more items had been visited at least
twice: The first time as related object, than as root. The next root of
course still be on the old version.

That has been fixed by keeping the state machine tracking visited items
for the duration of the `saveAll` calls around. Thus an related object
will not be updated twice.

For all of this to work in the most efficient way possible, a `java.util.Set` should
be used for related objects, along with a valid `equals/hashCode` pair. This
is however neglected.

By adding an additional check in the state machine falling back to ID extraction, we
can improve the user experience a lot and remove that need in many cases.

This commit fixes #2355.
@michael-simons michael-simons added this to the 6.0.13 (2020.0.13) milestone Aug 17, 2021
michael-simons added a commit that referenced this issue Aug 17, 2021
…sited objects.

This commit started as a bunch of tests that should show which
self-referential relationships with optimistic locking are supported.

The bug in #2355 could be reproduced with the `saveAll` call: The
underlying issue bieng that one or more items had been visited at least
twice: The first time as related object, than as root. The next root of
course still be on the old version.

That has been fixed by keeping the state machine tracking visited items
for the duration of the `saveAll` calls around. Thus an related object
will not be updated twice.

For all of this to work in the most efficient way possible, a `java.util.Set` should
be used for related objects, along with a valid `equals/hashCode` pair. This
is however neglected.

By adding an additional check in the state machine falling back to ID extraction, we
can improve the user experience a lot and remove that need in many cases.

This commit fixes #2355.
@michael-simons
Copy link
Collaborator

Hi. This is fixed.
Please have a look at this package https://github.com/spring-projects/spring-data-neo4j/tree/main/src/test/java/org/springframework/data/neo4j/integration/versioned_self_references for the supported use cases.

Thanks a lot for reporting this issue, I think the improvements made here are super valueable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

3 participants