jhipster / generator-jhipster

JHipster is a development platform to quickly generate, develop, & deploy modern web applications & microservice architectures.
https://www.jhipster.tech
Apache License 2.0
21.53k stars 4.02k forks source link

JHipster is not synchronizing bidirectional entity associations cache #17425

Open antoniosch85 opened 2 years ago

antoniosch85 commented 2 years ago
Overview of the issue

The annotation CacheConcurrencyStrategy.READ_WRITE seems not working. Always an empty result is returned, I need to restart server to see the correct return.

Motivation for or Use Case

I am not able to use the cache. In order to work, I have to eliminate the annotation @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)

Reproduce the error

I have 2 entities A and B with one to many relationship. I generated all service and in the entity A I can see:

@OneToMany(mappedBy = "entityA")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<entityB> entitiesB = new HashSet<>();

If I add a new entity B and set the relationship with entity A, my expectation is when I call entityA.getEntitiesB() the inserted entityB is returned but I get always an empty set. If I restart the server the correct list is returned.

Related issues
Suggest a Fix
JHipster Version(s)

v6.10.5

JHipster configuration
Entity configuration(s) entityName.json files generated in the .jhipster directory
Browsers and Operating System
pmverma commented 2 years ago

I notice the same issue with my application and today I can confirm it. This is really a serious bug since we are getting incorrect results with OneToMany.

Here is a reproducible jdl.

application {
  config {
    applicationType monolith
    authenticationType jwt
    baseName jhcache
    blueprints []
    buildTool maven
    cacheProvider hazelcast
    clientFramework angularX
    clientPackageManager npm
    clientTheme none
    creationTimestamp 1640705957630
    databaseType sql
    devDatabaseType h2Disk
    dtoSuffix DTO
    enableGradleEnterprise false
    enableHibernateCache true
    enableSwaggerCodegen false
    enableTranslation false
    gradleEnterpriseHost ""
    jhiPrefix jhi
    jhipsterVersion "7.4.1"
    jwtSecretKey "ZWJmMTkzOTE3MjY2OTYwYmIwZGQxMTk1YWJiYmU2N2FkMTI1Y2NlMDY3MGI2Y2YyOWZmY2U1MTA2MDc2Mjk5OTNkYmU2NjU5N2EwOGVhOWE4ZWJmNTAyMDRkNTk4MWI1N2U2ZjYxYTMzMmM4ZmI3M2JkOWJhMzc3MjJmYjAxYjY="
    languages [en, fr]
    messageBroker false
    nativeLanguage en
    otherModules []
    packageName com.mycompany.myapp
    prodDatabaseType postgresql
    reactive false
    searchEngine false
    serverPort 8080
    serviceDiscoveryType no
    skipUserManagement false
    testFrameworks []
    websocket false
    withAdminUi true
  }

  entities Child, Parent
}

entity Child {
  name String
}
entity Parent {
  name String
}
relationship OneToMany {
  Parent{child} to Child{parent(name)}
}

dto Child, Parent with mapstruct
service Child, Parent with serviceClass

Steps to reproduce

  1. Generate application with above jdl file. jhipster import-jdl jdlcontent.jh
  2. Add the following code to ParentService.findOne method to print -ToMany side.
    parentRepository.findById(id).ifPresent(parent -> parent.getChildren().forEach(System.out::println));
  3. Start the application
  4. Add a parent entity to a child's entity and visit the parent details page.
  5. Add the same parent to another child and visit again to the same parent details page.
  6. You can see that ParentService.findOne method is not printing the correct number of child entities.

I am checking for the same issue however I am wondering how many applications have been affected since it is reported for v6.10.5.

pascalgrimaud commented 2 years ago

Adding a bounty as the bug is confirmed by you, @pmverma If you know how to fix it, plz go ahead!

atomfrede commented 2 years ago

It looks like the problem seems to be related to the caching of the old parent entity. When assigning a new parent the old one is not changed and stays in the cache. When you save/update the old parent the cache is consistent again. Not sure if thats the root cause and if thats how the hibernate cache is supposed to work.

antoniosch85 commented 2 years ago

It looks like the problem seems to be related to the caching of the old parent entity. When assigning a new parent the old one is not changed and stays in the cache. When you save/update the old parent the cache is consistent again. Not sure if thats the root cause and if thats how the hibernate cache is supposed to work.

If you update the parent the cache is updated, the issue occurs only when a new parent has been inserted. I think it is how the hibernate cache is supposed to work.

pmverma commented 2 years ago

The issue here is we are not synchronizing bi-directional associations although we already have the synchronization code in parent classes. How to synchronize bidirectional entity associations with JPA and Hibernate Naturally, we would manage the synchronization from the parent side however if you want to do in child class/service it should work but then again at least we would need to load the parent so we can add a child to it.

Two solutions I can think of as of now.

Code snippet of ChildService.java
/**
 * Save a child.
 *
 * @param childDTO the entity to save.
 * @return the persisted entity.
 */
public ChildDTO save(ChildDTO childDTO) {
    log.debug("Request to save Child : {}", childDTO);
    Child child = childMapper.toEntity(childDTO);
    // solution1, syncing starts here
    if (Objects.nonNull(child.getParent()) && Objects.nonNull(child.getParent().getId())) {
        final var parentOptional = parentRepository.findById(child.getParent().getId());
        if (parentOptional.isPresent()) {
            final var parent = parentOptional.get();
            parent.addChild(child);
        }
    }
    // solution1, syncing ends here

    child = childRepository.save(child);

    // solution2, syncing starts here
    child.getParent().addChild(child);
    // solution2, syncing ends here

    return childMapper.toDto(child);
}

Solution1 is a more traditional approach but has some concerns.

Solution2 seems weird but it works and we can use the code we already have. No dependency. Not many lines of code even we have multiple parents.

I would rather like to wait for what experts say. cc @jhipster/developers

atomfrede commented 2 years ago

I totally agree, loading the parent inside child service is ugly (tried it too). So maybe we can somehow decouple it with some spring events (or jpa events?).

Tcharl commented 2 years ago

V8 could be the occasion to go modulith

pmverma commented 2 years ago

For v7, if there is no major concern I would like to go with solution2. For v8 or future major versions, we still can have some other suitable solution.

DanielFran commented 2 years ago

@pmverma Are you still available to do a PR?

ReginaBDR commented 1 year ago

@OneToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST }) does the trick