Skip to content
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

BaseService.update returns object that is not a hibernate proxy #450

Closed
simonseyock opened this issue Jan 14, 2022 · 2 comments · Fixed by #608
Closed

BaseService.update returns object that is not a hibernate proxy #450

simonseyock opened this issue Jan 14, 2022 · 2 comments · Fixed by #608

Comments

@simonseyock
Copy link
Member

simonseyock commented Jan 14, 2022

The BaseService.update method serializes the given entity, deserializes it again and saves it. This causes the resulting object to not be a proper hibernate proxy. (edit: to be more precise: any related objects are no hibernate proxies anymore, but merely the objects that got deserialized. It can be that the object only contains an Id if the field was using the BaseEntityIdSerializer). Also all other managed instances of the object are broken as well.

Firstly the question arises why this code with the serialization and deserialization exists:

    @PreAuthorize("hasRole('ROLE_ADMIN') or hasPermission(#entity, 'UPDATE')")
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public S update(Long id, S entity) throws IOException {
        Optional<S> persistedEntityOpt = repository.findById(id);

        ObjectNode jsonObject = objectMapper.valueToTree(entity);

        // Ensure the created timestamp will not be overridden.
        S persistedEntity = persistedEntityOpt.orElseThrow();
        OffsetDateTime createdTimestamp = persistedEntity.getCreated();
        String serialized = createdTimestamp != null ? createdTimestamp.toInstant().toString() : null;
        jsonObject.put("created", serialized);

        S updatedEntity = objectMapper.readerForUpdating(persistedEntity).readValue(jsonObject);

        return repository.save(updatedEntity);
    }

I suppose this in place to leave the entity intact. Is this really neccessary?

The simplest way to achieve the same would be:

  entity.setCreated(persistedEntity.getCreated());

Another way to solve this would be to use the lombok @SuperBuilder annotation and create a copy. See https://www.projectlombok.org/features/experimental/SuperBuilder

For our project we used the following workaround:

            var updatedEntity = super.update(entityId, entity);

            entityManager.flush(); // flush database changes
            entityManager.detach(updatedEntity); // this way the broken object is no longer a managed hibernate object
            // without the detach the findById would just return the broken managed object
            updatedEntity = repository.findById(entityId).orElseThrow(); // get a new managed hibernate proxy object

This might not be feasible for the BaseService as I assume that flush does have performance issues if called often.

@simonseyock
Copy link
Member Author

It might also be interesting to mention: partialUpdate does use a similar process.

@simonseyock
Copy link
Member Author

And well another option would be to not return the resulting object. But this would still cause the managed object to be broken.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
1 participant