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
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
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.
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).
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.
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.
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);
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)
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:
@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);
}
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);
}
}
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;
}
}
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);
}
}
@EnableJpaRepositories(repositoryFactoryBeanClass = DetachableJpaRepositoryFactoryBean.class)
public class MyApplication {...}
@Repository
public interface UserRepository extends DetachableJpaRepository<User, Long> {
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With