Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring JpaRepository - Detach and Attach entity

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

like image 943
amique Avatar asked Nov 07 '14 06:11

amique


People also ask

Which is better CrudRepository or JpaRepository?

Crud Repository doesn't provide methods for implementing pagination and sorting. JpaRepository ties your repositories to the JPA persistence technology so it should be avoided. We should use CrudRepository or PagingAndSortingRepository depending on whether you need sorting and paging or not.

What is JPA detach?

A detached entity (a.k.a. a detached object) is an object that has the same ID as an entity in the persistence store but that is no longer part of a persistence context (the scope of an EntityManager session).

What is difference between save and saveAndFlush in JPA?

The saveAndFlush() Method Unlike save(), the saveAndFlush() method flushes the data immediately during the execution. This method belongs to the JpaRepository interface of Spring Data JPA.


4 Answers

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.

like image 137
Predrag Maric Avatar answered Oct 20 '22 01:10

Predrag Maric


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);
like image 7
Xstian Avatar answered Oct 20 '22 02:10

Xstian


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)

like image 7
Chayne P. S. Avatar answered Oct 20 '22 00:10

Chayne P. S.


While the accepted, Predrag Maric's answer is correct and will work, I found it not really flexible when you want to add such a feature to all your repository interfaces, hence I'm using following approach, with my custom repository factory bean:

  1. Create intermediate interface for detach feature:
@NoRepositoryBean // this annotation is important here if the package you are putting this interface in is scanned for repositories (is in basePackage)
public interface DetachableJpaRepository<T, TID> extends JpaRepository<T, TID> { // you can also extend other repositories here, like JpaSpecificationExecutor
    void detach(T entity);
}
  1. Create implementation of the intermediate interface:
public class DetachableJpaRepositoryImpl<T, TID> extends SimpleJpaRepository<T, TID> implements DetachableJpaRepository<T, TID> {
    private final EntityManager entityManager;

    public DetachableJpaRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
        this.entityManager = entityManager;
    }

    @Override
    public void detach(T entity) {
        entityManager.detach(entity);
    }
}
  1. Create your custom repository factory:
public class DetachableJpaRepositoryFactory<T, TID> extends JpaRepositoryFactory {
    public DetachableJpaRepositoryFactory(EntityManager entityManager) {
        super(entityManager);
    }

    @Override
    protected JpaRepositoryImplementation<?, ?> getTargetRepository(RepositoryInformation information, EntityManager entityManager) {
        return new DetachableJpaRepositoryImpl<T, TID>((Class<T>) information.getDomainType(), entityManager);
    }

    @Override
    protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
        return DetachableJpaRepository.class;
    }
}
  1. Create custom repository factory bean, to declare usage of factory you created above:
public class DetachableJpaRepositoryFactoryBean<R extends JpaRepositoryImplementation<T, TID>, T, TID> extends JpaRepositoryFactoryBean<R, T, TID> {
    public DetachableJpaRepositoryFactoryBean(Class<? extends R> repositoryInterface) {
        super(repositoryInterface);
    }

    @Override
    protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
        return new DetachableJpaRepositoryFactory<T, TID>(entityManager);
    }
}
  1. Declare the custom factory bean to spring configuration:
@EnableJpaRepositories(repositoryFactoryBeanClass = DetachableJpaRepositoryFactoryBean.class)
public class MyApplication {...}
  1. Now you can use new intermediate interface on your repositories:
@Repository
public interface UserRepository extends DetachableJpaRepository<User, Long> {
}
  1. And detach method is available for you on these repositories:
@Autowired
private UserRepository userRepository;
...
userRepository.detach(user);

I found this approach very flexible, also to add other features to your custom repositories.

Additionaly I do not agree with Chayne P. S. answer, imo you should not use entity manager in service layer, this is dao layer responsibility to manage the entities state, what Chayne P. S proposes is bad design imo, service layer should not be aware of entity manager.

like image 1
bladekp Avatar answered Oct 20 '22 01:10

bladekp