Reputation: 1207
I am working on an application according to the Domain Driven Design. For this reason my fetched entities are mapped to the domain model. A modification to this domain model might have to be updated, so I map the domain model back to the entity (new instance) and try to persist it. This is when Hibernate slaps me on the wrist with a PersistentObjectException:
detached entity passed to persist
I tried to recreate this problem in a Spring Boot application, but for some reason Spring connects the new Entity instance to the attached instance. (Or so it seems.)
Summarizing: Entity (attached) -> Model -> Entity (detached)
Here I have a simple example project which faces the same issue:
https://gitlab.com/rmvanderspek/quarkus-multithreading/-/tree/persistence
UPDATE: The following does 'the trick', but it is a workaround and feels like there might be side-effects I didn't bargain for:
entity = respository.getEntityManager().merge(entity);
repository.persist(entity);
Upvotes: 0
Views: 3101
Reputation: 16420
The problem is when an entity has a generated id, but you set a value on a new instance created through a constructor. Such entities must use merge
for applying the changes to the persistent state. The other way around would be to use entityManager.find()
first to retrieve the managed entity and apply the changes on that object.
I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO/domain model could look like the following with Blaze-Persistence Entity-Views:
@EntityView(User.class)
@UpdatableEntityView
public interface UserDto {
@IdMapping
Long getId();
String getName();
void setName(String name);
@UpdatableMapping
Set<RoleDto> getRoles();
@EntityView(Role.class)
interface RoleDto {
@IdMapping
Long getId();
String getName();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
UserDto user = entityViewManager.find(entityManager, UserDto.class, id);
user.getRoles().add(entityViewManager.getReference(RoleDto.class, roleId));
entityViewManager.save(entityManager, user);
This will only flush the data that actually changed and also avoid unnecessary loads.
The Quarkus and JAX-RS integration make it super simple to use it in your application: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/#jaxrs-integration
@POST
@Path("/users/{id}")
@Consumes(MediaType.APPLICATION_JSON)
@Transactional
public Response updateUser(@EntityViewId("id") UserDto user) {
entityViewManager.save(entityManager, user);
return Response.ok(user.getId().toString()).build();
}
Upvotes: 1