Spring JpaRepository - Detach and Attach entity

60,548

Solution 1

If you are using JPA 2.0, you can use EntityManager#detach() to detach a single entity from persistence context. Also, Hibernate has a Session#evict() which serves the same purpose.

Since JpaRepository doesn't provide this functionality itself, you can add a custom implementation to it, something like this

public interface UserRepositoryCustom {
    ...
   void detachUser(User u);
    ...
}

public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
    ...
}

@Repository
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
    ...
    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public void detachUser(User u) {
        entityManager.detach(u);
    }
    ...
}

I haven't tried this code, but you should be able to make it work. You might even try to get a hold on EntityManager in your service class (where updateUser() is) with @PersistenceContext, and avoid the hustle of adding custom implementation to repository.

Solution 2

entityManager.clear() will disconnect all the JPA objects, so that might not be an appropriate solution in all the cases, if you have other objects you do plan to keep connected.

clear

/**
 * Clear the persistence context, causing all managed
 * entities to become detached. Changes made to entities that
 * have not been flushed to the database will not be
 * persisted.
 */
public void clear();

entityManager.detach(entity); Remove the given entity from the persistence context

detach

/**
 * Remove the given entity from the persistence context, causing
 * a managed entity to become detached.  Unflushed changes made
 * to the entity if any (including removal of the entity),
 * will not be synchronized to the database.  Entities which
 * previously referenced the detached entity will continue to
 * reference it.
 * @param entity  entity instance
 * @throws IllegalArgumentException if the instance is not an
 *         entity
 * @since Java Persistence 2.0
 */
public void detach(Object entity);

Solution 3

Using custom implementation as @Predrag Maric suggest is clearly the right answer for this question. However, I find doing detach in Service layer is much better as normally it knows that the entity should be detached or not.

Just wire it with @PersistenceContext in Service.

@Service
class ConsumerServiceImpl {

    @PersistenceContext
    private EntityManager entityManager
...

    entityManager.detach(en)

Share:
60,548
amique
Author by

amique

Updated on January 06, 2021

Comments

  • amique
    amique over 3 years

    I am using spring boot and hibernate over jpa. I am using JpaRepository interface to implement my repositories. As with following UserRepository

    public interface UserRepository extends JpaRepository<User, Long> {
    }
    

    I want to achieve following

    1. Load a User entity.
    2. Change the state of entity object e.g. user.setName("foo")
    3. Do an external system webservice call. Save the call result in DB
    4. Only on successful response of this webservice call, save the new state of user in repository.

    All above steps are not happening in one transaction i.e. the external service call is out of transaction.

    When I save my webservice result in DB via its repository, my changes in User entity are also saved. As per my understanding this is due to the flushing of underlaying persistence context at step # 3. After some google, I think I can achieve my purpose, if I can detach my user entity at step one and reattach it at step 4. Please confirm if my understanding is correct and how I can achieve this? There is not method in JpaRepository interface to detach an entity.

    Following is the code to illustrate

    public void updateUser(int id, String name, int changeReqId){
        User mUser = userRepository.findOne(id); //1
        mUser.setName(name); //2
    
        ChangeRequest cr = changeRequestRepository.findOne(changeReqId);
        ChangeResponse rs = userWebService.updateDetails(mUser); //3
    
        if(rs.isAccepted()){
            userRepository.saveAndFlush(mUser); //4
        }
    
        cr.setResponseCode(rs.getCode());
        changeRequestRepository.saveAndFlush(cr); //this call also saves the changes at step 2
    }
    

    Thanks

  • Jonik
    Jonik over 8 years
    Thanks, this approach worked in my Spring Boot app. (Took the liberty to add @Repository to the implementation in your example code; otherwise the custom repository cannot be autowired.)
  • DBS
    DBS over 6 years
    Can the same approach used for CrudRepository ?
  • Predrag Maric
    Predrag Maric over 6 years
    @DBS Didn't try it, but should be the same